From 6aadb593750736f239fd54a0ccef4b3834c08fc4 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 18:01:55 -0400 Subject: kernel: GSP-RM msgq emulation + TU104 hardware tables (GET_GSP_STATIC_INFO, GSP_RM_CONTROL) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the GSP-RM reply content layer for the msgq RPC channel: - kernel/gsp_rm_emulation.c/h: GSP-RM msgq emulation engine - Queue chain discovery (libos args → RMARGS → GSP_ARGUMENTS_CACHED) - Status queue link + GSP_INIT_DONE emission - Command queue polling + RPC dispatch - SET_GUEST_SYSTEM_INFO version negotiation (VGX 41.12) - GET_GSP_STATIC_INFO(65) with 1656-byte GspStaticConfigInfo - GSP_RM_CONTROL(76) sub-command dispatch: - FIFO_GET_DEVICE_INFO_TABLE (0x20801112) - INTERNAL_INTR_GET_KERNEL_TABLE (0x20800a5c) - GPU_GET_CONSTRUCTED_FALCON_INFO (0x208001b0) - INTERNAL_GET_DEVICE_INFO_TABLE (0x20800a40) - kernel/gsp_rm_tu104.h: TU104 hardware tables - 12-entry FIFO engine table (GR0, CE0-4, NVDEC0-1, NVENC0, SEC2, NVJPG, SW) - 8-entry interrupt table (from intr_phys_tu102.c) - 1-entry constructed falcon table (FBFLCN) - 11-entry internal device info table - GspStaticConfigInfo layout (1656 bytes, ABI-verified offsets) - Data from NVIDIA driver material (ext-tree) + hwref/turing/tu104 - tools/rmq/: Userspace RM API client (oracle for ground-truth tables) KNOWN ISSUE: gsp_rm_send crashes (fortify_panic) on multi-element messages when reply data exceeds one 4096-byte queue element. Affects cmd 0x20800a41 (paramsSize=8204). Multi-element splitting is the next fix. --- tools/rmq/Makefile | 19 +++++ tools/rmq/kmod/Makefile | 7 ++ tools/rmq/kmod/wpr2_reset_kmod.c | 90 +++++++++++++++++++++++ tools/rmq/rmq.c | 154 +++++++++++++++++++++++++++++++++++++++ tools/rmq/wpr2_reset | Bin 0 -> 16360 bytes tools/rmq/wpr2_reset.c | 50 +++++++++++++ 6 files changed, 320 insertions(+) create mode 100644 tools/rmq/Makefile create mode 100644 tools/rmq/kmod/Makefile create mode 100644 tools/rmq/kmod/wpr2_reset_kmod.c create mode 100644 tools/rmq/rmq.c create mode 100755 tools/rmq/wpr2_reset create mode 100644 tools/rmq/wpr2_reset.c (limited to 'tools') diff --git a/tools/rmq/Makefile b/tools/rmq/Makefile new file mode 100644 index 0000000..f58e4d9 --- /dev/null +++ b/tools/rmq/Makefile @@ -0,0 +1,19 @@ +# rmq - RM query tool (userspace RM API client) +# +# Queries the host NVIDIA Resource Manager via /dev/nvidia0 ioctls. +# Used as an "oracle" to extract ground-truth static GPU data (device info +# tables, static config) from the host's real GSP-RM for the GSP-RM emulation +# layer (kernel/gsp_rm_emulation.c). + +CC ?= gcc +CFLAGS ?= -O2 -Wall + +all: rmq + +rmq: rmq.c + $(CC) $(CFLAGS) -o $@ $< + +clean: + rm -f rmq + +.PHONY: all clean diff --git a/tools/rmq/kmod/Makefile b/tools/rmq/kmod/Makefile new file mode 100644 index 0000000..2fde4ef --- /dev/null +++ b/tools/rmq/kmod/Makefile @@ -0,0 +1,7 @@ +obj-m += wpr2_reset_kmod.o + +all: + make -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) modules + +clean: + make -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) clean diff --git a/tools/rmq/kmod/wpr2_reset_kmod.c b/tools/rmq/kmod/wpr2_reset_kmod.c new file mode 100644 index 0000000..523fac7 --- /dev/null +++ b/tools/rmq/kmod/wpr2_reset_kmod.c @@ -0,0 +1,90 @@ +/* wpr2_reset_kmod - kernel module that clears GPU WPR2 registers via BAR0 ioremap. + * + * When nvidia.ko is unloaded without a clean GSP teardown, GSP firmware stays + * resident in WPR2 (a write-protected FB region described by + * PFB_PRI_MMU_WPR2_ADDR_LO/HI). Every subsequent RmInitAdapter then fails with + * "unexpected WPR2 already up" and FLR does not help (GSP survives warm resets). + * + * This module ioremaps GPU BAR0 and pokes WPR2_ADDR_LO/HI to zero so the next + * driver load takes the normal (clean) GSP boot path. Equivalent to the effect + * of FWSEC-SB + Booter Unload teardown, minus running the ucodes. + * + * Usage: sudo insmod wpr2_reset_kmod.ko [pci_bdf=0000:01:00.0] + * result printed via pr_info (check dmesg) + */ +#include +#include +#include + +#define NV_PFB_PRI_MMU_WPR2_ADDR_LO 0x1fa824 +#define NV_PFB_PRI_MMU_WPR2_ADDR_HI 0x1fa828 + +static char *pci_bdf = "0000:01:00.0"; +module_param(pci_bdf, charp, 0444); +MODULE_PARM_DESC(pci_bdf, "PCI BDF of the GPU (default 0000:01:00.0)"); + +static int __init wpr2_reset_init(void) +{ + struct pci_dev *pdev = NULL; + unsigned int bus, dev, fn; + void __iomem *bar0; + u32 lo_before, hi_before, lo_after, hi_after; + + if (sscanf(pci_bdf, "0000:%x:%x.%u", &bus, &dev, &fn) != 3) { + pr_err("wpr2_reset: bad pci_bdf %s\n", pci_bdf); + return -EINVAL; + } + pdev = pci_get_domain_bus_and_slot(0, bus, PCI_DEVFN(dev, fn)); + if (!pdev) { + pr_err("wpr2_reset: device %s not found\n", pci_bdf); + return -ENODEV; + } + if (!pci_resource_start(pdev, 0)) { + pr_err("wpr2_reset: BAR0 not assigned\n"); + pci_dev_put(pdev); + return -ENODEV; + } + + if (pci_enable_device(pdev)) { + pr_err("wpr2_reset: pci_enable_device failed\n"); + pci_dev_put(pdev); + return -EIO; + } + pci_set_master(pdev); + bar0 = ioremap_wc(pci_resource_start(pdev, 0), pci_resource_len(pdev, 0)); + if (!bar0) { + pr_err("wpr2_reset: ioremap failed\n"); + pci_dev_put(pdev); + return -ENOMEM; + } + + lo_before = readl(bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_LO); + hi_before = readl(bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_HI); + pr_info("wpr2_reset: before LO=0x%08x HI=0x%08x\n", lo_before, hi_before); + + writel(0, bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_LO); + writel(0, bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_HI); + + lo_after = readl(bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_LO); + hi_after = readl(bar0 + NV_PFB_PRI_MMU_WPR2_ADDR_HI); + pr_info("wpr2_reset: after LO=0x%08x HI=0x%08x\n", lo_after, hi_after); + + if (hi_after == 0) + pr_info("wpr2_reset: WPR2 cleared, clean GSP boot now possible\n"); + else + pr_info("wpr2_reset: write dropped (PLM?) - WPR2 still up\n"); + + iounmap(bar0); + pci_dev_put(pdev); + return hi_after ? -EINVAL : 0; +} + +static void __exit wpr2_reset_exit(void) +{ + pr_info("wpr2_reset: unloaded\n"); +} + +module_init(wpr2_reset_init); +module_exit(wpr2_reset_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Clear GPU WPR2 registers to unstick GSP boot"); diff --git a/tools/rmq/rmq.c b/tools/rmq/rmq.c new file mode 100644 index 0000000..97d1bed --- /dev/null +++ b/tools/rmq/rmq.c @@ -0,0 +1,154 @@ +/* rmq - query host NVIDIA RM via /dev/nvidia0 ioctls (oracle for TU104 static data) */ +#include +#include +#include +#include +#include +#include +#include + +typedef uint32_t NvU32; +typedef uint16_t NvU16; +typedef uint8_t NvU8; +typedef uint64_t NvU64; +typedef uint32_t NvHandle; +typedef uint32_t NvV32; +typedef uint64_t NvP64; + +#define NV_ESC_RM_ALLOC 0x2B +#define NV_ESC_RM_CONTROL 0x2A +#define NV01_ROOT 0x0 +#define NV01_DEVICE_0 0x80 +#define NV20_SUBDEVICE_0 0x2080 + +typedef struct { + NvHandle hRoot; + NvHandle hObjectParent; + NvHandle hObjectNew; + NvV32 hClass; + NvP64 pAllocParms __attribute__((aligned(8))); + NvU32 paramsSize; + NvV32 status; +} NVOS21_PARAMETERS; + +typedef struct { + NvHandle hClient; + NvHandle hObject; + NvV32 cmd; + NvU32 flags; + NvP64 params __attribute__((aligned(8))); + NvU32 paramsSize; + NvV32 status; +} NVOS54_PARAMETERS; + +static int fd; + +static NvU32 rm_alloc(NvHandle hRoot, NvHandle hParent, NvU32 hClass, NvHandle *pOut) +{ + NVOS21_PARAMETERS p = {0}; + p.hRoot = hRoot; p.hObjectParent = hParent; p.hClass = hClass; + if (ioctl(fd, NV_ESC_RM_ALLOC, &p) < 0) { perror("alloc ioctl"); return 1; } + if (p.status) { fprintf(stderr, "alloc class 0x%x failed: 0x%x\n", hClass, p.status); return p.status; } + *pOut = p.hObjectNew; + return 0; +} + +static NvU32 rm_control(NvHandle hClient, NvHandle hObject, NvU32 cmd, void *params, NvU32 size) +{ + NVOS54_PARAMETERS p = {0}; + p.hClient = hClient; p.hObject = hObject; p.cmd = cmd; + p.params = (NvP64)params; p.paramsSize = size; + if (ioctl(fd, NV_ESC_RM_CONTROL, &p) < 0) { perror("control ioctl"); return 1; } + return p.status; +} + +/* NV2080_CTRL_FIFO_DEVICE_ENTRY (16 u32 engineData + 2 pbdmaIds + 2 pbdmaFaultIds + numPbdmas + name[16]) */ +typedef struct { + NvU32 engineData[16]; + NvU32 pbdmaIds[2]; + NvU32 pbdmaFaultIds[2]; + NvU32 numPbdmas; + char engineName[16]; +} FIFO_DEVICE_ENTRY; + +typedef struct { + NvU32 baseIndex; + NvU32 numEntries; + NvU32 bMore; + FIFO_DEVICE_ENTRY entries[32]; +} FIFO_GET_DEVICE_INFO_TABLE_PARAMS; + +typedef struct { + NvU32 faultId, instanceId, typeEnum, resetId, devicePriBase, isEngine; + NvU32 rlEngId, runlistPriBase, groupId, ginTargetId, deviceBroadcastPriBase, groupLocalInstanceId; +} INTERNAL_DEVICE_INFO; + +typedef struct { + NvU32 numEntries; + INTERNAL_DEVICE_INFO deviceInfoTable[512]; +} INTERNAL_GET_DEVICE_INFO_TABLE_PARAMS; + +int main(int argc, char **argv) +{ + NvHandle hClient = 0, hDevice = 0, hSubdevice = 0; + NvU32 st; + + fd = open("/dev/nvidia0", O_RDWR); + if (fd < 0) { perror("open /dev/nvidia0"); return 1; } + + if ((st = rm_alloc(0, 0, NV01_ROOT, &hClient))) return 1; + if ((st = rm_alloc(hClient, hClient, NV01_DEVICE_0, &hDevice))) return 1; + if ((st = rm_alloc(hClient, hDevice, NV20_SUBDEVICE_0, &hSubdevice))) return 1; + printf("hClient=0x%x hDevice=0x%x hSubdevice=0x%x\n", hClient, hDevice, hSubdevice); + + /* FIFO_GET_DEVICE_INFO_TABLE (0x20801112) */ + { + FIFO_GET_DEVICE_INFO_TABLE_PARAMS *t = calloc(1, sizeof(*t)); + NvU32 base = 0; + printf("\n=== FIFO_GET_DEVICE_INFO_TABLE (0x20801112) ===\n"); + do { + memset(t, 0, sizeof(*t)); + t->baseIndex = base; + st = rm_control(hClient, hSubdevice, 0x20801112, t, sizeof(*t)); + if (st) { printf("control failed: 0x%x\n", st); break; } + NvU32 i; + for (i = 0; i < t->numEntries; i++) { + FIFO_DEVICE_ENTRY *e = &t->entries[i]; + printf("[%3u] name=%-16s numPbdmas=%u pbdmaIds=[%u,%u] faultIds=[%u,%u] engineData=[", + base + i, e->engineName, e->numPbdmas, + e->pbdmaIds[0], e->pbdmaIds[1], + e->pbdmaFaultIds[0], e->pbdmaFaultIds[1]); + int j; + for (j = 0; j < 16; j++) printf("%s%u", j ? "," : "", e->engineData[j]); + printf("]\n"); + } + base += t->numEntries; + if (!t->bMore) break; + } while (1); + free(t); + } + + /* INTERNAL_GET_DEVICE_INFO_TABLE (0x20800a40) */ + { + INTERNAL_GET_DEVICE_INFO_TABLE_PARAMS *t = calloc(1, sizeof(*t)); + printf("\n=== INTERNAL_GET_DEVICE_INFO_TABLE (0x20800a40) ===\n"); + st = rm_control(hClient, hSubdevice, 0x20800a40, t, sizeof(*t)); + if (st) { + printf("control failed: 0x%x (expected from userspace if internal-only)\n", st); + } else { + NvU32 i; + printf("numEntries=%u\n", t->numEntries); + for (i = 0; i < t->numEntries; i++) { + INTERNAL_DEVICE_INFO *e = &t->deviceInfoTable[i]; + printf("[%3u] faultId=%u instanceId=%u typeEnum=%u resetId=%u devicePriBase=0x%x isEngine=%u rlEngId=%u runlistPriBase=0x%x groupId=%u ginTargetId=%u deviceBroadcastPriBase=0x%x groupLocalInstanceId=%u\n", + i, e->faultId, e->instanceId, e->typeEnum, e->resetId, + e->devicePriBase, e->isEngine, e->rlEngId, e->runlistPriBase, + e->groupId, e->ginTargetId, e->deviceBroadcastPriBase, + e->groupLocalInstanceId); + } + } + free(t); + } + + return 0; +} diff --git a/tools/rmq/wpr2_reset b/tools/rmq/wpr2_reset new file mode 100755 index 0000000..08d4f55 Binary files /dev/null and b/tools/rmq/wpr2_reset differ diff --git a/tools/rmq/wpr2_reset.c b/tools/rmq/wpr2_reset.c new file mode 100644 index 0000000..4435843 --- /dev/null +++ b/tools/rmq/wpr2_reset.c @@ -0,0 +1,50 @@ +/* wpr2_reset - clear GPU WPR2 registers via BAR0 MMIO to unstick GSP boot. + * + * When nvidia.ko is unloaded, GSP firmware can remain resident in WPR2 + * (write-protected region 2 in FB). The next RmInitAdapter then fails with + * "unexpected WPR2 already up". FLR does not clear WPR2; only a cold (power) + * reset does. This tool pokes WPR2_ADDR_LO/HI to zero directly through + * /sys/bus/pci/devices//resource0 while no driver is bound. + * + * Usage: sudo ./wpr2_reset [pci.bdf] (default 0000:01:00.0) + */ +#include +#include +#include +#include +#include +#include +#include + +#define NV_PFB_PRI_MMU_WPR2_ADDR_LO 0x1fa824 +#define NV_PFB_PRI_MMU_WPR2_ADDR_HI 0x1fa828 + +int main(int argc, char **argv) +{ + const char *bdf = argc > 1 ? argv[1] : "0000:01:00.0"; + char path[256]; + snprintf(path, sizeof(path), "/sys/bus/pci/devices/%s/resource0", bdf); + + int fd = open(path, O_RDWR | O_SYNC); + if (fd < 0) { perror(path); return 1; } + void *bar = mmap(NULL, 0x200000, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + if (bar == MAP_FAILED) { perror("mmap"); return 1; } + + volatile uint32_t *lo = (volatile uint32_t *)((char *)bar + NV_PFB_PRI_MMU_WPR2_ADDR_LO); + volatile uint32_t *hi = (volatile uint32_t *)((char *)bar + NV_PFB_PRI_MMU_WPR2_ADDR_HI); + + printf("before: WPR2_ADDR_LO=0x%08x WPR2_ADDR_HI=0x%08x\n", *lo, *hi); + *lo = 0; + *hi = 0; + /* read back */ + printf("after : WPR2_ADDR_LO=0x%08x WPR2_ADDR_HI=0x%08x\n", *lo, *hi); + + if (*hi == 0) + printf("WPR2 cleared - driver load should now boot GSP cleanly\n"); + else + printf("write dropped (PLM?) - WPR2 still up\n"); + + munmap(bar, 0x200000); + close(fd); + return *hi ? 1 : 0; +} -- cgit v1.2.3