[PATCH v1 3/5] iommufd/selftest: Convert cache invalidation mocks to the core array loop

From: Nicolin Chen

Date: Mon Jun 29 2026 - 17:17:57 EST


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 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.

Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Nicolin Chen <nicolinc@xxxxxxxxxx>
---
drivers/iommu/iommufd/selftest.c | 147 +++++++++++++++----------------
1 file changed, 72 insertions(+), 75 deletions(-)

diff --git a/drivers/iommu/iommufd/selftest.c b/drivers/iommu/iommufd/selftest.c
index af07c642a5260..51da687e432ef 100644
--- a/drivers/iommu/iommufd/selftest.c
+++ b/drivers/iommu/iommufd/selftest.c
@@ -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)
+ goto out;

- 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)
+ goto out;

- 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;
--
2.43.0