vfio: selftests: Add driver framework

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.

Acked-by: Shuah Khan <skhan@linuxfoundation.org>
Signed-off-by: David Matlack <dmatlack@google.com>
Link: https://lore.kernel.org/r/20250822212518.4156428-13-dmatlack@google.com
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
This commit is contained in:
David Matlack
2025-08-22 21:24:59 +00:00
committed by Alex Williamson
parent 50d8fe805f
commit 1b197032ac
4 changed files with 214 additions and 0 deletions

View File

@@ -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 */

View File

@@ -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))

View File

@@ -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);

View File

@@ -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);
}