Add a driver framework to VFIO selftests, so that devices can generate
DMA and interrupts in a common way that can be then utilized by tests.
This will enable VFIO selftests to exercise real hardware DMA and
interrupt paths, without needing any device-specific code in the test
itself.

Subsequent commits will introduce drivers for specific devices.

Signed-off-by: David Matlack <dmatl...@google.com>
---
 .../selftests/vfio/lib/include/vfio_util.h    |  92 ++++++++++++++
 tools/testing/selftests/vfio/lib/libvfio.mk   |   1 +
 .../selftests/vfio/lib/vfio_pci_device.c      |   5 +
 .../selftests/vfio/lib/vfio_pci_driver.c      | 116 ++++++++++++++++++
 4 files changed, 214 insertions(+)
 create mode 100644 tools/testing/selftests/vfio/lib/vfio_pci_driver.c

diff --git a/tools/testing/selftests/vfio/lib/include/vfio_util.h 
b/tools/testing/selftests/vfio/lib/include/vfio_util.h
index a51c971004cd..a7d05a4299a1 100644
--- a/tools/testing/selftests/vfio/lib/include/vfio_util.h
+++ b/tools/testing/selftests/vfio/lib/include/vfio_util.h
@@ -63,6 +63,85 @@ struct vfio_dma_region {
        u64 size;
 };
 
+struct vfio_pci_device;
+
+struct vfio_pci_driver_ops {
+       const char *name;
+
+       /**
+        * @probe() - Check if the driver supports the given device.
+        *
+        * Return: 0 on success, non-0 on failure.
+        */
+       int (*probe)(struct vfio_pci_device *device);
+
+       /**
+        * @init() - Initialize the driver for @device.
+        *
+        * Must be called after device->driver.region has been initialized.
+        */
+       void (*init)(struct vfio_pci_device *device);
+
+       /**
+        * remove() - Deinitialize the driver for @device.
+        */
+       void (*remove)(struct vfio_pci_device *device);
+
+       /**
+        * memcpy_start() - Kick off @count repeated memcpy operations from
+        * [@src, @src + @size) to [@dst, @dst + @size).
+        *
+        * Guarantees:
+        *  - The device will attempt DMA reads on [src, src + size).
+        *  - The device will attempt DMA writes on [dst, dst + size).
+        *  - The device will not generate any interrupts.
+        *
+        * memcpy_start() returns immediately, it does not wait for the
+        * copies to complete.
+        */
+       void (*memcpy_start)(struct vfio_pci_device *device,
+                            iova_t src, iova_t dst, u64 size, u64 count);
+
+       /**
+        * memcpy_wait() - Wait until the memcpy operations started by
+        * memcpy_start() have finished.
+        *
+        * Guarantees:
+        *  - All in-flight DMAs initiated by memcpy_start() are fully complete
+        *    before memcpy_wait() returns.
+        *
+        * Returns non-0 if the driver detects that an error occurred during the
+        * memcpy, 0 otherwise.
+        */
+       int (*memcpy_wait)(struct vfio_pci_device *device);
+
+       /**
+        * send_msi() - Make the device send the MSI device->driver.msi.
+        *
+        * Guarantees:
+        *  - The device will send the MSI once.
+        */
+       void (*send_msi)(struct vfio_pci_device *device);
+};
+
+struct vfio_pci_driver {
+       const struct vfio_pci_driver_ops *ops;
+       bool initialized;
+       bool memcpy_in_progress;
+
+       /* Region to be used by the driver (e.g. for in-memory descriptors) */
+       struct vfio_dma_region region;
+
+       /* The maximum size that can be passed to memcpy_start(). */
+       u64 max_memcpy_size;
+
+       /* The maximum count that can be passed to memcpy_start(). */
+       u64 max_memcpy_count;
+
+       /* The MSI vector the device will signal in ops->send_msi(). */
+       int msi;
+};
+
 struct vfio_pci_device {
        int fd;
        int group_fd;
@@ -79,6 +158,8 @@ struct vfio_pci_device {
 
        /* eventfds for MSI and MSI-x interrupts */
        int msi_eventfds[PCI_MSIX_FLAGS_QSIZE + 1];
+
+       struct vfio_pci_driver driver;
 };
 
 /*
@@ -174,4 +255,15 @@ static inline bool vfio_pci_device_match(struct 
vfio_pci_device *device,
                (device_id == vfio_pci_config_readw(device, PCI_DEVICE_ID));
 }
 
+void vfio_pci_driver_probe(struct vfio_pci_device *device);
+void vfio_pci_driver_init(struct vfio_pci_device *device);
+void vfio_pci_driver_remove(struct vfio_pci_device *device);
+int vfio_pci_driver_memcpy(struct vfio_pci_device *device,
+                          iova_t src, iova_t dst, u64 size);
+void vfio_pci_driver_memcpy_start(struct vfio_pci_device *device,
+                                 iova_t src, iova_t dst, u64 size,
+                                 u64 count);
+int vfio_pci_driver_memcpy_wait(struct vfio_pci_device *device);
+void vfio_pci_driver_send_msi(struct vfio_pci_device *device);
+
 #endif /* SELFTESTS_VFIO_LIB_INCLUDE_VFIO_UTIL_H */
diff --git a/tools/testing/selftests/vfio/lib/libvfio.mk 
b/tools/testing/selftests/vfio/lib/libvfio.mk
index 72e55a560eeb..a3c3bc9a7c00 100644
--- a/tools/testing/selftests/vfio/lib/libvfio.mk
+++ b/tools/testing/selftests/vfio/lib/libvfio.mk
@@ -1,6 +1,7 @@
 VFIO_DIR := $(selfdir)/vfio
 
 LIBVFIO_C := lib/vfio_pci_device.c
+LIBVFIO_C += lib/vfio_pci_driver.c
 
 LIBVFIO_O := $(patsubst %.c, $(OUTPUT)/%.o, $(LIBVFIO_C))
 
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c 
b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
index 36b4b30b75cf..d8bb227e869d 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
@@ -344,6 +344,8 @@ struct vfio_pci_device *vfio_pci_device_init(const char 
*bdf, int iommu_type)
        vfio_pci_iommu_setup(device, iommu_type);
        vfio_pci_device_setup(device, bdf);
 
+       vfio_pci_driver_probe(device);
+
        return device;
 }
 
