[PATCH 3/3] iommufd/selftest: Unmap areas that start inside a CPU page

From: Fred Griffoul

Date: Wed Nov 11 2026 - 14:00:33 EST


From: Fred Griffoul <fgriffo@xxxxxxxxxxxx>

On iommupt domains, iopt_area_unpin_domain() unmaps and collects PFNs
in one walk, which needs IOPTEs that line up with CPU pages. The mock
domain maps 2 KiB per IOPTE, so IOMMU_IOAS_MAP_FIXED_IOVA can place a
user address with a 2 KiB offset at a CPU page aligned IOVA. Such an
area must take the per-page read-back instead, or one of its pages
stays pinned.

Map a buffer at each MOCK_PAGE_SIZE offset in its first CPU page,
unmap it, and check that no page is left pinned.

Signed-off-by: Fred Griffoul <fgriffo@xxxxxxxxxxxx>
---
tools/testing/selftests/iommu/iommufd.c | 30 +++++++++++++++++++++++++
1 file changed, 30 insertions(+)

diff --git a/tools/testing/selftests/iommu/iommufd.c b/tools/testing/selftests/iommu/iommufd.c
index ca2b35e9d443a..14c8a12a3bc17 100644
--- a/tools/testing/selftests/iommu/iommufd.c
+++ b/tools/testing/selftests/iommu/iommufd.c
@@ -1890,6 +1890,36 @@ TEST_F(iommufd_mock_domain, all_aligns)
close(mfd);
}

+/*
+ * Map memory that starts inside a CPU page at a CPU page aligned IOVA, which
+ * the mock domain's MOCK_PAGE_SIZE IOPTEs allow, then unmap it and check that
+ * every page is unpinned.
+ */
+TEST_F(iommufd_mock_domain, fixed_iova_offsets)
+{
+ size_t buf_size = self->mmap_buf_size;
+ size_t length = buf_size - PAGE_SIZE;
+ unsigned int start;
+ uint8_t *buf;
+
+ if (variant->file)
+ SKIP(return, "Skipping file variant, the test maps anonymous memory");
+
+ buf = mmap(0, buf_size, PROT_READ | PROT_WRITE, self->mmap_flags, -1,
+ 0);
+ ASSERT_NE(MAP_FAILED, buf);
+ check_refs(buf, buf_size, 0);
+
+ for (start = 0; start < PAGE_SIZE; start += MOCK_PAGE_SIZE) {
+ test_ioctl_ioas_map_fixed(buf + start, length,
+ MOCK_APERTURE_START);
+ check_mock_iova(buf + start, MOCK_APERTURE_START, length);
+ test_ioctl_ioas_unmap(MOCK_APERTURE_START, length);
+ check_refs(buf, buf_size, 0);
+ }
+ ASSERT_EQ(0, munmap(buf, buf_size));
+}
+
TEST_F(iommufd_mock_domain, all_aligns_copy)
{
size_t test_step = variant->hugepages ? self->mmap_buf_size / 16 :
--
2.47.3