From: David Matlack <dmatlack@google.com>
To: Jacob Pan <jacob.pan@linux.microsoft.com>
Cc: linux-kernel@vger.kernel.org,
"iommu@lists.linux.dev" <iommu@lists.linux.dev>,
Jason Gunthorpe <jgg@nvidia.com>,
Alex Williamson <alex@shazbot.org>,
Joerg Roedel <joro@8bytes.org>,
Mostafa Saleh <smostafa@google.com>,
Robin Murphy <robin.murphy@arm.com>,
Nicolin Chen <nicolinc@nvidia.com>,
"Tian, Kevin" <kevin.tian@intel.com>, Yi Liu <yi.l.liu@intel.com>,
Baolu Lu <baolu.lu@linux.intel.com>,
Saurabh Sengar <ssengar@linux.microsoft.com>,
skhawaja@google.com, pasha.tatashin@soleen.com,
Will Deacon <will@kernel.org>
Subject: Re: [PATCH v6 6/7] selftests/vfio: Add iommufd noiommu mode selftest for cdev
Date: Thu, 21 May 2026 22:39:34 +0000 [thread overview]
Message-ID: <ag-JpivaL4ORKVT5@google.com> (raw)
In-Reply-To: <20260521221155.1375144-7-jacob.pan@linux.microsoft.com>
On 2026-05-21 03:11 PM, Jacob Pan wrote:
For the shortlog, please use "vfio: selftests: ..."
> Add comprehensive selftest for VFIO device operations with iommufd in
> noiommu mode. Tests cover:
> - Device binding to iommufd
> - IOAS (I/O Address Space) allocation, mapping with dummy IOVA
> - Retrieve PA from dummy IOVA
> - Device attach/detach operations as usual
High level feedback: Can you use the library for all the standard setup
and ioas mapping instead of reimplementing it in this test?
iommu = iommu_init(MODE_IOMMUFD);
device = vfio_pci_device_init(iommu, bdf);
__iommu_map(...);
__iommu_unma(...);
iommu_cleanup(iommu);
vfio_pci_device_cleanup(device);
If not, what are the gaps? It would be useful to fill in those gaps so
that it is easier to use VFIO selftests with noiommu setups.
>
> Signed-off-by: Jacob Pan <jacob.pan@linux.microsoft.com>
> ---
> v6:
> - Add test cases for get_pa length limit
> v4:
> - squash DSA specific selftest changes
> v2:
> - New selftest for generic noiommu bind/unbind
> ---
> tools/testing/selftests/vfio/Makefile | 1 +
> .../lib/include/libvfio/vfio_pci_device.h | 16 +
> .../selftests/vfio/lib/vfio_pci_device.c | 5 +-
> .../vfio/vfio_iommufd_noiommu_test.c | 664 ++++++++++++++++++
> 4 files changed, 684 insertions(+), 2 deletions(-)
> create mode 100644 tools/testing/selftests/vfio/vfio_iommufd_noiommu_test.c
>
> diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
> index 0684932d91bf..c9c02fdfd946 100644
> --- a/tools/testing/selftests/vfio/Makefile
> +++ b/tools/testing/selftests/vfio/Makefile
> @@ -9,6 +9,7 @@ CFLAGS = $(KHDR_INCLUDES)
> TEST_GEN_PROGS += vfio_dma_mapping_test
> TEST_GEN_PROGS += vfio_dma_mapping_mmio_test
> TEST_GEN_PROGS += vfio_iommufd_setup_test
> +TEST_GEN_PROGS += vfio_iommufd_noiommu_test
> TEST_GEN_PROGS += vfio_pci_device_test
> TEST_GEN_PROGS += vfio_pci_device_init_perf_test
> TEST_GEN_PROGS += vfio_pci_driver_test
> diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
> index 2858885a89bb..6218c91776b3 100644
> --- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
> +++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
> @@ -122,4 +122,20 @@ static inline bool vfio_pci_device_match(struct vfio_pci_device *device,
>
> const char *vfio_pci_get_cdev_path(const char *bdf);
>
> +static inline bool vfio_pci_noiommu_mode_enabled(void)
> +{
> + char buf[8] = {};
> + int fd, n;
> +
> + fd = open("/sys/module/vfio/parameters/enable_unsafe_noiommu_mode",
> + O_RDONLY);
Can you rebase on top of the latest changes Alex merged for 7.2? It
introduces the sysfs library from Raghu. Please add a helper there for
reading module parameters in a precursor patch.
> + if (fd < 0)
> + return false;
> +
> + n = read(fd, buf, sizeof(buf) - 1);
> + close(fd);
> +
> + return n > 0 && buf[0] == 'Y';
> +}
> +
> #endif /* SELFTESTS_VFIO_LIB_INCLUDE_LIBVFIO_VFIO_PCI_DEVICE_H */
> diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
> index fc75e04ef010..1a91658e812d 100644
> --- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
> +++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
> @@ -308,8 +308,9 @@ const char *vfio_pci_get_cdev_path(const char *bdf)
> VFIO_ASSERT_NOT_NULL(dir, "Failed to open directory %s\n", dir_path);
>
> while ((entry = readdir(dir)) != NULL) {
> - /* Find the file that starts with "vfio" */
> - if (strncmp("vfio", entry->d_name, 4))
> + /* Find the file that starts with "vfio" or "noiommu-vfio" */
> + if (strncmp("vfio", entry->d_name, 4) &&
> + strncmp("noiommu-vfio", entry->d_name, 12))
> continue;
>
> snprintf(cdev_path, PATH_MAX, "/dev/vfio/devices/%s", entry->d_name);
> diff --git a/tools/testing/selftests/vfio/vfio_iommufd_noiommu_test.c b/tools/testing/selftests/vfio/vfio_iommufd_noiommu_test.c
> new file mode 100644
> index 000000000000..d91b505fc60d
> --- /dev/null
> +++ b/tools/testing/selftests/vfio/vfio_iommufd_noiommu_test.c
> @@ -0,0 +1,664 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * VFIO iommufd NoIOMMU Mode Selftest
> + *
> + * Tests VFIO device operations with iommufd in noiommu mode, including:
> + * - Device binding to iommufd
> + * - IOAS (I/O Address Space) allocation and management
> + * - Device attach/detach to IOAS
> + * - Memory mapping in IOAS
> + * - Device info queries and reset
> + */
> +
> +#include <linux/limits.h>
> +#include <linux/vfio.h>
> +#include <linux/iommufd.h>
> +
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <dirent.h>
> +#include <sys/ioctl.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +#include <errno.h>
> +
> +#include <libvfio.h>
> +#include "kselftest_harness.h"
> +
> +static const char iommu_dev_path[] = "/dev/iommu";
I don't see why this needs to be global variables.
> +static const char *cdev_path;
> +
> +static char *vfio_noiommu_get_device_id(const char *bdf)
> +{
> + char *path = NULL;
> + char *vfio_id = NULL;
> + struct dirent *dentry;
> + DIR *dp;
> +
> + if (asprintf(&path, "/sys/bus/pci/devices/%s/vfio-dev", bdf) < 0)
> + return NULL;
> +
> + dp = opendir(path);
> + if (!dp) {
> + free(path);
> + return NULL;
> + }
> +
> + while ((dentry = readdir(dp)) != NULL) {
> + if (strncmp("noiommu-vfio", dentry->d_name, 12) == 0) {
> + vfio_id = strdup(dentry->d_name);
> + break;
> + }
> + }
> +
> + closedir(dp);
> + free(path);
> + return vfio_id;
> +}
> +
> +static char *vfio_noiommu_get_cdev_path(const char *bdf)
> +{
> + char *vfio_id = vfio_noiommu_get_device_id(bdf);
> + char *cdev = NULL;
> +
> + if (vfio_id) {
> + asprintf(&cdev, "/dev/vfio/devices/%s", vfio_id);
> + free(vfio_id);
> + }
> + return cdev;
> +}
Can we put this in the library and find a way to share code with
vfio_pci_get_cdev_path()?
> +
> +static int vfio_device_bind_iommufd_ioctl(int cdev_fd, int iommufd)
> +{
> + struct vfio_device_bind_iommufd bind_args = {
> + .argsz = sizeof(bind_args),
> + .iommufd = iommufd,
> + };
> +
> + return ioctl(cdev_fd, VFIO_DEVICE_BIND_IOMMUFD, &bind_args);
> +}
Please add the ioctl wrappers to the library so they can be used by
other tests or library code in the future.
VFIO device ioctls can go in vfio_pci_device.c and iommufd ioctls can go
in iommu.c.
> +
> +static int vfio_device_get_info_ioctl(int cdev_fd,
> + struct vfio_device_info *info)
> +{
> + info->argsz = sizeof(*info);
> + return ioctl(cdev_fd, VFIO_DEVICE_GET_INFO, info);
> +}
> +
> +static int vfio_device_ioas_alloc_ioctl(int iommufd,
> + struct iommu_ioas_alloc *alloc_args)
> +{
> + alloc_args->size = sizeof(*alloc_args);
> + alloc_args->flags = 0;
> + return ioctl(iommufd, IOMMU_IOAS_ALLOC, alloc_args);
> +}
> +
> +static int vfio_device_attach_iommufd_pt_ioctl(int cdev_fd, u32 pt_id)
> +{
> + struct vfio_device_attach_iommufd_pt attach_args = {
> + .argsz = sizeof(attach_args),
> + .pt_id = pt_id,
> + };
> +
> + return ioctl(cdev_fd, VFIO_DEVICE_ATTACH_IOMMUFD_PT, &attach_args);
> +}
> +
> +static int vfio_device_detach_iommufd_pt_ioctl(int cdev_fd)
> +{
> + struct vfio_device_detach_iommufd_pt detach_args = {
> + .argsz = sizeof(detach_args),
> + };
> +
> + return ioctl(cdev_fd, VFIO_DEVICE_DETACH_IOMMUFD_PT, &detach_args);
> +}
> +
> +static int vfio_device_get_region_info_ioctl(int cdev_fd, uint32_t index,
> + struct vfio_region_info *info)
> +{
> + info->argsz = sizeof(*info);
> + info->index = index;
> + return ioctl(cdev_fd, VFIO_DEVICE_GET_REGION_INFO, info);
> +}
> +
> +static int vfio_device_reset_ioctl(int cdev_fd)
> +{
> + return ioctl(cdev_fd, VFIO_DEVICE_RESET);
> +}
> +
> +static int ioas_map_pages(int iommufd, uint32_t ioas_id, uint64_t iova,
> + size_t length, bool hugepages)
> +{
> + struct iommu_ioas_map map_args = {
> + .size = sizeof(map_args),
> + .ioas_id = ioas_id,
> + .iova = iova,
> + .length = length,
> + .flags = IOMMU_IOAS_MAP_READABLE | IOMMU_IOAS_MAP_WRITEABLE | IOMMU_IOAS_MAP_FIXED_IOVA,
> + };
> + void *pages;
> + int ret;
> +
> + /* Allocate test pages */
> + if (hugepages)
> + pages = mmap(NULL, length, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, -1, 0);
> + else
> + pages = mmap(NULL, length, PROT_READ | PROT_WRITE,
> + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
> + if (pages == MAP_FAILED) {
> + printf("mmap failed for length 0x%lx\n", (unsigned long)length);
> + return -ENOMEM;
> + }
> +
> + /* Set up page pointer for mapping */
> + map_args.user_va = (uintptr_t)pages;
> +
> + printf(" ioas_map_pages: ioas_id=%u, iova=0x%lx, length=0x%lx, user_va=%p\n",
> + ioas_id, (unsigned long)iova, (unsigned long)length, pages);
> +
> + /* Map into IOAS */
> + ret = ioctl(iommufd, IOMMU_IOAS_MAP, &map_args);
> + if (ret != 0)
> + printf(" IOMMU_IOAS_MAP failed: %d (%s)\n", ret, strerror(errno));
> + else
> + printf(" IOMMU_IOAS_MAP succeeded, IOVA=0x%lx\n", (unsigned long)map_args.iova);
> +
> + munmap(pages, length);
> + return ret;
> +}
> +
> +static int ioas_unmap_pages(int iommufd, uint32_t ioas_id, uint64_t iova,
> + size_t length)
> +{
> + struct iommu_ioas_unmap unmap_args = {
> + .size = sizeof(unmap_args),
> + .ioas_id = ioas_id,
> + .iova = iova,
> + .length = length,
> + };
> +
> + return ioctl(iommufd, IOMMU_IOAS_UNMAP, &unmap_args);
> +}
> +
> +static int ioas_destroy_ioctl(int iommufd, uint32_t ioas_id)
> +{
> + struct iommu_destroy destroy_args = {
> + .size = sizeof(destroy_args),
> + .id = ioas_id,
> + };
> +
> + return ioctl(iommufd, IOMMU_DESTROY, &destroy_args);
> +}
> +
> +static int ioas_noiommu_get_pa_ioctl_len(int iommufd, uint32_t ioas_id,
> + uint64_t iova, uint64_t max_length,
> + uint64_t *phys_out, uint64_t *length_out)
> +{
> + struct iommu_ioas_noiommu_get_pa get_pa = {
> + .size = sizeof(get_pa),
> + .flags = 0,
> + .ioas_id = ioas_id,
> + .iova = iova,
> + .length = max_length,
> + };
> +
> + printf(" ioas_noiommu_get_pa_ioctl: ioas_id=%u, iova=0x%lx, max_length=0x%lx\n",
> + ioas_id, (unsigned long)iova, (unsigned long)max_length);
> +
> + if (ioctl(iommufd, IOMMU_IOAS_NOIOMMU_GET_PA, &get_pa) != 0) {
> + printf(" IOMMU_IOAS_NOIOMMU_GET_PA failed: %s (errno=%d)\n",
> + strerror(errno), errno);
> + return -1;
> + }
> +
> + printf(" IOMMU_IOAS_NOIOMMU_GET_PA succeeded: PA=0x%lx, length=0x%lx\n",
> + (unsigned long)get_pa.out_phys, (unsigned long)get_pa.length);
> +
> + if (phys_out)
> + *phys_out = get_pa.out_phys;
> + if (length_out)
> + *length_out = get_pa.length;
> +
> + return 0;
> +}
> +
> +static int ioas_noiommu_get_pa_ioctl(int iommufd, uint32_t ioas_id, uint64_t iova,
> + uint64_t *phys_out, uint64_t *length_out)
> +{
> + return ioas_noiommu_get_pa_ioctl_len(iommufd, ioas_id, iova, 0,
> + phys_out, length_out);
> +}
> +
> +FIXTURE(vfio_noiommu) {
> + int cdev_fd;
> + int iommufd;
> +};
> +
> +FIXTURE_SETUP(vfio_noiommu)
> +{
> + ASSERT_LE(0, (self->cdev_fd = open(cdev_path, O_RDWR, 0)));
> + ASSERT_LE(0, (self->iommufd = open(iommu_dev_path, O_RDWR, 0)));
> +}
> +
> +FIXTURE_TEARDOWN(vfio_noiommu)
> +{
> + if (self->cdev_fd >= 0)
> + close(self->cdev_fd);
> + if (self->iommufd >= 0)
> + close(self->iommufd);
> +}
> +
> +/*
> + * Test: Device cdev can be opened
> + */
> +TEST_F(vfio_noiommu, device_cdev_open)
> +{
> + ASSERT_LE(0, self->cdev_fd);
> +}
This is already tested by the FIXTURE_SETUP(). No need for a TEST_F().
> +
> +/*
> + * Test: Device can be bound to iommufd
> + */
> +TEST_F(vfio_noiommu, device_bind_iommufd)
> +{
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> +}
> +
> +/*
> + * Test: Device info can be queried after binding
> + */
> +TEST_F(vfio_noiommu, device_get_info_after_bind)
> +{
> + struct vfio_device_info info;
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_get_info_ioctl(self->cdev_fd, &info));
> + ASSERT_NE(0, info.argsz);
> +}
> +
> +/*
> + * Test: Getting device info fails without bind
> + */
> +TEST_F(vfio_noiommu, device_get_info_without_bind_fails)
> +{
> + struct vfio_device_info info;
> +
> + ASSERT_NE(0, vfio_device_get_info_ioctl(self->cdev_fd, &info));
> +}
> +
> +/*
> + * Test: Binding with invalid iommufd fails
> + */
> +TEST_F(vfio_noiommu, device_bind_bad_iommufd_fails)
> +{
> + ASSERT_NE(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd, -2));
> +}
Are all these tests really specific to noiommu?
> +
> +/*
> + * Test: Cannot bind twice to same device
> + */
> +TEST_F(vfio_noiommu, device_repeated_bind_fails)
> +{
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_NE(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> +}
> +
> +/*
> + * Test: IOAS can be allocated
> + */
> +TEST_F(vfio_noiommu, ioas_alloc)
> +{
> + struct iommu_ioas_alloc alloc_args;
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> + ASSERT_NE(0, alloc_args.out_ioas_id);
> +}
> +
> +/*
> + * Test: IOAS can be destroyed
> + */
> +TEST_F(vfio_noiommu, ioas_destroy)
> +{
> + struct iommu_ioas_alloc alloc_args;
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> + ASSERT_EQ(0, ioas_destroy_ioctl(self->iommufd,
> + alloc_args.out_ioas_id));
> +}
> +
> +/*
> + * Test: Device can attach to IOAS after binding
> + */
> +TEST_F(vfio_noiommu, device_attach_to_ioas)
> +{
> + struct iommu_ioas_alloc alloc_args;
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> +}
> +
> +/*
> + * Test: Attaching to invalid IOAS fails
> + */
> +TEST_F(vfio_noiommu, device_attach_invalid_ioas_fails)
> +{
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_NE(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + UINT32_MAX));
> +}
> +
> +/*
> + * Test: Device can detach from IOAS
> + */
> +TEST_F(vfio_noiommu, device_detach_from_ioas)
> +{
> + struct iommu_ioas_alloc alloc_args;
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> + ASSERT_EQ(0, vfio_device_detach_iommufd_pt_ioctl(self->cdev_fd));
> +}
> +
> +/*
> + * Test: Full lifecycle - bind, attach, detach, reset
> + */
> +TEST_F(vfio_noiommu, device_lifecycle)
> +{
> + struct iommu_ioas_alloc alloc_args;
> + struct vfio_device_info info;
> +
> + /* Bind device to iommufd */
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> +
> + /* Allocate IOAS */
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> +
> + /* Attach device to IOAS */
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> +
> + /* Query device info */
> + ASSERT_EQ(0, vfio_device_get_info_ioctl(self->cdev_fd, &info));
> +
> + /* Detach device from IOAS */
> + ASSERT_EQ(0, vfio_device_detach_iommufd_pt_ioctl(self->cdev_fd));
> +
> + /* Reset device */
> + ASSERT_EQ(0, vfio_device_reset_ioctl(self->cdev_fd));
> +}
> +
> +/*
> + * Test: Get region info
> + */
> +TEST_F(vfio_noiommu, device_get_region_info)
> +{
> + struct vfio_device_info dev_info;
> + struct vfio_region_info region_info;
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_get_info_ioctl(self->cdev_fd, &dev_info));
> +
> + /* Try to get first region info if device has regions */
> + if (dev_info.num_regions > 0) {
> + ASSERT_EQ(0, vfio_device_get_region_info_ioctl(self->cdev_fd, 0,
> + ®ion_info));
> + ASSERT_NE(0, region_info.argsz);
> + }
> +}
> +
> +TEST_F(vfio_noiommu, device_reset)
> +{
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_reset_ioctl(self->cdev_fd));
> +}
> +
> +TEST_F(vfio_noiommu, ioas_map_pages)
> +{
> + struct iommu_ioas_alloc alloc_args;
> + long page_size = sysconf(_SC_PAGESIZE);
> + uint64_t iova = 0x10000;
> + int i;
> +
> + ASSERT_GT(page_size, 0);
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> +
> + printf("Page size: %ld bytes\n", page_size);
> + /* Test mapping regions of different sizes: 1, 2, 4, 8 pages */
> + for (i = 0; i < 4; i++) {
> + size_t map_size = page_size * (1 << i); /* 1, 2, 4, 8 pages */
> + uint64_t test_iova = iova + (i * 0x100000);
> +
> + /* Attempt to map each region (may fail if not supported) */
> + ioas_map_pages(self->iommufd, alloc_args.out_ioas_id,
> + test_iova, map_size, false);
> + }
> +}
> +
> +TEST_F(vfio_noiommu, multiple_ioas_alloc)
> +{
> + struct iommu_ioas_alloc alloc1, alloc2;
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd, &alloc1));
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd, &alloc2));
> + ASSERT_NE(alloc1.out_ioas_id, alloc2.out_ioas_id);
> +}
> +
> +/*
> + * Test: Query physical address for IOVA
> + * Tests IOMMU_IOAS_NOIOMMU_GET_PA ioctl to translate IOVA to physical address
> + * Note: Device must be attached to IOAS for PA query to work
> + */
> +#define NR_PAGES 32
> +TEST_F(vfio_noiommu, ioas_noiommu_get_pa_mapped)
> +{
> + struct iommu_ioas_alloc alloc_args;
> + long page_size = sysconf(_SC_PAGESIZE);
> + uint64_t iova = 0x200000;
> + uint64_t phys = 0;
> + uint64_t length = 0;
> + int ret;
> +
> + ASSERT_GT(page_size, 0);
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> +
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> +
> + /*
> + * Map a page into an arbitrary IOAS, used as a cookie for lookup.
> + * Use hugepages to test contiguous PA. Make sure hugepages are
> + * available. e.g. echo 64 > /proc/sys/vm/nr_hugepages
> + */
> + ret = ioas_map_pages(self->iommufd, alloc_args.out_ioas_id,
> + iova, page_size * NR_PAGES, true);
> + if (ret != 0)
> + return;
> +
> + /* Query the physical address for the mapped dummy IOVA */
> + ret = ioas_noiommu_get_pa_ioctl(self->iommufd, alloc_args.out_ioas_id,
> + iova, &phys, &length);
> +
> + if (ret == 0) {
> + /* If we got a result, verify it's valid */
> + ASSERT_NE(0, phys);
> + ASSERT_GE((uint64_t)page_size * NR_PAGES, length);
> + }
> +
> + /*
> + * Query with a non-page-aligned IOVA. The returned length must
> + * not exceed the actual contiguous range starting from that
> + * offset, i.e. it must be reduced by the sub-page offset.
> + */
> + phys = 0;
> + length = 0;
> + ret = ioas_noiommu_get_pa_ioctl(self->iommufd, alloc_args.out_ioas_id,
> + iova + 0x80, &phys, &length);
> + if (ret == 0) {
> + ASSERT_NE(0, phys);
> + /* Length must account for the sub-page offset */
> + ASSERT_GE((uint64_t)page_size * NR_PAGES - 0x80, length);
> + ASSERT_LE(length, (uint64_t)page_size * NR_PAGES - 0x80);
> + /* Must not overshoot into the next page boundary */
> + ASSERT_EQ(0, (phys + length) % page_size);
> + }
> +}
> +
> +TEST_F(vfio_noiommu, ioas_noiommu_get_pa_unmapped_fails)
> +{
> + struct iommu_ioas_alloc alloc_args;
> +
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd,
> + &alloc_args));
> +
> + /* Try to retrieve unmapped IOVA (should fail) */
> + ASSERT_NE(0, ioas_noiommu_get_pa_ioctl(self->iommufd, alloc_args.out_ioas_id,
> + 0x10000, NULL, NULL));
> +}
> +
> +/*
> + * Test: length == 0 means no limit (backward compat default)
> + */
> +TEST_F(vfio_noiommu, ioas_noiommu_get_pa_length_zero_no_limit)
> +{
> + struct iommu_ioas_alloc alloc_args;
> + long page_size = sysconf(_SC_PAGESIZE);
> + uint64_t iova = 0x200000;
> + uint64_t phys_nolimit = 0, phys_zero = 0;
> + uint64_t len_nolimit = 0, len_zero = 0;
> + int ret;
> +
> + ASSERT_GT(page_size, 0);
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd, &alloc_args));
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> +
> + ret = ioas_map_pages(self->iommufd, alloc_args.out_ioas_id,
> + iova, page_size * NR_PAGES, true);
> + if (ret != 0)
> + return;
> +
> + /* Query with length=0 (no limit, default behavior) */
> + ret = ioas_noiommu_get_pa_ioctl_len(self->iommufd, alloc_args.out_ioas_id,
> + iova, 0, &phys_zero, &len_zero);
> + if (ret != 0)
> + return;
> +
> + /* Query with the wrapper (also passes 0) — must match */
> + ret = ioas_noiommu_get_pa_ioctl(self->iommufd, alloc_args.out_ioas_id,
> + iova, &phys_nolimit, &len_nolimit);
> + ASSERT_EQ(0, ret);
> + ASSERT_EQ(phys_zero, phys_nolimit);
> + ASSERT_EQ(len_zero, len_nolimit);
> +}
> +
> +/*
> + * Test: length caps the returned contiguous range
> + */
> +TEST_F(vfio_noiommu, ioas_noiommu_get_pa_length_capped)
> +{
> + struct iommu_ioas_alloc alloc_args;
> + long page_size = sysconf(_SC_PAGESIZE);
> + uint64_t iova = 0x200000;
> + uint64_t phys = 0;
> + uint64_t len_full = 0, len_capped = 0;
> + uint64_t cap;
> + int ret;
> +
> + ASSERT_GT(page_size, 0);
> +
> + ASSERT_EQ(0, vfio_device_bind_iommufd_ioctl(self->cdev_fd,
> + self->iommufd));
> + ASSERT_EQ(0, vfio_device_ioas_alloc_ioctl(self->iommufd, &alloc_args));
> + ASSERT_EQ(0, vfio_device_attach_iommufd_pt_ioctl(self->cdev_fd,
> + alloc_args.out_ioas_id));
> +
> + ret = ioas_map_pages(self->iommufd, alloc_args.out_ioas_id,
> + iova, page_size * NR_PAGES, true);
> + if (ret != 0)
> + return;
> +
> + /* First get the full uncapped length */
> + ret = ioas_noiommu_get_pa_ioctl(self->iommufd, alloc_args.out_ioas_id,
> + iova, &phys, &len_full);
> + if (ret != 0)
> + return;
> +
> + ASSERT_NE(0, phys);
> + ASSERT_NE(0, len_full);
> +
> + /* Cap to a single page — returned length must not exceed it */
> + cap = page_size;
> + ret = ioas_noiommu_get_pa_ioctl_len(self->iommufd, alloc_args.out_ioas_id,
> + iova, cap, &phys, &len_capped);
> + ASSERT_EQ(0, ret);
> + ASSERT_LE(len_capped, cap);
> + ASSERT_NE(0, len_capped);
> +
> + /*
> + * If full length was larger than one page, confirm capping works.
> + * Otherwise the mapping wasn't contiguous enough to test.
> + */
> + if (len_full > cap)
> + ASSERT_GT(len_full, len_capped);
> +
> + /* Cap to a very large value — should return the same as uncapped */
> + ret = ioas_noiommu_get_pa_ioctl_len(self->iommufd, alloc_args.out_ioas_id,
> + iova, UINT64_MAX, &phys, &len_capped);
> + ASSERT_EQ(0, ret);
> + ASSERT_EQ(len_full, len_capped);
> +}
> +
> +int main(int argc, char *argv[])
> +{
> + const char *device_bdf = vfio_selftests_get_bdf(&argc, argv);
> + char *cdev = NULL;
> +
> + if (!device_bdf) {
> + ksft_print_msg("No device BDF provided\n");
> + return KSFT_SKIP;
> + }
vfio_selftests_get_bdf() already handles exiting with KSFT_SKIP if it
can't find a BDF.
> +
> + cdev = vfio_noiommu_get_cdev_path(device_bdf);
> + if (!cdev) {
> + ksft_print_msg("Could not find cdev for device %s\n",
> + device_bdf);
nit: "Could not find niommu cdev for ..."
> + return KSFT_SKIP;
> + }
> +
> + cdev_path = cdev;
> + ksft_print_msg("Using cdev device %s for BDF %s\n", cdev_path,
> + device_bdf);
> +
> + return test_harness_run(argc, argv);
> +}
> --
> 2.43.0
>
next prev parent reply other threads:[~2026-05-21 22:39 UTC|newest]
Thread overview: 25+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-05-21 22:11 [PATCH v6 0/7] iommufd: Enable noiommu mode for cdev Jacob Pan
2026-05-21 22:11 ` [PATCH v6 1/7] iommufd: Support a HWPT without an iommu driver for noiommu Jacob Pan
2026-05-21 22:11 ` [PATCH v6 2/7] iommufd: Move igroup allocation to a function Jacob Pan
2026-05-22 6:00 ` Baolu Lu
2026-05-21 22:11 ` [PATCH v6 3/7] iommufd: Allow binding to a noiommu device Jacob Pan
2026-05-22 6:01 ` Baolu Lu
2026-05-21 22:11 ` [PATCH v6 4/7] iommufd: Add an ioctl to query PA from IOVA for noiommu mode Jacob Pan
2026-05-22 9:22 ` Yi Liu
2026-05-21 22:11 ` [PATCH v6 5/7] vfio: Enable cdev noiommu mode under iommufd Jacob Pan
2026-05-22 9:19 ` Yi Liu
2026-05-23 22:01 ` Jacob Pan
2026-05-25 6:29 ` Yi Liu
2026-05-28 18:52 ` Jacob Pan
2026-05-29 7:27 ` Yi Liu
2026-05-21 22:11 ` [PATCH v6 6/7] selftests/vfio: Add iommufd noiommu mode selftest for cdev Jacob Pan
2026-05-21 22:39 ` David Matlack [this message]
2026-06-03 0:13 ` Jacob Pan
2026-05-21 22:11 ` [PATCH v6 7/7] Documentation: Update VFIO NOIOMMU mode Jacob Pan
2026-05-22 9:42 ` Yi Liu
2026-05-23 3:42 ` Jacob Pan
2026-05-25 6:29 ` Yi Liu
2026-05-25 8:30 ` [PATCH v6 0/7] iommufd: Enable noiommu mode for cdev Tian, Kevin
2026-05-26 15:32 ` Jacob Pan
2026-05-26 17:57 ` Alex Williamson
2026-05-27 22:34 ` Jacob Pan
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=ag-JpivaL4ORKVT5@google.com \
--to=dmatlack@google.com \
--cc=alex@shazbot.org \
--cc=baolu.lu@linux.intel.com \
--cc=iommu@lists.linux.dev \
--cc=jacob.pan@linux.microsoft.com \
--cc=jgg@nvidia.com \
--cc=joro@8bytes.org \
--cc=kevin.tian@intel.com \
--cc=linux-kernel@vger.kernel.org \
--cc=nicolinc@nvidia.com \
--cc=pasha.tatashin@soleen.com \
--cc=robin.murphy@arm.com \
--cc=skhawaja@google.com \
--cc=smostafa@google.com \
--cc=ssengar@linux.microsoft.com \
--cc=will@kernel.org \
--cc=yi.l.liu@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox