On 2026-03-25 02:43 PM, Rubin Du wrote: > Add a new VFIO PCI driver for NVIDIA GPUs that enables DMA testing > via the Falcon (Fast Logic Controller) microcontrollers. This driver > extracts and adapts the DMA test functionality from NVIDIA's > gpu-admin-tools project and integrates it into the existing VFIO > selftest framework. > > Falcons are general-purpose microcontrollers present on NVIDIA GPUs > that can perform DMA operations between system memory and device > memory. By leveraging Falcon DMA, this driver allows NVIDIA GPUs to > be tested alongside Intel IOAT and DSA devices using the same > selftest infrastructure.
> The driver is named 'nv_falcon' to reflect that it specifically > controls the Falcon microcontrollers for DMA operations, rather > than exposing general GPU functionality. Do you forsee this driver ever expanding to utilize other functionality on the GPU other than the Falcon microcontroller, e.g. to add send_msi() support in the future and/or support asynchronous memcpy? If so, the name "nv_falcon" could get out of date and maybe "nv_gpu" would be a better name for this driver? > Reference implementation: > https://github.com/NVIDIA/gpu-admin-tools > > Signed-off-by: Rubin Du <[email protected]> > --- > .../vfio/lib/drivers/nv_falcons/hw.h | 365 +++++++++ > .../vfio/lib/drivers/nv_falcons/nv_falcons.c | 739 ++++++++++++++++++ Please make the directory and file names match the driver. i.e. s/nv_falcons/nv_falcon/. > diff --git a/tools/testing/selftests/vfio/lib/drivers/nv_falcons/hw.h > b/tools/testing/selftests/vfio/lib/drivers/nv_falcons/hw.h > new file mode 100644 > index 000000000000..a92cdcfec63f > --- /dev/null > +++ b/tools/testing/selftests/vfio/lib/drivers/nv_falcons/hw.h > @@ -0,0 +1,365 @@ > +/* SPDX-License-Identifier: GPL-2.0-only */ > +/* > + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. > + */ > +#ifndef _NV_FALCONS_HW_H_ > +#define _NV_FALCONS_HW_H_ > + > +/* PMC (Power Management Controller) Registers */ > +#define NV_PMC_BOOT_0 > 0x00000000 > +#define NV_PMC_ENABLE > 0x00000200 > +#define NV_PMC_ENABLE_PWR > 0x00002000 > +#define NV_PMC_ENABLE_HUB > 0x20000000 Please make the alignment of macro values consistent here and below. > + > +/* Falcon Base Pages for Different Engines */ > +#define NV_PPWR_FALCON_BASE 0x10a000 > +#define NV_PGSP_FALCON_BASE 0x110000 > + > +/* Falcon Common Register Offsets (relative to base_page) */ > +#define NV_FALCON_DMACTL_OFFSET 0x010c > +#define NV_FALCON_CPUCTL_OFFSET 0x0100 > +#define NV_FALCON_ENGINE_RESET_OFFSET 0x03c0 > + > +/* DMEM Control Register Flags */ > +#define NV_PPWR_FALCON_DMEMC_AINCR_TRUE 0x01000000 > +#define NV_PPWR_FALCON_DMEMC_AINCW_TRUE 0x02000000 > + > +/* Falcon DMEM port offsets (for port 0) */ > +#define NV_FALCON_DMEMC_OFFSET 0x1c0 > +#define NV_FALCON_DMEMD_OFFSET 0x1c4 > + > +/* DMA Register Offsets (relative to base_page) */ > +#define NV_FALCON_DMA_ADDR_LOW_OFFSET 0x110 > +#define NV_FALCON_DMA_MEM_OFFSET 0x114 > +#define NV_FALCON_DMA_CMD_OFFSET 0x118 > +#define NV_FALCON_DMA_BLOCK_OFFSET 0x11c > +#define NV_FALCON_DMA_ADDR_HIGH_OFFSET 0x128 > +struct gpu_device { > + enum gpu_arch arch; > + void *bar0; > + bool is_memory_clear_supported; > + const struct falcon *falcon; > + u32 pmc_enable_mask; > + bool fsp_dma_enabled; > + > + /* Pending memcpy parameters, set by memcpy_start() */ > + u64 memcpy_src; > + u64 memcpy_dst; > + u64 memcpy_size; > + u64 memcpy_count; > +}; nit: I would move this to the driver .c file since that's where its used and this isn't a hardware definition. It will make the driver easier to read to have this struct definition in the same file. > diff --git a/tools/testing/selftests/vfio/lib/drivers/nv_falcons/nv_falcons.c > b/tools/testing/selftests/vfio/lib/drivers/nv_falcons/nv_falcons.c > new file mode 100644 > index 000000000000..4b62793570a2 > --- /dev/null > +++ b/tools/testing/selftests/vfio/lib/drivers/nv_falcons/nv_falcons.c > @@ -0,0 +1,739 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. > + */ > +#include <stdint.h> > +#include <strings.h> > +#include <unistd.h> > +#include <stdbool.h> > +#include <string.h> > +#include <time.h> > + > +#include <linux/errno.h> > +#include <linux/io.h> > +#include <linux/pci_ids.h> > + > +#include <libvfio.h> > + > +#include "hw.h" > + > +static inline struct gpu_device *to_nv_gpu(struct vfio_pci_device *device) nit: Please align the naming here. i.e. rename this function to to_gpu_device() or rename struct gpu_device to struct nv_gpu. > +{ > + return device->driver.region.vaddr; > +} > +static int gpu_poll_register(struct vfio_pci_device *device, > + const char *name, u32 offset, > + u32 expected, u32 mask, u32 timeout_ms) > +{ > + struct gpu_device *gpu = to_nv_gpu(device); > + u32 value; > + struct timespec start, now; > + u64 elapsed_ms; > + > + clock_gettime(CLOCK_MONOTONIC, &start); > + > + for (;;) { > + value = gpu_read32(gpu, offset); > + if ((value & mask) == expected) > + return 0; > + > + clock_gettime(CLOCK_MONOTONIC, &now); > + elapsed_ms = (now.tv_sec - start.tv_sec) * 1000 > + + (now.tv_nsec - start.tv_nsec) / 1000000; > + > + if (elapsed_ms >= timeout_ms) > + break; > + > + usleep(1000); > + } > + > + dev_err(device, > + "Timeout polling %s (0x%x): value=0x%x expected=0x%x mask=0x%x > after %llu ms\n", > + name, offset, value, expected, mask, > + (unsigned long long)elapsed_ms); > + return -ETIMEDOUT; Please fix all the callers of this function that ignore the return value. Presumably you should be using VFIO_ASSERT_EQ(ret, 0) to bail the test if something goes wrong (other than during memcpy_wait(), which has a way to return an error to the caller). > +static void gpu_enable_bus_master(struct vfio_pci_device *device) > +{ > + u16 cmd; > + > + cmd = vfio_pci_config_readw(device, PCI_COMMAND); > + vfio_pci_config_writew(device, PCI_COMMAND, cmd | PCI_COMMAND_MASTER); > +} > + > +static void gpu_disable_bus_master(struct vfio_pci_device *device) > +{ > + u16 cmd; > + > + cmd = vfio_pci_config_readw(device, PCI_COMMAND); > + vfio_pci_config_writew(device, PCI_COMMAND, cmd & ~PCI_COMMAND_MASTER); > +} These can be generic to any PCI device so please add them as static inline helpers in vfio_pci_device.h for other tests to use in the future (and rename them to something more generic like vfio_pci_disable_bus_master()). > +static int nv_gpu_falcon_dma(struct vfio_pci_device *device, > + u64 address, > + u32 size_encoding, > + bool write) > +{ > + struct gpu_device *gpu = to_nv_gpu(device); > + const struct falcon *falcon = gpu->falcon; > + u32 dma_cmd; > + int ret; > + > + gpu_write32(gpu, NV_GPU_DMA_ADDR_TOP_BITS_REG, > + (address >> 47) & 0x1ffff); > + gpu_write32(gpu, falcon->base_page + NV_FALCON_DMA_ADDR_HIGH_OFFSET, > + (address >> 40) & 0x7f); > + gpu_write32(gpu, falcon->base_page + NV_FALCON_DMA_ADDR_LOW_OFFSET, > + (address >> 8) & 0xffffffff); > + gpu_write32(gpu, falcon->base_page + NV_FALCON_DMA_BLOCK_OFFSET, > + address & 0xff); > + gpu_write32(gpu, falcon->base_page + NV_FALCON_DMA_MEM_OFFSET, 0); > + > + dma_cmd = (size_encoding << NV_FALCON_DMA_CMD_SIZE_SHIFT); > + > + /* Set direction: write (DMEM->mem) or read (mem->DMEM) */ > + if (write) > + dma_cmd |= NV_FALCON_DMA_CMD_WRITE_BIT; > + > + gpu_write32(gpu, falcon->base_page + NV_FALCON_DMA_CMD_OFFSET, dma_cmd); > + > + ret = gpu_poll_register(device, "dma_done", > + falcon->base_page + NV_FALCON_DMA_CMD_OFFSET, > + NV_FALCON_DMA_CMD_DONE_BIT, NV_FALCON_DMA_CMD_DONE_BIT, > + 1000); > + if (ret) > + return ret; > + > + return 0; nit: This can just be "return ret;" or "return gpu_poll_register(...);". > +static void nv_gpu_init(struct vfio_pci_device *device) > +{ > + struct gpu_device *gpu = to_nv_gpu(device); > + const struct gpu_properties *props; > + enum gpu_arch gpu_arch; > + u32 pmc_boot_0; > + int ret; > + > + VFIO_ASSERT_GE(device->driver.region.size, sizeof(*gpu)); > + > + /* Read PMC_BOOT_0 register from BAR0 to identify GPU */ > + pmc_boot_0 = readl(device->bars[0].vaddr + NV_PMC_BOOT_0); > + > + /* Look up GPU architecture */ > + gpu_arch = nv_gpu_arch_lookup(pmc_boot_0); > + if (gpu_arch == GPU_ARCH_UNKNOWN) { > + dev_err(device, "Unsupported GPU architecture\n"); > + return; > + } > + > + props = &gpu_properties_map[gpu_arch]; > + > + /* Populate GPU structure */ > + gpu->arch = gpu_arch; > + gpu->bar0 = device->bars[0].vaddr; > + gpu->is_memory_clear_supported = props->memory_clear_supported; > + gpu->falcon = &falcon_map[props->falcon_type]; > + gpu->pmc_enable_mask = props->pmc_enable_mask; > + > + falcon_enable(device); > + > + /* Initialize falcon for DMA */ > + ret = nv_gpu_falcon_dma_init(device); > + VFIO_ASSERT_EQ(ret, 0, "Failed to initialize falcon DMA: %d\n", ret); > + > + device->driver.features |= VFIO_PCI_DRIVER_F_NO_SEND_MSI; > + device->driver.max_memcpy_size = NV_FALCON_DMA_MAX_TRANSFER_SIZE; > + device->driver.max_memcpy_count = NV_FALCON_DMA_MAX_TRANSFER_COUNT; The small memcpy size is going to be a problem tests that want to DMA to larger regions. They will have to do chunk up the memcpy in a loop. One option would be to have this driver expose a larger max_memcpy_size and implement the loop in the memcpy_start()/wait() callback, which is actually what you have below. But I think a better approach would be to just make the loop common across all drivers so that tests never have to care about max_memcpy_size when using vfio_pci_driver_memcpy(). Can you add a precursor commit to this series that adds a loop to vfio_pci_driver_memcpy() so that it performs whatever sized memcpy the user asks for by chunking it up in to max_memcpy_size or smaller chunks? In that same commit, please update the vfio_pci_driver_test so we get coverage of the new chunking logic (get rid of the code that rounds down self->size fo max_memcpy_size, except for the memcpy_storm test). In other words: - vfio_pci_driver_memcpy() should allow any size memcpy and will chunk it up as necessary depending on what the driver supports. - vfio_pci_driver_memcpy_start() semantics don't change. This still gives tests direct access to the driver memcpy_start() op and thus must obey max_memcpy_size. This should also allow you to greatly simplify the implementation of memcpy_wait() down below. > +static int nv_gpu_memcpy_wait(struct vfio_pci_device *device) > +{ > + struct gpu_device *gpu = to_nv_gpu(device); > + u64 iteration; > + u64 offset; > + int ret; > + > + VFIO_ASSERT_NE(gpu->memcpy_count, 0); > + > + for (iteration = 0; iteration < gpu->memcpy_count; iteration++) { This loop is unnecessary. You set max_memcpy_count to NV_FALCON_DMA_MAX_TRANSFER_COUNT which is 1. So vfio_pci_driver_memcpy_start() will guarantee that memcpy_count is at most 1. > + offset = 0; > + > + while (offset < gpu->memcpy_size) { > + int chunk_encoding; > + > + chunk_encoding = size_to_dma_encoding( > + gpu->memcpy_size - offset); This loop is also unnecessary. You set max_memcpy_size to NV_FALCON_DMA_MAX_TRANSFER_SIZE, so vfio_pci_driver_memcpy_start() will guarantee memcpy_size is at most that. > + if (chunk_encoding < 0) { > + ret = -EINVAL; > + goto out; > + } > + > + ret = nv_gpu_memcpy_chunk(device, > + gpu->memcpy_src + offset, > + gpu->memcpy_dst + offset, > + chunk_encoding); > + if (ret) > + goto out; > + > + offset += 0x4 << chunk_encoding; > + } > + } > + > + ret = 0; > +out: > + gpu->memcpy_count = 0; > + > + return ret; > +}

