summaryrefslogtreecommitdiff
path: root/tools/testing/selftests
diff options
context:
space:
mode:
authorMark Brown <broonie@kernel.org>2026-07-26 23:41:57 +0100
committerMark Brown <broonie@kernel.org>2026-07-26 23:41:57 +0100
commitfc4ab43d2fbf6b213225a17c967068e5cdd531b8 (patch)
treec8556eb23de6d16fa413c15e1f26c6332050ada8 /tools/testing/selftests
parent5d096f5a9b37437b711848b77deb2ed21fe04aec (diff)
parentdc77acfeb979dded39b247b60fef0399536bfa77 (diff)
downloadlinux-next-fc4ab43d2fbf6b213225a17c967068e5cdd531b8.tar.gz
linux-next-fc4ab43d2fbf6b213225a17c967068e5cdd531b8.zip
Merge branch 'next' of https://github.com/awilliam/linux-vfio.git
Diffstat (limited to 'tools/testing/selftests')
-rw-r--r--tools/testing/selftests/vfio/lib/drivers/nv_falcon/hw.h352
-rw-r--r--tools/testing/selftests/vfio/lib/drivers/nv_falcon/nv_falcon.c783
-rw-r--r--tools/testing/selftests/vfio/lib/include/libvfio/assert.h18
-rw-r--r--tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h14
-rw-r--r--tools/testing/selftests/vfio/lib/iommu.c12
-rw-r--r--tools/testing/selftests/vfio/lib/iova_allocator.c4
-rw-r--r--tools/testing/selftests/vfio/lib/libvfio.mk2
-rw-r--r--tools/testing/selftests/vfio/lib/sysfs.c3
-rw-r--r--tools/testing/selftests/vfio/lib/vfio_pci_device.c32
-rw-r--r--tools/testing/selftests/vfio/lib/vfio_pci_driver.c21
-rw-r--r--tools/testing/selftests/vfio/vfio_pci_device_init_perf_test.c4
-rw-r--r--tools/testing/selftests/vfio/vfio_pci_driver_test.c57
12 files changed, 1249 insertions, 53 deletions
diff --git a/tools/testing/selftests/vfio/lib/drivers/nv_falcon/hw.h b/tools/testing/selftests/vfio/lib/drivers/nv_falcon/hw.h
new file mode 100644
index 000000000000..edce130fd008
--- /dev/null
+++ b/tools/testing/selftests/vfio/lib/drivers/nv_falcon/hw.h
@@ -0,0 +1,352 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ */
+#ifndef _NV_FALCON_HW_H_
+#define _NV_FALCON_HW_H_
+
+#include <linux/types.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
+
+/* 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_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
+
+/* DMA Global Address Top Bits Register */
+#define NV_GPU_DMA_ADDR_TOP_BITS_REG 0x100f04
+
+/* DMA Command Register Bit Definitions */
+#define NV_FALCON_DMA_CMD_WRITE_BIT 0x20
+#define NV_FALCON_DMA_CMD_SIZE_SHIFT 8
+#define NV_FALCON_DMA_CMD_DONE_BIT 0x2
+
+/*
+ * Falcon DMA is synchronous, so a transfer size and count larger than
+ * its per-operation maximum adds no value.
+ */
+
+/* DMA block size and alignment */
+#define NV_FALCON_DMA_MIN_TRANSFER_SIZE 4
+#define NV_FALCON_DMA_MAX_TRANSFER_SIZE 256
+#define NV_FALCON_DMA_BLOCK_SIZE 256
+#define NV_FALCON_DMA_MAX_TRANSFER_COUNT 1
+
+/* DMACTL register bits */
+#define NV_FALCON_DMACTL_DMEM_SCRUBBING 0x1
+#define NV_FALCON_DMACTL_READY_MASK 0x6
+
+/* Falcon Core Selection Register */
+#define NV_FALCON_CORE_SELECT_OFFSET 0x1668
+#define NV_FALCON_CORE_SELECT_MASK 0x30
+
+/* Falcon mailbox register (for Ada+ reset check) */
+#define NV_FALCON_MAILBOX_TEST_OFFSET 0x40c
+#define NV_FALCON_MAILBOX_RESET_MAGIC 0xbadf5620
+
+/* Falcon Message Queue Register Offsets (relative to base_page) */
+#define NV_FALCON_QUEUE_HEAD_BASE_OFFSET 0x2c00
+#define NV_FALCON_QUEUE_TAIL_BASE_OFFSET 0x2c04
+#define NV_FALCON_QUEUE_STRIDE 0x8
+#define NV_FALCON_MSG_QUEUE_HEAD_BASE_OFFSET 0x2c80
+#define NV_FALCON_MSG_QUEUE_TAIL_BASE_OFFSET 0x2c84
+
+/* FSP Falcon Base Pages */
+#define NV_FSP_FALCON_BASE 0x8f0100
+/* base_page = cpuctl & ~0xfff */
+#define NV_FSP_FALCON_BASE_PAGE 0x8f0000
+#define NV_FSP_EMEM_BASE 0x8f2000
+
+/* FSP EMEM Port Offsets (relative to FSP EMEM base) */
+#define NV_FSP_EMEMC_OFFSET 0xac0
+#define NV_FSP_EMEMD_OFFSET 0xac4
+#define NV_FSP_EMEM_PORT_STRIDE 0x8
+
+/* EMEM Control Register Flags (same as DMEM) */
+#define NV_FALCON_EMEMC_AINCR 0x01000000
+#define NV_FALCON_EMEMC_AINCW 0x02000000
+
+/* FSP RPC channel configuration */
+#define NV_FSP_RPC_CHANNEL_SIZE 1024
+#define NV_FSP_RPC_MAX_PACKET_SIZE 1024
+#define NV_FSP_RPC_CHANNEL_HOPPER 2
+#define NV_FSP_RPC_EMEM_BASE \
+ (NV_FSP_RPC_CHANNEL_HOPPER * NV_FSP_RPC_CHANNEL_SIZE)
+
+/* FSP EMEM port 2 registers (pre-computed for Hopper channel 2) */
+#define NV_FSP_EMEM_PORT2_CTRL (NV_FSP_EMEM_BASE + NV_FSP_EMEMC_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FSP_EMEM_PORT_STRIDE)
+#define NV_FSP_EMEM_PORT2_DATA (NV_FSP_EMEM_BASE + NV_FSP_EMEMD_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FSP_EMEM_PORT_STRIDE)
+
+/* FSP queue register offsets (pre-computed for Hopper channel 2) */
+#define NV_FSP_QUEUE_HEAD \
+ (NV_FSP_FALCON_BASE_PAGE + NV_FALCON_QUEUE_HEAD_BASE_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FALCON_QUEUE_STRIDE)
+#define NV_FSP_QUEUE_TAIL \
+ (NV_FSP_FALCON_BASE_PAGE + NV_FALCON_QUEUE_TAIL_BASE_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FALCON_QUEUE_STRIDE)
+#define NV_FSP_MSG_QUEUE_HEAD \
+ (NV_FSP_FALCON_BASE_PAGE + NV_FALCON_MSG_QUEUE_HEAD_BASE_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FALCON_QUEUE_STRIDE)
+#define NV_FSP_MSG_QUEUE_TAIL \
+ (NV_FSP_FALCON_BASE_PAGE + NV_FALCON_MSG_QUEUE_TAIL_BASE_OFFSET + \
+ NV_FSP_RPC_CHANNEL_HOPPER * NV_FALCON_QUEUE_STRIDE)
+
+/* MCTP Header */
+#define NV_MCTP_HDR_SEID_SHIFT 16
+#define NV_MCTP_HDR_SEID_MASK 0xff
+#define NV_MCTP_HDR_SEQ_SHIFT 28
+#define NV_MCTP_HDR_SEQ_MASK 0x3
+#define NV_MCTP_HDR_EOM_BIT 0x40000000
+#define NV_MCTP_HDR_SOM_BIT 0x80000000
+
+/* MCTP Message Header */
+#define NV_MCTP_MSG_TYPE_SHIFT 0
+#define NV_MCTP_MSG_TYPE_MASK 0x7f
+#define NV_MCTP_MSG_TYPE_VENDOR_DEFINED 0x7e
+#define NV_MCTP_MSG_VENDOR_ID_SHIFT 8
+#define NV_MCTP_MSG_VENDOR_ID_MASK 0xffff
+#define NV_MCTP_MSG_VENDOR_ID_NVIDIA 0x10de
+#define NV_MCTP_MSG_NVDM_TYPE_SHIFT 24
+#define NV_MCTP_MSG_NVDM_TYPE_MASK 0xff
+
+/* NVDM response type */
+#define NV_NVDM_TYPE_RESPONSE 0x15
+
+/* Minimum response size: mctp_hdr + msg_hdr + status_hdr + type + status */
+#define NV_FSP_RPC_MIN_RESPONSE_WORDS 5
+
+/* FBIF (Frame Buffer Interface) Registers */
+/* Legacy PMU FBIF offsets (Kepler, Maxwell Gen1) */
+#define NV_PMU_LEGACY_FBIF_CTL_OFFSET 0x624
+#define NV_PMU_LEGACY_FBIF_TRANSCFG_OFFSET 0x600
+
+/* PMU FBIF offsets */
+#define NV_PMU_FBIF_CTL_OFFSET 0xe24
+#define NV_PMU_FBIF_TRANSCFG_OFFSET 0xe00
+
+/* GSP FBIF offsets */
+#define NV_GSP_FBIF_CTL_OFFSET 0x624
+#define NV_GSP_FBIF_TRANSCFG_OFFSET 0x600
+
+/* OFA Falcon Base Page and FBIF offsets (used for Hopper+ DMA) */
+#define NV_OFA_FALCON_BASE 0x844000
+#define NV_OFA_FBIF_CTL_OFFSET 0x424
+#define NV_OFA_FBIF_TRANSCFG_OFFSET 0x400
+
+/* OFA DMA support check register (Hopper+) */
+#define NV_OFA_DMA_SUPPORT_CHECK_REG 0x8443c0
+
+/* FSP NVDM command types */
+#define NV_NVDM_TYPE_FBDMA 0x22
+#define NV_FBDMA_SUBCMD_ENABLE 0x1
+
+/* FBIF CTL2 offset (relative to fbif_ctl) */
+#define NV_FBIF_CTL2_OFFSET 0x60
+
+/* FBIF TRANSCFG register bits */
+#define NV_FBIF_TRANSCFG_TARGET_MASK 0x3
+#define NV_FBIF_TRANSCFG_SYSMEM_DEFAULT 0x5
+
+/* FBIF CTL register bits */
+#define NV_FBIF_CTL_ALLOW_PHYS_MODE 0x10
+#define NV_FBIF_CTL_ALLOW_FULL_PHYS_MODE 0x80
+
+/* Memory clear register offsets */
+#define NV_MEM_CLEAR_OFFSET 0x100b20
+#define NV_BOOT_COMPLETE_OFFSET 0x118234
+#define NV_BOOT_COMPLETE_SUCCESS 0x3ff
+
+/* FSP boot complete register (Hopper+) */
+#define NV_FSP_BOOT_COMPLETE_OFFSET 0x200bc
+#define NV_FSP_BOOT_COMPLETE_SUCCESS 0xff
+
+enum gpu_arch {
+ GPU_ARCH_UNKNOWN = -1,
+ GPU_ARCH_KEPLER = 0,
+ GPU_ARCH_MAXWELL_GEN1,
+ GPU_ARCH_MAXWELL_GEN2,
+ GPU_ARCH_PASCAL,
+ GPU_ARCH_PASCAL_10X,
+ GPU_ARCH_VOLTA,
+ GPU_ARCH_TURING,
+ GPU_ARCH_AMPERE,
+ GPU_ARCH_ADA,
+ GPU_ARCH_HOPPER,
+};
+
+enum falcon_type {
+ FALCON_TYPE_PMU_LEGACY = 0,
+ FALCON_TYPE_PMU,
+ FALCON_TYPE_GSP,
+ FALCON_TYPE_OFA,
+};
+
+struct falcon {
+ u32 base_page;
+ u32 dmactl;
+ u32 engine_reset;
+ u32 fbif_ctl;
+ u32 fbif_ctl2;
+ u32 fbif_transcfg;
+ u32 dmem_control_reg;
+ u32 dmem_data_reg;
+ bool no_outside_reset;
+};
+
+struct gpu_properties {
+ u32 pmc_enable_mask;
+ bool memory_clear_supported;
+ enum falcon_type falcon_type;
+};
+
+static const u32 verified_gpu_map[] = {
+ 0x0e40a0a2, /* K520 */
+ 0x0e6000a1, /* GTX660 */
+ 0x0e63a0a1, /* K4000 */
+ 0x0f22d0a1, /* K80 */
+ 0x108000a1, /* GT635 */
+ 0x117010a2, /* GTX750 */
+ 0x117020a2, /* GTX745 */
+ 0x124320a1, /* M60 */
+ 0x130000a1, /* P100 */
+ 0x134000a1, /* P4 */
+ 0x132000a1, /* P40 */
+ 0x140000a1, /* V100 */
+ 0x164000a1, /* T4 */
+ 0xb77000a1, /* A16 */
+ 0x170000a1, /* A100 */
+ 0xb72000a1, /* A10 */
+ 0x180000a1, /* H100 */
+ 0x194000a1, /* L4 */
+ 0x192000a1, /* L40S */
+};
+
+#define VERIFIED_GPU_MAP_SIZE ARRAY_SIZE(verified_gpu_map)
+
+static const struct gpu_properties gpu_properties_map[] = {
+ [GPU_ARCH_KEPLER] = {
+ .pmc_enable_mask = NV_PMC_ENABLE_PWR | NV_PMC_ENABLE_HUB,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_PMU_LEGACY,
+ },
+ [GPU_ARCH_MAXWELL_GEN1] = {
+ .pmc_enable_mask = NV_PMC_ENABLE_PWR | NV_PMC_ENABLE_HUB,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_PMU_LEGACY,
+ },
+ [GPU_ARCH_MAXWELL_GEN2] = {
+ .pmc_enable_mask = NV_PMC_ENABLE_PWR,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_PMU,
+ },
+ [GPU_ARCH_PASCAL] = {
+ .pmc_enable_mask = NV_PMC_ENABLE_PWR,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_PMU,
+ },
+ [GPU_ARCH_PASCAL_10X] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_PMU,
+ },
+ [GPU_ARCH_VOLTA] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = false,
+ .falcon_type = FALCON_TYPE_GSP,
+ },
+ [GPU_ARCH_TURING] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = true,
+ .falcon_type = FALCON_TYPE_GSP,
+ },
+ [GPU_ARCH_AMPERE] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = true,
+ .falcon_type = FALCON_TYPE_GSP,
+ },
+ [GPU_ARCH_ADA] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = true,
+ .falcon_type = FALCON_TYPE_PMU,
+ },
+ [GPU_ARCH_HOPPER] = {
+ .pmc_enable_mask = 0,
+ .memory_clear_supported = true,
+ .falcon_type = FALCON_TYPE_OFA,
+ },
+};
+
+static const struct falcon falcon_map[] = {
+ [FALCON_TYPE_PMU_LEGACY] = {
+ .base_page = NV_PPWR_FALCON_BASE,
+ .dmactl = NV_PPWR_FALCON_BASE + NV_FALCON_DMACTL_OFFSET,
+ .engine_reset = NV_PPWR_FALCON_BASE + NV_FALCON_ENGINE_RESET_OFFSET,
+ .fbif_ctl = NV_PPWR_FALCON_BASE + NV_PMU_LEGACY_FBIF_CTL_OFFSET,
+ .fbif_ctl2 = NV_PPWR_FALCON_BASE +
+ NV_PMU_LEGACY_FBIF_CTL_OFFSET + NV_FBIF_CTL2_OFFSET,
+ .fbif_transcfg = NV_PPWR_FALCON_BASE + NV_PMU_LEGACY_FBIF_TRANSCFG_OFFSET,
+ .dmem_control_reg = NV_PPWR_FALCON_BASE + NV_FALCON_DMEMC_OFFSET,
+ .dmem_data_reg = NV_PPWR_FALCON_BASE + NV_FALCON_DMEMD_OFFSET,
+ .no_outside_reset = false,
+ },
+ [FALCON_TYPE_PMU] = {
+ .base_page = NV_PPWR_FALCON_BASE,
+ .dmactl = NV_PPWR_FALCON_BASE + NV_FALCON_DMACTL_OFFSET,
+ .engine_reset = NV_PPWR_FALCON_BASE + NV_FALCON_ENGINE_RESET_OFFSET,
+ .fbif_ctl = NV_PPWR_FALCON_BASE + NV_PMU_FBIF_CTL_OFFSET,
+ .fbif_ctl2 = NV_PPWR_FALCON_BASE + NV_PMU_FBIF_CTL_OFFSET + NV_FBIF_CTL2_OFFSET,
+ .fbif_transcfg = NV_PPWR_FALCON_BASE + NV_PMU_FBIF_TRANSCFG_OFFSET,
+ .dmem_control_reg = NV_PPWR_FALCON_BASE + NV_FALCON_DMEMC_OFFSET,
+ .dmem_data_reg = NV_PPWR_FALCON_BASE + NV_FALCON_DMEMD_OFFSET,
+ .no_outside_reset = false,
+ },
+ [FALCON_TYPE_GSP] = {
+ .base_page = NV_PGSP_FALCON_BASE,
+ .dmactl = NV_PGSP_FALCON_BASE + NV_FALCON_DMACTL_OFFSET,
+ .engine_reset = NV_PGSP_FALCON_BASE + NV_FALCON_ENGINE_RESET_OFFSET,
+ .fbif_ctl = NV_PGSP_FALCON_BASE + NV_GSP_FBIF_CTL_OFFSET,
+ .fbif_ctl2 = NV_PGSP_FALCON_BASE + NV_GSP_FBIF_CTL_OFFSET + NV_FBIF_CTL2_OFFSET,
+ .fbif_transcfg = NV_PGSP_FALCON_BASE + NV_GSP_FBIF_TRANSCFG_OFFSET,
+ .dmem_control_reg = NV_PGSP_FALCON_BASE + NV_FALCON_DMEMC_OFFSET,
+ .dmem_data_reg = NV_PGSP_FALCON_BASE + NV_FALCON_DMEMD_OFFSET,
+ .no_outside_reset = false,
+ },
+ [FALCON_TYPE_OFA] = {
+ .base_page = NV_OFA_FALCON_BASE,
+ .dmactl = NV_OFA_FALCON_BASE + NV_FALCON_DMACTL_OFFSET,
+ .engine_reset = NV_OFA_FALCON_BASE + NV_FALCON_ENGINE_RESET_OFFSET,
+ .fbif_ctl = NV_OFA_FALCON_BASE + NV_OFA_FBIF_CTL_OFFSET,
+ .fbif_ctl2 = NV_OFA_FALCON_BASE + NV_OFA_FBIF_CTL_OFFSET + NV_FBIF_CTL2_OFFSET,
+ .fbif_transcfg = NV_OFA_FALCON_BASE + NV_OFA_FBIF_TRANSCFG_OFFSET,
+ .dmem_control_reg = NV_OFA_FALCON_BASE + NV_FALCON_DMEMC_OFFSET,
+ .dmem_data_reg = NV_OFA_FALCON_BASE + NV_FALCON_DMEMD_OFFSET,
+ .no_outside_reset = true,
+ },
+};
+
+#endif /* _NV_FALCON_HW_H_ */
diff --git a/tools/testing/selftests/vfio/lib/drivers/nv_falcon/nv_falcon.c b/tools/testing/selftests/vfio/lib/drivers/nv_falcon/nv_falcon.c
new file mode 100644
index 000000000000..c08aa81c44f4
--- /dev/null
+++ b/tools/testing/selftests/vfio/lib/drivers/nv_falcon/nv_falcon.c
@@ -0,0 +1,783 @@
+// 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"
+
+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;
+};
+
+static inline struct gpu_device *to_gpu_device(struct vfio_pci_device *device)
+{
+ return device->driver.region.vaddr;
+}
+
+static enum gpu_arch nv_gpu_arch_lookup(u32 pmc_boot_0)
+{
+ u32 arch = (pmc_boot_0 >> 24) & 0x1f;
+
+ switch (arch) {
+ case 0x0e:
+ case 0x0f:
+ case 0x10:
+ return GPU_ARCH_KEPLER;
+ case 0x11:
+ return GPU_ARCH_MAXWELL_GEN1;
+ case 0x12:
+ return GPU_ARCH_MAXWELL_GEN2;
+ case 0x13:
+ /* P100 (impl 0) uses PMC reset; P4/P40 use engine reset */
+ if (((pmc_boot_0 >> 20) & 0xf) == 0)
+ return GPU_ARCH_PASCAL;
+ return GPU_ARCH_PASCAL_10X;
+ case 0x14:
+ return GPU_ARCH_VOLTA;
+ case 0x16:
+ return GPU_ARCH_TURING;
+ case 0x17:
+ return GPU_ARCH_AMPERE;
+ case 0x18:
+ return GPU_ARCH_HOPPER;
+ case 0x19:
+ return GPU_ARCH_ADA;
+ default:
+ return GPU_ARCH_UNKNOWN;
+ }
+}
+
+static inline u32 gpu_read32(struct gpu_device *gpu, u32 offset)
+{
+ return readl(gpu->bar0 + offset);
+}
+
+static inline void gpu_write32(struct gpu_device *gpu, u32 offset, u32 value)
+{
+ writel(value, gpu->bar0 + offset);
+}
+
+static u64 get_elapsed_ms(struct timespec *start)
+{
+ struct timespec now;
+
+ clock_gettime(CLOCK_MONOTONIC, &now);
+
+ return (now.tv_sec - start->tv_sec) * 1000
+ + (now.tv_nsec - start->tv_nsec) / 1000000;
+}
+
+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_gpu_device(device);
+ struct timespec start;
+ u64 elapsed_ms;
+ u32 value;
+
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ for (;;) {
+ value = gpu_read32(gpu, offset);
+ if ((value & mask) == expected)
+ return 0;
+
+ elapsed_ms = get_elapsed_ms(&start);
+
+ 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 %lu ms\n",
+ name, offset, value, expected, mask, elapsed_ms);
+ return -ETIMEDOUT;
+}
+
+static int fsp_poll_queue(struct vfio_pci_device *device, const char *name,
+ u32 head_reg, u32 tail_reg, bool wait_empty,
+ u32 timeout_ms)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ struct timespec start;
+ u64 elapsed_ms;
+ u32 head, tail;
+
+ clock_gettime(CLOCK_MONOTONIC, &start);
+
+ for (;;) {
+ head = gpu_read32(gpu, head_reg);
+ tail = gpu_read32(gpu, tail_reg);
+ if (wait_empty ? (head == tail) : (head != tail))
+ return 0;
+
+ elapsed_ms = get_elapsed_ms(&start);
+
+ if (elapsed_ms >= timeout_ms)
+ break;
+
+ usleep(1000);
+ }
+
+ dev_err(device,
+ "Timeout polling %s: head=0x%x tail=0x%x wait_empty=%d after %lu ms\n",
+ name, head, tail, wait_empty, elapsed_ms);
+ return -ETIMEDOUT;
+}
+
+static void fsp_emem_write(struct vfio_pci_device *device, u32 offset,
+ const u32 *data, u32 count)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 i;
+
+ /* Configure port with auto-increment for read and write */
+ gpu_write32(gpu, NV_FSP_EMEM_PORT2_CTRL,
+ offset | NV_FALCON_EMEMC_AINCR | NV_FALCON_EMEMC_AINCW);
+
+ for (i = 0; i < count; i++)
+ gpu_write32(gpu, NV_FSP_EMEM_PORT2_DATA, data[i]);
+}
+
+static void fsp_emem_read(struct vfio_pci_device *device, u32 offset,
+ u32 *data, u32 count)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 i;
+
+ /* Configure port with auto-increment for read and write */
+ gpu_write32(gpu, NV_FSP_EMEM_PORT2_CTRL,
+ offset | NV_FALCON_EMEMC_AINCR | NV_FALCON_EMEMC_AINCW);
+
+ for (i = 0; i < count; i++)
+ data[i] = gpu_read32(gpu, NV_FSP_EMEM_PORT2_DATA);
+}
+
+static int fsp_rpc_send_data(struct vfio_pci_device *device, const u32 *data,
+ u32 count)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ int ret;
+
+ ret = fsp_poll_queue(device, "fsp_cmd_queue_empty",
+ NV_FSP_QUEUE_HEAD, NV_FSP_QUEUE_TAIL, true, 1000);
+ if (ret)
+ return ret;
+
+ fsp_emem_write(device, NV_FSP_RPC_EMEM_BASE, data, count);
+
+ /* Update queue head/tail to signal data is ready */
+ gpu_write32(gpu, NV_FSP_QUEUE_TAIL,
+ NV_FSP_RPC_EMEM_BASE + (count - 1) * 4);
+ gpu_write32(gpu, NV_FSP_QUEUE_HEAD, NV_FSP_RPC_EMEM_BASE);
+
+ return ret;
+}
+
+static int fsp_rpc_receive_data(struct vfio_pci_device *device, u32 *data,
+ u32 max_count, u32 timeout_ms)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 head, tail;
+ u32 msg_size_words;
+ int ret;
+
+ ret = fsp_poll_queue(device, "fsp_msg_queue_ready",
+ NV_FSP_MSG_QUEUE_HEAD, NV_FSP_MSG_QUEUE_TAIL,
+ false, timeout_ms);
+ if (ret)
+ return ret;
+
+ head = gpu_read32(gpu, NV_FSP_MSG_QUEUE_HEAD);
+ tail = gpu_read32(gpu, NV_FSP_MSG_QUEUE_TAIL);
+
+ msg_size_words = (tail - head + 4) / 4;
+ if (msg_size_words > max_count)
+ msg_size_words = max_count;
+
+ fsp_emem_read(device, NV_FSP_RPC_EMEM_BASE, data, msg_size_words);
+
+ /* Reset message queue tail to acknowledge receipt */
+ gpu_write32(gpu, NV_FSP_MSG_QUEUE_TAIL, head);
+
+ return msg_size_words;
+}
+
+static void fsp_reset_rpc_state(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 head, tail;
+
+ head = gpu_read32(gpu, NV_FSP_QUEUE_HEAD);
+ tail = gpu_read32(gpu, NV_FSP_QUEUE_TAIL);
+
+ if (head == tail) {
+ head = gpu_read32(gpu, NV_FSP_MSG_QUEUE_HEAD);
+ tail = gpu_read32(gpu, NV_FSP_MSG_QUEUE_TAIL);
+ if (head == tail)
+ return;
+ }
+
+ /* Best-effort drain; timeout is expected if no pending message. */
+ fsp_poll_queue(device, "fsp_msg_queue_drain",
+ NV_FSP_MSG_QUEUE_HEAD, NV_FSP_MSG_QUEUE_TAIL,
+ false, 5000);
+
+ gpu_write32(gpu, NV_FSP_QUEUE_TAIL, NV_FSP_RPC_EMEM_BASE);
+ gpu_write32(gpu, NV_FSP_QUEUE_HEAD, NV_FSP_RPC_EMEM_BASE);
+ gpu_write32(gpu, NV_FSP_MSG_QUEUE_TAIL, NV_FSP_RPC_EMEM_BASE);
+ gpu_write32(gpu, NV_FSP_MSG_QUEUE_HEAD, NV_FSP_RPC_EMEM_BASE);
+}
+
+static inline u32 mctp_header_build(u8 seid, u8 seq, bool som, bool eom)
+{
+ u32 hdr = 0;
+
+ hdr |= (seid & NV_MCTP_HDR_SEID_MASK) << NV_MCTP_HDR_SEID_SHIFT;
+ hdr |= (seq & NV_MCTP_HDR_SEQ_MASK) << NV_MCTP_HDR_SEQ_SHIFT;
+ if (som)
+ hdr |= NV_MCTP_HDR_SOM_BIT;
+ if (eom)
+ hdr |= NV_MCTP_HDR_EOM_BIT;
+
+ return hdr;
+}
+
+static inline u32 mctp_msg_header_build(u8 nvdm_type)
+{
+ u32 hdr = 0;
+
+ hdr |= (NV_MCTP_MSG_TYPE_VENDOR_DEFINED & NV_MCTP_MSG_TYPE_MASK)
+ << NV_MCTP_MSG_TYPE_SHIFT;
+ hdr |= (NV_MCTP_MSG_VENDOR_ID_NVIDIA & NV_MCTP_MSG_VENDOR_ID_MASK)
+ << NV_MCTP_MSG_VENDOR_ID_SHIFT;
+ hdr |= (nvdm_type & NV_MCTP_MSG_NVDM_TYPE_MASK)
+ << NV_MCTP_MSG_NVDM_TYPE_SHIFT;
+
+ return hdr;
+}
+
+static inline u8 mctp_msg_header_get_nvdm_type(u32 hdr)
+{
+ return (hdr >> NV_MCTP_MSG_NVDM_TYPE_SHIFT) &
+ NV_MCTP_MSG_NVDM_TYPE_MASK;
+}
+
+static int fsp_rpc_send_cmd(struct vfio_pci_device *device, u8 nvdm_type,
+ const u32 *data, u32 data_count, u32 timeout_ms)
+{
+ u32 max_packet_words = NV_FSP_RPC_MAX_PACKET_SIZE / 4;
+ u32 packet[256];
+ u32 resp_buf[256];
+ u32 total_words;
+ int resp_words;
+ u8 resp_nvdm_type;
+ int ret;
+
+ total_words = 2 + data_count;
+ if (total_words > max_packet_words)
+ return -EINVAL;
+
+ packet[0] = mctp_header_build(0, 0, true, true);
+ packet[1] = mctp_msg_header_build(nvdm_type);
+
+ if (data_count > 0)
+ memcpy(&packet[2], data, data_count * sizeof(u32));
+
+ ret = fsp_rpc_send_data(device, packet, total_words);
+ if (ret)
+ return ret;
+
+ resp_words = fsp_rpc_receive_data(device, resp_buf, 256, timeout_ms);
+ if (resp_words < 0)
+ return resp_words;
+
+ if (resp_words < NV_FSP_RPC_MIN_RESPONSE_WORDS)
+ return -EPROTO;
+
+ resp_nvdm_type = mctp_msg_header_get_nvdm_type(resp_buf[1]);
+ if (resp_nvdm_type != NV_NVDM_TYPE_RESPONSE)
+ return -EPROTO;
+
+ if (resp_buf[3] != nvdm_type)
+ return -EPROTO;
+
+ if (resp_buf[4] != 0)
+ return -resp_buf[4];
+
+ return 0;
+}
+
+static int fsp_init(struct vfio_pci_device *device)
+{
+ int ret;
+
+ ret = gpu_poll_register(device, "fsp_boot_complete",
+ NV_FSP_BOOT_COMPLETE_OFFSET,
+ NV_FSP_BOOT_COMPLETE_SUCCESS, 0xffffffff, 5000);
+ if (ret)
+ return ret;
+
+ fsp_reset_rpc_state(device);
+ return ret;
+}
+
+static int fsp_fbdma_enable(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 cmd_data = NV_FBDMA_SUBCMD_ENABLE;
+ int ret = 0;
+
+ if (gpu->fsp_dma_enabled)
+ return ret;
+
+ ret = fsp_rpc_send_cmd(device, NV_NVDM_TYPE_FBDMA, &cmd_data, 1, 5000);
+ if (ret)
+ return ret;
+
+ gpu->fsp_dma_enabled = true;
+ return ret;
+}
+
+static bool fsp_check_ofa_dma_support(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ u32 val = gpu_read32(gpu, NV_OFA_DMA_SUPPORT_CHECK_REG);
+
+ return (val >> 16) != 0xbadf;
+}
+
+static u32 size_to_dma_encoding(u64 size)
+{
+ VFIO_ASSERT_LE(size, NV_FALCON_DMA_MAX_TRANSFER_SIZE);
+ VFIO_ASSERT_GE(size, NV_FALCON_DMA_MIN_TRANSFER_SIZE);
+ VFIO_ASSERT_EQ(size & (size - 1), 0, "size must be power-of-2\n");
+
+ return ffs(size) - 3;
+}
+
+static void falcon_dmem_port_configure(struct vfio_pci_device *device,
+ u32 offset, bool auto_inc_read,
+ bool auto_inc_write)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct falcon *falcon = gpu->falcon;
+ u32 memc_value = offset;
+
+ /* Set auto-increment flags */
+ if (auto_inc_read)
+ memc_value |= NV_PPWR_FALCON_DMEMC_AINCR_TRUE;
+ if (auto_inc_write)
+ memc_value |= NV_PPWR_FALCON_DMEMC_AINCW_TRUE;
+
+ gpu_write32(gpu, falcon->dmem_control_reg, memc_value);
+}
+
+static void falcon_select_core_falcon(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct falcon *falcon = gpu->falcon;
+ u32 core_select_reg = falcon->base_page + NV_FALCON_CORE_SELECT_OFFSET;
+ u32 core_select;
+
+ core_select = gpu_read32(gpu, core_select_reg);
+
+ /* Clear bits 4:5 to select falcon core (not RISCV) */
+ core_select &= ~NV_FALCON_CORE_SELECT_MASK;
+
+ gpu_write32(gpu, core_select_reg, core_select);
+}
+
+static int falcon_enable(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct falcon *falcon = gpu->falcon;
+ u32 mailbox_test_reg;
+ u32 mailbox_val;
+
+ if (falcon->no_outside_reset)
+ return 0;
+
+ /* Ada-specific: Check if falcon needs reset before enable */
+ if (gpu->arch == GPU_ARCH_ADA) {
+ mailbox_test_reg = falcon->base_page +
+ NV_FALCON_MAILBOX_TEST_OFFSET;
+ mailbox_val = gpu_read32(gpu, mailbox_test_reg);
+ if (mailbox_val == NV_FALCON_MAILBOX_RESET_MAGIC)
+ gpu_write32(gpu, falcon->engine_reset, 1);
+ }
+
+ /* Enable the falcon based on control method */
+ if (gpu->pmc_enable_mask != 0) {
+ u32 pmc_enable;
+
+ /* Enable via PMC_ENABLE register */
+ pmc_enable = gpu_read32(gpu, NV_PMC_ENABLE);
+ gpu_write32(gpu, NV_PMC_ENABLE,
+ pmc_enable | gpu->pmc_enable_mask);
+ } else {
+ /* Enable by deasserting engine reset */
+ gpu_write32(gpu, falcon->engine_reset, 0);
+ }
+
+ if (gpu->arch < GPU_ARCH_HOPPER) {
+ falcon_select_core_falcon(device);
+
+ /* Wait for DMACTL to be ready (bits 1:2 should be 0) */
+ return gpu_poll_register(device, "falcon_dmactl",
+ falcon->dmactl, 0,
+ NV_FALCON_DMACTL_READY_MASK, 1000);
+ }
+
+ return 0;
+}
+
+static void falcon_disable(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct falcon *falcon = gpu->falcon;
+ u32 pmc_enable;
+
+ if (falcon->no_outside_reset)
+ return;
+
+ if (gpu->pmc_enable_mask != 0) {
+ /* Disable via PMC_ENABLE */
+ pmc_enable = gpu_read32(gpu, NV_PMC_ENABLE);
+ gpu_write32(gpu, NV_PMC_ENABLE,
+ pmc_enable & ~gpu->pmc_enable_mask);
+ } else {
+ /* Disable by asserting engine reset */
+ gpu_write32(gpu, falcon->engine_reset, 1);
+ }
+}
+
+static int falcon_reset(struct vfio_pci_device *device)
+{
+ falcon_disable(device);
+
+ return falcon_enable(device);
+}
+
+static int nv_falcon_dma_init(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct falcon *falcon;
+ u32 transcfg;
+ u32 dmactl;
+ u32 ctl;
+ int ret = 0;
+
+ falcon = gpu->falcon;
+
+ vfio_pci_cmd_set(device, PCI_COMMAND_MASTER);
+
+ if (gpu->arch >= GPU_ARCH_HOPPER) {
+ ret = fsp_init(device);
+ if (ret) {
+ dev_err(device, "Failed to init FSP: %d\n", ret);
+ return ret;
+ }
+
+ ret = fsp_fbdma_enable(device);
+ if (ret) {
+ dev_err(device,
+ "Failed to enable FSP FBDMA: %d\n", ret);
+ return ret;
+ }
+
+ if (!fsp_check_ofa_dma_support(device)) {
+ dev_err(device,
+ "OFA DMA not supported with current firmware\n");
+ return -EOPNOTSUPP;
+ }
+ }
+
+ if (gpu->is_memory_clear_supported) {
+ /* For Turing+, wait for boot to complete first */
+ if (gpu->arch >= GPU_ARCH_TURING) {
+ /* Wait for boot complete - Hopper+ uses FSP register */
+ if (gpu->arch >= GPU_ARCH_HOPPER) {
+ ret = gpu_poll_register(device,
+ "fsp_boot_complete",
+ NV_FSP_BOOT_COMPLETE_OFFSET,
+ NV_FSP_BOOT_COMPLETE_SUCCESS,
+ 0xffffffff, 5000);
+ } else {
+ ret = gpu_poll_register(device,
+ "boot_complete",
+ NV_BOOT_COMPLETE_OFFSET,
+ NV_BOOT_COMPLETE_SUCCESS,
+ 0xffffffff, 5000);
+ }
+ if (ret)
+ return ret;
+
+ ret = gpu_poll_register(device,
+ "memory_clear_finished",
+ NV_MEM_CLEAR_OFFSET, 0x1, 0xffffffff, 5000);
+ if (ret)
+ return ret;
+ }
+ }
+
+ ret = falcon_reset(device);
+ if (ret)
+ return ret;
+
+ falcon_dmem_port_configure(device, 0, false, false);
+
+ transcfg = gpu_read32(gpu, falcon->fbif_transcfg);
+ transcfg &= ~NV_FBIF_TRANSCFG_TARGET_MASK;
+ transcfg |= NV_FBIF_TRANSCFG_SYSMEM_DEFAULT;
+ gpu_write32(gpu, falcon->fbif_transcfg, transcfg);
+
+ gpu_write32(gpu, falcon->fbif_ctl2, 0x1);
+
+ ctl = gpu_read32(gpu, falcon->fbif_ctl);
+ ctl |= NV_FBIF_CTL_ALLOW_PHYS_MODE | NV_FBIF_CTL_ALLOW_FULL_PHYS_MODE;
+ gpu_write32(gpu, falcon->fbif_ctl, ctl);
+
+ dmactl = gpu_read32(gpu, falcon->dmactl);
+ dmactl &= ~NV_FALCON_DMACTL_DMEM_SCRUBBING;
+ gpu_write32(gpu, falcon->dmactl, dmactl);
+
+ return ret;
+}
+
+static int nv_falcon_dma(struct vfio_pci_device *device,
+ u64 address, u64 size,
+ bool write)
+{
+ struct gpu_device *gpu = to_gpu_device(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_to_dma_encoding(size) << 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)
+ dev_err(device, "Failed DMA %s (addr=0x%lx, size=%lu)\n",
+ write ? "write" : "read", address, size);
+
+ return ret;
+}
+
+static int nv_falcon_memcpy_chunk(struct vfio_pci_device *device,
+ iova_t src, iova_t dst, u64 size)
+{
+ int ret;
+
+ ret = nv_falcon_dma(device, src, size, false);
+ if (ret)
+ return ret;
+
+ return nv_falcon_dma(device, dst, size, true);
+}
+
+static int nv_falcon_probe(struct vfio_pci_device *device)
+{
+ enum gpu_arch gpu_arch;
+ u32 pmc_boot_0;
+ void *bar0;
+ int i;
+
+ if (vfio_pci_config_readw(device, PCI_VENDOR_ID) !=
+ PCI_VENDOR_ID_NVIDIA)
+ return -ENODEV;
+
+ if (vfio_pci_config_readw(device, PCI_CLASS_DEVICE) >> 8 !=
+ PCI_BASE_CLASS_DISPLAY)
+ return -ENODEV;
+
+ /* Get BAR0 pointer for reading GPU registers */
+ bar0 = device->bars[0].vaddr;
+ if (!bar0)
+ return -ENODEV;
+
+ /* Read PMC_BOOT_0 register from BAR0 to identify GPU */
+ pmc_boot_0 = readl(bar0 + NV_PMC_BOOT_0);
+
+ /* Look up GPU architecture to verify this is a supported GPU */
+ gpu_arch = nv_gpu_arch_lookup(pmc_boot_0);
+ if (gpu_arch == GPU_ARCH_UNKNOWN) {
+ dev_err(device,
+ "Unsupported GPU architecture for PMC_BOOT_0: 0x%x\n",
+ pmc_boot_0);
+ return -ENODEV;
+ }
+
+ /* Check verified GPU map */
+ for (i = 0; i < VERIFIED_GPU_MAP_SIZE; i++) {
+ if (verified_gpu_map[i] == pmc_boot_0)
+ return 0;
+ }
+
+ dev_info(device,
+ "Unvalidated GPU: PMC_BOOT_0: 0x%x, possibly not supported\n",
+ pmc_boot_0);
+
+ return 0;
+}
+
+static void nv_falcon_init(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ const struct gpu_properties *props;
+ 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);
+
+ props = &gpu_properties_map[gpu->arch];
+
+ /* Populate GPU structure */
+ 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;
+
+ /* Initialize falcon for DMA */
+ ret = nv_falcon_dma_init(device);
+ VFIO_ASSERT_EQ(ret, 0, "Failed to initialize falcon DMA: %d\n", ret);
+
+ device->driver.max_memcpy_size = NV_FALCON_DMA_MAX_TRANSFER_SIZE;
+ device->driver.max_memcpy_count = NV_FALCON_DMA_MAX_TRANSFER_COUNT;
+}
+
+static void nv_falcon_remove(struct vfio_pci_device *device)
+{
+ falcon_disable(device);
+ vfio_pci_cmd_clear(device, PCI_COMMAND_MASTER);
+}
+
+/*
+ * Falcon DMA can only process one transfer at a time,
+ * so the actual work is deferred to memcpy_wait() to conform to the
+ * memcpy_start()/memcpy_wait() contract.
+ */
+static void nv_falcon_memcpy_start(struct vfio_pci_device *device,
+ iova_t src, iova_t dst, u64 size, u64 count)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+
+ VFIO_ASSERT_EQ(count, 1);
+ VFIO_ASSERT_EQ(size & (NV_FALCON_DMA_MIN_TRANSFER_SIZE - 1), 0,
+ "size 0x%lx must be %u-byte aligned\n",
+ (unsigned long)size, NV_FALCON_DMA_MIN_TRANSFER_SIZE);
+
+ gpu->memcpy_src = src;
+ gpu->memcpy_dst = dst;
+ gpu->memcpy_size = size;
+}
+
+/*
+ * Return the largest power-of-2 bytes we can transfer from @addr
+ * without crossing a DMA block boundary.
+ */
+static u64 dma_block_remain(u64 addr)
+{
+ u64 offset = addr & (NV_FALCON_DMA_BLOCK_SIZE - 1);
+
+ if (!offset)
+ return NV_FALCON_DMA_BLOCK_SIZE;
+
+ /* Lowest set bit of the offset is the largest aligned chunk */
+ return 1ULL << (ffs(offset) - 1);
+}
+
+static u64 rounddown_pow_of_two(u64 x)
+{
+ return 1ULL << (63 - __builtin_clzll(x));
+}
+
+static int nv_falcon_memcpy_wait(struct vfio_pci_device *device)
+{
+ struct gpu_device *gpu = to_gpu_device(device);
+ iova_t src = gpu->memcpy_src;
+ iova_t dst = gpu->memcpy_dst;
+ u64 remaining = gpu->memcpy_size;
+ int ret = 0;
+
+ /*
+ * Falcon DMA supports power-of-2 transfer sizes in [4, 256] and
+ * cannot cross 256-byte block boundaries. Decompose the request
+ * into the largest valid chunk at each step.
+ */
+ while (remaining) {
+ u64 chunk = rounddown_pow_of_two(remaining);
+
+ chunk = min(chunk, dma_block_remain(src));
+ chunk = min(chunk, dma_block_remain(dst));
+
+ ret = nv_falcon_memcpy_chunk(device, src, dst, chunk);
+ if (ret)
+ break;
+
+ src += chunk;
+ dst += chunk;
+ remaining -= chunk;
+ }
+
+ return ret;
+}
+
+const struct vfio_pci_driver_ops nv_falcon_ops = {
+ .name = "nv_falcon",
+ .probe = nv_falcon_probe,
+ .init = nv_falcon_init,
+ .remove = nv_falcon_remove,
+ .memcpy_start = nv_falcon_memcpy_start,
+ .memcpy_wait = nv_falcon_memcpy_wait,
+};
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/assert.h b/tools/testing/selftests/vfio/lib/include/libvfio/assert.h
index 77b68c7129a6..9fff88f6e4e1 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/assert.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/assert.h
@@ -3,6 +3,7 @@
#define SELFTESTS_VFIO_LIB_INCLUDE_LIBVFIO_ASSERT_H
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
@@ -45,6 +46,23 @@
VFIO_LOG_AND_EXIT(_fmt, ##__VA_ARGS__); \
} while (0)
+#define malloc_assert(_size) ({ \
+ size_t __size = (_size); \
+ void *__ptr = malloc(__size); \
+ VFIO_ASSERT_NOT_NULL(__ptr, "malloc(%zu) failed", \
+ __size); \
+ __ptr; \
+})
+
+#define calloc_assert(_nmemb, _size) ({ \
+ size_t __nmemb = (_nmemb); \
+ size_t __size = (_size); \
+ void *__ptr = calloc(__nmemb, __size); \
+ VFIO_ASSERT_NOT_NULL(__ptr, "calloc(%zu, %zu) failed", \
+ __nmemb, __size); \
+ __ptr; \
+})
+
#define ioctl_assert(_fd, _op, _arg) do { \
void *__arg = (_arg); \
int __ret = ioctl((_fd), (_op), (__arg)); \
diff --git a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
index 3eabead717bb..59acef381d05 100644
--- a/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
+++ b/tools/testing/selftests/vfio/lib/include/libvfio/vfio_pci_device.h
@@ -67,6 +67,20 @@ void vfio_pci_config_access(struct vfio_pci_device *device, bool write,
#define vfio_pci_config_writew(_d, _o, _v) vfio_pci_config_write(_d, _o, _v, u16)
#define vfio_pci_config_writel(_d, _o, _v) vfio_pci_config_write(_d, _o, _v, u32)
+static inline void vfio_pci_cmd_set(struct vfio_pci_device *device, u16 bits)
+{
+ u16 cmd = vfio_pci_config_readw(device, PCI_COMMAND);
+
+ vfio_pci_config_writew(device, PCI_COMMAND, cmd | bits);
+}
+
+static inline void vfio_pci_cmd_clear(struct vfio_pci_device *device, u16 bits)
+{
+ u16 cmd = vfio_pci_config_readw(device, PCI_COMMAND);
+
+ vfio_pci_config_writew(device, PCI_COMMAND, cmd & ~bits);
+}
+
void vfio_pci_irq_enable(struct vfio_pci_device *device, u32 index,
u32 vector, int count);
void vfio_pci_irq_disable(struct vfio_pci_device *device, u32 index);
diff --git a/tools/testing/selftests/vfio/lib/iommu.c b/tools/testing/selftests/vfio/lib/iommu.c
index 035dac069d60..b6f3c5c84e01 100644
--- a/tools/testing/selftests/vfio/lib/iommu.c
+++ b/tools/testing/selftests/vfio/lib/iommu.c
@@ -286,8 +286,7 @@ static struct vfio_iommu_type1_info *vfio_iommu_get_info(int container_fd)
{
struct vfio_iommu_type1_info *info;
- info = malloc(sizeof(*info));
- VFIO_ASSERT_NOT_NULL(info);
+ info = malloc_assert(sizeof(*info));
*info = (struct vfio_iommu_type1_info) {
.argsz = sizeof(*info),
@@ -324,8 +323,7 @@ static struct iommu_iova_range *vfio_iommu_iova_ranges(struct iommu *iommu,
cap_range = container_of(hdr, struct vfio_iommu_type1_info_cap_iova_range, header);
VFIO_ASSERT_GT(cap_range->nr_iovas, 0);
- ranges = calloc(cap_range->nr_iovas, sizeof(*ranges));
- VFIO_ASSERT_NOT_NULL(ranges);
+ ranges = calloc_assert(cap_range->nr_iovas, sizeof(*ranges));
for (u32 i = 0; i < cap_range->nr_iovas; i++) {
ranges[i] = (struct iommu_iova_range){
@@ -357,8 +355,7 @@ static struct iommu_iova_range *iommufd_iova_ranges(struct iommu *iommu,
VFIO_ASSERT_EQ(errno, EMSGSIZE);
VFIO_ASSERT_GT(query.num_iovas, 0);
- ranges = calloc(query.num_iovas, sizeof(*ranges));
- VFIO_ASSERT_NOT_NULL(ranges);
+ ranges = calloc_assert(query.num_iovas, sizeof(*ranges));
query.allowed_iovas = (uintptr_t)ranges;
@@ -424,8 +421,7 @@ struct iommu *iommu_init(const char *iommu_mode)
struct iommu *iommu;
int version;
- iommu = calloc(1, sizeof(*iommu));
- VFIO_ASSERT_NOT_NULL(iommu);
+ iommu = calloc_assert(1, sizeof(*iommu));
INIT_LIST_HEAD(&iommu->dma_regions);
diff --git a/tools/testing/selftests/vfio/lib/iova_allocator.c b/tools/testing/selftests/vfio/lib/iova_allocator.c
index 8c1cc86b70cd..4a660f636f49 100644
--- a/tools/testing/selftests/vfio/lib/iova_allocator.c
+++ b/tools/testing/selftests/vfio/lib/iova_allocator.c
@@ -29,8 +29,7 @@ struct iova_allocator *iova_allocator_init(struct iommu *iommu)
ranges = iommu_iova_ranges(iommu, &nranges);
VFIO_ASSERT_NOT_NULL(ranges);
- allocator = malloc(sizeof(*allocator));
- VFIO_ASSERT_NOT_NULL(allocator);
+ allocator = malloc_assert(sizeof(*allocator));
*allocator = (struct iova_allocator){
.ranges = ranges,
@@ -90,4 +89,3 @@ next_range:
allocator->range_offset = 0;
}
}
-
diff --git a/tools/testing/selftests/vfio/lib/libvfio.mk b/tools/testing/selftests/vfio/lib/libvfio.mk
index 67942b085068..82132b648744 100644
--- a/tools/testing/selftests/vfio/lib/libvfio.mk
+++ b/tools/testing/selftests/vfio/lib/libvfio.mk
@@ -15,6 +15,8 @@ LIBVFIO_C += drivers/ioat/ioat.c
LIBVFIO_C += drivers/dsa/dsa.c
endif
+LIBVFIO_C += drivers/nv_falcon/nv_falcon.c
+
LIBVFIO_OUTPUT := $(OUTPUT)/libvfio
LIBVFIO_O := $(patsubst %.c, $(LIBVFIO_OUTPUT)/%.o, $(LIBVFIO_C))
diff --git a/tools/testing/selftests/vfio/lib/sysfs.c b/tools/testing/selftests/vfio/lib/sysfs.c
index 11415448b2e2..98a46a2543cd 100644
--- a/tools/testing/selftests/vfio/lib/sysfs.c
+++ b/tools/testing/selftests/vfio/lib/sysfs.c
@@ -107,8 +107,7 @@ char *sysfs_sriov_vf_bdf_get(const char *pf_bdf, int i)
char *out_vf_bdf;
/* Fit "0000:00:00.0" */
- out_vf_bdf = calloc(16, sizeof(char));
- VFIO_ASSERT_NOT_NULL(out_vf_bdf);
+ out_vf_bdf = calloc_assert(16, sizeof(char));
snprintf_assert(path, PATH_MAX, "/sys/bus/pci/devices/%s/virtfn%d", pf_bdf, i);
readlink_base(path, "%s", out_vf_bdf);
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_device.c b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
index 94dc5fcecbeb..28868343f2ab 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_device.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_device.c
@@ -30,13 +30,11 @@
static void vfio_pci_irq_set(struct vfio_pci_device *device,
u32 index, u32 vector, u32 count, int *fds)
{
- u8 buf[sizeof(struct vfio_irq_set) + sizeof(int) * count];
- struct vfio_irq_set *irq = (void *)&buf;
- int *irq_fds = (void *)&irq->data;
+ size_t argsz = sizeof(struct vfio_irq_set) + sizeof(int) * count;
+ struct vfio_irq_set *irq;
- memset(buf, 0, sizeof(buf));
-
- irq->argsz = sizeof(buf);
+ irq = calloc_assert(1, argsz);
+ irq->argsz = argsz;
irq->flags = VFIO_IRQ_SET_ACTION_TRIGGER;
irq->index = index;
irq->start = vector;
@@ -44,12 +42,13 @@ static void vfio_pci_irq_set(struct vfio_pci_device *device,
if (count) {
irq->flags |= VFIO_IRQ_SET_DATA_EVENTFD;
- memcpy(irq_fds, fds, sizeof(int) * count);
+ memcpy(irq->data, fds, sizeof(int) * count);
} else {
irq->flags |= VFIO_IRQ_SET_DATA_NONE;
}
ioctl_assert(device->fd, VFIO_DEVICE_SET_IRQS, irq);
+ free(irq);
}
void vfio_pci_irq_trigger(struct vfio_pci_device *device, u32 index, u32 vector)
@@ -118,15 +117,20 @@ static void vfio_pci_irq_get(struct vfio_pci_device *device, u32 index,
static int vfio_device_feature_ioctl(int fd, u32 flags, void *data,
size_t data_size)
{
- u8 buffer[sizeof(struct vfio_device_feature) + data_size] = {};
- struct vfio_device_feature *feature = (void *)buffer;
+ size_t argsz = sizeof(struct vfio_device_feature) + data_size;
+ struct vfio_device_feature *feature;
+ int ret;
+ feature = calloc_assert(1, argsz);
memcpy(feature->data, data, data_size);
- feature->argsz = sizeof(buffer);
+ feature->argsz = argsz;
feature->flags = flags;
- return ioctl(fd, VFIO_DEVICE_FEATURE, feature);
+ ret = ioctl(fd, VFIO_DEVICE_FEATURE, feature);
+ free(feature);
+
+ return ret;
}
static void vfio_device_feature_set(int fd, u16 feature, void *data, size_t data_size)
@@ -343,8 +347,7 @@ const char *vfio_pci_get_cdev_path(const char *bdf)
char *cdev_path;
DIR *dir;
- cdev_path = calloc(PATH_MAX, 1);
- VFIO_ASSERT_NOT_NULL(cdev_path);
+ cdev_path = calloc_assert(PATH_MAX, 1);
snprintf_assert(dir_path, sizeof(dir_path), "/sys/bus/pci/devices/%s/vfio-dev/", bdf);
@@ -425,8 +428,7 @@ struct vfio_pci_device *vfio_pci_device_alloc(const char *bdf, struct iommu *iom
{
struct vfio_pci_device *device;
- device = calloc(1, sizeof(*device));
- VFIO_ASSERT_NOT_NULL(device);
+ device = calloc_assert(1, sizeof(*device));
VFIO_ASSERT_NOT_NULL(iommu);
device->iommu = iommu;
diff --git a/tools/testing/selftests/vfio/lib/vfio_pci_driver.c b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
index 6827f4a6febe..153bf4a7a19f 100644
--- a/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
+++ b/tools/testing/selftests/vfio/lib/vfio_pci_driver.c
@@ -7,11 +7,14 @@ extern struct vfio_pci_driver_ops dsa_ops;
extern struct vfio_pci_driver_ops ioat_ops;
#endif
+extern struct vfio_pci_driver_ops nv_falcon_ops;
+
static struct vfio_pci_driver_ops *driver_ops[] = {
#ifdef __x86_64__
&dsa_ops,
&ioat_ops,
#endif
+ &nv_falcon_ops,
};
void vfio_pci_driver_probe(struct vfio_pci_device *device)
@@ -106,7 +109,21 @@ int vfio_pci_driver_memcpy_wait(struct vfio_pci_device *device)
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);
+ struct vfio_pci_driver *driver = &device->driver;
+ u64 offset = 0;
+
+ while (offset < size) {
+ u64 chunk = min(size - offset, driver->max_memcpy_size);
+ int ret;
+
+ vfio_pci_driver_memcpy_start(device, src + offset,
+ dst + offset, chunk, 1);
+ ret = vfio_pci_driver_memcpy_wait(device);
+ if (ret)
+ return ret;
+
+ offset += chunk;
+ }
- return vfio_pci_driver_memcpy_wait(device);
+ return 0;
}
diff --git a/tools/testing/selftests/vfio/vfio_pci_device_init_perf_test.c b/tools/testing/selftests/vfio/vfio_pci_device_init_perf_test.c
index 33b0c31fe2ed..e1a54e153cd3 100644
--- a/tools/testing/selftests/vfio/vfio_pci_device_init_perf_test.c
+++ b/tools/testing/selftests/vfio/vfio_pci_device_init_perf_test.c
@@ -45,8 +45,8 @@ FIXTURE_SETUP(vfio_pci_device_init_perf_test)
int i;
self->iommu = iommu_init(variant->iommu_mode);
- self->threads = calloc(nr_devices, sizeof(self->threads[0]));
- self->thread_args = calloc(nr_devices, sizeof(self->thread_args[0]));
+ self->threads = calloc_assert(nr_devices, sizeof(self->threads[0]));
+ self->thread_args = calloc_assert(nr_devices, sizeof(self->thread_args[0]));
pthread_barrier_init(&self->barrier, NULL, nr_devices);
diff --git a/tools/testing/selftests/vfio/vfio_pci_driver_test.c b/tools/testing/selftests/vfio/vfio_pci_driver_test.c
index afa0480ddd9b..761bf117d624 100644
--- a/tools/testing/selftests/vfio/vfio_pci_driver_test.c
+++ b/tools/testing/selftests/vfio/vfio_pci_driver_test.c
@@ -11,11 +11,18 @@
static const char *device_bdf;
-#define ASSERT_NO_MSI(_eventfd) do { \
- u64 __value; \
- \
- ASSERT_EQ(-1, read(_eventfd, &__value, 8)); \
- ASSERT_EQ(EAGAIN, errno); \
+#define fcntl_set_msi_nonblock(_self) do { \
+ if (_self->device->driver.ops->send_msi) \
+ fcntl_set_nonblock(_self->msi_fd); \
+} while (0)
+
+#define ASSERT_NO_MSI(_self) do { \
+ u64 __value; \
+ \
+ if (!_self->device->driver.ops->send_msi) \
+ break; \
+ ASSERT_EQ(-1, read(_self->msi_fd, &__value, 8)); \
+ ASSERT_EQ(EAGAIN, errno); \
} while (0)
static void region_setup(struct iommu *iommu,
@@ -89,12 +96,12 @@ FIXTURE_SETUP(vfio_pci_driver_test)
self->msi_fd = self->device->msi_eventfds[driver->msi];
/*
- * Use the maximum size supported by the device for memcpy operations,
- * slimmed down to fit into the memcpy region (divided by 2 so src and
- * dst regions do not overlap).
+ * Use 4x the driver's max_memcpy_size to exercise the chunking
+ * logic in vfio_pci_driver_memcpy(). Cap to half the memcpy
+ * region so src and dst do not overlap.
*/
- self->size = self->device->driver.max_memcpy_size;
- self->size = min(self->size, self->memcpy_region.size / 2);
+ self->size = min_t(u64, driver->max_memcpy_size * 4,
+ self->memcpy_region.size / 2);
self->src = self->memcpy_region.vaddr;
self->dst = self->src + self->size;
@@ -129,7 +136,7 @@ TEST_F(vfio_pci_driver_test, init_remove)
TEST_F(vfio_pci_driver_test, memcpy_success)
{
- fcntl_set_nonblock(self->msi_fd);
+ fcntl_set_msi_nonblock(self);
memset(self->src, 'x', self->size);
memset(self->dst, 'y', self->size);
@@ -140,12 +147,12 @@ TEST_F(vfio_pci_driver_test, memcpy_success)
self->size));
ASSERT_EQ(0, memcmp(self->src, self->dst, self->size));
- ASSERT_NO_MSI(self->msi_fd);
+ ASSERT_NO_MSI(self);
}
TEST_F(vfio_pci_driver_test, memcpy_from_unmapped_iova)
{
- fcntl_set_nonblock(self->msi_fd);
+ fcntl_set_msi_nonblock(self);
/*
* Ignore the return value since not all devices will detect and report
@@ -154,12 +161,12 @@ TEST_F(vfio_pci_driver_test, memcpy_from_unmapped_iova)
vfio_pci_driver_memcpy(self->device, self->unmapped_iova,
self->dst_iova, self->size);
- ASSERT_NO_MSI(self->msi_fd);
+ ASSERT_NO_MSI(self);
}
TEST_F(vfio_pci_driver_test, memcpy_to_unmapped_iova)
{
- fcntl_set_nonblock(self->msi_fd);
+ fcntl_set_msi_nonblock(self);
/*
* Ignore the return value since not all devices will detect and report
@@ -168,13 +175,16 @@ TEST_F(vfio_pci_driver_test, memcpy_to_unmapped_iova)
vfio_pci_driver_memcpy(self->device, self->src_iova,
self->unmapped_iova, self->size);
- ASSERT_NO_MSI(self->msi_fd);
+ ASSERT_NO_MSI(self);
}
TEST_F(vfio_pci_driver_test, send_msi)
{
u64 value;
+ if (!self->device->driver.ops->send_msi)
+ SKIP(return, "Driver does not support send_msi()\n");
+
vfio_pci_driver_send_msi(self->device);
ASSERT_EQ(8, read(self->msi_fd, &value, 8));
ASSERT_EQ(1, value);
@@ -201,6 +211,9 @@ TEST_F(vfio_pci_driver_test, mix_and_match)
self->dst_iova,
self->size);
+ if (!self->device->driver.ops->send_msi)
+ continue;
+
vfio_pci_driver_send_msi(self->device);
ASSERT_EQ(8, read(self->msi_fd, &value, 8));
ASSERT_EQ(1, value);
@@ -211,9 +224,10 @@ TEST_F_TIMEOUT(vfio_pci_driver_test, memcpy_storm, 60)
{
struct vfio_pci_driver *driver = &self->device->driver;
u64 total_size;
+ u64 size;
u64 count;
- fcntl_set_nonblock(self->msi_fd);
+ fcntl_set_msi_nonblock(self);
/*
* Perform up to 250GiB worth of DMA reads and writes across several
@@ -221,16 +235,17 @@ TEST_F_TIMEOUT(vfio_pci_driver_test, memcpy_storm, 60)
* will take too long.
*/
total_size = 250UL * SZ_1G;
- count = min(total_size / self->size, driver->max_memcpy_count);
+ size = min(driver->max_memcpy_size, self->memcpy_region.size / 2);
+ count = min(total_size / size, driver->max_memcpy_count);
- printf("Kicking off %lu memcpys of size 0x%lx\n", count, self->size);
+ printf("Kicking off %lu memcpys of size 0x%lx\n", count, size);
vfio_pci_driver_memcpy_start(self->device,
self->src_iova,
self->dst_iova,
- self->size, count);
+ size, count);
ASSERT_EQ(0, vfio_pci_driver_memcpy_wait(self->device));
- ASSERT_NO_MSI(self->msi_fd);
+ ASSERT_NO_MSI(self);
}
static bool device_has_selftests_driver(const char *bdf)