summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-10-03 02:05:06 +0200
committerMark Brown <broonie@kernel.org>2026-10-03 02:05:06 +0200
commit0ffa262de2bee00d2346f58fc93c03dd2c1b6fbe (patch)
tree35940cb16ade2f4dcac207cbda9088bafb3c2d89
parent51da7f2c332907224cb9f1ae7a90c1a7d816a9e8 (diff)
parent54dadb030c7e2350957855d3995de05ae02c2e66 (diff)
downloadlinux-next-0ffa262de2bee00d2346f58fc93c03dd2c1b6fbe.tar.gz
linux-next-0ffa262de2bee00d2346f58fc93c03dd2c1b6fbe.zip
Merge branch 'for-next' of https://git.kernel.org/pub/scm/linux/kernel/git/jgg/iommufd.git
-rw-r--r--drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c197
-rw-r--r--drivers/iommu/intel/nested.c54
-rw-r--r--drivers/iommu/iommufd/hw_pagetable.c25
-rw-r--r--drivers/iommu/iommufd/selftest.c153
-rw-r--r--include/linux/iommu.h6
-rw-r--r--include/linux/iommufd.h2
-rw-r--r--include/uapi/linux/iommufd.h6
-rw-r--r--tools/testing/selftests/iommu/iommufd.c14
-rw-r--r--tools/testing/selftests/iommu/iommufd_fail_nth.c14
9 files changed, 311 insertions, 160 deletions
diff --git a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
index 25982bdbcbd9..ab1078a97d80 100644
--- a/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
+++ b/drivers/iommu/arm/arm-smmu-v3/arm-smmu-v3-iommufd.c
@@ -322,6 +322,106 @@ 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, u64 data[2])
+{
+ bool range = !!(data[0] & (CMDQ_TLBI_0_NUM | CMDQ_TLBI_0_SCALE));
+ u8 ttl = FIELD_GET(CMDQ_TLBI_1_TTL, data[1]);
+ u8 tg = FIELD_GET(CMDQ_TLBI_1_TG, 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))
+ return -EIO;
+ /* NUM == 0, SCALE == 0 with TTL == 0 is a reserved combination */
+ if (!range && !ttl)
+ return -EIO;
+ return 0;
+}
+
+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_NSNH_ALL:
+ break;
+ 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, data))
+ 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;
+ default:
+ return -EIO;
+ }
+
+ /*
+ * Reject any other bit, e.g. a RES0 bit or a Secure bit, before the
+ * 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;
+ 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
@@ -332,33 +432,40 @@ struct arm_vsmmu_invalidation_cmd {
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 */
- 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]);
- switch (cmd->cmd.data[0] & CMDQ_0_OP) {
+ ret = arm_vsmmu_validate_user_cmd(vsmmu, data);
+ if (ret)
+ return ret;
+
+ 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:
@@ -371,49 +478,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;
}
diff --git a/drivers/iommu/intel/nested.c b/drivers/iommu/intel/nested.c
index f84fc8b41fde..df72724242bf 100644
--- a/drivers/iommu/intel/nested.c
+++ b/drivers/iommu/intel/nested.c
@@ -95,7 +95,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) {
@@ -103,31 +103,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;
diff --git a/drivers/iommu/iommufd/hw_pagetable.c b/drivers/iommu/iommufd/hw_pagetable.c
index ef6e119c2a75..ae62db50885c 100644
--- a/drivers/iommu/iommufd/hw_pagetable.c
+++ b/drivers/iommu/iommufd/hw_pagetable.c
@@ -514,6 +514,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;
@@ -540,31 +542,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);
diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index ee706f18f7e9..25387969ab75 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -633,70 +633,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;
}
@@ -877,42 +870,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;
@@ -2056,11 +2053,9 @@ static int iommufd_test_dmabuf_get(struct iommufd_ucmd *ucmd,
}
rc = dma_buf_fd(dmabuf, open_flags);
- if (rc < 0) {
+ if (rc < 0)
dma_buf_put(dmabuf);
- return rc;
- }
- return 0;
+ return rc;
err_free:
kfree(priv->memory);
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index ac43b8b93f14..05f7cee1375e 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -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
diff --git a/include/linux/iommufd.h b/include/linux/iommufd.h
index 6e7efe83bc5d..3087f5b2def8 100644
--- a/include/linux/iommufd.h
+++ b/include/linux/iommufd.h
@@ -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
diff --git a/include/uapi/linux/iommufd.h b/include/uapi/linux/iommufd.h
index 206fa667c782..b4c7194cb08d 100644
--- a/include/uapi/linux/iommufd.h
+++ b/include/uapi/linux/iommufd.h
@@ -776,7 +776,7 @@ enum iommufd_hw_info_flags {
* @out_data_type: Output the iommu hardware info type as defined in the enum
* iommu_hw_info_type.
* @out_capabilities: Output the generic iommu capability info type as defined
- * in the enum iommu_hw_capabilities.
+ * in the enum iommufd_hw_capabilities.
* @out_max_pasid_log2: Output the width of PASIDs. 0 means no PASID support.
* PCI devices turn to out_capabilities to check if the
* specific capabilities is supported or not.
@@ -948,7 +948,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];
diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c
index d44b34b05757..44193d171ba9 100644
--- a/tools/testing/selftests/iommu/iommufd.c
+++ b/tools/testing/selftests/iommu/iommufd.c
@@ -3613,4 +3613,16 @@ TEST_F(iommufd_device_pasid, pasid_attach)
test_cmd_mock_domain_replace(self->stdev_id, self->ioas_id);
}
-TEST_HARNESS_MAIN
+static bool iommufd_mock_available(void)
+{
+ return access("/sys/bus/iommufd_mock", F_OK) == 0;
+}
+
+int main(int argc, char **argv)
+{
+ if (!iommufd_mock_available())
+ ksft_exit_skip(
+ "no iommufd mock device, the tests need CONFIG_IOMMUFD_TEST=y and the iommufd module loaded\n");
+
+ return test_harness_run(argc, argv);
+}
diff --git a/tools/testing/selftests/iommu/iommufd_fail_nth.c b/tools/testing/selftests/iommu/iommufd_fail_nth.c
index 25495d8dceb3..1a31fff3f56c 100644
--- a/tools/testing/selftests/iommu/iommufd_fail_nth.c
+++ b/tools/testing/selftests/iommu/iommufd_fail_nth.c
@@ -745,4 +745,16 @@ TEST_FAIL_NTH(basic_fail_nth, device)
return 0;
}
-TEST_HARNESS_MAIN
+static bool iommufd_mock_available(void)
+{
+ return access("/sys/bus/iommufd_mock", F_OK) == 0;
+}
+
+int main(int argc, char **argv)
+{
+ if (!iommufd_mock_available())
+ ksft_exit_skip(
+ "no iommufd mock device, the tests need CONFIG_IOMMUFD_TEST=y and the iommufd module loaded\n");
+
+ return test_harness_run(argc, argv);
+}