iommufd: Iterate the cache invalidation array in the core
Subsystems (4)
v3 cover iommufd: Iterate the cache invalidation array in the core
The vIOMMU cache_invalidate() and the nested-HWPT cache_invalidate_user() ops are each handed the full user invalidation array and must report, via array->entry_num, how many of its entries they handled. That makes every driver open-code the same array walk, with real downsides:
- each driver carries its own loop and sub-array bookkeeping;
- the ARM SMMUv3 driver allocates a buffer sized to the whole array just to iterate over it;
- hand-rolling the loop left the ARM SMMUv3 driver with two long-standing bugs:
1) on a conversion failure it counts commands that it converted but never issued, so user space skips invalidations that never reached the cmdq;
2) it rejects a zero-length array, which the uAPI documents as a valid request that only probes the data type.
The walk is identical for every driver, so move it into the iommufd core.
The core now drives the iteration:
- it invokes the op on a sub-array starting at the first not-yet-handled entry;
- the op handles one chunk from the front of that sub-array and reports the count via array->entry_num;
- the core advances and re-invokes until the whole array is consumed or the op returns an error.
A driver then only has to handle one bounded chunk per call, e.g. the ARM SMMUv3 op copies a single cmdq batch into a fixed on-stack buffer and drops its whole-array allocation. An op still handling the entire array in one call keeps working, so each driver converts independently.
These are long-standing corner cases, so this targets for-next, not for-rc.
This is on Github:
https://github.com/nicolinc/iommufd/commits/iommufd_invalidation_loop-v2
[Note to Jason and Will]
This has some conflicts with Ashish's ARM_SMMU_OPT_REPEAT_TLBI_CFGI series:
https://lore.kernel.org/all/[email protected]/
Changelog
v3
* Patch-1: Add a minimal FEAT_DS detection and allow the two DS-only range encodings on a DS-capable SMMU
* Patch-1: Mask the host's scale value to keep its 5-bit truncation
v2
* Add "Reviewed-by" from Kevin to patches 2-5
* Patch-1: Allow the ATC_INV Global bit gated on ssid_bits, correcting the wrong every-device claim: per the spec it only broadens a single device's invalidation across its PASIDs
* Patch-1: Move the FEAT_ATS check into the allowlist switch
* Patch-1: Gate the TTL range field on FEAT_RANGE_INV too
* Patch-1: Accept only asid_bits of the ASID field
* Patch-1: Reject Reserved range field value combinations
* Patch-1: Reject an ATC_INV Size above 52
* Patch-1: Add local smmu and data variables to simplify the long lines
* Patch-1: Document the valid-command contract in the uAPI header
* Patch-1: Note that unchecked out-of-range values are UNPREDICTABLE
* Patch-1: Note that SSID/Global are IGNORED, not RES0, when SSV == 0
* Patch-2: Consolidate the two invalidation loops into one
* Patch-2: Multiply by the size_t entry_len to avoid a u32 overflow
* Patch-3/5: Return 0 directly on a zero-length array
* Patch-4: Use a processed counter and an out label like the mock driver
v1
https://lore.kernel.org/all/[email protected]/
Nicolin Chen (5):
iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation
commands
iommufd: Iterate the cache invalidation array in the core iommufd/selftest: Convert cache invalidation mocks to the core array
loop
iommu/arm-smmu-v3-iommufd: Convert cache invalidation to the core
array loop
iommu/vt-d: Convert nested cache invalidation to the core array loop
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +- include/linux/iommu.h | 6 +- include/linux/iommufd.h | 2 + include/uapi/linux/iommufd.h | 4 +- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 184 ++++++++++++++---- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +- drivers/iommu/intel/nested.c | 54 ++--- drivers/iommu/iommufd/hw_pagetable.c | 25 ++- drivers/iommu/iommufd/selftest.c | 147 +++++++------- 9 files changed, 277 insertions(+), 155 deletions(-)
Signature · 1 line
2.43.0
PATCH v3 1/5 iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation commands
The arm_vsmmu_cache_invalidate() op hands a guest's invalidation commands to the trusted main command queue after enforcing only the VMID or the SID, and passes the rest of the command through to the queue unchanged.
That lets a guest set bits the host never meant to forward: a reserved or undefined bit makes a command malformed; per the Arm SMMUv3 specification, in its section 4.1.3 "Command errors", a CERROR_ILL is raised, among other cases, when:
A valid command opcode is used and a Reserved or undefined field is optionally detected as non-zero, which results in the command being treated as malformed.
Restrict each opcode to the fields that the driver supports and reject the command with -EIO if it sets any other bit, before the command reaches the queue. This stops the host from forwarding any bit whose meaning it does not control. Document this contract in the uAPI header, so user space must take the responsibility to forward valid commands only.
Some fields and whole opcodes are legal only on an SMMU that implements the matching feature, so accept them conditionally:
- NUM, SCALE, TG and TTL need FEAT_RANGE_INV.
- SCALE bit 25, for values above 31, and TTL == 0b01 with a 16KB TG need SMMU_IDR5.DS. The raw IDR5 that iommufd reports has let a VMM expose DS to its guest all along, so add the minimal ARM_SMMU_FEAT_DS detection and gate the two encodings on it, rather than regress such a VMM by rejecting them unconditionally. Widening the SCALE field does not change what the host emits in the range invalidation path, which now masks its scale value explicitly to keep the pre-existing 5-bit truncation.
- ASID is limited to asid_bits, since its upper 8 bits are RES0 on an SMMU that only supports 8-bit ASIDs.
- ATC_INV needs FEAT_ATS. Per the specification's section 4.5 "ATS and PRI", CMD_ATC_INV is ILLEGAL when:
SMMU_IDR0.ATS == 0 and this command is issued on a Non-secure or Secure Command queue.
- SSV, SSID and Global need a non-zero ssid_bits. Without it, setting them is not illegal but CONSTRAINED UNPREDICTABLE, which a guest should not be able to provoke. Global also takes effect only when SSV == 1, broadening the invalidation from the one SSID to all the PASIDs of the single device that the SID field addresses.
Some values inside the accepted fields are Reserved too:
- NUM == 0, SCALE == 0 and TTL == 0 together are a Reserved combination and cause a CERROR_ILL.
- NUM, SCALE and TTL turn RES0 when TG == 0.
- An ATC_INV Size above 52, the invalidate-all span, is permitted to raise a CERROR_ILL.
Reject these Reserved values the same way. In contrast, an out-of-range address or ID value is defined as CONSTRAINED UNPREDICTABLE that would be scoped to the guest itself, so it does not deserve a check.
--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +- include/uapi/linux/iommufd.h | 4 +- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 116 ++++++++++++++++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +- 4 files changed, 116 insertions(+), 14 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +3 -1
@@ -64,6 +64,7 @@ struct arm_vsmmu;#define ARM_SMMU_IDR5 0x14#define IDR5_STALL_MAX GENMASK(31, 16) +#define IDR5_DS (1 << 7)#define IDR5_GRAN64K (1 << 6)#define IDR5_GRAN16K (1 << 5)#define IDR5_GRAN4K (1 << 4)
@@ -415,7 +416,7 @@ struct arm_smmu_cmd {#define CMDQ_TLBI_0_NUM GENMASK_ULL(16, 12)#define CMDQ_TLBI_RANGE_NUM_MAX 31 -#define CMDQ_TLBI_0_SCALE GENMASK_ULL(24, 20) +#define CMDQ_TLBI_0_SCALE GENMASK_ULL(25, 20)#define CMDQ_TLBI_0_VMID GENMASK_ULL(47, 32)#define CMDQ_TLBI_0_ASID GENMASK_ULL(63, 48)#define CMDQ_TLBI_1_LEAF (1UL << 0)
@@ -921,6 +922,7 @@ struct arm_smmu_device {#define ARM_SMMU_FEAT_HD (1 << 22)#define ARM_SMMU_FEAT_S2FWB (1 << 23)#define ARM_SMMU_FEAT_BBML2 (1 << 24) +#define ARM_SMMU_FEAT_DS (1 << 25)u32 features;#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)
include/uapi/linux/iommufd.h +3 -1
@@ -909,7 +909,9 @@ struct iommu_hwpt_vtd_s1_invalidate {* CMDQ_OP_CFGI_CD * CMDQ_OP_CFGI_CD_ALL * -* -EIO will be returned if the command is not supported. +* User space must forward only valid commands: the kernel rejects, with +* -EIO, any command carrying an unsupported opcode, an unsupported field, +* or a field value that the underlying SMMU hardware does not implement. */ struct iommu_viommu_arm_smmuv3_invalidate {__aligned_le64 cmd[2 ];
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +105 -11
144 lines not on this page · raw message
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +5 -1
@@ -2446,9 +2446,10 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,/* Determine how many chunks of 2^scale size we have */ num = (num_pages >> scale) &CMDQ_TLBI_RANGE_NUM_MAX ; +/* Keep the pre-DS 5-bit truncation when scale > 31 */ cmd->data[0 ] = orig_data0 |FIELD_PREP (CMDQ_TLBI_0_NUM , num -1 ) | -FIELD_PREP (CMDQ_TLBI_0_SCALE , scale); +FIELD_PREP (CMDQ_TLBI_0_SCALE , scale &0x1f );/* range is num * 2^scale * pgsize */ inv_range = num << (scale + tg);
@@ -5098,6 +5099,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)/* Maximum number of outstanding stalls */ smmu->evtq.max_stalls =FIELD_GET (IDR5_STALL_MAX , reg); +if (reg &IDR5_DS ) + smmu->features |=ARM_SMMU_FEAT_DS ; +/* Page sizes */ if (reg &IDR5_GRAN64K ) smmu->pgsize_bitmap |=SZ_64K |SZ_512M ;
Signature · 1 line
2.43.0
Re: [PATCH v3 1/5] iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation commands
Quoted 90 lines · Nicolin Chen
The
arm_vsmmu_cache_invalidate()op hands a guest's invalidation commands to the trusted main command queue after enforcing only the VMID or the SID, and passes the rest of the command through to the queue unchanged.That lets a guest set bits the host never meant to forward: a reserved or undefined bit makes a command malformed; per the Arm SMMUv3 specification, in its section 4.1.3 "Command errors", a
CERROR_ILLis raised, among other cases, when:A valid command opcode is used and a Reserved or undefined field is optionally detected as non-zero, which results in the command being treated as malformed.
Restrict each opcode to the fields that the driver supports and reject the command with
-EIOif it sets any other bit, before the command reaches the queue. This stops the host from forwarding any bit whose meaning it does not control. Document this contract in the uAPI header, so user space must take the responsibility to forward valid commands only.Some fields and whole opcodes are legal only on an SMMU that implements the matching feature, so accept them conditionally:
- NUM, SCALE, TG and TTL need
FEAT_RANGE_INV.
- SCALE bit 25, for values above 31, and TTL == 0b01 with a 16KB TG need SMMU_IDR5.DS. The raw IDR5 that iommufd reports has let a VMM expose DS to its guest all along, so add the minimalARM_SMMU_FEAT_DSdetection and gate the two encodings on it, rather than regress such a VMM by rejecting them unconditionally. Widening the SCALE field does not change what the host emits in the range invalidation path, which now masks its scale value explicitly to keep the pre-existing 5-bit truncation.
- ASID is limited toasid_bits, since its upper 8 bits are RES0 on an SMMU that only supports 8-bit ASIDs.
-ATC_INVneedsFEAT_ATS. Per the specification's section 4.5 "ATS and PRI",CMD_ATC_INVis ILLEGAL when:SMMU_IDR0.ATS == 0 and this command is issued on a Non-secure or Secure Command queue.
- SSV, SSID and Global need a non-zero
ssid_bits. Without it, setting them is not illegal but CONSTRAINED UNPREDICTABLE, which a guest should not be able to provoke. Global also takes effect only when SSV == 1, broadening the invalidation from the one SSID to all the PASIDs of the single device that the SID field addresses.Some values inside the accepted fields are Reserved too:
- NUM == 0, SCALE == 0 and TTL == 0 together are a Reserved combination and cause a
CERROR_ILL.
- NUM, SCALE and TTL turn RES0 when TG == 0.
- AnATC_INVSize above 52, the invalidate-all span, is permitted to raise aCERROR_ILL.Reject these Reserved values the same way. In contrast, an out-of-range address or ID value is defined as CONSTRAINED UNPREDICTABLE that would be scoped to the guest itself, so it does not deserve a check.
Fixes d68beb276ba2 ("iommu/arm-smmu-v3: Support IOMMU_HWPT_INVALIDATE using a VIOMMU object")Assisted-by Claude:claude-fable-5Signed-off-by Nicolin Chen <[email protected]>--- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h | 4 +- include/uapi/linux/iommufd.h | 4 +- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 116 ++++++++++++++++-- drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c | 6 +- 4 files changed, 116 insertions(+), 14 deletions(-)drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +2
-1@@ -64,6 +64,7 @@ struct arm_vsmmu;#define ARM_SMMU_IDR5 0x14#define IDR5_STALL_MAX GENMASK(31, 16) +#define IDR5_DS (1 << 7)#define IDR5_GRAN64K (1 << 6)#define IDR5_GRAN16K (1 << 5)#define IDR5_GRAN4K (1 << 4)@@ -415,7 +416,7 @@ struct arm_smmu_cmd {#define CMDQ_TLBI_0_NUM GENMASK_ULL(16, 12)#define CMDQ_TLBI_RANGE_NUM_MAX 31 -#define CMDQ_TLBI_0_SCALE GENMASK_ULL(24, 20) +#define CMDQ_TLBI_0_SCALE GENMASK_ULL(25, 20)#define CMDQ_TLBI_0_VMID GENMASK_ULL(47, 32)#define CMDQ_TLBI_0_ASID GENMASK_ULL(63, 48)#define CMDQ_TLBI_1_LEAF (1UL << 0)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h struct arm_smmu_device { · line 927
Quoted 6 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +1
-0@@ -921,6 +922,7 @@ struct arm_smmu_device {#define ARM_SMMU_FEAT_HD (1 << 22)#define ARM_SMMU_FEAT_S2FWB (1 << 23)#define ARM_SMMU_FEAT_BBML2 (1 << 24) +#define ARM_SMMU_FEAT_DS (1 << 25)u32 features;
This should probably be a separate patch clubbed with the IDR5_DS detection. Something like "Support IDR5.DS and widen TLBI SCALE field" (Sorry for being nitpicky, but such distinct commits help track history better).
Quoted 21 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.h +0
-0#define ARM_SMMU_OPT_SKIP_PREFETCH (1 << 0)include/uapi/linux/iommufd.h +3
-1@@ -909,7 +909,9 @@ struct iommu_hwpt_vtd_s1_invalidate {* CMDQ_OP_CFGI_CD * CMDQ_OP_CFGI_CD_ALL * -* -EIO will be returned if the command is not supported. +* User space must forward only valid commands: the kernel rejects, with +* -EIO, any command carrying an unsupported opcode, an unsupported field, +* or a field value that the underlying SMMU hardware does not implement. */ struct iommu_viommu_arm_smmuv3_invalidate {__aligned_le64 cmd[2 ];drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +0
-0
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c struct arm_vsmmu_invalidation_cmd { · line 312
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +5
-0@@ -305,6 +305,26 @@ struct arm_vsmmu_invalidation_cmd { }; }; +/* Reject the range field values that the spec defines as Reserved */ +static int arm_vsmmu_validate_range (struct arm_smmu_device *smmu, +struct arm_vsmmu_invalidation_cmd *cmd) +{ +bool range = cmd->cmd.data[0 ] & (CMDQ_TLBI_0_NUM |CMDQ_TLBI_0_SCALE );
Nit: Prefer range = !! (cmd->cmd.data[0] & CMDQ_TLBI ...) for bool.
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c struct arm_vsmmu_invalidation_cmd { · line 320
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +8
-0+u8 ttl =FIELD_GET (CMDQ_TLBI_1_TTL , cmd->cmd.data[1 ]); +u8 tg =FIELD_GET (CMDQ_TLBI_1_TG , cmd->cmd.data[1 ]); + +/* NUM, SCALE and TTL are RES0 when TG == 0 */ +if (!tg) +return (range || ttl) ? -EIO :0 ; +/* TTL == 0b01 with a 16KB TG requires SMMU_IDR5.DS */ +if (tg ==2 && ttl ==1 && !(smmu->features &ARM_SMMU_FEAT_DS ))
I agree we must check this but I'm a little confused, How is a user / VMM depending on the IDR5.DS bit today? The IDR5 is exposed to the user via hw_info AND in the uAPI documentation block we explicitly mention for struct iommu_hw_info_arm_smmuv3 the following valid fields for IDR5:
idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K
Thus, no obedient user should be trying to use the DS bit features today. Are we aware of any users/VMMs that have been ignoring the documentation and relying on the raw bit? If so, does this DS protection need to be separated into an -rc fix for stable trees?
Regarding this series, since we are formally adding support for the DS bit in this series for range invalidations (preferably as a separate patch as mentioned above). We should also update the uAPI doc for struct iommu_hw_info_arm_smmuv3 in include/uapi/linux/iommufd.h? Like:
* idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K, DS
Quoted 84 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +74
-2+return -EIO ; +/* NUM == 0, SCALE == 0 with TTL == 0 is a reserved combination */ +if (!range && !ttl) +return -EIO ; +return 0 ; +} +/* * Convert, in place, the raw invalidation command into an internal format that * can be passed to arm_smmu_cmdq_issue_cmdlist(). Internally commands are @@ -315,33 +335,107 @@ struct arm_vsmmu_invalidation_cmd {static int arm_vsmmu_convert_user_cmd (struct arm_vsmmu *vsmmu,struct arm_vsmmu_invalidation_cmd *cmd) { +struct arm_smmu_device *smmu = vsmmu->smmu; +u64 allowed[2 ] = {CMDQ_0_OP }; +u64 *data = cmd->cmd.data; +/* Commands are le64 stored in u64 */ - cmd->cmd.data[0 ] =le64_to_cpu (cmd->ucmd.cmd[0 ]); - cmd->cmd.data[1 ] =le64_to_cpu (cmd->ucmd.cmd[1 ]); + data[0 ] =le64_to_cpu (cmd->ucmd.cmd[0 ]); + data[1 ] =le64_to_cpu (cmd->ucmd.cmd[1 ]); + +/* Collect the fields userspace is allowed to set for each opcode */ +switch (data[0 ] &CMDQ_0_OP ) { +case CMDQ_OP_TLBI_NH_VA : +/* An SMMU with 8-bit ASIDs treats the upper 8 bits as RES0 */ + allowed[0 ] |=FIELD_PREP (CMDQ_TLBI_0_ASID , +GENMASK (smmu->asid_bits -1 ,0 )); + fallthrough; +case CMDQ_OP_TLBI_NH_VAA : +/* NUM/SCALE/TG/TTL are range fields gated on FEAT_RANGE_INV */ +if (smmu->features &ARM_SMMU_FEAT_RANGE_INV ) { +if (arm_vsmmu_validate_range (smmu, cmd)) +return -EIO ; + allowed[0 ] |=CMDQ_TLBI_0_NUM |CMDQ_TLBI_0_SCALE ; + allowed[1 ] |=CMDQ_TLBI_1_TG |CMDQ_TLBI_1_TTL ; +/* SCALE bit 25 (values above 31) is RES0 without DS */ +if (!(smmu->features &ARM_SMMU_FEAT_DS )) + allowed[0 ] &= ~FIELD_PREP (CMDQ_TLBI_0_SCALE , +BIT (5 )); + } + allowed[0 ] |=CMDQ_TLBI_0_VMID ; + allowed[1 ] |=CMDQ_TLBI_1_LEAF |CMDQ_TLBI_1_VA_MASK ; +break ; +case CMDQ_OP_TLBI_NH_ASID : +/* An SMMU with 8-bit ASIDs treats the upper 8 bits as RES0 */ + allowed[0 ] |=FIELD_PREP (CMDQ_TLBI_0_ASID , +GENMASK (smmu->asid_bits -1 ,0 )); + fallthrough; +case CMDQ_OP_TLBI_NH_ALL : + allowed[0 ] |=CMDQ_TLBI_0_VMID ; +break ; +case CMDQ_OP_ATC_INV : +/* ATC_INV is illegal unless the SMMU implements ATS */ +if (!(smmu->features &ARM_SMMU_FEAT_ATS )) +return -EIO ; +/* A Size above 52 (invalidate-all) may raise a CERROR_ILL */ +if (FIELD_GET (CMDQ_ATC_1_SIZE , data[1 ]) >ATC_INV_SIZE_ALL ) +return -EIO ; +/* +* SSV/SSID/Global need substream support. SSID and Global are +* IGNORED (not RES0) when SSV == 0, so they need no SSV check. +*/ +if (smmu->ssid_bits) + allowed[0 ] |=CMDQ_0_SSV |CMDQ_ATC_0_SSID | +CMDQ_ATC_0_GLOBAL ; + allowed[0 ] |=CMDQ_ATC_0_SID ; + allowed[1 ] |=CMDQ_ATC_1_SIZE |CMDQ_ATC_1_ADDR_MASK ; +break ; +case CMDQ_OP_CFGI_CD : +/* No SSV for CFGI_CD; SSID requires substream support */ +if (smmu->ssid_bits) + allowed[0 ] |=CMDQ_CFGI_0_SSID ; + allowed[1 ] |=CMDQ_CFGI_1_LEAF ; + fallthrough; +case CMDQ_OP_CFGI_CD_ALL : + allowed[0 ] |=CMDQ_CFGI_0_SID ; +break ; + } + +/* +* Reject any other bit, e.g. a RES0 bit or a Secure bit, before the
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c struct arm_vsmmu_invalidation_cmd { · line 415
Quoted 10 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +9
-0+* command reaches the trusted main cmdq, so a guest cannot wedge the +* shared queue for every device with a CERROR_ILL. +* +* By contrast, an out-of-range address or ID value does not need a +* check: the spec defines it as CONSTRAINED UNPREDICTABLE, which is +* scoped to the guest itself and does not raise a CERROR_ILL. +*/ +if ((data[0 ] & ~allowed[0 ]) || (data[1 ] & ~allowed[1 ])) +return -EIO ;
Could we split the validation logic into it's own helper function? That way the helper does arch validation and this function converts the cmd. That'd make it easier to add new opcodes in the future and improve readability as well. Something along these lines:
static int arm_vsmmu_validate_user_cmd(struct arm_vsmmu *vsmmu, u64 data[2])
{
struct arm_smmu_device *smmu = vsmmu->smmu;
u64 allowed[2] = { CMDQ_0_OP };
/* Collect the fields userspace is allowed to set for each opcode */
switch (data[0] & CMDQ_0_OP) {
case CMDQ_OP_TLBI_NH_VA:
/* ... (all the allowlist logic) ... */
}
/*
* Reject any other bit, e.g. a RES0 bit or a Secure bit...
*/
if ((data[0] & ~allowed[0]) || (data[1] & ~allowed[1]))
return -EIO;
return 0;
}static int arm_vsmmu_convert_user_cmd(struct arm_vsmmu *vsmmu,
struct arm_vsmmu_invalidation_cmd *cmd)
{
u64 *data = cmd->cmd.data;
int ret;
/* Commands are le64 stored in u64 */
data[0] = le64_to_cpu(cmd->ucmd.cmd[0]);
data[1] = le64_to_cpu(cmd->ucmd.cmd[1]);
ret = arm_vsmmu_validate_user_cmd(vsmmu, data);
if (ret)
return ret;
/* Command Transformation */
switch (data[0] & CMDQ_0_OP) {
case CMDQ_OP_TLBI_NSNH_ALL:
/* ... (VMID/SID patching) ... */
}
return 0;
}Apart from that, the arch checks/allowlisting logic looks good.
Quoted 52 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +9
-9-switch (cmd->cmd.data[0 ] &CMDQ_0_OP ) { +switch (data[0 ] &CMDQ_0_OP ) {case CMDQ_OP_TLBI_NSNH_ALL :/* Convert to NH_ALL */ - cmd->cmd.data[0 ] =CMDQ_OP_TLBI_NH_ALL | -FIELD_PREP (CMDQ_TLBI_0_VMID , vsmmu->vmid); - cmd->cmd.data[1 ] =0 ; + data[0 ] =CMDQ_OP_TLBI_NH_ALL | +FIELD_PREP (CMDQ_TLBI_0_VMID , vsmmu->vmid); + data[1 ] =0 ;break ;case CMDQ_OP_TLBI_NH_VA :case CMDQ_OP_TLBI_NH_VAA :case CMDQ_OP_TLBI_NH_ALL :case CMDQ_OP_TLBI_NH_ASID : - cmd->cmd.data[0 ] &= ~CMDQ_TLBI_0_VMID ; - cmd->cmd.data[0 ] |=FIELD_PREP (CMDQ_TLBI_0_VMID , vsmmu->vmid); + data[0 ] &= ~CMDQ_TLBI_0_VMID ; + data[0 ] |=FIELD_PREP (CMDQ_TLBI_0_VMID , vsmmu->vmid);break ;case CMDQ_OP_ATC_INV :case CMDQ_OP_CFGI_CD :case CMDQ_OP_CFGI_CD_ALL : { -u32 sid, vsid =FIELD_GET (CMDQ_CFGI_0_SID , cmd->cmd.data[0 ]); +u32 sid, vsid =FIELD_GET (CMDQ_CFGI_0_SID , data[0 ]);if (arm_vsmmu_vsid_to_sid (vsmmu, vsid, &sid))return -EIO ; - cmd->cmd.data[0 ] &= ~CMDQ_CFGI_0_SID ; - cmd->cmd.data[0 ] |=FIELD_PREP (CMDQ_CFGI_0_SID , sid); + data[0 ] &= ~CMDQ_CFGI_0_SID ; + data[0 ] |=FIELD_PREP (CMDQ_CFGI_0_SID , sid);break ; }default :drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +2
-1@@ -2446,9 +2446,10 @@ static void arm_smmu_cmdq_batch_add_range(struct arm_smmu_device *smmu,/* Determine how many chunks of 2^scale size we have */ num = (num_pages >> scale) &CMDQ_TLBI_RANGE_NUM_MAX ; +/* Keep the pre-DS 5-bit truncation when scale > 31 */ cmd->data[0 ] = orig_data0 |FIELD_PREP (CMDQ_TLBI_0_NUM , num -1 ) | -FIELD_PREP (CMDQ_TLBI_0_SCALE , scale); +FIELD_PREP (CMDQ_TLBI_0_SCALE , scale &0x1f );/* range is num * 2^scale * pgsize */ inv_range = num << (scale + tg);
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu) · line 5104
Quoted 6 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +3
-0@@ -5098,6 +5099,9 @@ static int arm_smmu_device_hw_probe(struct arm_smmu_device *smmu)/* Maximum number of outstanding stalls */ smmu->evtq.max_stalls =FIELD_GET (IDR5_STALL_MAX , reg); +if (reg &IDR5_DS ) + smmu->features |=ARM_SMMU_FEAT_DS ; +
Same as above, let's break DS support into a separate patch.
Quoted 7 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3.c +0
-0/* Page sizes */ if (reg &IDR5_GRAN64K ) smmu->pgsize_bitmap |=SZ_64K |SZ_512M ;Signature · 1 line
2.43.0
Thanks,
Praan
Jason Gunthorpe
maintainerRe: [PATCH v3 1/5] iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation commands
Quoted 14 lines · Pranjal Shrivastava
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c struct arm_vsmmu_invalidation_cmd { · line 320
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +8
-0+u8 ttl =FIELD_GET (CMDQ_TLBI_1_TTL , cmd->cmd.data[1 ]); +u8 tg =FIELD_GET (CMDQ_TLBI_1_TG , cmd->cmd.data[1 ]); + +/* NUM, SCALE and TTL are RES0 when TG == 0 */ +if (!tg) +return (range || ttl) ? -EIO :0 ; +/* TTL == 0b01 with a 16KB TG requires SMMU_IDR5.DS */ +if (tg ==2 && ttl ==1 && !(smmu->features &ARM_SMMU_FEAT_DS ))I agree we must check this but I'm a little confused, How is a user / VMM depending on the IDR5.DS bit today? The IDR5 is exposed to the user via
hw_infoAND in the uAPI documentation block we explicitly mention for structiommu_hw_info_arm_smmuv3the following valid fields for IDR5:
Quoted 1 line
idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K
Probably soon we will add DS to that comment and it will be fully backwards compatible so a new VMM following the new guidance can just read DS.
Thus we can't have any kernel release that doesn't work with a VMM following DS...
Jason
Re: [PATCH v3 1/5] iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation commands
Quoted 15 lines · Jason Gunthorpe
Quoted 14 lines · Pranjal Shrivastava
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c struct arm_vsmmu_invalidation_cmd { · line 320
Quoted 8 lines
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +8
-0+u8 ttl =FIELD_GET (CMDQ_TLBI_1_TTL , cmd->cmd.data[1 ]); +u8 tg =FIELD_GET (CMDQ_TLBI_1_TG , cmd->cmd.data[1 ]); + +/* NUM, SCALE and TTL are RES0 when TG == 0 */ +if (!tg) +return (range || ttl) ? -EIO :0 ; +/* TTL == 0b01 with a 16KB TG requires SMMU_IDR5.DS */ +if (tg ==2 && ttl ==1 && !(smmu->features &ARM_SMMU_FEAT_DS ))I agree we must check this but I'm a little confused, How is a user / VMM depending on the IDR5.DS bit today? The IDR5 is exposed to the user via
hw_infoAND in the uAPI documentation block we explicitly mention for structiommu_hw_info_arm_smmuv3the following valid fields for IDR5:
Quoted 9 lines
Quoted 1 line
idr[5]: VAX, GRAN64K, GRAN16K, GRAN4K
Probably soon we will add DS to that comment and it will be fully backwards compatible so a new VMM following the new guidance can just read DS.
Thus we can't have any kernel release that doesn't work with a VMM following DS...
Ack. Understood.
Praan
Re: [PATCH v3 1/5] iommu/arm-smmu-v3-iommufd: Reject unsupported bits in invalidation commands
In reply to Pranjal ShrivastavaQuoted 4 lines · Pranjal Shrivastava
Regarding this series, since we are formally adding support for the DS bit in this series for range invalidations (preferably as a separate patch as mentioned above). We should also update the uAPI doc for struct
iommu_hw_info_arm_smmuv3ininclude/uapi/linux/iommufd.h? Like:
I sent v4 that should addressed all your comments here.
Thanks
Nicolin
PATCH v3 2/5 iommufd: Iterate the cache invalidation array in the core
In reply to Nicolin ChenThe cache invalidation ops, cache_invalidate_user() for a nested HWPT and the cache_invalidate() for a vIOMMU, are each handed the full user request array and report how many of the array entries they handled by setting the array->entry_num. Every driver therefore implements its own loop over the array, and a driver wanting to process that array in fixed-size chunks (e.g. to issue commands out of a fixed-size on-stack buffer) has to carry the loop and its sub-array bookkeeping all on its own.
Move the iteration into the iommufd core instead. Invoke the op with a sub-array that starts at the first not-yet-handled entry, let it handle a prefix of that sub-array and report the count via array->entry_num, then advance the base pointer and re-invoke the op until the entire array has been consumed or until the op returns an error along the way.
A driver that handles the entire window in one single call, as all of the current drivers happen to do, finishes the loop in just one pass, so this does not change any of the existing behavior. It instead lets each of the drivers convert to bounded chunk processing on its own, done by each of the subsequent changes.
--- include/linux/iommu.h | 6 ++++-- include/linux/iommufd.h | 2 ++ drivers/iommu/iommufd/hw_pagetable.c | 25 +++++++++++++++---------- 3 files changed, 21 insertions(+), 12 deletions(-)
include/linux/iommu.h +4 -2
@@ -773,8 +773,10 @@ struct iommu_ops {* passes in the cache invalidation requests, in form * of a driver data structure. The driver must update * array->entry_num to report the number of handled -* invalidation requests. The driver data structure -* must be defined in include/uapi/linux/iommufd.h +* invalidation requests. A driver may handle fewer than +* the requested, in which case the core re-invokes the +* op for the remainder. The driver data structure must +* be defined in include/uapi/linux/iommufd.h * @iova_to_phys: translate iova to physical address * @enforce_cache_coherency: Prevent any kind of DMA from bypassing IOMMU_CACHE, * including no-snoop TLPs on PCIe or other platform
include/linux/iommufd.h +2 -0
@@ -154,6 +154,8 @@ struct iommufd_hw_queue {* The @array passes in the cache invalidation requests, in * form of a driver data structure. A driver must update the * array->entry_num to report the number of handled requests. +* A driver may handle fewer than the requested entry_num, in +* which case the core re-invokes the op for the remainder. * The data structure of the array entry must be defined in * include/uapi/linux/iommufd.h * @vdevice_size: Size of the driver-defined vDEVICE structure per this vIOMMU
drivers/iommu/iommufd/hw_pagetable.c +15 -10
@@ -501,6 +501,8 @@ int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd) .entry_len = cmd->entry_len, .entry_num = cmd->entry_num, }; +struct iommufd_hw_pagetable *hwpt =NULL ; +struct iommufd_viommu *viommu =NULL ;struct iommufd_object *pt_obj;u32 done_num =0 ;int rc;
@@ -527,31 +529,34 @@ int iommufd_hwpt_invalidate(struct iommufd_ucmd *ucmd) goto out; }if (pt_obj->type ==IOMMUFD_OBJ_HWPT_NESTED ) { -struct iommufd_hw_pagetable *hwpt = - container_of(pt_obj, struct iommufd_hw_pagetable, obj); - + hwpt = container_of(pt_obj, struct iommufd_hw_pagetable, obj);if (!hwpt->domain->ops || !hwpt->domain->ops->cache_invalidate_user) { rc = -EOPNOTSUPP ; goto out_put_pt; } - rc = hwpt->domain->ops->cache_invalidate_user (hwpt->domain, - &data_array); }else if (pt_obj->type ==IOMMUFD_OBJ_VIOMMU ) { -struct iommufd_viommu *viommu = - container_of(pt_obj, struct iommufd_viommu, obj); - + viommu = container_of(pt_obj, struct iommufd_viommu, obj);if (!viommu->ops || !viommu->ops->cache_invalidate) { rc = -EOPNOTSUPP ; goto out_put_pt; } - rc = viommu->ops->cache_invalidate (viommu, &data_array); }else { rc = -EINVAL ; goto out_put_pt; } - done_num = data_array.entry_num; +do { +if (viommu) + rc = viommu->ops->cache_invalidate (viommu, &data_array); +else + rc = hwpt->domain->ops->cache_invalidate_user ( + hwpt->domain, &data_array); + + done_num += data_array.entry_num; + data_array.uptr += data_array.entry_num * data_array.entry_len; + data_array.entry_num = cmd->entry_num - done_num; + }while (!rc && done_num != cmd->entry_num); out_put_pt:iommufd_put_object (ucmd->ictx, pt_obj);
Signature · 1 line
2.43.0
Quoted 24 lines · Nicolin Chen
The cache invalidation ops,
cache_invalidate_user()for a nested HWPT and thecache_invalidate()for a vIOMMU, are each handed the full user request array and report how many of the array entries they handled by setting thearray->entry_num. Every driver therefore implements its own loop over the array, and a driver wanting to process that array in fixed-size chunks (e.g. to issue commands out of a fixed-size on-stack buffer) has to carry the loop and its sub-array bookkeeping all on its own.Move the iteration into the iommufd core instead. Invoke the op with a sub-array that starts at the first not-yet-handled entry, let it handle a prefix of that sub-array and report the count via
array->entry_num, then advance the base pointer and re-invoke the op until the entire array has been consumed or until the op returns an error along the way.A driver that handles the entire window in one single call, as all of the current drivers happen to do, finishes the loop in just one pass, so this does not change any of the existing behavior. It instead lets each of the drivers convert to bounded chunk processing on its own, done by each of the subsequent changes.
Suggested-by Jason Gunthorpe<[email protected]>Reviewed-by Kevin Tian<[email protected]>Assisted-by Claude:claude-opus-4-8Signed-off-by Nicolin Chen<[email protected]>
Quoted 5 lines
--- include/linux/iommu.h | 6 ++++-- include/linux/iommufd.h | 2 ++ drivers/iommu/iommufd/hw_pagetable.c | 25 +++++++++++++++---------- 3 files changed, 21 insertions(+), 12 deletions(-)
Quoted 20 lines · Nicolin Chen
The cache invalidation ops,
cache_invalidate_user()for a nested HWPT and thecache_invalidate()for a vIOMMU, are each handed the full user request array and report how many of the array entries they handled by setting thearray->entry_num. Every driver therefore implements its own loop over the array, and a driver wanting to process that array in fixed-size chunks (e.g. to issue commands out of a fixed-size on-stack buffer) has to carry the loop and its sub-array bookkeeping all on its own.Move the iteration into the iommufd core instead. Invoke the op with a sub-array that starts at the first not-yet-handled entry, let it handle a prefix of that sub-array and report the count via
array->entry_num, then advance the base pointer and re-invoke the op until the entire array has been consumed or until the op returns an error along the way.A driver that handles the entire window in one single call, as all of the current drivers happen to do, finishes the loop in just one pass, so this does not change any of the existing behavior. It instead lets each of the drivers convert to bounded chunk processing on its own, done by each of the subsequent changes.
Quoted 4 lines
Suggested-by Jason Gunthorpe <[email protected]>Reviewed-by Kevin Tian <[email protected]>Assisted-by Claude:claude-opus-4-8Signed-off-by Nicolin Chen <[email protected]>
Thanks,
Praan
PATCH v3 3/5 iommufd/selftest: Convert cache invalidation mocks to the core array loop
In reply to Nicolin ChenThe vIOMMU and the nested-domain selftest invalidation mocks each used to walk the whole request array on their own, with the vIOMMU mock even allocating a buffer sized to the entire array in order to do so first.
The iommufd core now iterates the request array itself and re-invokes the op with the not-yet-handled sub-array, so handle just a single request per call out of the front of that sub-array and report one handled entry via the array->entry_num. Drop both of the loops and the kzalloc_objs() in the viommu callback function, and keep returning a success for an empty array as a probe of the selftest data type.
--- drivers/iommu/iommufd/selftest.c | 147 +++++++++++++++---------------- 1 file changed, 72 insertions(+), 75 deletions(-)
drivers/iommu/iommufd/selftest.c +72 -75
@@ -631,70 +631,63 @@ mock_viommu_alloc_domain_nested(struct iommufd_viommu *viommu, u32 flags,static int mock_viommu_cache_invalidate (struct iommufd_viommu *viommu,struct iommu_user_data_array *array) { -struct iommu_viommu_invalidate_selftest *cmds; -struct iommu_viommu_invalidate_selftest *cur; -struct iommu_viommu_invalidate_selftest *end; -int rc; +struct iommu_viommu_invalidate_selftest cmd; +struct mock_dev *mdev; +struct device *dev; +u32 processed =0 ; +int rc =0 ; +int i; -/* A zero-length array is allowed to validate the array type */ -if (array->entry_num ==0 && - array->type ==IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST ) { - array->entry_num =0 ; -return 0 ; +if (array->type !=IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST ) { + rc = -EINVAL ; + goto out; } - cmds =kzalloc_objs (*cmds, array->entry_num); -if (!cmds) -return -ENOMEM ; - cur = cmds; - end = cmds + array->entry_num; +/* +* The core re-invokes this op for the remaining requests, so handle one +* request per call. A zero-length array only probes the type, validated +* above. +*/ +if (!array->entry_num) +return 0 ; -static_assert (sizeof (*cmds) ==3 *sizeof (u32)); - rc =iommu_copy_struct_from_full_user_array ( - cmds,sizeof (*cmds), array, -IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST ); + rc =iommu_copy_struct_from_user_array ( + &cmd, array,IOMMU_VIOMMU_INVALIDATE_DATA_SELFTEST ,0 , + cache_id);if (rc) goto out; -while (cur != end) { -struct mock_dev *mdev; -struct device *dev; -int i; - -if (cur->flags & ~IOMMU_TEST_INVALIDATE_FLAG_ALL ) { - rc = -EOPNOTSUPP ; - goto out; - } - -if (cur->cache_id >MOCK_DEV_CACHE_ID_MAX ) { - rc = -EINVAL ; - goto out; - } +if (cmd.flags & ~IOMMU_TEST_INVALIDATE_FLAG_ALL ) { + rc = -EOPNOTSUPP ; + goto out; + } -xa_lock (&viommu->vdevs); - dev =iommufd_viommu_find_dev (viommu, - (unsigned long )cur->vdev_id); -if (!dev) { -xa_unlock (&viommu->vdevs); - rc = -EINVAL ; - goto out; - } - mdev = container_of(dev, struct mock_dev, dev); +if (cmd.cache_id >MOCK_DEV_CACHE_ID_MAX ) { + rc = -EINVAL ; + goto out; + } -if (cur->flags &IOMMU_TEST_INVALIDATE_FLAG_ALL ) { -/* Invalidate all cache entries and ignore cache_id */ -for (i =0 ; i <MOCK_DEV_CACHE_NUM ; i++) - mdev->cache[i] =0 ; - }else { - mdev->cache[cur->cache_id] =0 ; - } +xa_lock (&viommu->vdevs); + dev =iommufd_viommu_find_dev (viommu, (unsigned long )cmd.vdev_id); +if (!dev) {xa_unlock (&viommu->vdevs); - - cur++; + rc = -EINVAL ; + goto out; + } + mdev = container_of(dev, struct mock_dev, dev); + +if (cmd.flags &IOMMU_TEST_INVALIDATE_FLAG_ALL ) { +/* Invalidate all cache entries and ignore cache_id */ +for (i =0 ; i <MOCK_DEV_CACHE_NUM ; i++) + mdev->cache[i] =0 ; + }else { + mdev->cache[cmd.cache_id] =0 ; } +xa_unlock (&viommu->vdevs); + + processed =1 ; out: - array->entry_num = cur - cmds; -kfree (cmds); + array->entry_num = processed;return rc; }
@@ -875,42 +868,46 @@ mock_domain_cache_invalidate_user(struct iommu_domain *domain,struct mock_iommu_domain_nested *mock_nested =to_mock_nested (domain);struct iommu_hwpt_invalidate_selftest inv;u32 processed =0 ; -int i =0 , j;int rc =0 ; +int i;if (array->type !=IOMMU_HWPT_INVALIDATE_DATA_SELFTEST ) { rc = -EINVAL ; goto out; } -for ( ; i < array->entry_num; i++) { - rc =iommu_copy_struct_from_user_array (&inv, array, -IOMMU_HWPT_INVALIDATE_DATA_SELFTEST , - i, iotlb_id); -if (rc) -break ; +/* +* The core re-invokes this op for the remaining requests, so handle one +* request per call. A zero-length array only probes the type, validated +* above. +*/ +if (!array->entry_num) +return 0 ; -if (inv.flags & ~IOMMU_TEST_INVALIDATE_FLAG_ALL ) { - rc = -EOPNOTSUPP ; -break ; - } + rc =iommu_copy_struct_from_user_array ( + &inv, array,IOMMU_HWPT_INVALIDATE_DATA_SELFTEST ,0 , iotlb_id); +if (rc) + goto out; -if (inv.iotlb_id >MOCK_NESTED_DOMAIN_IOTLB_ID_MAX ) { - rc = -EINVAL ; -break ; - } +if (inv.flags & ~IOMMU_TEST_INVALIDATE_FLAG_ALL ) { + rc = -EOPNOTSUPP ; + goto out; + } -if (inv.flags &IOMMU_TEST_INVALIDATE_FLAG_ALL ) { -/* Invalidate all mock iotlb entries and ignore iotlb_id */ -for (j =0 ; j <MOCK_NESTED_DOMAIN_IOTLB_NUM ; j++) - mock_nested->iotlb[j] =0 ; - }else { - mock_nested->iotlb[inv.iotlb_id] =0 ; - } +if (inv.iotlb_id >MOCK_NESTED_DOMAIN_IOTLB_ID_MAX ) { + rc = -EINVAL ; + goto out; + } - processed++; +if (inv.flags &IOMMU_TEST_INVALIDATE_FLAG_ALL ) { +/* Invalidate all mock iotlb entries and ignore iotlb_id */ +for (i =0 ; i <MOCK_NESTED_DOMAIN_IOTLB_NUM ; i++) + mock_nested->iotlb[i] =0 ; + }else { + mock_nested->iotlb[inv.iotlb_id] =0 ; } + processed =1 ; out: array->entry_num = processed;return rc;
Signature · 1 line
2.43.0
Re: [PATCH v3 3/5] iommufd/selftest: Convert cache invalidation mocks to the core array loop
Quoted 11 lines · Nicolin Chen
The vIOMMU and the nested-domain selftest invalidation mocks each used to walk the whole request array on their own, with the vIOMMU mock even allocating a buffer sized to the entire array in order to do so first.
The iommufd core now iterates the request array itself and re-invokes the op with the not-yet-handled sub-array, so handle just a single request per call out of the front of that sub-array and report one handled entry via the
array->entry_num. Drop both of the loops and thekzalloc_objs()in the viommu callback function, and keep returning a success for an empty array as a probe of the selftest data type.
Quoted 3 lines
Reviewed-by Kevin Tian <[email protected]>Assisted-by Claude:claude-opus-4-8Signed-off-by Nicolin Chen <[email protected]>
Thanks,
Praan
PATCH v3 4/5 iommu/arm-smmu-v3-iommufd: Convert cache invalidation to the core array loop
In reply to Nicolin Chenarm_vsmmu_cache_invalidate() allocated a buffer for the entire user request array, walked the array converting each of the commands, and issued those converted commands to the cmdq in CMDQ_BATCH_ENTRIES sized chunks, carrying the sub-array bookkeeping all on its own.
The iommufd core now iterates the invalidation array and re-invokes the op with the not-yet-handled sub-array, so the driver only has to proceed with a single chunk per call.
Instead of a per-array allocation, use a fixed on-stack batch to copy from the userspace array. If the copy fails due to nonzero padding (VMM violates the ABI), fail the entire batch.
Convert the whole batch before issuing any of it: a malformed command is a userspace bug, so the first illegal command fails the batch as a unit, issuing nothing and leaving array->entry_num at zero, the same way the copy above bails on nonzero padding. A batch that converts cleanly is issued in full, so the op returns either a handled count with no error or zero with an error.
A zero-length array now returns success once the data type gets validated, matching the documented probe behavior, rather than the -EINVAL that the full-array copy helper would previously return.
This also fixes two long-standing bugs:
1) On a conversion failure the old code reported commands that it had converted but not yet issued, so user space advanced its consumer index past invalidations that never reached the cmdq.
2) A zero-length array was rejected with -EINVAL, although the uAPI documents it as a valid request that only probes the data type.
--- .../arm/arm-smmu-v3/arm-smmu-v3-iommufd.c | 68 +++++++++++-------- 1 file changed, 38 insertions(+), 30 deletions(-)
drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c +38 -30
@@ -448,49 +448,57 @@ int arm_vsmmu_cache_invalidate(struct iommufd_viommu *viommu, struct iommu_user_data_array *array) { struct arm_vsmmu *vsmmu = container_of(viommu, struct arm_vsmmu, core); +struct arm_vsmmu_invalidation_cmd cmds[CMDQ_BATCH_ENTRIES -1 ];struct arm_smmu_device *smmu = vsmmu->smmu; -struct arm_vsmmu_invalidation_cmd *last; -struct arm_vsmmu_invalidation_cmd *cmds; -struct arm_vsmmu_invalidation_cmd *cur; -struct arm_vsmmu_invalidation_cmd *end; +struct iommu_user_data_array batch = { + .type = array->type, + .uptr = array->uptr, + .entry_len = array->entry_len, + }; +u32 processed =0 ;int ret; - - cmds =kzalloc_objs (*cmds, array->entry_num); -if (!cmds) -return -ENOMEM ; - cur = cmds; - end = cmds + array->entry_num; +u32 i;static_assert (sizeof (*cmds) ==2 *sizeof (u64)); + +if (array->type !=IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3 ) { + ret = -EINVAL ; + goto out; + } + +/* A zero-length array only probes the type, validated above */ +if (!array->entry_num) +return 0 ; + +/* +* The core re-invokes this op for the remaining requests, so copy one +* cmdq batch worth of commands into a fixed on-stack buffer rather than +* allocating for the whole array. +*/ + batch.entry_num =min_t (u32, array->entry_num,ARRAY_SIZE (cmds)); ret =iommu_copy_struct_from_full_user_array ( - cmds,sizeof (*cmds), array, + cmds,sizeof (*cmds), &batch,IOMMU_VIOMMU_INVALIDATE_DATA_ARM_SMMUV3 );if (ret) goto out; - last = cmds; -while (cur != end) { - ret =arm_vsmmu_convert_user_cmd (vsmmu, cur); +/* +* Convert the whole batch. Sending an illegal command is a VMM bug, so +* a single one fails the entire batch, issuing nothing. +*/ +for (i =0 ; i < batch.entry_num; i++) { + ret =arm_vsmmu_convert_user_cmd (vsmmu, &cmds[i]);if (ret) goto out; - -/* FIXME work in blocks of CMDQ_BATCH_ENTRIES and copy each block? */ - cur++; -if (cur != end && (cur - last) !=CMDQ_BATCH_ENTRIES -1 ) -continue ; - -/* FIXME always uses the main cmdq rather than trying to group by type */ - ret =arm_smmu_cmdq_issue_cmdlist (smmu, &smmu->cmdq, &last->cmd, - cur - last, true); -if (ret) { - cur--; - goto out; - } - last = cur; } + +/* FIXME always uses the main cmdq rather than trying to group by type */ + ret =arm_smmu_cmdq_issue_cmdlist (smmu, &smmu->cmdq, &cmds->cmd, + batch.entry_num, true); +if (!ret) + processed = batch.entry_num; out: - array->entry_num = cur - cmds; -kfree (cmds); + array->entry_num = processed;return ret; }
Signature · 1 line
2.43.0
Re: [PATCH v3 4/5] iommu/arm-smmu-v3-iommufd: Convert cache invalidation to the core array loop
Quoted 31 lines · Nicolin Chen
arm_vsmmu_cache_invalidate()allocated a buffer for the entire user request array, walked the array converting each of the commands, and issued those converted commands to the cmdq inCMDQ_BATCH_ENTRIESsized chunks, carrying the sub-array bookkeeping all on its own.The iommufd core now iterates the invalidation array and re-invokes the op with the not-yet-handled sub-array, so the driver only has to proceed with a single chunk per call.
Instead of a per-array allocation, use a fixed on-stack batch to copy from the userspace array. If the copy fails due to nonzero padding (VMM violates the ABI), fail the entire batch.
Convert the whole batch before issuing any of it: a malformed command is a userspace bug, so the first illegal command fails the batch as a unit, issuing nothing and leaving
array->entry_numat zero, the same way the copy above bails on nonzero padding. A batch that converts cleanly is issued in full, so the op returns either a handled count with no error or zero with an error.A zero-length array now returns success once the data type gets validated, matching the documented probe behavior, rather than the
-EINVALthat the full-array copy helper would previously return.This also fixes two long-standing bugs:
1) On a conversion failure the old code reported commands that it had converted but not yet issued, so user space advanced its consumer index past invalidations that never reached the cmdq.
2) A zero-length array was rejected with-EINVAL, although the uAPI documents it as a valid request that only probes the data type.
Quoted 3 lines
Reviewed-by Kevin Tian <[email protected]>Assisted-by Claude:claude-opus-4-8Signed-off-by Nicolin Chen <[email protected]>
Thanks,
Praan
PATCH v3 5/5 iommu/vt-d: Convert nested cache invalidation to the core array loop
In reply to Nicolin Chenintel_nested_cache_invalidate_user() used to walk the whole request array on its own, copying and then flushing one single entry at a time.
The iommufd core now iterates the request array itself and re-invokes the op with the not-yet-handled sub-array, so handle just a single request per call out of the front of that sub-array and report one handled entry via the array->entry_num. An empty array keeps returning a success, used as a probe of IOMMU_HWPT_INVALIDATE_DATA_VTD_S1 support.
--- drivers/iommu/intel/nested.c | 54 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 24 deletions(-)
drivers/iommu/intel/nested.c +30 -24
@@ -93,7 +93,7 @@ static int intel_nested_cache_invalidate_user(struct iommu_domain *domain, {struct dmar_domain *dmar_domain =to_dmar_domain (domain);struct iommu_hwpt_vtd_s1_invalidate inv_entry; -u32 index, processed =0 ; +u32 processed =0 ;int ret =0 ;if (array->type !=IOMMU_HWPT_INVALIDATE_DATA_VTD_S1 ) {
@@ -101,31 +101,37 @@ static int intel_nested_cache_invalidate_user(struct iommu_domain *domain, goto out; } -for (index =0 ; index < array->entry_num; index++) { - ret =iommu_copy_struct_from_user_array (&inv_entry, array, -IOMMU_HWPT_INVALIDATE_DATA_VTD_S1 , - index, __reserved); -if (ret) -break ; - -if ((inv_entry.flags & ~IOMMU_VTD_INV_FLAGS_LEAF ) || - inv_entry.__reserved) { - ret = -EOPNOTSUPP ; -break ; - } - -if (!IS_ALIGNED (inv_entry.addr,VTD_PAGE_SIZE ) || - ((inv_entry.npages ==U64_MAX ) && inv_entry.addr)) { - ret = -EINVAL ; -break ; - } - -cache_tag_flush_range (dmar_domain, inv_entry.addr, - inv_entry.addr +nrpages_to_size (inv_entry.npages) -1 , - inv_entry.flags &IOMMU_VTD_INV_FLAGS_LEAF ); - processed++; +/* +* The core re-invokes this op for the remaining requests, so handle one +* request per call. A zero-length array only probes the type, validated +* above. +*/ +if (!array->entry_num) +return 0 ; + + ret =iommu_copy_struct_from_user_array ( + &inv_entry, array,IOMMU_HWPT_INVALIDATE_DATA_VTD_S1 ,0 , + __reserved); +if (ret) + goto out; + +if ((inv_entry.flags & ~IOMMU_VTD_INV_FLAGS_LEAF ) || + inv_entry.__reserved) { + ret = -EOPNOTSUPP ; + goto out; + } + +if (!IS_ALIGNED (inv_entry.addr,VTD_PAGE_SIZE ) || + (inv_entry.npages ==U64_MAX && inv_entry.addr)) { + ret = -EINVAL ; + goto out; } +cache_tag_flush_range (dmar_domain, inv_entry.addr, + inv_entry.addr + +nrpages_to_size (inv_entry.npages) -1 , + inv_entry.flags &IOMMU_VTD_INV_FLAGS_LEAF ); + processed =1 ; out: array->entry_num = processed;return ret;
Signature · 1 line
2.43.0
Re: [PATCH v3 5/5] iommu/vt-d: Convert nested cache invalidation to the core array loop
Quoted 9 lines · Nicolin Chen
intel_nested_cache_invalidate_user()used to walk the whole request array on its own, copying and then flushing one single entry at a time.The iommufd core now iterates the request array itself and re-invokes the op with the not-yet-handled sub-array, so handle just a single request per call out of the front of that sub-array and report one handled entry via the
array->entry_num. An empty array keeps returning a success, used as a probe ofIOMMU_HWPT_INVALIDATE_DATA_VTD_S1support.
Quoted 6 lines
Reviewed-by Kevin Tian<[email protected]>Assisted-by Claude:claude-opus-4-8Signed-off-by Nicolin Chen<[email protected]>--- drivers/iommu/intel/nested.c | 54 ++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 24 deletions(-)