* [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test
@ 2026-09-10 15:33 Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 1/4] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
` (4 more replies)
0 siblings, 5 replies; 10+ messages in thread
From: Aaron Lewis @ 2026-09-10 15:33 UTC (permalink / raw)
To: kvm; +Cc: alex, dmatlack, jgg, Aaron Lewis
Add a new VFIO selftest, vfio_dma_mapping_perf_test, to provide a
configurable tool for measuring DMA mapping and unmapping latencies.
This selftest introduces command-line parameters to provide a convenient
way to tune the test for specific situations. For example, it can be used
to observe the lengthy unmap times of the Type 1 IOMMU on large memory
regions, or to compare DMA mapping performance between the Type 1 IOMMU
and IOMMUFD. These scenarios can now be easily configured and run via the
command line.
Changes in v3:
- Replaced raw printf() calls with ksft_print_msg()
- Updated the TIME macro to preserve errno
- Added 'fd' and 'fd_offset' to struct dma_region
- Simplified teardown_memfd() by removing redundant error checks
- Replaced the command-line argument passthrough (-a "...") approach with
first-class support in kselftest_harness.h
- Added collision validation in optstring_append_custom() to prevent custom
options from overlapping with built-in harness options.
- Switched test_enabled() to match exact option characters rather than
tolower(opt), ensuring custom options that differ only by case (such as
'R') do not mistakenly match built-in single-case options like '-r'.
Aaron Lewis (4):
vfio: selftests: Assert the region was unmapped in iommu_unmap()
vfio: selftests: Introduce vfio_dma_mapping_perf_test
vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test
vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
tools/testing/selftests/kselftest_harness.h | 86 +++++-
tools/testing/selftests/vfio/Makefile | 1 +
.../selftests/vfio/lib/include/libvfio.h | 31 +++
.../vfio/lib/include/libvfio/iommu.h | 14 +-
tools/testing/selftests/vfio/lib/iommu.c | 24 ++
.../vfio/vfio_dma_mapping_perf_test.c | 263 ++++++++++++++++++
.../selftests/vfio/vfio_dma_mapping_test.c | 11 +-
7 files changed, 406 insertions(+), 24 deletions(-)
create mode 100644 tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v3 1/4] vfio: selftests: Assert the region was unmapped in iommu_unmap()
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-09-10 15:33 ` Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (3 subsequent siblings)
4 siblings, 0 replies; 10+ messages in thread
From: Aaron Lewis @ 2026-09-10 15:33 UTC (permalink / raw)
To: kvm; +Cc: alex, dmatlack, jgg, Aaron Lewis
Add an assertion in iommu_unmap() to verify that the region was fully
unmapped. Centralizing this check simplifies the selftests by removing
the need for individual tests to handle the verification themselves.
Suggested-by: David Matlack <dmatlack@google.com>
Reviewed-by: David Matlack <dmatlack@google.com>
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
.../selftests/vfio/lib/include/libvfio/iommu.h | 5 ++++-
tools/testing/selftests/vfio/vfio_dma_mapping_test.c | 11 ++---------
2 files changed, 6 insertions(+), 10 deletions(-)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
index e9a3386a4719..5c77875240f4 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
@@ -46,7 +46,10 @@ int __iommu_unmap(struct iommu *iommu, struct dma_region *region, u64 *unmapped)
static inline void iommu_unmap(struct iommu *iommu, struct dma_region *region)
{
- VFIO_ASSERT_EQ(__iommu_unmap(iommu, region, NULL), 0);
+ u64 unmapped;
+
+ VFIO_ASSERT_EQ(__iommu_unmap(iommu, region, &unmapped), 0);
+ VFIO_ASSERT_EQ(unmapped, region->size);
}
int __iommu_unmap_all(struct iommu *iommu, u64 *unmapped);
diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_test.c
index 7d0de8c79de1..1b90441644a2 100644
--- a/tools/testing/selftests/vfio/vfio_dma_mapping_test.c
+++ b/tools/testing/selftests/vfio/vfio_dma_mapping_test.c
@@ -138,7 +138,6 @@ TEST_F(vfio_dma_mapping_test, dma_map_unmap)
struct dma_region region;
struct iommu_mapping mapping;
u64 mapping_size = size;
- u64 unmapped;
int rc;
region.vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0);
@@ -190,9 +189,7 @@ TEST_F(vfio_dma_mapping_test, dma_map_unmap)
}
unmap:
- rc = __iommu_unmap(self->iommu, ®ion, &unmapped);
- ASSERT_EQ(rc, 0);
- ASSERT_EQ(unmapped, region.size);
+ iommu_unmap(self->iommu, ®ion);
printf("Unmapped IOVA 0x%lx\n", region.iova);
ASSERT_NE(0, __to_iova(self->device, region.vaddr, NULL));
ASSERT_NE(0, iommu_mapping_get(device_bdf, region.iova, &mapping));
@@ -260,15 +257,11 @@ FIXTURE_TEARDOWN(vfio_dma_map_limit_test)
TEST_F(vfio_dma_map_limit_test, unmap_range)
{
struct dma_region *region = &self->region;
- u64 unmapped;
- int rc;
iommu_map(self->iommu, region);
ASSERT_EQ(region->iova, to_iova(self->device, region->vaddr));
- rc = __iommu_unmap(self->iommu, region, &unmapped);
- ASSERT_EQ(rc, 0);
- ASSERT_EQ(unmapped, region->size);
+ iommu_unmap(self->iommu, region);
}
TEST_F(vfio_dma_map_limit_test, unmap_all)
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 1/4] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
@ 2026-09-10 15:33 ` Aaron Lewis
2026-10-08 9:48 ` David Matlack
2026-09-10 15:33 ` [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
` (2 subsequent siblings)
4 siblings, 1 reply; 10+ messages in thread
From: Aaron Lewis @ 2026-09-10 15:33 UTC (permalink / raw)
To: kvm; +Cc: alex, dmatlack, jgg, Aaron Lewis
Introduce vfio_dma_mapping_perf_test to aid in latency testing. Rather
than a traditional pass/fail test, which tends to be flaky when
enforcing latency bounds, this test directly reports the latency of
various stages in the mapping and unmapping process.
Start with a basic test that performs a simple DMA map/unmap and tracks
the duration of each operation. The test reports the following metrics:
1. Time taken by mmap().
2. Time taken to map a DMA region.
3. Time taken to unmap a DMA region.
4. Time taken by munmap().
Subsequent commits in this series will introduce more features to allow
for a wider variety of testing.
To ensure consistent DMA mapping times, the test uses MAP_POPULATE when
calling mmap(). This forces page faults to be handled upfront, isolating
the DMA mapping times from them.
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
tools/testing/selftests/vfio/Makefile | 1 +
.../selftests/vfio/lib/include/libvfio.h | 31 +++++++
.../vfio/vfio_dma_mapping_perf_test.c | 90 +++++++++++++++++++
3 files changed, 122 insertions(+)
create mode 100644 tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index 2c32c48db509..ed5856af5f9a 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -8,6 +8,7 @@ else
CFLAGS = $(KHDR_INCLUDES)
TEST_GEN_PROGS += vfio_dma_mapping_test
TEST_GEN_PROGS += vfio_dma_mapping_mmio_test
+TEST_GEN_PROGS += vfio_dma_mapping_perf_test
TEST_GEN_PROGS += vfio_iommufd_setup_test
TEST_GEN_PROGS += vfio_pci_device_test
TEST_GEN_PROGS += vfio_pci_device_init_perf_test
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio.h b/tools/testing/selftests/vfio/lib/include/libvfio.h
index 07862b470777..8496b8b4da3c 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio.h
@@ -9,6 +9,37 @@
#include <libvfio/vfio_pci_device.h>
#include <libvfio/vfio_pci_driver.h>
+#include <stdint.h>
+#include <time.h>
+#include <linux/time64.h>
+
+static inline void timer_start(struct timespec *start)
+{
+ clock_gettime(CLOCK_MONOTONIC, start);
+}
+
+static inline uint64_t timer_elapsed_ns(struct timespec start)
+{
+ struct timespec end;
+
+ clock_gettime(CLOCK_MONOTONIC, &end);
+
+ return (uint64_t)(end.tv_sec - start.tv_sec) * NSEC_PER_SEC +
+ (uint64_t)(end.tv_nsec - start.tv_nsec);
+}
+
+#define TIME(_name, _expression) do { \
+ struct timespec __start; \
+ int __errno; \
+ \
+ timer_start(&__start); \
+ _expression; \
+ __errno = errno; \
+ ksft_print_msg(_name " = %.2lfms\n", \
+ (double)timer_elapsed_ns(__start) / NSEC_PER_MSEC); \
+ errno = __errno; \
+} while (0)
+
/*
* Return the BDF string of the device that the test should use.
*
diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
new file mode 100644
index 000000000000..4d41fecf2372
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
@@ -0,0 +1,90 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <limits.h>
+#include <stdio.h>
+#include <sys/mman.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <linux/iommufd.h>
+#include <linux/limits.h>
+#include <linux/memfd.h>
+#include <linux/mman.h>
+#include <linux/sizes.h>
+#include <linux/time64.h>
+#include <linux/vfio.h>
+
+#include <libvfio.h>
+
+#include "kselftest_harness.h"
+
+static const char *device_bdf;
+
+FIXTURE(vfio_dma_mapping_perf_test) {
+ struct iommu *iommu;
+ struct vfio_pci_device *device;
+ struct iova_allocator *iova_allocator;
+};
+
+FIXTURE_VARIANT(vfio_dma_mapping_perf_test) {
+ const char *iommu_mode;
+ int mmap_flags;
+};
+
+#define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode, _name, _mmap_flags) \
+FIXTURE_VARIANT_ADD(vfio_dma_mapping_perf_test, _iommu_mode ## _ ## _name) { \
+ .iommu_mode = #_iommu_mode, \
+ .mmap_flags = MAP_ANONYMOUS | MAP_PRIVATE | MAP_POPULATE | (_mmap_flags), \
+}
+
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous, 0);
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_2mb, MAP_HUGETLB | MAP_HUGE_2MB);
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, MAP_HUGETLB | MAP_HUGE_1GB);
+
+#undef FIXTURE_VARIANT_ADD_IOMMU_MODE
+
+FIXTURE_SETUP(vfio_dma_mapping_perf_test)
+{
+ self->iommu = iommu_init(variant->iommu_mode);
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ self->iova_allocator = iova_allocator_init(self->iommu);
+}
+
+FIXTURE_TEARDOWN(vfio_dma_mapping_perf_test)
+{
+ iova_allocator_cleanup(self->iova_allocator);
+ vfio_pci_device_cleanup(self->device);
+ iommu_cleanup(self->iommu);
+}
+
+TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
+{
+ const u64 size = SZ_1G;
+ const int flags = variant->mmap_flags;
+ struct dma_region region;
+
+ ksft_print_msg("mmap size = %lluG\n", (unsigned long long)(size / SZ_1G));
+
+ TIME("mmap",
+ region.vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0));
+
+ /* Skip the test if there aren't enough HugeTLB pages available. */
+ if (flags & MAP_HUGETLB && region.vaddr == MAP_FAILED)
+ SKIP(return, "mmap() failed: %s (%d)\n", strerror(errno), errno);
+ else
+ ASSERT_NE(region.vaddr, MAP_FAILED);
+
+ region.iova = iova_allocator_alloc(self->iova_allocator, size);
+ region.size = size;
+
+ TIME("IOMMU map", iommu_map(self->iommu, ®ion));
+ ASSERT_EQ(region.iova, to_iova(self->device, region.vaddr));
+
+ TIME("IOMMU unmap", iommu_unmap(self->iommu, ®ion));
+ TIME("munmap", ASSERT_EQ(0, munmap(region.vaddr, size)));
+}
+
+int main(int argc, char *argv[])
+{
+ device_bdf = vfio_selftests_get_bdf(&argc, argv);
+ return test_harness_run(argc, argv);
+}
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 1/4] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-09-10 15:33 ` Aaron Lewis
2026-10-08 10:03 ` David Matlack
2026-09-10 15:33 ` [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
2026-10-08 10:30 ` [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test David Matlack
4 siblings, 1 reply; 10+ messages in thread
From: Aaron Lewis @ 2026-09-10 15:33 UTC (permalink / raw)
To: kvm; +Cc: alex, dmatlack, jgg, Aaron Lewis
Add a second test to "vfio_dma_mapping_perf_test" to evaluate memfd
latencies. A key motivator for adding this test is to demonstrate that
the IOMMU_IOAS_MAP_FILE ioctl is significantly faster than other methods
of mapping DMA regions. While this performance difference is not fully
apparent with the test's current capabilities, it will become evident
as more features are introduced later in the series.
To support this, add IOMMU_IOAS_MAP_FILE ioctl support to the VFIO
selftest library via iommufd_map_file().
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
.../vfio/lib/include/libvfio/iommu.h | 9 ++
tools/testing/selftests/vfio/lib/iommu.c | 24 +++++
.../vfio/vfio_dma_mapping_perf_test.c | 101 ++++++++++++++++++
3 files changed, 134 insertions(+)
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
index 5c77875240f4..2336dcebce5c 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
@@ -22,6 +22,8 @@ struct dma_region {
void *vaddr;
iova_t iova;
u64 size;
+ int fd;
+ u64 fd_offset;
};
struct iommu {
@@ -35,6 +37,13 @@ struct iommu {
struct iommu *iommu_init(const char *iommu_mode);
void iommu_cleanup(struct iommu *iommu);
+int __iommufd_map_file(struct iommu *iommu, struct dma_region *region);
+
+static inline void iommufd_map_file(struct iommu *iommu, struct dma_region *region)
+{
+ VFIO_ASSERT_EQ(__iommufd_map_file(iommu, region), 0);
+}
+
int __iommu_map(struct iommu *iommu, struct dma_region *region);
static inline void iommu_map(struct iommu *iommu, struct dma_region *region)
diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
index b6f3c5c84e01..a0a3dc4b968c 100644
--- a/tools/testing/selftests/vfio/lib/iommu.c
+++ b/tools/testing/selftests/vfio/lib/iommu.c
@@ -97,6 +97,30 @@ iova_t iommu_hva2iova(struct iommu *iommu, void *vaddr)
return iova;
}
+int __iommufd_map_file(struct iommu *iommu, struct dma_region *region)
+{
+ VFIO_ASSERT_TRUE(iommu->iommufd, "IOMMU_IOAS_MAP_FILE is an IOMMUFD IOCTL.");
+
+ struct iommu_ioas_map_file args = {
+ .size = sizeof(args),
+ .flags = IOMMU_IOAS_MAP_READABLE |
+ IOMMU_IOAS_MAP_WRITEABLE |
+ IOMMU_IOAS_MAP_FIXED_IOVA,
+ .ioas_id = iommu->ioas_id,
+ .fd = region->fd,
+ .start = region->fd_offset,
+ .iova = region->iova,
+ .length = region->size,
+ };
+
+ if (ioctl(iommu->iommufd, IOMMU_IOAS_MAP_FILE, &args))
+ return -errno;
+
+ list_add(®ion->link, &iommu->dma_regions);
+
+ return 0;
+}
+
static int vfio_iommu_map(struct iommu *iommu, struct dma_region *region)
{
struct vfio_iommu_type1_dma_map args = {
diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
index 4d41fecf2372..4113a20127de 100644
--- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
+++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
@@ -83,6 +83,107 @@ TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
TIME("munmap", ASSERT_EQ(0, munmap(region.vaddr, size)));
}
+FIXTURE(vfio_dma_mapping_perf_memfd_test) {
+ struct iommu *iommu;
+ struct vfio_pci_device *device;
+ struct iova_allocator *iova_allocator;
+};
+
+FIXTURE_VARIANT(vfio_dma_mapping_perf_memfd_test) {
+ const char *iommu_mode;
+ int mmap_flags;
+ int memfd_flags;
+};
+
+#define FIXTURE_VARIANT_ADD_MEMFD_MODE(_name, _mmap_flags, _memfd_flags) \
+FIXTURE_VARIANT_ADD(vfio_dma_mapping_perf_memfd_test, iommufd ## _ ## _name) {\
+ .iommu_mode = MODE_IOMMUFD, \
+ .mmap_flags = MAP_SHARED | MAP_POPULATE | (_mmap_flags), \
+ .memfd_flags = (_memfd_flags), \
+}
+
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd, 0, 0);
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_2mb,
+ MAP_HUGETLB | MAP_HUGE_2MB,
+ MFD_HUGETLB | MFD_HUGE_2MB);
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_1gb,
+ MAP_HUGETLB | MAP_HUGE_1GB,
+ MFD_HUGETLB | MFD_HUGE_1GB);
+
+#undef FIXTURE_VARIANT_ADD_MEMFD_MODE
+
+FIXTURE_SETUP(vfio_dma_mapping_perf_memfd_test)
+{
+ self->iommu = iommu_init(variant->iommu_mode);
+ self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ self->iova_allocator = iova_allocator_init(self->iommu);
+}
+
+FIXTURE_TEARDOWN(vfio_dma_mapping_perf_memfd_test)
+{
+ iova_allocator_cleanup(self->iova_allocator);
+ vfio_pci_device_cleanup(self->device);
+ iommu_cleanup(self->iommu);
+}
+
+static void *setup_memfd(int *fd, u64 size, int mmap_flags, int mfd_flags)
+{
+ void *buf = MAP_FAILED;
+
+ TIME("memfd_create",
+ *fd = memfd_create("vfio_dma_mapping_perf_memfd_test", mfd_flags));
+ if (*fd < 0)
+ return MAP_FAILED;
+
+ if (ftruncate(*fd, size))
+ goto out;
+
+ TIME("mmap",
+ buf = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, *fd, 0));
+
+out:
+ if (buf == MAP_FAILED)
+ close(*fd);
+
+ return buf;
+}
+
+static void teardown_memfd(int fd, u64 size, void *vaddr)
+{
+ TIME("munmap", VFIO_ASSERT_EQ(0, munmap(vaddr, size)));
+ TIME("close", VFIO_ASSERT_EQ(0, close(fd)));
+}
+
+TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
+{
+ const u64 size = SZ_1G;
+ const int mmap_flags = variant->mmap_flags;
+ struct dma_region region;
+ int fd;
+
+ ksft_print_msg("mmap size = %lluG\n", (unsigned long long)(size / SZ_1G));
+
+ region.vaddr = setup_memfd(&fd, size, mmap_flags, variant->memfd_flags);
+
+ /* Skip the test if there aren't enough HugeTLB pages available. */
+ if (mmap_flags & MAP_HUGETLB && region.vaddr == MAP_FAILED)
+ SKIP(return, "setup_memfd() failed: %s (%d)\n", strerror(errno), errno);
+ else
+ ASSERT_NE(region.vaddr, MAP_FAILED);
+
+ region.iova = iova_allocator_alloc(self->iova_allocator, size);
+ region.size = size;
+ region.fd = fd;
+ region.fd_offset = 0;
+
+ TIME("IOMMU map", iommufd_map_file(self->iommu, ®ion));
+ ASSERT_EQ(region.iova, to_iova(self->device, region.vaddr));
+
+ TIME("IOMMU unmap", iommu_unmap(self->iommu, ®ion));
+
+ teardown_memfd(fd, size, region.vaddr);
+}
+
int main(int argc, char *argv[])
{
device_bdf = vfio_selftests_get_bdf(&argc, argv);
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (2 preceding siblings ...)
2026-09-10 15:33 ` [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-09-10 15:33 ` Aaron Lewis
2026-09-10 15:41 ` sashiko-bot
2026-10-08 10:27 ` David Matlack
2026-10-08 10:30 ` [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test David Matlack
4 siblings, 2 replies; 10+ messages in thread
From: Aaron Lewis @ 2026-09-10 15:33 UTC (permalink / raw)
To: kvm; +Cc: alex, dmatlack, jgg, Aaron Lewis
Allow the user to specify a DMA region size via the command line for
vfio_dma_mapping_perf_test.
Because the selftest harness parses command-line parameters, adding
custom options directly to individual tests requires the test harness
to recognize them.
Add support to kselftest_harness.h for custom command-line options via
struct test_harness_cli_opts and test_harness_run_opts(). Then use this
interface in vfio_dma_mapping_perf_test to introduce a new '-b' option to
specify the DMA region size.
For example, both the test size and test harness options can now be
passed together naturally:
./vfio_dma_mapping_perf_test -b 16G -v vfio_type1_iommu_memfd_hugetlb_1gb
Assert if the custom options string is problematic (e.g. conflicting with
built-in options or exceeding the buffer limit). This should only happen
when developing a test, and asserting makes the issue immediately obvious
to the developer.
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
tools/testing/selftests/kselftest_harness.h | 86 ++++++++++++++++---
.../vfio/vfio_dma_mapping_perf_test.c | 86 +++++++++++++++++--
2 files changed, 151 insertions(+), 21 deletions(-)
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
index 29a19bc87084..5065eb7144bc 100644
--- a/tools/testing/selftests/kselftest_harness.h
+++ b/tools/testing/selftests/kselftest_harness.h
@@ -54,6 +54,7 @@
#define _GNU_SOURCE
#endif
#include <asm/types.h>
+#include <assert.h>
#include <ctype.h>
#include <errno.h>
#include <linux/unistd.h>
@@ -76,6 +77,18 @@ static inline void __kselftest_memset_safe(void *s, int c, size_t n)
memset(s, c, n);
}
+/**
+ * struct test_harness_cli_opts - Custom command-line options for test harness
+ * @optstring: getopt option string; must not conflict with harness options.
+ * @handler: Callback to handle custom options; returns KSFT_PASS or KSFT_FAIL.
+ * @help: Optional callback to print custom option help text.
+ */
+struct test_harness_cli_opts {
+ const char *optstring;
+ int (*handler)(int opt, char *optarg);
+ void (*help)(void);
+};
+
#define KSELFTEST_PRIO_TEST 20000
#define KSELFTEST_PRIO_XFAIL 20001
@@ -1097,11 +1110,36 @@ static void test_harness_list_tests(void)
}
}
-static int test_harness_argv_check(int argc, char **argv)
+#define OPTSTRING_LEN 128
+
+static void optstring_append_custom(char *optstring, size_t size,
+ const struct test_harness_cli_opts *opts)
{
+ const char *c;
+
+ if (!opts || !opts->optstring)
+ return;
+
+ assert(strlen(optstring) + strlen(opts->optstring) < size);
+
+ for (c = opts->optstring; *c; c++) {
+ if (isalnum(*c))
+ assert(!strchr(optstring, *c));
+ }
+
+ strncat(optstring, opts->optstring, size - strlen(optstring) - 1);
+}
+
+static int test_harness_argv_check(int argc, char **argv,
+ const struct test_harness_cli_opts *opts)
+{
+ char optstring[OPTSTRING_LEN] = "dhlF:f:V:v:t:T:r:";
int opt;
- while ((opt = getopt(argc, argv, "dhlF:f:V:v:t:T:r:")) != -1) {
+ optstring_append_custom(optstring, sizeof(optstring), opts);
+
+ optind = 1;
+ while ((opt = getopt(argc, argv, optstring)) != -1) {
switch (opt) {
case 'f':
case 'F':
@@ -1118,7 +1156,6 @@ static int test_harness_argv_check(int argc, char **argv)
ksft_debug_enabled = true;
break;
case 'h':
- default:
fprintf(stderr,
"Usage: %s [-h|-l|-d] [-t|-T|-v|-V|-f|-F|-r name]\n"
"\t-h print help\n"
@@ -1139,7 +1176,14 @@ static int test_harness_argv_check(int argc, char **argv)
"include all tests from variant 'bla'\n"
"but not test 'foo' specify '-T foo -v bla'.\n"
"", argv[0]);
- return opt == 'h' ? KSFT_SKIP : KSFT_FAIL;
+ if (opts && opts->help)
+ opts->help();
+ return KSFT_SKIP;
+ default:
+ if (opts && opts->handler &&
+ opts->handler(opt, optarg) == KSFT_PASS)
+ break;
+ return KSFT_FAIL;
}
}
@@ -1149,31 +1193,39 @@ static int test_harness_argv_check(int argc, char **argv)
static bool test_enabled(int argc, char **argv,
struct __fixture_metadata *f,
struct __fixture_variant_metadata *v,
- struct __test_metadata *t)
+ struct __test_metadata *t,
+ const struct test_harness_cli_opts *opts)
{
unsigned int flen = 0, vlen = 0, tlen = 0;
+ char optstring[OPTSTRING_LEN] = "dF:f:V:v:t:T:r:";
bool has_positive = false;
int opt;
- optind = 1;
- while ((opt = getopt(argc, argv, "dF:f:V:v:t:T:r:")) != -1) {
- if (opt != 'd')
- has_positive |= islower(opt);
+ optstring_append_custom(optstring, sizeof(optstring), opts);
- switch (tolower(opt)) {
+ optind = 1;
+ while ((opt = getopt(argc, argv, optstring)) != -1) {
+ switch (opt) {
case 't':
+ case 'T':
+ has_positive |= islower(opt);
if (!strcmp(t->name, optarg))
return islower(opt);
break;
case 'f':
+ case 'F':
+ has_positive |= islower(opt);
if (!strcmp(f->name, optarg))
return islower(opt);
break;
case 'v':
+ case 'V':
+ has_positive |= islower(opt);
if (!strcmp(v->name, optarg))
return islower(opt);
break;
case 'r':
+ has_positive = true;
if (!tlen) {
flen = strlen(f->name);
vlen = strlen(v->name);
@@ -1262,7 +1314,8 @@ static void __run_test(struct __fixture_metadata *f,
diagnostic ? "%s" : NULL, diagnostic);
}
-static int test_harness_run(int argc, char **argv)
+static int test_harness_run_opts(int argc, char **argv,
+ const struct test_harness_cli_opts *opts)
{
struct __fixture_variant_metadata no_variant = { .name = "", };
struct __fixture_variant_metadata *v;
@@ -1274,7 +1327,7 @@ static int test_harness_run(int argc, char **argv)
unsigned int count = 0;
unsigned int pass_count = 0;
- ret = test_harness_argv_check(argc, argv);
+ ret = test_harness_argv_check(argc, argv, opts);
if (ret != KSFT_PASS)
return ret;
@@ -1283,7 +1336,7 @@ static int test_harness_run(int argc, char **argv)
unsigned int old_tests = test_count;
for (t = f->tests; t; t = t->next)
- if (test_enabled(argc, argv, f, v, t))
+ if (test_enabled(argc, argv, f, v, t, opts))
test_count++;
if (old_tests != test_count)
@@ -1301,7 +1354,7 @@ static int test_harness_run(int argc, char **argv)
for (f = __fixture_list; f; f = f->next) {
for (v = f->variant ?: &no_variant; v; v = v->next) {
for (t = f->tests; t; t = t->next) {
- if (!test_enabled(argc, argv, f, v, t))
+ if (!test_enabled(argc, argv, f, v, t, opts))
continue;
count++;
t->results = results;
@@ -1324,6 +1377,11 @@ static int test_harness_run(int argc, char **argv)
return KSFT_FAIL;
}
+static inline int test_harness_run(int argc, char **argv)
+{
+ return test_harness_run_opts(argc, argv, NULL);
+}
+
static void __attribute__((constructor(KSELFTEST_PRIO_TEST))) __constructor_order_first(void)
{
__constructor_order_forward = true;
diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
index 4113a20127de..84a973a53b8c 100644
--- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
+++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <sys/mman.h>
@@ -17,7 +18,78 @@
#include "kselftest_harness.h"
-static const char *device_bdf;
+static struct {
+ u64 size;
+ const char *device_bdf;
+} test_params = {
+ .size = SZ_1G,
+};
+
+/*
+ * Parses "[0-9]+[kmgt]?".
+ */
+static u64 parse_size(const char *size)
+{
+ int shift = 0;
+ char *scale;
+ u64 base;
+
+ VFIO_ASSERT_TRUE(size && isdigit(size[0]),
+ "Need at least one digit in '%s'.", size);
+
+ base = strtoull(size, &scale, 0);
+
+ VFIO_ASSERT_TRUE(base != ULLONG_MAX, "Overflow parsing size!");
+
+ switch (tolower(*scale)) {
+ case 't':
+ shift = 40;
+ break;
+ case 'g':
+ shift = 30;
+ break;
+ case 'm':
+ shift = 20;
+ break;
+ case 'k':
+ shift = 10;
+ break;
+ case 'b':
+ case '\0':
+ shift = 0;
+ break;
+ default:
+ VFIO_FAIL("Unknown size letter '%c'.", *scale);
+ }
+
+ VFIO_ASSERT_TRUE((base << shift) >> shift == base,
+ "Overflow scaling size!");
+
+ return base << shift;
+}
+
+static void opt_custom_help(void)
+{
+ fprintf(stderr,
+ "\nCustom options:\n"
+ "\t-b bytes Specify the size of the DMA region to be mapped\n"
+ "\t and unmapped. e.g. 16M or 8G, (default: 1G)\n");
+}
+
+static int opt_custom_handler(int opt, char *optarg)
+{
+ if (opt == 'b') {
+ test_params.size = parse_size(optarg);
+ return KSFT_PASS;
+ }
+ return KSFT_FAIL;
+}
+
+static const struct test_harness_cli_opts opts = {
+ .optstring = "b:",
+ .handler = opt_custom_handler,
+ .help = opt_custom_help,
+};
FIXTURE(vfio_dma_mapping_perf_test) {
struct iommu *iommu;
@@ -45,7 +117,7 @@ FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, MAP_HUGETLB | MAP_HUG
FIXTURE_SETUP(vfio_dma_mapping_perf_test)
{
self->iommu = iommu_init(variant->iommu_mode);
- self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ self->device = vfio_pci_device_init(test_params.device_bdf, self->iommu);
self->iova_allocator = iova_allocator_init(self->iommu);
}
@@ -58,7 +130,7 @@ FIXTURE_TEARDOWN(vfio_dma_mapping_perf_test)
TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
{
- const u64 size = SZ_1G;
+ const u64 size = test_params.size;
const int flags = variant->mmap_flags;
struct dma_region region;
@@ -115,7 +187,7 @@ FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_1gb,
FIXTURE_SETUP(vfio_dma_mapping_perf_memfd_test)
{
self->iommu = iommu_init(variant->iommu_mode);
- self->device = vfio_pci_device_init(device_bdf, self->iommu);
+ self->device = vfio_pci_device_init(test_params.device_bdf, self->iommu);
self->iova_allocator = iova_allocator_init(self->iommu);
}
@@ -156,7 +228,7 @@ static void teardown_memfd(int fd, u64 size, void *vaddr)
TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
{
- const u64 size = SZ_1G;
+ const u64 size = test_params.size;
const int mmap_flags = variant->mmap_flags;
struct dma_region region;
int fd;
@@ -186,6 +258,6 @@ TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
int main(int argc, char *argv[])
{
- device_bdf = vfio_selftests_get_bdf(&argc, argv);
- return test_harness_run(argc, argv);
+ test_params.device_bdf = vfio_selftests_get_bdf(&argc, argv);
+ return test_harness_run_opts(argc, argv, &opts);
}
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
2026-09-10 15:33 ` [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-09-10 15:41 ` sashiko-bot
2026-10-08 10:27 ` David Matlack
1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-09-10 15:41 UTC (permalink / raw)
To: Aaron Lewis; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] Silent failure on invalid or unrecognized command-line options due to missing usage print.
--
commit 6144d66da4643ccf9bc4b7ab3160a7d8485045d3
Author: Aaron Lewis <aaronlewis@google.com>
vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
This commit introduces a mechanism in the selftest harness to parse custom
command-line options. It uses this new functionality to add a '-b' option
to vfio_dma_mapping_perf_test for specifying the DMA region size.
> diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
> index 29a19bc870846..5065eb7144bce 100644
> --- a/tools/testing/selftests/kselftest_harness.h
> +++ b/tools/testing/selftests/kselftest_harness.h
[ ... ]
> @@ -1118,7 +1156,6 @@ static int test_harness_argv_check(int argc, char **argv)
> ksft_debug_enabled = true;
> break;
> case 'h':
> - default:
> fprintf(stderr,
> "Usage: %s [-h|-l|-d] [-t|-T|-v|-V|-f|-F|-r name]\n"
> "\t-h print help\n"
> @@ -1139,7 +1176,14 @@ static int test_harness_argv_check(int argc, char **argv)
> "include all tests from variant 'bla'\n"
> "but not test 'foo' specify '-T foo -v bla'.\n"
> "", argv[0]);
> - return opt == 'h' ? KSFT_SKIP : KSFT_FAIL;
> + if (opts && opts->help)
> + opts->help();
> + return KSFT_SKIP;
> + default:
> + if (opts && opts->handler &&
> + opts->handler(opt, optarg) == KSFT_PASS)
> + break;
> + return KSFT_FAIL;
[Severity: Medium]
Is it intended to silently fail here in test_harness_argv_check() without
printing the usage string?
Previously, unrecognized options would fall through to the 'h' case and print
the help text before returning KSFT_FAIL. By removing the default fallthrough
to the 'h' case, passing an invalid option to the test binary now exits
silently, which might make it harder for users to realize they've provided an
invalid flag.
> }
> }
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260910153326.3085937-1-aaronlewis@google.com?part=4
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test
2026-09-10 15:33 ` [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-10-08 9:48 ` David Matlack
0 siblings, 0 replies; 10+ messages in thread
From: David Matlack @ 2026-10-08 9:48 UTC (permalink / raw)
To: Aaron Lewis; +Cc: kvm, alex, jgg
On 2026-09-10 03:33 PM, Aaron Lewis wrote:
> Introduce vfio_dma_mapping_perf_test to aid in latency testing. Rather
> than a traditional pass/fail test, which tends to be flaky when
> enforcing latency bounds, this test directly reports the latency of
> various stages in the mapping and unmapping process.
>
> Start with a basic test that performs a simple DMA map/unmap and tracks
> the duration of each operation. The test reports the following metrics:
>
> 1. Time taken by mmap().
> 2. Time taken to map a DMA region.
> 3. Time taken to unmap a DMA region.
> 4. Time taken by munmap().
>
> Subsequent commits in this series will introduce more features to allow
> for a wider variety of testing.
>
> To ensure consistent DMA mapping times, the test uses MAP_POPULATE when
> calling mmap(). This forces page faults to be handled upfront, isolating
> the DMA mapping times from them.
>
> Signed-off-by: Aaron Lewis <aaronlewis@google.com>
Aside from the nit,
Reviewed-by: David Matlack <dmatlack@google.com>
> ---
> tools/testing/selftests/vfio/Makefile | 1 +
> .../selftests/vfio/lib/include/libvfio.h | 31 +++++++
> .../vfio/vfio_dma_mapping_perf_test.c | 90 +++++++++++++++++++
> 3 files changed, 122 insertions(+)
> create mode 100644 tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
>
> diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
> index 2c32c48db509..ed5856af5f9a 100644
> --- a/tools/testing/selftests/vfio/Makefile
> +++ b/tools/testing/selftests/vfio/Makefile
> @@ -8,6 +8,7 @@ else
> CFLAGS = $(KHDR_INCLUDES)
> TEST_GEN_PROGS += vfio_dma_mapping_test
> TEST_GEN_PROGS += vfio_dma_mapping_mmio_test
> +TEST_GEN_PROGS += vfio_dma_mapping_perf_test
> TEST_GEN_PROGS += vfio_iommufd_setup_test
> TEST_GEN_PROGS += vfio_pci_device_test
> TEST_GEN_PROGS += vfio_pci_device_init_perf_test
> diff --git a/tools/testing/selftests/vfio/lib/include/libvfio.h b/tools/testing/selftests/vfio/lib/include/libvfio.h
> index 07862b470777..8496b8b4da3c 100644
> --- a/tools/testing/selftests/vfio/lib/include/libvfio.h
> +++ b/tools/testing/selftests/vfio/lib/include/libvfio.h
> @@ -9,6 +9,37 @@
> #include <libvfio/vfio_pci_device.h>
> #include <libvfio/vfio_pci_driver.h>
>
> +#include <stdint.h>
> +#include <time.h>
> +#include <linux/time64.h>
> +
> +static inline void timer_start(struct timespec *start)
> +{
> + clock_gettime(CLOCK_MONOTONIC, start);
> +}
> +
> +static inline uint64_t timer_elapsed_ns(struct timespec start)
> +{
> + struct timespec end;
> +
> + clock_gettime(CLOCK_MONOTONIC, &end);
Maybe rename timer_start() to timer_get() and re-use it here.
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test
2026-09-10 15:33 ` [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-10-08 10:03 ` David Matlack
0 siblings, 0 replies; 10+ messages in thread
From: David Matlack @ 2026-10-08 10:03 UTC (permalink / raw)
To: Aaron Lewis; +Cc: kvm, alex, jgg
On 2026-09-10 03:33 PM, Aaron Lewis wrote:
> Add a second test to "vfio_dma_mapping_perf_test" to evaluate memfd
> latencies. A key motivator for adding this test is to demonstrate that
> the IOMMU_IOAS_MAP_FILE ioctl is significantly faster than other methods
> of mapping DMA regions. While this performance difference is not fully
> apparent with the test's current capabilities, it will become evident
> as more features are introduced later in the series.
nit: Don't refer to series in commit messages. Prefer something like
"subsequent commit".
Also this sentence is extremely vague. How about:
This performance difference is more apparent with larger memory
regions, which will be added in a subsequent commit.
>
> To support this, add IOMMU_IOAS_MAP_FILE ioctl support to the VFIO
> selftest library via iommufd_map_file().
>
> Signed-off-by: Aaron Lewis <aaronlewis@google.com>
Aside from the minor comments,
Reviewed-by: David Matlack <dmatlack@google.com>
> ---
> .../vfio/lib/include/libvfio/iommu.h | 9 ++
> tools/testing/selftests/vfio/lib/iommu.c | 24 +++++
> .../vfio/vfio_dma_mapping_perf_test.c | 101 ++++++++++++++++++
> 3 files changed, 134 insertions(+)
>
> diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
> index 5c77875240f4..2336dcebce5c 100644
> --- a/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
> +++ b/tools/testing/selftests/vfio/lib/include/libvfio/iommu.h
> @@ -22,6 +22,8 @@ struct dma_region {
> void *vaddr;
> iova_t iova;
> u64 size;
> + int fd;
> + u64 fd_offset;
> };
>
> struct iommu {
> @@ -35,6 +37,13 @@ struct iommu {
> struct iommu *iommu_init(const char *iommu_mode);
> void iommu_cleanup(struct iommu *iommu);
>
> +int __iommufd_map_file(struct iommu *iommu, struct dma_region *region);
> +
> +static inline void iommufd_map_file(struct iommu *iommu, struct dma_region *region)
> +{
> + VFIO_ASSERT_EQ(__iommufd_map_file(iommu, region), 0);
> +}
> +
> int __iommu_map(struct iommu *iommu, struct dma_region *region);
>
> static inline void iommu_map(struct iommu *iommu, struct dma_region *region)
> diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
> index b6f3c5c84e01..a0a3dc4b968c 100644
> --- a/tools/testing/selftests/vfio/lib/iommu.c
> +++ b/tools/testing/selftests/vfio/lib/iommu.c
> @@ -97,6 +97,30 @@ iova_t iommu_hva2iova(struct iommu *iommu, void *vaddr)
> return iova;
> }
>
> +int __iommufd_map_file(struct iommu *iommu, struct dma_region *region)
> +{
> + VFIO_ASSERT_TRUE(iommu->iommufd, "IOMMU_IOAS_MAP_FILE is an IOMMUFD IOCTL.");
> +
> + struct iommu_ioas_map_file args = {
> + .size = sizeof(args),
> + .flags = IOMMU_IOAS_MAP_READABLE |
> + IOMMU_IOAS_MAP_WRITEABLE |
> + IOMMU_IOAS_MAP_FIXED_IOVA,
> + .ioas_id = iommu->ioas_id,
> + .fd = region->fd,
> + .start = region->fd_offset,
> + .iova = region->iova,
> + .length = region->size,
> + };
> +
> + if (ioctl(iommu->iommufd, IOMMU_IOAS_MAP_FILE, &args))
> + return -errno;
> +
> + list_add(®ion->link, &iommu->dma_regions);
> +
> + return 0;
> +}
> +
> static int vfio_iommu_map(struct iommu *iommu, struct dma_region *region)
> {
> struct vfio_iommu_type1_dma_map args = {
> diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> index 4d41fecf2372..4113a20127de 100644
> --- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> +++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> @@ -83,6 +83,107 @@ TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
> TIME("munmap", ASSERT_EQ(0, munmap(region.vaddr, size)));
> }
>
> +FIXTURE(vfio_dma_mapping_perf_memfd_test) {
> + struct iommu *iommu;
> + struct vfio_pci_device *device;
> + struct iova_allocator *iova_allocator;
> +};
> +
> +FIXTURE_VARIANT(vfio_dma_mapping_perf_memfd_test) {
> + const char *iommu_mode;
> + int mmap_flags;
> + int memfd_flags;
> +};
> +
> +#define FIXTURE_VARIANT_ADD_MEMFD_MODE(_name, _mmap_flags, _memfd_flags) \
> +FIXTURE_VARIANT_ADD(vfio_dma_mapping_perf_memfd_test, iommufd ## _ ## _name) {\
> + .iommu_mode = MODE_IOMMUFD, \
Do not put constants in the fixture variant.
> + .mmap_flags = MAP_SHARED | MAP_POPULATE | (_mmap_flags), \
> + .memfd_flags = (_memfd_flags), \
> +}
> +
> +FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd, 0, 0);
> +FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_2mb,
> + MAP_HUGETLB | MAP_HUGE_2MB,
> + MFD_HUGETLB | MFD_HUGE_2MB);
> +FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_1gb,
> + MAP_HUGETLB | MAP_HUGE_1GB,
> + MFD_HUGETLB | MFD_HUGE_1GB);
> +
> +#undef FIXTURE_VARIANT_ADD_MEMFD_MODE
> +
> +FIXTURE_SETUP(vfio_dma_mapping_perf_memfd_test)
> +{
> + self->iommu = iommu_init(variant->iommu_mode);
> + self->device = vfio_pci_device_init(device_bdf, self->iommu);
> + self->iova_allocator = iova_allocator_init(self->iommu);
> +}
> +
> +FIXTURE_TEARDOWN(vfio_dma_mapping_perf_memfd_test)
> +{
> + iova_allocator_cleanup(self->iova_allocator);
> + vfio_pci_device_cleanup(self->device);
> + iommu_cleanup(self->iommu);
> +}
> +
> +static void *setup_memfd(int *fd, u64 size, int mmap_flags, int mfd_flags)
> +{
> + void *buf = MAP_FAILED;
> + TIME("memfd_create",
> + *fd = memfd_create("vfio_dma_mapping_perf_memfd_test", mfd_flags));
> + if (*fd < 0)
> + return MAP_FAILED;
> +
> + if (ftruncate(*fd, size))
> + goto out;
> +
> + TIME("mmap",
> + buf = mmap(NULL, size, PROT_READ | PROT_WRITE, mmap_flags, *fd, 0));
> +
> +out:
> + if (buf == MAP_FAILED)
> + close(*fd);
> +
> + return buf;
> +}
> +
> +static void teardown_memfd(int fd, u64 size, void *vaddr)
> +{
> + TIME("munmap", VFIO_ASSERT_EQ(0, munmap(vaddr, size)));
> + TIME("close", VFIO_ASSERT_EQ(0, close(fd)));
> +}
> +
> +TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
> +{
> + const u64 size = SZ_1G;
> + const int mmap_flags = variant->mmap_flags;
> + struct dma_region region;
> + int fd;
> +
> + ksft_print_msg("mmap size = %lluG\n", (unsigned long long)(size / SZ_1G));
> +
> + region.vaddr = setup_memfd(&fd, size, mmap_flags, variant->memfd_flags);
> +
> + /* Skip the test if there aren't enough HugeTLB pages available. */
> + if (mmap_flags & MAP_HUGETLB && region.vaddr == MAP_FAILED)
> + SKIP(return, "setup_memfd() failed: %s (%d)\n", strerror(errno), errno);
> + else
> + ASSERT_NE(region.vaddr, MAP_FAILED);
> +
> + region.iova = iova_allocator_alloc(self->iova_allocator, size);
> + region.size = size;
> + region.fd = fd;
> + region.fd_offset = 0;
> +
> + TIME("IOMMU map", iommufd_map_file(self->iommu, ®ion));
Should this be "IOMMU map file"?
> + ASSERT_EQ(region.iova, to_iova(self->device, region.vaddr));
> +
> + TIME("IOMMU unmap", iommu_unmap(self->iommu, ®ion));
> +
> + teardown_memfd(fd, size, region.vaddr);
> +}
> +
> int main(int argc, char *argv[])
> {
> device_bdf = vfio_selftests_get_bdf(&argc, argv);
> --
> 2.55.0.1003.g10538fe699-goog
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
2026-09-10 15:33 ` [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
2026-09-10 15:41 ` sashiko-bot
@ 2026-10-08 10:27 ` David Matlack
1 sibling, 0 replies; 10+ messages in thread
From: David Matlack @ 2026-10-08 10:27 UTC (permalink / raw)
To: Aaron Lewis; +Cc: kvm, alex, jgg
On 2026-09-10 03:33 PM, Aaron Lewis wrote:
> Allow the user to specify a DMA region size via the command line for
> vfio_dma_mapping_perf_test.
>
> Because the selftest harness parses command-line parameters, adding
> custom options directly to individual tests requires the test harness
> to recognize them.
>
> Add support to kselftest_harness.h for custom command-line options via
> struct test_harness_cli_opts and test_harness_run_opts(). Then use this
> interface in vfio_dma_mapping_perf_test to introduce a new '-b' option to
> specify the DMA region size.
>
> For example, both the test size and test harness options can now be
> passed together naturally:
>
> ./vfio_dma_mapping_perf_test -b 16G -v vfio_type1_iommu_memfd_hugetlb_1gb
>
> Assert if the custom options string is problematic (e.g. conflicting with
> built-in options or exceeding the buffer limit). This should only happen
> when developing a test, and asserting makes the issue immediately obvious
> to the developer.
>
> Signed-off-by: Aaron Lewis <aaronlewis@google.com>
> ---
> tools/testing/selftests/kselftest_harness.h | 86 ++++++++++++++++---
Please split out the kselftest_harness.h change into its own commit. We
need to get that commit acked by the selftests maintainers.
> .../vfio/vfio_dma_mapping_perf_test.c | 86 +++++++++++++++++--
> 2 files changed, 151 insertions(+), 21 deletions(-)
>
> diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
> index 29a19bc87084..5065eb7144bc 100644
> --- a/tools/testing/selftests/kselftest_harness.h
> +++ b/tools/testing/selftests/kselftest_harness.h
> @@ -54,6 +54,7 @@
> #define _GNU_SOURCE
> #endif
> #include <asm/types.h>
> +#include <assert.h>
> #include <ctype.h>
> #include <errno.h>
> #include <linux/unistd.h>
> @@ -76,6 +77,18 @@ static inline void __kselftest_memset_safe(void *s, int c, size_t n)
> memset(s, c, n);
> }
>
> +/**
> + * struct test_harness_cli_opts - Custom command-line options for test harness
> + * @optstring: getopt option string; must not conflict with harness options.
> + * @handler: Callback to handle custom options; returns KSFT_PASS or KSFT_FAIL.
> + * @help: Optional callback to print custom option help text.
> + */
> +struct test_harness_cli_opts {
> + const char *optstring;
> + int (*handler)(int opt, char *optarg);
> + void (*help)(void);
> +};
> +
> #define KSELFTEST_PRIO_TEST 20000
> #define KSELFTEST_PRIO_XFAIL 20001
>
> @@ -1097,11 +1110,36 @@ static void test_harness_list_tests(void)
> }
> }
>
> -static int test_harness_argv_check(int argc, char **argv)
> +#define OPTSTRING_LEN 128
> +
> +static void optstring_append_custom(char *optstring, size_t size,
> + const struct test_harness_cli_opts *opts)
> {
> + const char *c;
> +
> + if (!opts || !opts->optstring)
> + return;
> +
> + assert(strlen(optstring) + strlen(opts->optstring) < size);
> +
> + for (c = opts->optstring; *c; c++) {
> + if (isalnum(*c))
> + assert(!strchr(optstring, *c));
> + }
> +
> + strncat(optstring, opts->optstring, size - strlen(optstring) - 1);
> +}
> +
> +static int test_harness_argv_check(int argc, char **argv,
> + const struct test_harness_cli_opts *opts)
> +{
> + char optstring[OPTSTRING_LEN] = "dhlF:f:V:v:t:T:r:";
> int opt;
>
> - while ((opt = getopt(argc, argv, "dhlF:f:V:v:t:T:r:")) != -1) {
> + optstring_append_custom(optstring, sizeof(optstring), opts);
> +
> + optind = 1;
> + while ((opt = getopt(argc, argv, optstring)) != -1) {
> switch (opt) {
> case 'f':
> case 'F':
> @@ -1118,7 +1156,6 @@ static int test_harness_argv_check(int argc, char **argv)
> ksft_debug_enabled = true;
> break;
> case 'h':
> - default:
> fprintf(stderr,
> "Usage: %s [-h|-l|-d] [-t|-T|-v|-V|-f|-F|-r name]\n"
> "\t-h print help\n"
> @@ -1139,7 +1176,14 @@ static int test_harness_argv_check(int argc, char **argv)
> "include all tests from variant 'bla'\n"
> "but not test 'foo' specify '-T foo -v bla'.\n"
> "", argv[0]);
> - return opt == 'h' ? KSFT_SKIP : KSFT_FAIL;
> + if (opts && opts->help)
> + opts->help();
> + return KSFT_SKIP;
> + default:
> + if (opts && opts->handler &&
> + opts->handler(opt, optarg) == KSFT_PASS)
> + break;
If this fails it should still print the help message to match the
previous behavior.
> + return KSFT_FAIL;
> }
> }
>
> @@ -1149,31 +1193,39 @@ static int test_harness_argv_check(int argc, char **argv)
> static bool test_enabled(int argc, char **argv,
> struct __fixture_metadata *f,
> struct __fixture_variant_metadata *v,
> - struct __test_metadata *t)
> + struct __test_metadata *t,
> + const struct test_harness_cli_opts *opts)
> {
> unsigned int flen = 0, vlen = 0, tlen = 0;
> + char optstring[OPTSTRING_LEN] = "dF:f:V:v:t:T:r:";
> bool has_positive = false;
> int opt;
>
> - optind = 1;
> - while ((opt = getopt(argc, argv, "dF:f:V:v:t:T:r:")) != -1) {
> - if (opt != 'd')
> - has_positive |= islower(opt);
> + optstring_append_custom(optstring, sizeof(optstring), opts);
Can the full optstring be constructed once and then passed into
test_enabled()? That would also eliminate the duplicate hard-coded
opt-string.
>
> - switch (tolower(opt)) {
> + optind = 1;
> + while ((opt = getopt(argc, argv, optstring)) != -1) {
> + switch (opt) {
> case 't':
> + case 'T':
> + has_positive |= islower(opt);
> if (!strcmp(t->name, optarg))
> return islower(opt);
> break;
> case 'f':
> + case 'F':
> + has_positive |= islower(opt);
> if (!strcmp(f->name, optarg))
> return islower(opt);
> break;
> case 'v':
> + case 'V':
> + has_positive |= islower(opt);
> if (!strcmp(v->name, optarg))
> return islower(opt);
> break;
> case 'r':
> + has_positive = true;
> if (!tlen) {
> flen = strlen(f->name);
> vlen = strlen(v->name);
> @@ -1262,7 +1314,8 @@ static void __run_test(struct __fixture_metadata *f,
> diagnostic ? "%s" : NULL, diagnostic);
> }
>
> -static int test_harness_run(int argc, char **argv)
> +static int test_harness_run_opts(int argc, char **argv,
> + const struct test_harness_cli_opts *opts)
> {
> struct __fixture_variant_metadata no_variant = { .name = "", };
> struct __fixture_variant_metadata *v;
> @@ -1274,7 +1327,7 @@ static int test_harness_run(int argc, char **argv)
> unsigned int count = 0;
> unsigned int pass_count = 0;
>
> - ret = test_harness_argv_check(argc, argv);
> + ret = test_harness_argv_check(argc, argv, opts);
> if (ret != KSFT_PASS)
> return ret;
>
> @@ -1283,7 +1336,7 @@ static int test_harness_run(int argc, char **argv)
> unsigned int old_tests = test_count;
>
> for (t = f->tests; t; t = t->next)
> - if (test_enabled(argc, argv, f, v, t))
> + if (test_enabled(argc, argv, f, v, t, opts))
> test_count++;
>
> if (old_tests != test_count)
> @@ -1301,7 +1354,7 @@ static int test_harness_run(int argc, char **argv)
> for (f = __fixture_list; f; f = f->next) {
> for (v = f->variant ?: &no_variant; v; v = v->next) {
> for (t = f->tests; t; t = t->next) {
> - if (!test_enabled(argc, argv, f, v, t))
> + if (!test_enabled(argc, argv, f, v, t, opts))
> continue;
> count++;
> t->results = results;
> @@ -1324,6 +1377,11 @@ static int test_harness_run(int argc, char **argv)
> return KSFT_FAIL;
> }
>
> +static inline int test_harness_run(int argc, char **argv)
> +{
> + return test_harness_run_opts(argc, argv, NULL);
> +}
> +
> static void __attribute__((constructor(KSELFTEST_PRIO_TEST))) __constructor_order_first(void)
> {
> __constructor_order_forward = true;
> diff --git a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> index 4113a20127de..84a973a53b8c 100644
> --- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> +++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> @@ -1,4 +1,5 @@
> // SPDX-License-Identifier: GPL-2.0-only
> +#include <ctype.h>
> #include <limits.h>
> #include <stdio.h>
> #include <sys/mman.h>
> @@ -17,7 +18,78 @@
>
> #include "kselftest_harness.h"
>
> -static const char *device_bdf;
> +static struct {
> + u64 size;
> + const char *device_bdf;
> +} test_params = {
> + .size = SZ_1G,
> +};
> +
> +/*
> + * Parses "[0-9]+[kmgt]?".
> + */
> +static u64 parse_size(const char *size)
> +{
> + int shift = 0;
> + char *scale;
> + u64 base;
> +
> + VFIO_ASSERT_TRUE(size && isdigit(size[0]),
> + "Need at least one digit in '%s'.", size);
> +
> + base = strtoull(size, &scale, 0);
> +
> + VFIO_ASSERT_TRUE(base != ULLONG_MAX, "Overflow parsing size!");
> +
> + switch (tolower(*scale)) {
> + case 't':
> + shift = 40;
> + break;
> + case 'g':
> + shift = 30;
> + break;
> + case 'm':
> + shift = 20;
> + break;
> + case 'k':
> + shift = 10;
> + break;
> + case 'b':
> + case '\0':
> + shift = 0;
> + break;
> + default:
> + VFIO_FAIL("Unknown size letter '%c'.", *scale);
> + }
> +
> + VFIO_ASSERT_TRUE((base << shift) >> shift == base,
> + "Overflow scaling size!");
> +
> + return base << shift;
> +}
> +
> +static void opt_custom_help(void)
> +{
> + fprintf(stderr,
> + "\nCustom options:\n"
It shouldn't be on every test to print a header and leading newline like
this for their custom options. kselftest_harness.h should provide this.
e.g.
if (opts && opts->help) {
fprintf(stderr, "\nCustom options:\n");
ops->help();
}
> + "\t-b bytes Specify the size of the DMA region to be mapped\n"
> + "\t and unmapped. e.g. 16M or 8G, (default: 1G)\n");
> +}
> +
> +static int opt_custom_handler(int opt, char *optarg)
> +{
> + if (opt == 'b') {
> + test_params.size = parse_size(optarg);
> + return KSFT_PASS;
> + }
> + return KSFT_FAIL;
> +}
> +
> +static const struct test_harness_cli_opts opts = {
> + .optstring = "b:",
> + .handler = opt_custom_handler,
> + .help = opt_custom_help,
nit: "custom" seems unnecessary. And to align the naming it should be
opts not opt.
> +};
>
> FIXTURE(vfio_dma_mapping_perf_test) {
> struct iommu *iommu;
> @@ -45,7 +117,7 @@ FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, MAP_HUGETLB | MAP_HUG
> FIXTURE_SETUP(vfio_dma_mapping_perf_test)
> {
> self->iommu = iommu_init(variant->iommu_mode);
> - self->device = vfio_pci_device_init(device_bdf, self->iommu);
> + self->device = vfio_pci_device_init(test_params.device_bdf, self->iommu);
> self->iova_allocator = iova_allocator_init(self->iommu);
> }
>
> @@ -58,7 +130,7 @@ FIXTURE_TEARDOWN(vfio_dma_mapping_perf_test)
>
> TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
> {
> - const u64 size = SZ_1G;
> + const u64 size = test_params.size;
> const int flags = variant->mmap_flags;
> struct dma_region region;
>
> @@ -115,7 +187,7 @@ FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_1gb,
> FIXTURE_SETUP(vfio_dma_mapping_perf_memfd_test)
> {
> self->iommu = iommu_init(variant->iommu_mode);
> - self->device = vfio_pci_device_init(device_bdf, self->iommu);
> + self->device = vfio_pci_device_init(test_params.device_bdf, self->iommu);
> self->iova_allocator = iova_allocator_init(self->iommu);
> }
>
> @@ -156,7 +228,7 @@ static void teardown_memfd(int fd, u64 size, void *vaddr)
>
> TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
> {
> - const u64 size = SZ_1G;
> + const u64 size = test_params.size;
> const int mmap_flags = variant->mmap_flags;
> struct dma_region region;
> int fd;
> @@ -186,6 +258,6 @@ TEST_F(vfio_dma_mapping_perf_memfd_test, dma_map_unmap_from_file)
>
> int main(int argc, char *argv[])
> {
> - device_bdf = vfio_selftests_get_bdf(&argc, argv);
> - return test_harness_run(argc, argv);
> + test_params.device_bdf = vfio_selftests_get_bdf(&argc, argv);
> + return test_harness_run_opts(argc, argv, &opts);
> }
> --
> 2.55.0.1003.g10538fe699-goog
>
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (3 preceding siblings ...)
2026-09-10 15:33 ` [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-10-08 10:30 ` David Matlack
4 siblings, 0 replies; 10+ messages in thread
From: David Matlack @ 2026-10-08 10:30 UTC (permalink / raw)
To: Aaron Lewis; +Cc: kvm, alex, jgg
On 2026-09-10 03:33 PM, Aaron Lewis wrote:
> Add a new VFIO selftest, vfio_dma_mapping_perf_test, to provide a
> configurable tool for measuring DMA mapping and unmapping latencies.
>
> This selftest introduces command-line parameters to provide a convenient
> way to tune the test for specific situations. For example, it can be used
> to observe the lengthy unmap times of the Type 1 IOMMU on large memory
> regions, or to compare DMA mapping performance between the Type 1 IOMMU
> and IOMMUFD. These scenarios can now be easily configured and run via the
> command line.
>
> Changes in v3:
> - Replaced raw printf() calls with ksft_print_msg()
> - Updated the TIME macro to preserve errno
> - Added 'fd' and 'fd_offset' to struct dma_region
> - Simplified teardown_memfd() by removing redundant error checks
> - Replaced the command-line argument passthrough (-a "...") approach with
> first-class support in kselftest_harness.h
This looks a lot better, thanks for making the change!
> - Added collision validation in optstring_append_custom() to prevent custom
> options from overlapping with built-in harness options.
> - Switched test_enabled() to match exact option characters rather than
> tolower(opt), ensuring custom options that differ only by case (such as
> 'R') do not mistakenly match built-in single-case options like '-r'.
>
> Aaron Lewis (4):
> vfio: selftests: Assert the region was unmapped in iommu_unmap()
> vfio: selftests: Introduce vfio_dma_mapping_perf_test
> vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test
> vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
>
> tools/testing/selftests/kselftest_harness.h | 86 +++++-
> tools/testing/selftests/vfio/Makefile | 1 +
> .../selftests/vfio/lib/include/libvfio.h | 31 +++
> .../vfio/lib/include/libvfio/iommu.h | 14 +-
> tools/testing/selftests/vfio/lib/iommu.c | 24 ++
> .../vfio/vfio_dma_mapping_perf_test.c | 263 ++++++++++++++++++
> .../selftests/vfio/vfio_dma_mapping_test.c | 11 +-
> 7 files changed, 406 insertions(+), 24 deletions(-)
> create mode 100644 tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
>
> --
> 2.55.0.1003.g10538fe699-goog
>
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-10-08 10:30 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-10 15:33 [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 1/4] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
2026-09-10 15:33 ` [PATCH v3 2/4] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-10-08 9:48 ` David Matlack
2026-09-10 15:33 ` [PATCH v3 3/4] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
2026-10-08 10:03 ` David Matlack
2026-09-10 15:33 ` [PATCH v3 4/4] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
2026-09-10 15:41 ` sashiko-bot
2026-10-08 10:27 ` David Matlack
2026-10-08 10:30 ` [PATCH v3 0/4] Introduce vfio_dma_mapping_perf_test David Matlack
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox