diff options
| author | Mark Brown <broonie@kernel.org> | 2026-10-03 02:05:06 +0200 |
|---|---|---|
| committer | Mark Brown <broonie@kernel.org> | 2026-10-03 02:05:06 +0200 |
| commit | 0ffa262de2bee00d2346f58fc93c03dd2c1b6fbe (patch) | |
| tree | 35940cb16ade2f4dcac207cbda9088bafb3c2d89 | |
| parent | 51da7f2c332907224cb9f1ae7a90c1a7d816a9e8 (diff) | |
| parent | 54dadb030c7e2350957855d3995de05ae02c2e66 (diff) | |
| download | linux-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.c | 197 | ||||
| -rw-r--r-- | drivers/iommu/intel/nested.c | 54 | ||||
| -rw-r--r-- | drivers/iommu/iommufd/hw_pagetable.c | 25 | ||||
| -rw-r--r-- | drivers/iommu/iommufd/selftest.c | 153 | ||||
| -rw-r--r-- | include/linux/iommu.h | 6 | ||||
| -rw-r--r-- | include/linux/iommufd.h | 2 | ||||
| -rw-r--r-- | include/uapi/linux/iommufd.h | 6 | ||||
| -rw-r--r-- | tools/testing/selftests/iommu/iommufd.c | 14 | ||||
| -rw-r--r-- | tools/testing/selftests/iommu/iommufd_fail_nth.c | 14 |
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); +} |
