[PATCH v6 21/27] vfio: selftests: Add helpers to map DMA memory at IOVA == PA in noiommu mode
From: Vipin Sharma
Date: Mon Nov 02 2026 - 11:45:10 EST
Selftest drivers program the IOVA returned by to_iova() into the device.
Because a device in noiommu mode uses those addresses directly as
physical addresses, DMA buffers must be mapped at IOVA == PA so that
to_iova() returns the buffer's physical address without changing the
drivers.
Add __iommu_noiommu_get_pa() to wrap IOMMU_IOAS_NOIOMMU_GET_PA and
iommu_map_identity() to map a physically contiguous buffer at IOVA == PA
by first pinning it at a scratch IOVA (BIT_ULL(52), above the 52-bit
output address space of the noiommu page table), querying its physical
address and contiguity, mapping it at IOVA == PA, and then unmapping the
scratch range while keeping the pages pinned.
Signed-off-by: Vipin Sharma <vipinsh@xxxxxxxxxx>
---
.../vfio/lib/include/libvfio/iommu.h | 26 ++++++++
tools/testing/selftests/vfio/lib/iommu.c | 61 +++++++++++++++++++
2 files changed, 87 insertions(+)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
index e652b8caf5bb..d449813f9bc4 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
@@ -65,6 +65,32 @@ static inline void iommu_unmap_all(struct iommu *iommu)
int __iommu_hva2iova(struct iommu *iommu, void *vaddr, iova_t *iova);
iova_t iommu_hva2iova(struct iommu *iommu, void *vaddr);
+/*
+ * Look up the physical address @iova maps to in the noiommu IOAS of @iommu
+ * (IOMMU_IOAS_NOIOMMU_GET_PA, requires CAP_SYS_RAWIO). On success, *@length is
+ * the number of physically contiguous bytes from *@pa, at most @max_length.
+ */
+int __iommu_noiommu_get_pa(struct iommu *iommu, iova_t iova, u64 max_length,
+ u64 *pa, u64 *length);
+
+/*
+ * Map @region->size bytes at @region->vaddr at IOVA == PA, and set
+ * @region->iova to that PA. Devices in noiommu mode use the addresses that
+ * drivers program into them, i.e. to_iova(), as physical addresses, which such
+ * a mapping makes right.
+ *
+ * The buffer must be physically contiguous (e.g. a hugepage or a THP), and a
+ * noiommu device must be attached to @iommu already. The memory stays pinned,
+ * and so at the same physical address, until iommu_unmap(@region), which must
+ * not be called before the device stopped accessing it.
+ */
+int __iommu_map_identity(struct iommu *iommu, struct dma_region *region);
+
+static inline void iommu_map_identity(struct iommu *iommu, struct dma_region *region)
+{
+ VFIO_ASSERT_EQ(__iommu_map_identity(iommu, region), 0);
+}
+
struct iommu_iova_range *iommu_iova_ranges(struct iommu *iommu, u32 *nranges);
#define MODE_VFIO_TYPE1_IOMMU "vfio_type1_iommu"
diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
index 0df49646d016..46e9635cbfc5 100644
--- a/tools/testing/selftests/vfio/lib/iommu.c
+++ b/tools/testing/selftests/vfio/lib/iommu.c
@@ -11,6 +11,7 @@
#include <sys/ioctl.h>
#include <sys/mman.h>
+#include <linux/bits.h>
#include <linux/limits.h>
#include <linux/mman.h>
#include <linux/types.h>
@@ -244,6 +245,66 @@ int __iommu_unmap_all(struct iommu *iommu, u64 *unmapped)
return 0;
}
+/*
+ * The noiommu IOAS page table translates to physical addresses of at most 52
+ * bits (drivers/iommu/iommufd/hwpt_noiommu.c), so a mapping at or above this
+ * IOVA never overlaps one at IOVA == PA.
+ */
+#define NOIOMMU_SCRATCH_IOVA BIT_ULL(52)
+
+int __iommu_noiommu_get_pa(struct iommu *iommu, iova_t iova, u64 max_length,
+ u64 *pa, u64 *length)
+{
+ struct iommu_ioas_noiommu_get_pa args = {
+ .size = sizeof(args),
+ .ioas_id = iommu->ioas_id,
+ .iova = iova,
+ .length = max_length,
+ };
+
+ if (!iommu->iommufd || !iommu->mode->noiommu)
+ return -EOPNOTSUPP;
+
+ if (ioctl(iommu->iommufd, IOMMU_IOAS_NOIOMMU_GET_PA, &args))
+ return -errno;
+
+ *pa = args.out_phys;
+ *length = args.length;
+ return 0;
+}
+
+int __iommu_map_identity(struct iommu *iommu, struct dma_region *region)
+{
+ struct dma_region scratch = {
+ .vaddr = region->vaddr,
+ .iova = NOIOMMU_SCRATCH_IOVA,
+ .size = region->size,
+ };
+ u64 pa, length;
+ int ret;
+
+ /* Pin the buffer with a scratch mapping to look up its physical address. */
+ ret = __iommu_map(iommu, &scratch);
+ if (ret)
+ return ret;
+
+ ret = __iommu_noiommu_get_pa(iommu, scratch.iova, scratch.size, &pa, &length);
+ if (!ret && length != region->size)
+ ret = -EINVAL; /* Not physically contiguous. */
+
+ /*
+ * Map it at IOVA == PA before dropping the scratch mapping, so that it
+ * stays pinned, and hence at the same physical address, throughout.
+ */
+ if (!ret) {
+ region->iova = pa;
+ ret = __iommu_map(iommu, region);
+ }
+
+ VFIO_ASSERT_EQ(__iommu_unmap(iommu, &scratch, NULL), 0);
+ return ret;
+}
+
static struct vfio_info_cap_header *next_cap_hdr(void *buf, u32 bufsz,
u32 *cap_offset)
{
--
2.56.0.317.g454f8302e1-goog