From: David Matlack <dmatlack@google.com>
To: dri-devel@lists.freedesktop.org, iommu@lists.linux.dev,
kvm@vger.kernel.org, linaro-mm-sig@lists.linaro.org,
linux-kernel@vger.kernel.org, linux-kselftest@vger.kernel.org,
linux-media@vger.kernel.org, linux-pci@vger.kernel.org
Cc: "Alex Mastro" <amastro@fb.com>,
"Alex Williamson" <alex@shazbot.org>,
"Bjorn Helgaas" <bhelgaas@google.com>,
"Christian König" <christian.koenig@amd.com>,
"David Matlack" <dmatlack@google.com>,
"Jason Gunthorpe" <jgg@nvidia.com>,
"Josh Hilke" <jrhilke@google.com>,
"Kevin Tian" <kevin.tian@intel.com>,
"Leon Romanovsky" <leon@kernel.org>,
"Logan Gunthorpe" <logang@deltatee.com>,
"Matt Evans" <mattev@meta.com>, "Matt Evans" <matt@ozlabs.org>,
"Mina Almasry" <almasrymina@google.com>,
"Samiullah Khawaja" <skhawaja@google.com>,
"Shuah Khan" <shuah@kernel.org>,
"Sumit Semwal" <sumit.semwal@linaro.org>,
"Vipin Sharma" <vipinsh@google.com>
Subject: [RFC PATCH 7/7] vfio: selftests: Add P2P DMA selftest
Date: Wed, 11 Nov 2026 19:24:11 +0000 [thread overview]
Message-ID: <20261111192411.4133780-8-dmatlack@google.com> (raw)
In-Reply-To: <20261111192411.4133780-1-dmatlack@google.com>
Add vfio_pci_p2pdma_test to validate peer-to-peer DMA between an
initiator device driven by the VFIO selftests driver framework and a
target device exposing a memory BAR located via
vfio_pci_find_p2p_pair().
Map the target BAR into the IOMMU across all supported IOMMU modes
(using VFIO_IOMMU_MAP_DMA on the BAR mmap for type1/type1v2 and
VFIO_DEVICE_FEATURE_DMA_BUF + IOMMU_IOAS_MAP_FILE for iommufd) and test
RAM-to-BAR writes, BAR-to-RAM reads, BAR-to-BAR copies, RAM-BAR-RAM
roundtrips, and iommufd dmabuf revocation when PCI_COMMAND_MEMORY is
cleared on the target.
Signed-off-by: David Matlack <dmatlack@google.com>
---
tools/testing/selftests/vfio/Makefile | 1 +
.../selftests/vfio/vfio_pci_p2pdma_test.c | 435 ++++++++++++++++++
2 files changed, 436 insertions(+)
create mode 100644 tools/testing/selftests/vfio/vfio_pci_p2pdma_test.c
diff --git a/tools/testing/selftests/vfio/Makefile b/tools/testing/selftests/vfio/Makefile
index 2c32c48db509..b391d22b52ca 100644
--- a/tools/testing/selftests/vfio/Makefile
+++ b/tools/testing/selftests/vfio/Makefile
@@ -12,6 +12,7 @@ TEST_GEN_PROGS += vfio_iommufd_setup_test
TEST_GEN_PROGS += vfio_pci_device_test
TEST_GEN_PROGS += vfio_pci_device_init_perf_test
TEST_GEN_PROGS += vfio_pci_driver_test
+TEST_GEN_PROGS += vfio_pci_p2pdma_test
TEST_GEN_PROGS += vfio_pci_sriov_uapi_test
TEST_FILES += scripts/cleanup.sh
diff --git a/tools/testing/selftests/vfio/vfio_pci_p2pdma_test.c b/tools/testing/selftests/vfio/vfio_pci_p2pdma_test.c
new file mode 100644
index 000000000000..43e1537076ad
--- /dev/null
+++ b/tools/testing/selftests/vfio/vfio_pci_p2pdma_test.c
@@ -0,0 +1,435 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#include <linux/align.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/sizes.h>
+#include <linux/vfio.h>
+
+#include <libvfio.h>
+
+#include "../kselftest_harness.h"
+
+static char **device_bdfs;
+static int nr_bdfs;
+
+static void set_msi_nonblock(struct vfio_pci_device *dev)
+{
+ if (dev->driver.ops->send_msi)
+ fcntl_set_nonblock(dev->msi_eventfds[dev->driver.msi]);
+}
+
+static void assert_no_msi(struct vfio_pci_device *dev)
+{
+ u64 value;
+ int fd;
+
+ if (!dev->driver.ops->send_msi)
+ return;
+
+ fd = dev->msi_eventfds[dev->driver.msi];
+ VFIO_ASSERT_EQ(read(fd, &value, sizeof(value)), -1);
+ VFIO_ASSERT_EQ(errno, EAGAIN);
+}
+
+static void ram_region_setup(struct iommu *iommu,
+ struct iova_allocator *iova_allocator,
+ struct dma_region *region, u64 size)
+{
+ void *vaddr;
+
+ vaddr = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_ANONYMOUS, -1, 0);
+ VFIO_ASSERT_NE(vaddr, MAP_FAILED);
+
+ region->vaddr = vaddr;
+ region->iova = iova_allocator_alloc(iova_allocator, size);
+ region->size = size;
+
+ iommu_map(iommu, region);
+}
+
+static void ram_region_teardown(struct iommu *iommu, struct dma_region *region)
+{
+ if (!region->vaddr)
+ return;
+
+ iommu_unmap(iommu, region);
+ VFIO_ASSERT_EQ(munmap(region->vaddr, region->size), 0);
+ region->vaddr = NULL;
+}
+
+static u64 pattern_word(size_t index, u8 seed)
+{
+ return (u64)seed * 0x0101010101010101ULL ^
+ ((u64)index * 0x9e3779b97f4a7c15ULL);
+}
+
+static void fill_ram(void *buf, size_t size, u8 seed)
+{
+ __le64 *p = buf;
+ size_t i;
+
+ VFIO_ASSERT_TRUE(IS_ALIGNED(size, sizeof(u64)));
+ for (i = 0; i < size / sizeof(u64); i++)
+ p[i] = cpu_to_le64(pattern_word(i, seed));
+}
+
+static void fill_bar(void *bar, size_t size, u8 seed)
+{
+ size_t i;
+
+ VFIO_ASSERT_TRUE(IS_ALIGNED(size, sizeof(u64)));
+ for (i = 0; i < size / sizeof(u64); i++)
+ writeq(pattern_word(i, seed), bar + i * sizeof(u64));
+}
+
+static void assert_ram_eq_bar(const void *ram, const void *bar, size_t size)
+{
+ const __le64 *pr = ram;
+ size_t i;
+
+ VFIO_ASSERT_TRUE(IS_ALIGNED(size, sizeof(u64)));
+ for (i = 0; i < size / sizeof(u64); i++)
+ VFIO_ASSERT_EQ(le64_to_cpu(pr[i]),
+ readq(bar + i * sizeof(u64)),
+ "Mismatch at byte offset %zu\n",
+ i * sizeof(u64));
+}
+
+static void assert_bar_eq_bar(const void *bar_a, const void *bar_b,
+ size_t size)
+{
+ size_t i;
+
+ VFIO_ASSERT_TRUE(IS_ALIGNED(size, sizeof(u64)));
+ for (i = 0; i < size / sizeof(u64); i++)
+ VFIO_ASSERT_EQ(readq(bar_a + i * sizeof(u64)),
+ readq(bar_b + i * sizeof(u64)),
+ "Mismatch at byte offset %zu\n",
+ i * sizeof(u64));
+}
+
+static void assert_no_pattern_leak(const void *bar_pattern, const void *dst,
+ size_t size)
+{
+ const u8 *pb = bar_pattern;
+ const u8 *pd = dst;
+ size_t offset, chunk;
+
+ for (offset = 0; offset < size; offset += chunk) {
+ chunk = min_t(size_t, 64, size - offset);
+ VFIO_ASSERT_NE(memcmp(pb + offset, pd + offset, chunk), 0,
+ "Leaked BAR data at byte offset %zu\n", offset);
+ }
+}
+
+FIXTURE(vfio_pci_p2pdma_test) {
+ struct iommu *iommu;
+ struct iova_allocator *iova_allocator;
+ struct vfio_pci_device **devices;
+ int nr_open;
+
+ struct vfio_pci_device *initiator;
+ struct vfio_pci_device *target;
+ struct vfio_pci_mem_bar mem_bar;
+
+ struct dma_region ram_region;
+ struct dma_region bar_region;
+ bool bar_mapped;
+ int dmabuf_fd;
+
+ u64 size;
+ void *ram_src;
+ void *ram_dst;
+ iova_t ram_src_iova;
+ iova_t ram_dst_iova;
+ iova_t bar_iova;
+};
+
+FIXTURE_VARIANT(vfio_pci_p2pdma_test) {
+ const char *iommu_mode;
+};
+
+#define FIXTURE_VARIANT_ADD_IOMMU_MODE(_iommu_mode) \
+FIXTURE_VARIANT_ADD(vfio_pci_p2pdma_test, _iommu_mode) { \
+ .iommu_mode = #_iommu_mode, \
+}
+
+FIXTURE_VARIANT_ADD_ALL_IOMMU_MODES();
+
+#undef FIXTURE_VARIANT_ADD_IOMMU_MODE
+
+FIXTURE_SETUP(vfio_pci_p2pdma_test)
+{
+ const u64 page_size = getpagesize();
+ const u64 min_bar_size = max_t(u64, 2 * page_size, SZ_128K);
+ struct vfio_pci_p2p_pair pair;
+ struct vfio_pci_driver *driver;
+ u64 bar_map_size;
+ int i, rc;
+
+ self->dmabuf_fd = -1;
+
+ self->iommu = iommu_init(variant->iommu_mode);
+ self->devices = calloc_assert(nr_bdfs, sizeof(*self->devices));
+
+ for (i = 0; i < nr_bdfs; i++) {
+ self->devices[i] = vfio_pci_device_init(device_bdfs[i],
+ self->iommu);
+ self->nr_open++;
+ }
+
+ if (!vfio_pci_find_p2p_pair(self->devices, self->nr_open, min_bar_size,
+ &pair))
+ SKIP(return,
+ "No (initiator with driver, target with >= %lluB memory BAR) pair found",
+ (unsigned long long)min_bar_size);
+
+ self->initiator = pair.initiator;
+ self->target = pair.target;
+ self->mem_bar = pair.mem_bar;
+
+ printf("P2P pair: initiator %s (%s) -> target %s BAR%u (size 0x%lx)\n",
+ self->initiator->bdf, self->initiator->driver.ops->name,
+ self->target->bdf, self->mem_bar.bar->info.index,
+ (unsigned long)self->mem_bar.size);
+
+ vfio_pci_cmd_set(self->target, PCI_COMMAND_MEMORY);
+
+ self->iova_allocator = iova_allocator_init(self->iommu);
+
+ driver = &self->initiator->driver;
+ ram_region_setup(self->iommu, self->iova_allocator, &driver->region,
+ driver->region.size);
+ ram_region_setup(self->iommu, self->iova_allocator, &self->ram_region,
+ SZ_16M);
+
+ bar_map_size = min_t(u64, round_down(self->mem_bar.size, page_size),
+ SZ_16M);
+ self->bar_region.vaddr = self->mem_bar.vaddr;
+ self->bar_region.size = bar_map_size;
+ self->bar_region.iova = iova_allocator_alloc(self->iova_allocator,
+ bar_map_size);
+
+ if (self->iommu->iommufd) {
+ u32 bar_idx = self->mem_bar.bar->info.index;
+
+ self->dmabuf_fd = __vfio_pci_dmabuf_create(self->target,
+ bar_idx,
+ self->mem_bar.offset,
+ bar_map_size,
+ O_RDWR | O_CLOEXEC);
+ if (self->dmabuf_fd == -EOPNOTSUPP)
+ SKIP(return, "Kernel does not support VFIO_DEVICE_FEATURE_DMA_BUF");
+ ASSERT_GE(self->dmabuf_fd, 0);
+
+ iommu_map_file(self->iommu, self->dmabuf_fd, 0,
+ &self->bar_region);
+ } else {
+ rc = __iommu_map(self->iommu, &self->bar_region);
+ if (rc && !strncmp(variant->iommu_mode, "iommufd_compat_", 15))
+ SKIP(return,
+ "IOMMU mode %s does not support mapping MMIO BARs (rc=%d)",
+ variant->iommu_mode, rc);
+ ASSERT_EQ(0, rc);
+ }
+ self->bar_mapped = true;
+
+ vfio_pci_driver_init(self->initiator);
+
+ self->size = min_t(u64, driver->max_memcpy_size * 4,
+ min_t(u64, self->ram_region.size / 2,
+ bar_map_size / 2));
+
+ self->ram_src = self->ram_region.vaddr;
+ self->ram_dst = self->ram_src + self->size;
+
+ self->ram_src_iova = to_iova(self->initiator, self->ram_src);
+ self->ram_dst_iova = to_iova(self->initiator, self->ram_dst);
+ self->bar_iova = to_iova(self->initiator, self->mem_bar.vaddr);
+}
+
+FIXTURE_TEARDOWN(vfio_pci_p2pdma_test)
+{
+ int i;
+
+ if (self->initiator && self->initiator->driver.initialized)
+ vfio_pci_driver_remove(self->initiator);
+
+ if (self->bar_mapped)
+ iommu_unmap(self->iommu, &self->bar_region);
+
+ if (self->dmabuf_fd >= 0)
+ VFIO_ASSERT_EQ(close(self->dmabuf_fd), 0);
+
+ if (self->initiator)
+ ram_region_teardown(self->iommu,
+ &self->initiator->driver.region);
+ ram_region_teardown(self->iommu, &self->ram_region);
+
+ if (self->iova_allocator)
+ iova_allocator_cleanup(self->iova_allocator);
+
+ for (i = 0; i < self->nr_open; i++)
+ vfio_pci_device_cleanup(self->devices[i]);
+ free(self->devices);
+
+ iommu_cleanup(self->iommu);
+}
+
+TEST_F(vfio_pci_p2pdma_test, ram_to_bar)
+{
+ set_msi_nonblock(self->initiator);
+
+ fill_ram(self->ram_src, self->size, 0x11);
+ fill_bar(self->mem_bar.vaddr, self->size, 0xee);
+
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->ram_src_iova,
+ self->bar_iova,
+ self->size));
+
+ assert_ram_eq_bar(self->ram_src, self->mem_bar.vaddr, self->size);
+ assert_no_msi(self->initiator);
+}
+
+TEST_F(vfio_pci_p2pdma_test, bar_to_ram)
+{
+ set_msi_nonblock(self->initiator);
+
+ fill_bar(self->mem_bar.vaddr, self->size, 0x22);
+ memset(self->ram_dst, 0, self->size);
+
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->bar_iova,
+ self->ram_dst_iova,
+ self->size));
+
+ assert_ram_eq_bar(self->ram_dst, self->mem_bar.vaddr, self->size);
+ assert_no_msi(self->initiator);
+}
+
+TEST_F(vfio_pci_p2pdma_test, bar_to_bar)
+{
+ void *bar_src = self->mem_bar.vaddr;
+ void *bar_dst = self->mem_bar.vaddr + self->size;
+
+ set_msi_nonblock(self->initiator);
+
+ fill_bar(bar_src, self->size, 0x33);
+ fill_bar(bar_dst, self->size, 0xcc);
+
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->bar_iova,
+ self->bar_iova + self->size,
+ self->size));
+
+ assert_bar_eq_bar(bar_src, bar_dst, self->size);
+ assert_no_msi(self->initiator);
+}
+
+TEST_F(vfio_pci_p2pdma_test, roundtrip)
+{
+ set_msi_nonblock(self->initiator);
+
+ fill_ram(self->ram_src, self->size, 0x44);
+ fill_bar(self->mem_bar.vaddr, self->size, 0x00);
+ memset(self->ram_dst, 0xff, self->size);
+
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->ram_src_iova,
+ self->bar_iova,
+ self->size));
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->bar_iova,
+ self->ram_dst_iova,
+ self->size));
+
+ ASSERT_EQ(0, memcmp(self->ram_src, self->ram_dst, self->size));
+ assert_no_msi(self->initiator);
+}
+
+TEST_F(vfio_pci_p2pdma_test, dmabuf_revoke)
+{
+ if (self->dmabuf_fd < 0)
+ SKIP(return, "Test requires dmabuf-backed IOMMU mapping");
+
+ set_msi_nonblock(self->initiator);
+
+ fill_bar(self->mem_bar.vaddr, self->size, 0x55);
+ fill_ram(self->ram_src, self->size, 0xaa);
+
+ /*
+ * Clearing PCI_COMMAND_MEMORY on the target revokes the dmabuf and
+ * tears down its IOMMU mapping in place. P2P DMA must not reach the
+ * BAR, and remapping the revoked dmabuf must fail with -ENODEV until
+ * PCI_COMMAND_MEMORY is re-enabled.
+ */
+ vfio_pci_cmd_clear(self->target, PCI_COMMAND_MEMORY);
+
+ /*
+ * Ignore the return values: DMA writes are posted, and not all
+ * devices report accesses to unmapped IOVAs as errors on DMA reads
+ * (e.g. QEMU's emulated igb completes reads from unmapped IOVAs with
+ * zeros). Verify isolation via the destination buffers instead.
+ */
+ fill_ram(self->ram_dst, self->size, 0x33);
+ vfio_pci_driver_memcpy(self->initiator, self->ram_src_iova,
+ self->bar_iova, self->size);
+ vfio_pci_driver_memcpy(self->initiator, self->bar_iova,
+ self->ram_dst_iova, self->size);
+ assert_no_msi(self->initiator);
+
+ iommu_unmap(self->iommu, &self->bar_region);
+ self->bar_mapped = false;
+
+ ASSERT_EQ(-ENODEV, __iommu_map_file(self->iommu, self->dmabuf_fd, 0,
+ &self->bar_region));
+
+ vfio_pci_cmd_set(self->target, PCI_COMMAND_MEMORY);
+
+ /*
+ * Revoked P2P writes must not have modified the BAR, and revoked P2P
+ * reads must not have leaked BAR contents into RAM.
+ */
+ fill_ram(self->ram_src, self->size, 0x55);
+ assert_ram_eq_bar(self->ram_src, self->mem_bar.vaddr, self->size);
+ assert_no_pattern_leak(self->ram_src, self->ram_dst, self->size);
+
+ /* Once memory decode is re-enabled, the dmabuf can be mapped again. */
+ iommu_map_file(self->iommu, self->dmabuf_fd, 0, &self->bar_region);
+ self->bar_mapped = true;
+
+ memset(self->ram_dst, 0, self->size);
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->bar_iova,
+ self->ram_dst_iova,
+ self->size));
+ ASSERT_EQ(0, memcmp(self->ram_src, self->ram_dst, self->size));
+
+ fill_ram(self->ram_src, self->size, 0x77);
+ ASSERT_EQ(0, vfio_pci_driver_memcpy(self->initiator,
+ self->ram_src_iova,
+ self->bar_iova,
+ self->size));
+ assert_ram_eq_bar(self->ram_src, self->mem_bar.vaddr, self->size);
+ assert_no_msi(self->initiator);
+}
+
+int main(int argc, char *argv[])
+{
+ device_bdfs = vfio_selftests_get_bdfs(&argc, argv, &nr_bdfs);
+ if (nr_bdfs < 2) {
+ fprintf(stderr,
+ "P2P DMA test requires at least 2 devices (got %d), skipping.\n",
+ nr_bdfs);
+ return KSFT_SKIP;
+ }
+
+ return test_harness_run(argc, argv);
+}
--
2.56.0.567.gdb80ae8cd5-goog
prev parent reply other threads:[~2026-11-11 19:24 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-11 19:24 [RFC PATCH 0/7] vfio: selftests: Add peer-to-peer " David Matlack
2026-11-11 19:24 ` [RFC PATCH 1/7] vfio: selftests: Add vfio_pci_find_mem_bar() helper David Matlack
2026-11-11 19:24 ` [RFC PATCH 2/7] vfio: selftests: Verify memory behavior in vfio_pci_find_mem_bar() David Matlack
2026-11-11 19:24 ` [RFC PATCH 3/7] vfio: selftests: Allow multiple BDFs in VFIO_SELFTESTS_BDF David Matlack
2026-11-11 19:24 ` [RFC PATCH 4/7] vfio: selftests: Add vfio_pci_dmabuf_create() helper David Matlack
2026-11-11 19:24 ` [RFC PATCH 5/7] vfio: selftests: Add iommu_map_file() helper David Matlack
2026-11-11 19:24 ` [RFC PATCH 6/7] vfio: selftests: Add vfio_pci_find_p2p_pair() helper David Matlack
2026-11-11 19:24 ` David Matlack [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261111192411.4133780-8-dmatlack@google.com \
--to=dmatlack@google.com \
--cc=alex@shazbot.org \
--cc=almasrymina@google.com \
--cc=amastro@fb.com \
--cc=bhelgaas@google.com \
--cc=christian.koenig@amd.com \
--cc=dri-devel@lists.freedesktop.org \
--cc=iommu@lists.linux.dev \
--cc=jgg@nvidia.com \
--cc=jrhilke@google.com \
--cc=kevin.tian@intel.com \
--cc=kvm@vger.kernel.org \
--cc=leon@kernel.org \
--cc=linaro-mm-sig@lists.linaro.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-pci@vger.kernel.org \
--cc=logang@deltatee.com \
--cc=matt@ozlabs.org \
--cc=mattev@meta.com \
--cc=shuah@kernel.org \
--cc=skhawaja@google.com \
--cc=sumit.semwal@linaro.org \
--cc=vipinsh@google.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®