@@ -351,6 +353,9 @@ void vfio_pci_device_cleanup(struct vfio_pci_device *device)
 {
        int i;
 
+       if (device->driver.initialized)
+               vfio_pci_driver_remove(device);
+
        vfio_pci_bar_unmap_all(device);
 
        VFIO_ASSERT_EQ(close(device->fd), 0);
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_driver.c 
b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
new file mode 100644
index 000000000000..c98bd2d31d8a
--- /dev/null
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
@@ -0,0 +1,116 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <stdio.h>
+
+#include "../../../kselftest.h"
+#include <vfio_util.h>
+
+static struct vfio_pci_driver_ops *driver_ops[] = {};
+
+void vfio_pci_driver_probe(struct vfio_pci_device *device)
+{
+       struct vfio_pci_driver_ops *ops;
+       int i;
+
+       VFIO_ASSERT_NULL(device->driver.ops);
+
+       for (i = 0; i < ARRAY_SIZE(driver_ops); i++) {
+               ops = driver_ops[i];
+
+               if (ops->probe(device))
+                       continue;
+
+               printf("Driver found: %s\n", ops->name);
+               device->driver.ops = ops;
+       }
+}
+
+static void vfio_check_driver_op(struct vfio_pci_driver *driver, void *op,
+                                const char *op_name)
+{
+       VFIO_ASSERT_NOT_NULL(driver->ops);
+       VFIO_ASSERT_NOT_NULL(op, "Driver has no %s()\n", op_name);
+       VFIO_ASSERT_EQ(driver->initialized, op != driver->ops->init);
+       VFIO_ASSERT_EQ(driver->memcpy_in_progress, op == 
driver->ops->memcpy_wait);
+}
+
+#define VFIO_CHECK_DRIVER_OP(_driver, _op) do {                                
\
+       struct vfio_pci_driver *__driver = (_driver);                   \
+       vfio_check_driver_op(__driver, __driver->ops->_op, #_op);       \
+} while (0)
+
+void vfio_pci_driver_init(struct vfio_pci_device *device)
+{
+       struct vfio_pci_driver *driver = &device->driver;
+
+       VFIO_ASSERT_NOT_NULL(driver->region.vaddr);
+       VFIO_CHECK_DRIVER_OP(driver, init);
+
+       driver->ops->init(device);
+
+       driver->initialized = true;
+
+       printf("%s: region: vaddr %p, iova 0x%lx, size 0x%lx\n",
+              driver->ops->name,
+              driver->region.vaddr,
+              driver->region.iova,
+              driver->region.size);
+
+       printf("%s: max_memcpy_size 0x%lx, max_memcpy_count 0x%lx\n",
+              driver->ops->name,
+              driver->max_memcpy_size,
+              driver->max_memcpy_count);
+}
+
+void vfio_pci_driver_remove(struct vfio_pci_device *device)
+{
+       struct vfio_pci_driver *driver = &device->driver;
+
+       VFIO_CHECK_DRIVER_OP(driver, remove);
+
+       driver->ops->remove(device);
+       driver->initialized = false;
+}
+
+void vfio_pci_driver_send_msi(struct vfio_pci_device *device)
+{
+       struct vfio_pci_driver *driver = &device->driver;
+
+       VFIO_CHECK_DRIVER_OP(driver, send_msi);
+
+       driver->ops->send_msi(device);
+}
+
+void vfio_pci_driver_memcpy_start(struct vfio_pci_device *device,
+                                 iova_t src, iova_t dst, u64 size,
+                                 u64 count)
+{
+       struct vfio_pci_driver *driver = &device->driver;
+
+       VFIO_ASSERT_LE(size, driver->max_memcpy_size);
+       VFIO_ASSERT_LE(count, driver->max_memcpy_count);
+       VFIO_CHECK_DRIVER_OP(driver, memcpy_start);
+
+       driver->ops->memcpy_start(device, src, dst, size, count);
+       driver->memcpy_in_progress = true;
+}
+
+int vfio_pci_driver_memcpy_wait(struct vfio_pci_device *device)
+{
+       struct vfio_pci_driver *driver = &device->driver;
+       int r;
+
+       VFIO_CHECK_DRIVER_OP(driver, memcpy_wait);
+
+       r = driver->ops->memcpy_wait(device);
+       driver->memcpy_in_progress = false;
+
+       return r;
+}
+
+int vfio_pci_driver_memcpy(struct vfio_pci_device *device,
+                          iova_t src, iova_t dst, u64 size)
+{
+       vfio_pci_driver_memcpy_start(device, src, dst, size, 1);
+
+       return vfio_pci_driver_memcpy_wait(device);
+}
-- 
2.50.0.rc2.701.gf1e915cc24-goog


Reply via email to