* [PATCH v4 1/5] vfio: selftests: Assert the region was unmapped in iommu_unmap()
2026-11-14 13:21 [PATCH v4 0/5] Introduce vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-11-14 13:21 ` Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 2/5] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (3 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Aaron Lewis @ 2026-11-14 13:21 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.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 2/5] vfio: selftests: Introduce vfio_dma_mapping_perf_test
2026-11-14 13:21 [PATCH v4 0/5] Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 1/5] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
@ 2026-11-14 13:21 ` Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 3/5] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
` (2 subsequent siblings)
4 siblings, 0 replies; 7+ messages in thread
From: Aaron Lewis @ 2026-11-14 13:21 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 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.
Reviewed-by: David Matlack <dmatlack@google.com>
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..05802a2c3aba 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_get(struct timespec *ts)
+{
+ clock_gettime(CLOCK_MONOTONIC, ts);
+}
+
+static inline uint64_t timer_elapsed_ns(struct timespec start)
+{
+ struct timespec end;
+
+ timer_get(&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_get(&__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.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 3/5] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test
2026-11-14 13:21 [PATCH v4 0/5] Introduce vfio_dma_mapping_perf_test Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 1/5] vfio: selftests: Assert the region was unmapped in iommu_unmap() Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 2/5] vfio: selftests: Introduce vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-11-14 13:21 ` Aaron Lewis
2026-11-14 13:21 ` [PATCH v4 4/5] selftests: harness: Support custom command-line options Aaron Lewis
2026-11-14 13:22 ` [PATCH v4 5/5] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
4 siblings, 0 replies; 7+ messages in thread
From: Aaron Lewis @ 2026-11-14 13:21 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. 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().
Reviewed-by: David Matlack <dmatlack@google.com>
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 | 99 +++++++++++++++++++
3 files changed, 132 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..086a63a50df8 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,105 @@ 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) {
+ 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) {\
+ .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(MODE_IOMMUFD);
+ 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 file", 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.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 4/5] selftests: harness: Support custom command-line options
2026-11-14 13:21 [PATCH v4 0/5] Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (2 preceding siblings ...)
2026-11-14 13:21 ` [PATCH v4 3/5] vfio: selftests: Add memfd test to vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-11-14 13:21 ` Aaron Lewis
2026-11-14 13:22 ` [PATCH v4 5/5] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
4 siblings, 0 replies; 7+ messages in thread
From: Aaron Lewis @ 2026-11-14 13:21 UTC (permalink / raw)
To: kvm
Cc: alex, dmatlack, jgg, Aaron Lewis, Kees Cook, Andy Lutomirski,
Will Drewry, Shuah Khan, linux-kselftest
Because the selftest harness parses command-line parameters directly,
adding custom options 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(), allowing tests
to register a custom getopt optstring, option handler, and optional help
callback while keeping test_harness_run() unchanged for existing callers.
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.
Also update test_enabled() to match exact option characters rather than
tolower(opt) so custom options that differ only by case (such as 'R') do
not mistakenly match built-in single-case options like '-r'.
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
tools/testing/selftests/kselftest_harness.h | 127 +++++++++++++++-----
1 file changed, 94 insertions(+), 33 deletions(-)
diff --git a/tools/testing/selftests/kselftest_harness.h b/tools/testing/selftests/kselftest_harness.h
index 29a19bc87084..562d4c0edac5 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,60 @@ 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 void test_harness_usage(const char *prog,
+ const struct test_harness_cli_opts *opts)
+{
+ fprintf(stderr,
+ "Usage: %s [-h|-l|-d] [-t|-T|-v|-V|-f|-F|-r name]\n"
+ "\t-h print help\n"
+ "\t-l list all tests\n"
+ "\t-d enable debug prints\n"
+ "\n"
+ "\t-t name include test\n"
+ "\t-T name exclude test\n"
+ "\t-v name include variant\n"
+ "\t-V name exclude variant\n"
+ "\t-f name include fixture\n"
+ "\t-F name exclude fixture\n"
+ "\t-r name run specified test\n"
+ "\n"
+ "Test filter options can be specified multiple times. The filtering stops\n"
+ "at the first match. For example to include all tests from variant 'bla'\n"
+ "but not test 'foo' specify '-T foo -v bla'.\n", prog);
+
+ if (opts && opts->help) {
+ fprintf(stderr, "\nCustom options:\n");
+ opts->help();
+ }
+}
+
+static int test_harness_argv_check(int argc, char **argv, const char *optstring,
+ const struct test_harness_cli_opts *opts)
{
int opt;
- while ((opt = getopt(argc, argv, "dhlF:f:V:v:t:T:r:")) != -1) {
+ optind = 1;
+ while ((opt = getopt(argc, argv, optstring)) != -1) {
switch (opt) {
case 'f':
case 'F':
@@ -1118,35 +1180,21 @@ static int test_harness_argv_check(int argc, char **argv)
ksft_debug_enabled = true;
break;
case 'h':
+ test_harness_usage(argv[0], opts);
+ return KSFT_SKIP;
default:
- fprintf(stderr,
- "Usage: %s [-h|-l|-d] [-t|-T|-v|-V|-f|-F|-r name]\n"
- "\t-h print help\n"
- "\t-l list all tests\n"
- "\t-d enable debug prints\n"
- "\n"
- "\t-t name include test\n"
- "\t-T name exclude test\n"
- "\t-v name include variant\n"
- "\t-V name exclude variant\n"
- "\t-f name include fixture\n"
- "\t-F name exclude fixture\n"
- "\t-r name run specified test\n"
- "\n"
- "Test filter options can be specified "
- "multiple times. The filtering stops\n"
- "at the first match. For example to "
- "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->handler &&
+ opts->handler(opt, optarg) == KSFT_PASS)
+ break;
+ test_harness_usage(argv[0], opts);
+ return KSFT_FAIL;
}
}
return KSFT_PASS;
}
-static bool test_enabled(int argc, char **argv,
+static bool test_enabled(int argc, char **argv, const char *optstring,
struct __fixture_metadata *f,
struct __fixture_variant_metadata *v,
struct __test_metadata *t)
@@ -1156,24 +1204,28 @@ static bool test_enabled(int argc, char **argv,
int opt;
optind = 1;
- while ((opt = getopt(argc, argv, "dF:f:V:v:t:T:r:")) != -1) {
- if (opt != 'd')
- has_positive |= islower(opt);
-
- switch (tolower(opt)) {
+ 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,8 +1314,10 @@ 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)
{
+ char optstring[OPTSTRING_LEN] = "dhlF:f:V:v:t:T:r:";
struct __fixture_variant_metadata no_variant = { .name = "", };
struct __fixture_variant_metadata *v;
struct __fixture_metadata *f;
@@ -1274,7 +1328,9 @@ 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);
+ optstring_append_custom(optstring, sizeof(optstring), opts);
+
+ ret = test_harness_argv_check(argc, argv, optstring, opts);
if (ret != KSFT_PASS)
return ret;
@@ -1283,7 +1339,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, optstring, f, v, t))
test_count++;
if (old_tests != test_count)
@@ -1301,7 +1357,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, optstring, f, v, t))
continue;
count++;
t->results = results;
@@ -1324,6 +1380,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;
--
2.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* [PATCH v4 5/5] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
2026-11-14 13:21 [PATCH v4 0/5] Introduce vfio_dma_mapping_perf_test Aaron Lewis
` (3 preceding siblings ...)
2026-11-14 13:21 ` [PATCH v4 4/5] selftests: harness: Support custom command-line options Aaron Lewis
@ 2026-11-14 13:22 ` Aaron Lewis
2026-11-14 13:29 ` sashiko-bot
4 siblings, 1 reply; 7+ messages in thread
From: Aaron Lewis @ 2026-11-14 13:22 UTC (permalink / raw)
To: kvm
Cc: alex, dmatlack, jgg, Aaron Lewis, Kees Cook, Andy Lutomirski,
Will Drewry, Shuah Khan, linux-kselftest
Allow the user to specify a DMA region size via the command line for
vfio_dma_mapping_perf_test.
Use test_harness_run_opts() to introduce a '-b' option to specify the
DMA region size so that both the test size and test harness options can
be passed together naturally:
./vfio_dma_mapping_perf_test -b 16G -v vfio_type1_iommu_anonymous_hugetlb_1gb
Signed-off-by: Aaron Lewis <aaronlewis@google.com>
---
.../vfio/vfio_dma_mapping_perf_test.c | 144 ++++++++++++++----
1 file changed, 117 insertions(+), 27 deletions(-)
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 086a63a50df8..6e38cc134a49 100644
--- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
+++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
@@ -1,10 +1,12 @@
// SPDX-License-Identifier: GPL-2.0-only
+#include <ctype.h>
#include <limits.h>
#include <stdio.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
+#include <linux/align.h>
#include <linux/iommufd.h>
#include <linux/limits.h>
#include <linux/memfd.h>
@@ -17,7 +19,81 @@
#include "kselftest_harness.h"
-static const char *device_bdf;
+static struct {
+ u64 size;
+ const char *size_str;
+ const char *device_bdf;
+} test_params = {
+ .size = SZ_1G,
+ .size_str = "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_GT(base, 0, "Size must be greater than 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 opts_help(void)
+{
+ fprintf(stderr,
+ "\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 opts_handler(int opt, char *optarg)
+{
+ if (opt == 'b') {
+ test_params.size = parse_size(optarg);
+ test_params.size_str = optarg;
+ return KSFT_PASS;
+ }
+ return KSFT_FAIL;
+}
+
+static const struct test_harness_cli_opts opts = {
+ .optstring = "b:",
+ .handler = opts_handler,
+ .help = opts_help,
+};
FIXTURE(vfio_dma_mapping_perf_test) {
struct iommu *iommu;
@@ -27,25 +103,27 @@ FIXTURE(vfio_dma_mapping_perf_test) {
FIXTURE_VARIANT(vfio_dma_mapping_perf_test) {
const char *iommu_mode;
+ u64 page_size;
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), \
+#define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode, _name, _page_size, _mmap_flags) \
+FIXTURE_VARIANT_ADD(vfio_dma_mapping_perf_test, _iommu_mode ## _ ## _name) { \
+ .iommu_mode = #_iommu_mode, \
+ .page_size = (_page_size), \
+ .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);
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous, 0, 0);
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_2mb, SZ_2M, MAP_HUGETLB | MAP_HUGE_2MB);
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES(anonymous_hugetlb_1gb, SZ_1G, 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->device = vfio_pci_device_init(test_params.device_bdf, self->iommu);
self->iova_allocator = iova_allocator_init(self->iommu);
}
@@ -58,11 +136,16 @@ FIXTURE_TEARDOWN(vfio_dma_mapping_perf_test)
TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
{
- const u64 size = SZ_1G;
+ const u64 page_size = variant->page_size ?: getpagesize();
+ const u64 size = test_params.size;
const int flags = variant->mmap_flags;
struct dma_region region;
- ksft_print_msg("mmap size = %lluG\n", (unsigned long long)(size / SZ_1G));
+ if (!IS_ALIGNED(size, page_size))
+ SKIP(return, "Size 0x%llx is not aligned to page size 0x%llx",
+ (unsigned long long)size, (unsigned long long)page_size);
+
+ ksft_print_msg("mmap size = %s\n", test_params.size_str);
TIME("mmap",
region.vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0));
@@ -90,30 +173,32 @@ FIXTURE(vfio_dma_mapping_perf_memfd_test) {
};
FIXTURE_VARIANT(vfio_dma_mapping_perf_memfd_test) {
+ u64 page_size;
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) {\
- .mmap_flags = MAP_SHARED | MAP_POPULATE | (_mmap_flags), \
- .memfd_flags = (_memfd_flags), \
+#define FIXTURE_VARIANT_ADD_MEMFD_MODE(_name, _page_size, _mmap_flags, _memfd_flags) \
+FIXTURE_VARIANT_ADD(vfio_dma_mapping_perf_memfd_test, iommufd ## _ ## _name) { \
+ .page_size = (_page_size), \
+ .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);
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd, 0, 0, 0);
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_2mb, SZ_2M,
+ MAP_HUGETLB | MAP_HUGE_2MB,
+ MFD_HUGETLB | MFD_HUGE_2MB);
+FIXTURE_VARIANT_ADD_MEMFD_MODE(memfd_hugetlb_1gb, SZ_1G,
+ 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(MODE_IOMMUFD);
- 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);
}
@@ -154,12 +239,17 @@ 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 page_size = variant->page_size ?: getpagesize();
+ const u64 size = test_params.size;
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));
+ if (!IS_ALIGNED(size, page_size))
+ SKIP(return, "Size 0x%llx is not aligned to page size 0x%llx",
+ (unsigned long long)size, (unsigned long long)page_size);
+
+ ksft_print_msg("mmap size = %s\n", test_params.size_str);
region.vaddr = setup_memfd(&fd, size, mmap_flags, variant->memfd_flags);
@@ -184,6 +274,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.56.0.611.g1030f0b940-goog
^ permalink raw reply related [flat|nested] 7+ messages in thread* Re: [PATCH v4 5/5] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
2026-11-14 13:22 ` [PATCH v4 5/5] vfio: selftests: Allow a size for vfio_dma_mapping_perf_test Aaron Lewis
@ 2026-11-14 13:29 ` sashiko-bot
0 siblings, 0 replies; 7+ messages in thread
From: sashiko-bot @ 2026-11-14 13:29 UTC (permalink / raw)
To: Aaron Lewis, kvm
Thank you for your contribution! Sashiko AI review found 1 potential
issue to consider:
- [Low] vfio: selftests: non-power-of-2 -b size aborts in
iova_allocator_alloc()
--
commit 7eb32375cc90a8bc43da0741f8174df52036eeb6
Author: Aaron Lewis <aaronlewis@google.com>
vfio: selftests: Allow a size for vfio_dma_mapping_perf_test
This commit adds a '-b' command-line option to vfio_dma_mapping_perf_test to
allow specifying the DMA region size to map and unmap.
> 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 086a63a50df80..6e38cc134a49c 100644
> --- a/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
> +++ b/tools/testing/selftests/vfio/vfio_dma_mapping_perf_test.c
[ ... ]
> @@ -58,11 +136,16 @@ FIXTURE_TEARDOWN(vfio_dma_mapping_perf_test)
>
> TEST_F(vfio_dma_mapping_perf_test, dma_map_unmap)
> {
> - const u64 size = SZ_1G;
> + const u64 page_size = variant->page_size ?: getpagesize();
> + const u64 size = test_params.size;
> const int flags = variant->mmap_flags;
> struct dma_region region;
>
> - ksft_print_msg("mmap size = %lluG\n", (unsigned long long)(size / SZ_1G));
> + if (!IS_ALIGNED(size, page_size))
> + SKIP(return, "Size 0x%llx is not aligned to page size 0x%llx",
> + (unsigned long long)size, (unsigned long long)page_size);
> +
> + ksft_print_msg("mmap size = %s\n", test_params.size_str);
>
> TIME("mmap",
> region.vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE, flags, -1, 0));
[Severity: Low]
Will passing a non-power-of-2 size such as "-b 3G" abort the test inside
iova_allocator_alloc()?
parse_size() accepts any positive multiplier and both dma_map_unmap and
dma_map_unmap_from_file only check IS_ALIGNED(size, page_size), but after
populating the mapping they pass size directly to iova_allocator_alloc(),
which requires a power of two:
tools/testing/selftests/vfio/lib/iova_allocator.c:iova_allocator_alloc() {
VFIO_ASSERT_GT(size, 0, "Invalid size arg, zero\n");
VFIO_ASSERT_EQ(size & (size - 1), 0, "Invalid size arg, non-power-of-2\n");
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261114132200.113697-1-aaronlewis@google.com?part=5
^ permalink raw reply [flat|nested] 7+ messages in thread