summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 18:01:55 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-21 18:01:55 -0400
commit6aadb593750736f239fd54a0ccef4b3834c08fc4 (patch)
tree5b3d39d82a09cdfd3ca8022cd9c0f83040f23229
parentc8249854284c3e8181587b96e3c5b4177d705e0c (diff)
downloadopen-iov-6aadb593750736f239fd54a0ccef4b3834c08fc4.tar.gz
open-iov-6aadb593750736f239fd54a0ccef4b3834c08fc4.zip
kernel: GSP-RM msgq emulation + TU104 hardware tables (GET_GSP_STATIC_INFO, GSP_RM_CONTROL)
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.
-rw-r--r--.gitignore7
-rw-r--r--kernel/Makefile1
-rw-r--r--kernel/bar_emulation.c27
-rw-r--r--kernel/gsp_emulation.c135
-rw-r--r--kernel/gsp_emulation.h37
-rw-r--r--kernel/gsp_rm_emulation.c1022
-rw-r--r--kernel/gsp_rm_emulation.h151
-rw-r--r--kernel/gsp_rm_tu104.h694
-rw-r--r--tools/rmq/Makefile19
-rw-r--r--tools/rmq/kmod/Makefile7
-rw-r--r--tools/rmq/kmod/wpr2_reset_kmod.c90
-rw-r--r--tools/rmq/rmq.c154
-rwxr-xr-xtools/rmq/wpr2_resetbin0 -> 16360 bytes
-rw-r--r--tools/rmq/wpr2_reset.c50
14 files changed, 2331 insertions, 63 deletions
diff --git a/.gitignore b/.gitignore
index 9d8fd7e..c3a7629 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,6 +45,13 @@ userspace/tests/*_test
# ----------------------------------------------------------------------------
/nvidia-open-gpu-kernel-modules/
+# NVIDIA open-gpu-kernel-modules @ 570.144 (local checkout of the open kernel modules
+# driver's exact wire protocol - used by kernel/gsp_rm_emulation.c)
+/nvidia-570.144-reference/
+
+# rmq build artifact (source stays)
+tools/rmq/rmq
+
# ----------------------------------------------------------------------------
# GSP firmware blobs and decompiled GSP output
# (large, license-restricted NVIDIA binaries; analysis scripts/docs stay)
diff --git a/kernel/Makefile b/kernel/Makefile
index 98adfd4..83eab9b 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -6,6 +6,7 @@ open-iov-vfio-y := \
bar_emulation.o \
gsp_comm.o \
gsp_emulation.o \
+ gsp_rm_emulation.o \
gsp_rpc_client.o \
gsp_rpc_integration.o \
memory.o \
diff --git a/kernel/bar_emulation.c b/kernel/bar_emulation.c
index e120b3c..eaa006c 100644
--- a/kernel/bar_emulation.c
+++ b/kernel/bar_emulation.c
@@ -253,6 +253,33 @@ int nv_vgpu_bar_emul_init(struct nv_vgpu_device *vgpu)
*(u32 *)((u8 *)ctx->bar0_shadow + 0x110100) = 0x00000010;
/*
+ * More post-GFW-boot state the driver validates before/while booting
+ * GSP-RM:
+ * 0x1100f4 GSP falcon HWCFG2 bit10 RISCV = ENABLE. Checked by
+ * kflcnIsRiscvCpuEnabled in kgspPrepareForBootstrap;
+ * without it probe dies with "RISC-V core is not enabled".
+ * 0x009430 NV_PTIMER_TIME_PRIV_LEVEL_MASK bit4
+ * WRITE_PROTECTION_LEVEL0 = ENABLE. FWSEC lowers PLMs
+ * after GFW boot; driver asserts "Write to PTIMER
+ * attempted even though Level 0 PLM is disabled" when
+ * setting the GPU clock otherwise.
+ */
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x1100f4) = 0x00000400;
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x009430) = 0x00000010;
+
+ /*
+ * 0x100ce0 NV_PFB_PRI_MMU_LOCAL_MEMORY_RANGE - usable FB size.
+ * Encoded as fbSize = LOWER_MAG << (LOWER_SCALE + 20). Set to
+ * 256MB (matches our BAR1): mag=1, scale=8 -> 1 << 28. On real
+ * hardware VBIOS devinit programs this after GFW boot; the guest
+ * driver's kgspPopulateWprMeta reads it via kmemsysGetUsableFbSize
+ * and uses it to compute the FRTS offset (fbSize - reservedFromEnd).
+ * With it 0, the FRTS offset underflows and FWSEC verification
+ * fails with "WPR2 initialized at an unexpected location".
+ */
+ *(u32 *)((u8 *)ctx->bar0_shadow + 0x100ce0) = 0x00000018;
+
+ /*
* Serve a valid VBIOS at the PROM window (BAR0+0x300000).
*
* The guest driver's physical GSP boot (kgspInitRm -> TU102 path)
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index 81c319f..f96e521 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -21,6 +21,7 @@
#include "nvidia-vgpu-vfio.h"
#include "gsp_emulation.h"
+#include "gsp_rm_emulation.h"
#include "gsp_rpc_client.h"
#include "bar_emulation.h"
#include "irq.h"
@@ -95,6 +96,9 @@ static const struct {
{ 0x1fa824, "WPR2_ADDR_LO" },
{ 0x1fa828, "WPR2_ADDR_HI" },
{ 0x001438, "VBIOS_SCRATCH_0E" },
+ { 0x840040, "SEC2_MAILBOX0" },
+ { 0x840100, "SEC2_CPUCTL" },
+ { 0x8403c0, "SEC2_ENGINE" },
};
static void gsp_emu_poll_falcon_watch(struct gsp_emu_context *ctx)
@@ -124,19 +128,8 @@ static void gsp_emu_poll_falcon_watch(struct gsp_emu_context *ctx)
* Physical GSP Boot Emulation (FWSEC / Booter falcon auto-complete)
* ============================================================ */
-/* BAR0 shadow offsets used by the guest's physical GSP boot */
-#define GSP_EMU_REG_GSP_CPUCTL 0x110100 /* bit1 STARTCPU, bit4 HALTED */
-#define GSP_EMU_REG_GSP_ENGINE 0x1103c0 /* bit0 RESET */
-#define GSP_EMU_REG_GSP_MAILBOX0 0x110040
-#define GSP_EMU_REG_VBIOS_SCRATCH_0E 0x001438 /* FRTS err code (0=NONE) */
-#define GSP_EMU_REG_WPR2_ADDR_LO 0x1fa824
-#define GSP_EMU_REG_WPR2_ADDR_HI 0x1fa828
-#define GSP_EMU_REG_RISCV_STATUS 0x110240 /* bit0 ACTIVE (NV_PRISCV_RISCV
- CORE_SWITCH_RISCV_STATUS at
- GSP riscv base 0x110000) */
-
-#define GSP_EMU_CPUCTL_STARTCPU 0x2
-#define GSP_EMU_CPUCTL_HALTED 0x10
+/* Register offsets + CPUCTL bits live in gsp_emulation.h (shared with
+ * gsp_rm_emulation.c), as are gsp_emu_shadow_read32/write32. */
enum gsp_emu_boot_stage {
GSP_BOOT_STAGE_IDLE = 0, /* waiting for first kick (FWSEC) */
@@ -144,17 +137,6 @@ enum gsp_emu_boot_stage {
GSP_BOOT_STAGE_BOOTER_DONE, /* Booter Load completed */
};
-static inline void gsp_emu_shadow_write32(struct gsp_emu_context *ctx,
- u32 offset, u32 val)
-{
- writel(val, ctx->bar0_vaddr + offset);
-}
-
-static inline u32 gsp_emu_shadow_read32(struct gsp_emu_context *ctx, u32 offset)
-{
- return readl(ctx->bar0_vaddr + offset);
-}
-
/*
* gsp_emu_poll_boot - drive the guest's physical GSP boot to completion.
*
@@ -167,69 +149,74 @@ static inline u32 gsp_emu_shadow_read32(struct gsp_emu_context *ctx, u32 offset)
*/
static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
{
- u32 cpuctl = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_CPUCTL);
+ u32 gsp_cpuctl = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_CPUCTL);
+ u32 sec2_cpuctl = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_SEC2_CPUCTL);
u32 engine = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_ENGINE);
- /* ENGINE reset asserted -> falcon rebooted, restart stage tracking */
+ /* GSP ENGINE reset asserted -> falcon rebooted, restart stage tracking */
if ((engine & 0x1) && ctx->boot_stage != GSP_BOOT_STAGE_IDLE) {
ctx->boot_stage = GSP_BOOT_STAGE_IDLE;
ctx->flcn_kick_count = 0;
nv_vgpu_info(ctx->vgpu, "GSP-BOOT: falcon reset, back to IDLE");
+ /* The guest is rebooting the GSP (new probe or unload): the
+ * GSP-RM queue state is stale, reset it too. */
+ gsp_rm_emu_reset(ctx);
return;
}
- /* Nothing to do unless the guest kicked the falcon */
- if (!(cpuctl & GSP_EMU_CPUCTL_STARTCPU))
- return;
-
- ctx->flcn_kick_count++;
-
- switch (ctx->boot_stage) {
- case GSP_BOOT_STAGE_IDLE:
- /*
- * First kick after reset: FWSEC (FRTS setup). Complete it:
- * - CPUCTL: clear STARTCPU, set HALTED
- * - VBIOS_SCRATCH[0x0E] FRTS_ERR_CODE = NONE (0)
- * - WPR2_ADDR_HI != 0, WPR2_ADDR_LO = frtsOffset >> 0xc
- * frtsOffset is learned from the driver's own error message if
- * our value mismatches (deterministic for fixed FB + fw image).
- */
+ /*
+ * FWSEC kick on the GSP falcon (FRTS setup). Complete it:
+ * - CPUCTL: clear STARTCPU, set HALTED
+ * - VBIOS_SCRATCH[0x0E] FRTS_ERR_CODE = NONE (0)
+ * - WPR2_ADDR_HI != 0, WPR2_ADDR_LO = frtsOffset >> 0xc (in _VAL bits31:4)
+ */
+ if ((gsp_cpuctl & GSP_EMU_CPUCTL_STARTCPU) &&
+ ctx->boot_stage == GSP_BOOT_STAGE_IDLE) {
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_VBIOS_SCRATCH_0E, 0);
- gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_HI, 0x1);
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_HI, 0x100000);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_LO,
ctx->wpr2_addr_lo);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
GSP_EMU_CPUCTL_HALTED);
ctx->boot_stage = GSP_BOOT_STAGE_FWSEC_DONE;
+ ctx->flcn_kick_count++;
nv_vgpu_info(ctx->vgpu, "GSP-BOOT: FWSEC kick completed "
"(WPR2_LO=0x%x)", ctx->wpr2_addr_lo);
- break;
-
- case GSP_BOOT_STAGE_FWSEC_DONE:
- /*
- * Second kick: Booter Load (loads/authenticates GSP-RM image,
- * starts the RISCV). Complete it:
- * - CPUCTL: clear STARTCPU, set HALTED
- * - MAILBOX0 = 0 (booter success status)
- * - RISCV_STATUS = ACTIVE (kflcnIsRiscvActive passes)
- */
- gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_MAILBOX0, 0);
+ return;
+ }
+
+ /*
+ * Booter Load kick on the SEC2 falcon (loads/authenticates the GSP-RM
+ * image and starts the GSP RISCV). Complete it:
+ * - SEC2 CPUCTL: clear STARTCPU, set HALTED
+ * - SEC2 MAILBOX0 = 0 (booter error code, 0 = success)
+ * - GSP RISCV_STATUS = ACTIVE (Booter started the RISCV;
+ * kflcnIsRiscvActive passes)
+ */
+ if ((sec2_cpuctl & GSP_EMU_CPUCTL_STARTCPU) &&
+ ctx->boot_stage == GSP_BOOT_STAGE_FWSEC_DONE) {
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_SEC2_MAILBOX0, 0);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_RISCV_STATUS, 0x1);
- gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_SEC2_CPUCTL,
GSP_EMU_CPUCTL_HALTED);
ctx->boot_stage = GSP_BOOT_STAGE_BOOTER_DONE;
+ ctx->flcn_kick_count++;
nv_vgpu_info(ctx->vgpu, "GSP-BOOT: Booter kick completed, "
"RISCV active");
- break;
+ /* The driver has programmed the libos init args address into
+ * GSP MAILBOX0/1 by now; parse the queue chain, link the
+ * status queue and emit GSP_INIT_DONE. */
+ gsp_rm_emu_on_booter_done(ctx);
+ return;
+ }
- default:
- /* Later kicks (e.g. resume/unload): just complete them */
+ /* Any other STARTCPU kick (resume/unload/etc.): just complete it */
+ if (gsp_cpuctl & GSP_EMU_CPUCTL_STARTCPU)
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
GSP_EMU_CPUCTL_HALTED);
- nv_vgpu_info(ctx->vgpu, "GSP-BOOT: extra kick #%u completed",
- ctx->flcn_kick_count);
- break;
- }
+ if (sec2_cpuctl & GSP_EMU_CPUCTL_STARTCPU)
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_SEC2_CPUCTL,
+ GSP_EMU_CPUCTL_HALTED);
}
/* ============================================================
@@ -449,6 +436,9 @@ static int gsp_emu_thread_fn(void *data)
/* Drive the guest's physical GSP boot to completion */
gsp_emu_poll_boot(ctx);
+ /* GSP-RM msgq emulation (physical path: sysmem cmd/status queues) */
+ gsp_rm_emu_poll(ctx);
+
if (doorbell_val != 0) {
/* ACK doorbell immediately */
gsp_emu_write_doorbell(ctx, 0);
@@ -518,6 +508,17 @@ int gsp_emu_init(struct nv_vgpu_device *vgpu)
init_completion(&ctx->thread_stopped);
spin_lock_init(&ctx->lock);
+ /* Physical GSP boot emulation state. wpr2_addr_lo matches what the
+ * guest driver expects WPR2_ADDR_LO to read after FWSEC FRTS:
+ * frtsOffset = fbSize - reservedFromEnd. With fbSize = 256MB
+ * (LOCAL_MEMORY_RANGE) and the 570.144 image's 2MB reservation,
+ * frtsOffset = 0x10000000 - 0x200000 = 0xfe00000; the register's
+ * _VAL field (bits31:4) must equal frtsOffset >> 12 = 0xfe00, so the
+ * raw register value is 0xfe000. */
+ ctx->boot_stage = 0; /* GSP_BOOT_STAGE_IDLE */
+ ctx->flcn_kick_count = 0;
+ ctx->wpr2_addr_lo = 0x000fe000;
+
/* The guest's BAR0 is the emulation shadow (vzalloc'd by
* nv_vgpu_bar_emul_init, mapped to the guest via VFIO mmap) - NOT the
* host physical BAR0 (vgpu->bars[0].vaddr, which belongs to the real GPU
@@ -556,6 +557,13 @@ int gsp_emu_init(struct nv_vgpu_device *vgpu)
/* Enable emulation mode in RPC client */
gsp_rpc_emulation_set(true);
+ /* Initialize GSP-RM msgq emulation (physical path) */
+ ret = gsp_rm_emu_init(vgpu, ctx);
+ if (ret) {
+ nv_vgpu_err(vgpu, "Failed to init GSP-RM emulation: %d", ret);
+ goto err_fini_client;
+ }
+
/* NOTE: MSI-X eventfd triggers are NOT copied here - QEMU wires them
* via VFIO_DEVICE_SET_IRQS later, so gsp_emu_inject_completion_interrupt
* looks them up live from vgpu->irqs[]. */
@@ -605,6 +613,9 @@ void gsp_emu_cleanup(struct nv_vgpu_device *vgpu)
}
ctx->running = false;
+ /* Teardown GSP-RM msgq emulation */
+ gsp_rm_emu_fini(ctx);
+
/* Disable emulation mode */
gsp_rpc_emulation_set(false);
diff --git a/kernel/gsp_emulation.h b/kernel/gsp_emulation.h
index 53560e9..668d567 100644
--- a/kernel/gsp_emulation.h
+++ b/kernel/gsp_emulation.h
@@ -38,7 +38,27 @@
* physical GSP boot sequence is visible on the host (guest BAR0 access is
* mmap-based and otherwise invisible). Offsets are BAR0-relative, GSP falcon
* block at 0x110000. */
-#define GSP_EMU_FALCON_WATCH_COUNT 17
+#define GSP_EMU_FALCON_WATCH_COUNT 20
+
+/* BAR0 shadow offsets used by the guest's physical GSP boot */
+#define GSP_EMU_REG_GSP_CPUCTL 0x110100 /* bit1 STARTCPU, bit4 HALTED */
+#define GSP_EMU_REG_GSP_ENGINE 0x1103c0 /* bit0 RESET */
+#define GSP_EMU_REG_GSP_MAILBOX0 0x110040
+#define GSP_EMU_REG_VBIOS_SCRATCH_0E 0x001438 /* FRTS err code (0=NONE) */
+#define GSP_EMU_REG_WPR2_ADDR_LO 0x1fa824
+#define GSP_EMU_REG_WPR2_ADDR_HI 0x1fa828
+#define GSP_EMU_REG_RISCV_STATUS 0x111240 /* bit0 ACTIVE (NV_PRISCV_RISCV
+ CORE_SWITCH_RISCV_STATUS=0x240 at
+ GSP riscvRegisterBase =
+ NV_FALCON2_GSP_BASE=0x111000) */
+
+/* SEC2 falcon (Booter Load/Unload runs here, not on the GSP falcon) */
+#define GSP_EMU_REG_SEC2_CPUCTL 0x840100
+#define GSP_EMU_REG_SEC2_MAILBOX0 0x840040
+#define GSP_EMU_REG_SEC2_ENGINE 0x8403c0
+
+#define GSP_EMU_CPUCTL_STARTCPU 0x2
+#define GSP_EMU_CPUCTL_HALTED 0x10
/* ============================================================
* GSP Emulation Context
@@ -78,8 +98,23 @@ struct gsp_emu_context {
u32 boot_stage; /* enum gsp_emu_boot_stage */
u32 flcn_kick_count; /* STARTCPU kicks seen since last ENGINE reset */
u32 wpr2_addr_lo; /* WPR2_ADDR_LO value to report after FWSEC */
+
+ /* GSP-RM msgq emulation (physical path, guest-sysmem queues) */
+ struct gsp_rm_emu *rm_emu;
};
+/* Shadow accessors shared with gsp_rm_emulation.c */
+static inline void gsp_emu_shadow_write32(struct gsp_emu_context *ctx,
+ u32 offset, u32 val)
+{
+ writel(val, ctx->bar0_vaddr + offset);
+}
+
+static inline u32 gsp_emu_shadow_read32(struct gsp_emu_context *ctx, u32 offset)
+{
+ return readl(ctx->bar0_vaddr + offset);
+}
+
/* ============================================================
* Public API
* ============================================================ */
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
new file mode 100644
index 0000000..d3e4548
--- /dev/null
+++ b/kernel/gsp_rm_emulation.c
@@ -0,0 +1,1022 @@
+/*
+ * GSP-RM Emulation (physical-path) for Host-Side vGPU
+ *
+ * Emulates the GSP-RM msgq RPC channel in guest system memory.
+ * See gsp_rm_emulation.h for the design overview.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/vfio.h>
+
+#include "nvidia-vgpu-vfio.h"
+#include "gsp_emulation.h"
+#include "gsp_rm_emulation.h"
+#include "gsp_rm_tu104.h"
+
+/* ============================================================
+ * Guest-memory access ("virtual DMA" on behalf of the device)
+ * ============================================================ */
+
+static int gsp_rm_read(struct gsp_emu_context *ctx, u64 gpa, void *buf,
+ size_t len)
+{
+ struct nv_vgpu_device *vgpu = ctx->vgpu;
+
+ if (atomic_read(&vgpu->vfio_dev_open_count) <= 0)
+ return -ENODEV;
+
+ return vfio_dma_rw(&vgpu->vfio_dev, gpa, buf, len, false);
+}
+
+static int gsp_rm_write(struct gsp_emu_context *ctx, u64 gpa, void *buf,
+ size_t len)
+{
+ struct nv_vgpu_device *vgpu = ctx->vgpu;
+
+ if (atomic_read(&vgpu->vfio_dev_open_count) <= 0)
+ return -ENODEV;
+
+ return vfio_dma_rw(&vgpu->vfio_dev, gpa, buf, len, true);
+}
+
+/* Translate a shared-buffer offset to a GPA via the buffer's page table */
+static u64 gsp_rm_off_to_gpa(struct gsp_rm_emu *rm, u64 off)
+{
+ u64 idx = off >> 12;
+
+ if (idx >= rm->mq.pageTableEntryCount)
+ return 0;
+ return rm->ptes[idx] + (off & 0xfff);
+}
+
+/* ============================================================
+ * Status queue element construction
+ * ============================================================ */
+
+/* XOR-fold checksum over u64s (message_queue_priv.h _checkSum32) */
+static u32 gsp_rm_checksum32(const void *data, u32 len)
+{
+ const u64 *p = data;
+ const u64 *end = (const u64 *)((const u8 *)data + len);
+ u64 sum = 0;
+
+ while (p < end)
+ sum ^= *p++;
+
+ return (u32)(sum >> 32) ^ (u32)sum;
+}
+
+/*
+ * 570.144 GSP_MSG_QUEUE_ELEMENT wire layout (guest driver is 570.144 stock):
+ * 0x00 authTagBuffer[16] (zero - no CC)
+ * 0x10 aadBuffer[16] (zero - no CC)
+ * 0x20 checkSum (XOR-fold over msgLen, makes total 0)
+ * 0x24 seqNum (per-record sequence)
+ * 0x28 elemCount (elements this record spans)
+ * 0x30 rpc_message_header_v (32 bytes) + payload
+ * GSP_MSG_QUEUE_ELEMENT_HDR_SIZE = offsetof(rpc) = 0x30.
+ */
+#define GSPRM_ELEM_HDR_SIZE 0x30
+
+/*
+ * Build one 4KB queue element carrying an rpc_message_header_v + payload.
+ * Returns message length (element header + rpc length).
+ */
+static u32 gsp_rm_build_element(struct gsp_rm_emu *rm, u8 *elem,
+ u32 function, u32 sequence, u32 result,
+ const void *data, u32 data_len)
+{
+ struct gsprm_rpc_header *hdr =
+ (struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE);
+ u32 rpc_len = sizeof(*hdr) + data_len;
+ u32 msg_len = GSPRM_ELEM_HDR_SIZE + rpc_len;
+ u32 aligned_len;
+ u32 elem_count = 1;
+
+ memset(elem, 0, GSPRM_QUEUE_ELEMENT_SIZE);
+ /* elem+0x20: checksum, filled below */
+ memcpy(elem + 0x24, &rm->stat_seq, 4);
+ memcpy(elem + 0x28, &elem_count, 4);
+
+ hdr->header_version = GSPRM_RPC_HEADER_VERSION;
+ hdr->signature = GSPRM_RPC_SIGNATURE;
+ hdr->length = rpc_len;
+ hdr->function = function;
+ hdr->rpc_result = result;
+ hdr->rpc_result_private = result;
+ hdr->sequence = sequence;
+ hdr->spare = 0;
+ if (data && data_len)
+ memcpy((u8 *)(hdr + 1), data, data_len);
+
+ /* checksum covers msg_len zero-padded to 8 (buffer already zeroed) */
+ aligned_len = (msg_len + 7) & ~7U;
+ *(u32 *)(elem + 0x20) = gsp_rm_checksum32(elem, aligned_len);
+
+ return aligned_len;
+}
+
+/*
+ * Write one status queue element and advance the status TX writePtr.
+ */
+static int gsp_rm_send(struct gsp_emu_context *ctx, u32 function,
+ u32 sequence, u32 result, const void *data, u32 data_len)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ u8 *elem;
+ u64 elem_gpa;
+ u32 slot;
+ u32 new_wp;
+ int ret;
+
+ elem = kzalloc(GSPRM_QUEUE_ELEMENT_SIZE, GFP_KERNEL);
+ if (!elem)
+ return -ENOMEM;
+
+ gsp_rm_build_element(rm, elem, function, sequence, result, data,
+ data_len);
+
+ slot = rm->stat_write % rm->stat_msgcount;
+ elem_gpa = gsp_rm_off_to_gpa(rm, rm->stat_off + rm->entry_off +
+ (u64)slot * GSPRM_QUEUE_ELEMENT_SIZE);
+
+ ret = gsp_rm_write(ctx, elem_gpa, elem, GSPRM_QUEUE_ELEMENT_SIZE);
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: status element write failed gpa=0x%llx ret=%d",
+ elem_gpa, ret);
+ kfree(elem);
+ return ret;
+ }
+ kfree(elem);
+
+ rm->stat_write++;
+ rm->stat_seq++;
+
+ /* bump status TX header writePtr (offset 0x10) */
+ new_wp = rm->stat_write;
+ ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + 0x10,
+ &new_wp, sizeof(new_wp));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: status writePtr update failed ret=%d", ret);
+ return ret;
+ }
+
+ /* Notify the guest (MSI-X vector 0). Boot path polls anyway; this
+ * covers the driver's interrupt-driven drain later on. */
+ gsp_emu_inject_completion_interrupt(ctx, rm->stat_seq);
+
+ return 0;
+}
+
+/* ============================================================
+ * Queue-chain discovery (libos args -> RMARGS -> GSP_ARGUMENTS_CACHED)
+ * ============================================================ */
+
+static int gsp_rm_parse_queue_chain(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ struct gsprm_libos_region *regions;
+ int ret, i;
+ u64 gsp_args_gpa = 0;
+ u32 total_size;
+
+ regions = kmalloc_array(GSPRM_MAX_LIBOS_REGIONS, sizeof(*regions),
+ GFP_KERNEL);
+ if (!regions)
+ return -ENOMEM;
+
+ /* 1. Read the libos init args array */
+ ret = gsp_rm_read(ctx, rm->libos_args_gpa, regions,
+ GSPRM_MAX_LIBOS_REGIONS * sizeof(*regions));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: libos args read failed gpa=0x%llx ret=%d",
+ rm->libos_args_gpa, ret);
+ goto out;
+ }
+
+ /* 2. Find the RMARGS region -> GSP_ARGUMENTS_CACHED */
+ for (i = 0; i < GSPRM_MAX_LIBOS_REGIONS; i++) {
+ if (regions[i].id8 == 0)
+ break;
+ nv_vgpu_info(ctx->vgpu, "GSPRM: libos region[%d] id8=0x%llx pa=0x%llx size=0x%llx kind=%u loc=%u",
+ i, regions[i].id8, regions[i].pa, regions[i].size,
+ regions[i].kind, regions[i].loc);
+ if (regions[i].id8 == GSPRM_LIBOS_ID_RMARGS)
+ gsp_args_gpa = regions[i].pa;
+ }
+ if (!gsp_args_gpa) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: RMARGS region not found");
+ ret = -ENOENT;
+ goto out;
+ }
+ rm->gsp_args_gpa = gsp_args_gpa;
+
+ /* 3. Read GSP_ARGUMENTS_CACHED (MESSAGE_QUEUE_INIT_ARGUMENTS first) */
+ ret = gsp_rm_read(ctx, gsp_args_gpa, &rm->mq, sizeof(rm->mq));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GSP_ARGUMENTS_CACHED read failed ret=%d", ret);
+ goto out;
+ }
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: queue args sharedPA=0x%llx ptes=%u cmdOff=0x%llx statOff=0x%llx hdrSize=0x%llx elemMin=0x%llx elemMax=0x%llx hdrAlign=%u elemAlign=%u",
+ rm->mq.sharedMemPhysAddr, rm->mq.pageTableEntryCount,
+ rm->mq.cmdQueueOffset, rm->mq.statQueueOffset,
+ rm->mq.queueElementHdrSize, rm->mq.queueElementSizeMin,
+ rm->mq.queueElementSizeMax, rm->mq.queueHeaderAlign,
+ rm->mq.queueElementAlign);
+ print_hex_dump(KERN_INFO, "GSPRM args raw: ", DUMP_PREFIX_OFFSET,
+ 16, 8, &rm->mq, sizeof(rm->mq), false);
+
+ if (!rm->mq.sharedMemPhysAddr || !rm->mq.pageTableEntryCount ||
+ rm->mq.pageTableEntryCount > 1024) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: invalid queue args");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /* 4. Read the page table (RmPhysAddr per 4K page of the shared buffer) */
+ rm->ptes = kmalloc_array(rm->mq.pageTableEntryCount, sizeof(u64),
+ GFP_KERNEL);
+ if (!rm->ptes) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ ret = gsp_rm_read(ctx, rm->mq.sharedMemPhysAddr, rm->ptes,
+ rm->mq.pageTableEntryCount * sizeof(u64));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: page table read failed ret=%d", ret);
+ goto out;
+ }
+
+ /* 5. Compute queue geometry. NOTE: the shared buffer is
+ * NV_MEMORY_NONCONTIGUOUS - only queue-offsets are linear, GPAs are
+ * not. Every access must translate through rm->ptes (gsp_rm_off_to_gpa). */
+ rm->cmd_off = rm->mq.cmdQueueOffset;
+ rm->stat_off = rm->mq.statQueueOffset;
+ rm->cmd_gpa = gsp_rm_off_to_gpa(rm, rm->cmd_off);
+ rm->stat_gpa = gsp_rm_off_to_gpa(rm, rm->stat_off);
+ rm->cmd_size = (u32)(rm->mq.statQueueOffset - rm->mq.cmdQueueOffset);
+ total_size = rm->mq.pageTableEntryCount << 12;
+ rm->stat_size = total_size - (u32)rm->mq.statQueueOffset;
+ rm->entry_off = 0x1000; /* ALIGN_UP(0x20 + 4, 1 << elemAlign(12)) */
+ rm->cmd_msgcount = (rm->cmd_size - rm->entry_off) / GSPRM_QUEUE_ELEMENT_SIZE;
+ rm->stat_msgcount = (rm->stat_size - rm->entry_off) / GSPRM_QUEUE_ELEMENT_SIZE;
+
+ if (!rm->cmd_gpa || !rm->stat_gpa || !rm->cmd_msgcount || !rm->stat_msgcount) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: bad queue geometry cmd_gpa=0x%llx stat_gpa=0x%llx cmd_mc=%u stat_mc=%u",
+ rm->cmd_gpa, rm->stat_gpa, rm->cmd_msgcount,
+ rm->stat_msgcount);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: cmd queue gpa=0x%llx size=0x%x mc=%u; status queue gpa=0x%llx size=0x%x mc=%u",
+ rm->cmd_gpa, rm->cmd_size, rm->cmd_msgcount,
+ rm->stat_gpa, rm->stat_size, rm->stat_msgcount);
+
+ ret = 0;
+out:
+ kfree(regions);
+ return ret;
+}
+
+/*
+ * Write the status queue TX header (what GSP-RM's msgqTxCreate produces).
+ */
+static int gsp_rm_link_status_queue(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ struct gsprm_msgq_tx_header tx;
+ u32 zero = 0;
+ int ret;
+
+ memset(&tx, 0, sizeof(tx));
+ tx.version = GSPRM_MSGQ_VERSION;
+ tx.size = rm->stat_size;
+ tx.msgSize = GSPRM_QUEUE_ELEMENT_SIZE;
+ tx.msgCount = rm->stat_msgcount;
+ tx.writePtr = 0;
+ tx.flags = GSPRM_MSGQ_FLAGS_SWAP_RX;
+ tx.rxHdrOff = 0x20; /* ALIGN_UP(sizeof(msgqTxHeader)=32, 1<<hdrAlign(4)=16) */
+ tx.entryOff = rm->entry_off;
+
+ ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off), &tx, sizeof(tx));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: status TX header write failed ret=%d", ret);
+ return ret;
+ }
+
+ /* status queue RX header (our command-queue read position, swap mode) */
+ ret = gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + tx.rxHdrOff,
+ &zero, sizeof(zero));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: status RX header write failed ret=%d", ret);
+ return ret;
+ }
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: status queue linked (size=0x%x msgCount=%u)",
+ tx.size, tx.msgCount);
+ return 0;
+}
+
+/* ============================================================
+ * Command processing
+ * ============================================================ */
+
+static const char *gsp_rm_rpc_name(u32 function)
+{
+ switch (function) {
+ case GSPRM_RPC_FUNC_SET_GUEST_SYSTEM_INFO: return "SET_GUEST_SYSTEM_INFO";
+ case GSPRM_RPC_FUNC_ALLOC_ROOT: return "ALLOC_ROOT";
+ case GSPRM_RPC_FUNC_GET_GSP_STATIC_INFO: return "GET_GSP_STATIC_INFO";
+ case GSPRM_RPC_FUNC_GSP_SET_SYSTEM_INFO: return "GSP_SET_SYSTEM_INFO";
+ case GSPRM_RPC_FUNC_SET_REGISTRY: return "SET_REGISTRY";
+ case GSPRM_RPC_FUNC_CONTINUATION_RECORD: return "CONTINUATION_RECORD";
+ case GSPRM_RPC_FUNC_GSP_RM_CONTROL: return "GSP_RM_CONTROL";
+ default: return "UNKNOWN";
+ }
+}
+
+/*
+ * SET_GUEST_SYSTEM_INFO: the driver negotiates the RPC interface version
+ * from the reply's vgxVersionMajorNum/MinorNum fields. Without them the
+ * version-gated HALs stay stubs and every subsequent RPC fails with
+ * NV_VGPU_MSG_RESULT_RPC_UNKNOWN_FUNCTION (0xff100001). Echo the driver's
+ * own version back (no host-guest version skew).
+ */
+static void gsp_rm_handle_set_guest_system_info(struct gsp_emu_context *ctx,
+ const u8 *elem,
+ u32 sequence)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const u8 *req_data = elem + GSPRM_ELEM_HDR_SIZE +
+ sizeof(struct gsprm_rpc_header);
+ u8 *reply;
+ u32 major, minor;
+
+ memcpy(&major, req_data + 0, 4);
+ memcpy(&minor, req_data + 4, 4);
+ if (!major)
+ major = GSPRM_VGX_VERSION_MAJOR;
+ if (!minor && major != GSPRM_VGX_VERSION_MAJOR)
+ minor = GSPRM_VGX_VERSION_MINOR;
+
+ reply = kzalloc(GSPRM_SET_GUEST_INFO_SIZE, GFP_KERNEL);
+ if (!reply) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: SET_GUEST_SYSTEM_INFO reply alloc failed");
+ return;
+ }
+ memcpy(reply + 0, &major, 4);
+ memcpy(reply + 4, &minor, 4);
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: SET_GUEST_SYSTEM_INFO seq=%u -> VGX %u.%u",
+ sequence, major, minor);
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_SET_GUEST_SYSTEM_INFO, sequence, 0,
+ reply, GSPRM_SET_GUEST_INFO_SIZE))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: SET_GUEST_SYSTEM_INFO reply send failed");
+ else
+ rm->rpcs_replied++;
+
+ kfree(reply);
+}
+
+/* ============================================================
+ * GET_GSP_STATIC_INFO (function 65)
+ *
+ * Builds the 1656-byte GspStaticConfigInfo for TU104.
+ * ============================================================ */
+
+static void gsp_rm_build_static_info(u8 *buf)
+{
+ u64 val64;
+ u32 val32;
+ u8 *p;
+
+ memset(buf, 0, TU104_GSCI_SIZE);
+
+ /* grCapsBits (offset 0, 23 bytes) - basic GR caps */
+ buf[0] = 0x01; /* GR present */
+
+ /* gidInfo (offset 24, 268 bytes): index(4) + flags(4) + length(4) + data(256) */
+ p = buf + TU104_GSCI_OFF_gidInfo;
+ val32 = 0; memcpy(p + 0, &val32, 4); /* index */
+ val32 = 0x80000000; memcpy(p + 4, &val32, 4); /* flags: valid */
+ val32 = 16; memcpy(p + 8, &val32, 4); /* length = 16 (UUID) */
+ /* GPU UUID - deterministic fake for TU104 */
+ memcpy(p + 12, "GPU-9da5e221-18", 15);
+
+ /* SKUInfo (offset 292, 48 bytes) */
+ p = buf + TU104_GSCI_OFF_SKUInfo;
+ memcpy(p + 4, "TU104", 5); /* chipSKU */
+ memcpy(p + 13, "400", 3); /* chipSKUMod */
+ memcpy(p + 20, "N18E", 4); /* project */
+ memcpy(p + 24, "G3", 2); /* projectSKU */
+
+ /* fbRegionInfoParams (offset 344, 776 bytes) */
+ p = buf + TU104_GSCI_OFF_fbRegionInfoParams;
+ val32 = 1; memcpy(p, &val32, 4); /* numFBRegions = 1 */
+ /* Region 0: base=0, limit=256MB-1, reserved=0 */
+ {
+ struct tu104_fb_region *region = (void *)(p + 8); /* 8-aligned */
+ val64 = 0; memcpy(&region->base, &val64, 8);
+ val64 = 0x0FFFFFFF; memcpy(&region->limit, &val64, 8);
+ val64 = 0; memcpy(&region->reserved, &val64, 8);
+ val32 = 0; memcpy(&region->performance, &val32, 4);
+ region->supportCompressed = 0;
+ region->supportISO = 0;
+ region->bProtected = 0;
+ memset(region->blackList, 0, sizeof(region->blackList));
+ }
+
+ /* sriovCaps (offset 1120, 80 bytes) - all zeros (not SR-IOV) */
+
+ /* sriovMaxGfid (offset 1200, 4 bytes) */
+ val32 = 0;
+ memcpy(buf + TU104_GSCI_OFF_sriovMaxGfid, &val32, 4);
+
+ /* engineCaps (offset 1204, 12 bytes) */
+ val32 = 0x60003E02; /* GR0|CE0-4|NVDEC0|NVDEC1 */
+ memcpy(buf + TU104_GSCI_OFF_engineCaps, &val32, 4);
+ val32 = 0x00362020; /* NVENC0|SW|SEC2|HOST|PMU|FBFLCN */
+ memcpy(buf + TU104_GSCI_OFF_engineCaps + 4, &val32, 4);
+ val32 = 0;
+ memcpy(buf + TU104_GSCI_OFF_engineCaps + 8, &val32, 4);
+
+ /* poisonFuseEnabled (offset 1216, 1 byte) */
+ buf[TU104_GSCI_OFF_poisonFuseEnabled] = 0;
+
+ /* fb_length (offset 1224, 8 bytes) - 256MB */
+ val64 = 0x10000000ULL;
+ memcpy(buf + TU104_GSCI_OFF_fb_length, &val64, 8);
+
+ /* fbio_mask (offset 1232, 8 bytes) - 8 FBIOs for 256-bit bus */
+ val64 = 0xFF;
+ memcpy(buf + TU104_GSCI_OFF_fbio_mask, &val64, 8);
+
+ /* fb_bus_width (offset 1240, 4 bytes) - 256-bit */
+ val32 = 256;
+ memcpy(buf + TU104_GSCI_OFF_fb_bus_width, &val32, 4);
+
+ /* fb_ram_type (offset 1244, 4 bytes) - GDDR6 = 6 */
+ val32 = 6;
+ memcpy(buf + TU104_GSCI_OFF_fb_ram_type, &val32, 4);
+
+ /* fbp_mask (offset 1248, 8 bytes) - 6 FBPs for 6 GPCs */
+ val64 = 0x3F;
+ memcpy(buf + TU104_GSCI_OFF_fbp_mask, &val64, 8);
+
+ /* l2_cache_size (offset 1256, 4 bytes) - 4MB */
+ val32 = 0x400000;
+ memcpy(buf + TU104_GSCI_OFF_l2_cache_size, &val32, 4);
+
+ /* gpuNameString (offset 1260, 64 bytes) */
+ strncpy((char *)(buf + TU104_GSCI_OFF_gpuNameString),
+ "NVIDIA GeForce RTX 2080 SUPER", 64);
+
+ /* gpuShortNameString (offset 1324, 64 bytes) */
+ strncpy((char *)(buf + TU104_GSCI_OFF_gpuShortNameString),
+ "TU104", 64);
+
+ /* gpuNameString_Unicode (offset 1388, 128 bytes) - skip for now */
+
+ /* Branding booleans */
+ buf[TU104_GSCI_OFF_bIsMobile] = 1; /* Mobile/Max-Q */
+
+ /* bar1PdeBase (offset 1536, 8 bytes) */
+ val64 = 0;
+ memcpy(buf + TU104_GSCI_OFF_bar1PdeBase, &val64, 8);
+
+ /* bar2PdeBase (offset 1544, 8 bytes) */
+ val64 = 0;
+ memcpy(buf + TU104_GSCI_OFF_bar2PdeBase, &val64, 8);
+
+ /* bVbiosValid (offset 1552, 1 byte) */
+ buf[TU104_GSCI_OFF_bVbiosValid] = 1;
+
+ /* vbiosSubVendor (offset 1556, 4 bytes) - ASUS */
+ val32 = 0x1043;
+ memcpy(buf + TU104_GSCI_OFF_vbiosSubVendor, &val32, 4);
+
+ /* vbiosSubDevice (offset 1560, 4 bytes) */
+ val32 = 0x1F01;
+ memcpy(buf + TU104_GSCI_OFF_vbiosSubDevice, &val32, 4);
+
+ /* hInternalClient/Device/Subdevice */
+ val32 = TU104_H_INTERNAL_CLIENT;
+ memcpy(buf + TU104_GSCI_OFF_hInternalClient, &val32, 4);
+ val32 = TU104_H_INTERNAL_DEVICE;
+ memcpy(buf + TU104_GSCI_OFF_hInternalDevice, &val32, 4);
+ val32 = TU104_H_INTERNAL_SUBDEVICE;
+ memcpy(buf + TU104_GSCI_OFF_hInternalSubdevice, &val32, 4);
+
+ /* ecidInfo (offset 1616, 24 bytes) - leave zeroed */
+
+ /* fwWprLayoutOffset (offset 1640, 16 bytes): nonWprHeapOffset, frtsOffset */
+ val64 = 0;
+ memcpy(buf + TU104_GSCI_OFF_fwWprLayoutOffset, &val64, 8);
+ val64 = 0xFE00000; /* frtsOffset = fbSize - 2MB */
+ memcpy(buf + TU104_GSCI_OFF_fwWprLayoutOffset + 8, &val64, 8);
+}
+
+static void gsp_rm_handle_get_static_info(struct gsp_emu_context *ctx,
+ u32 sequence)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ u8 *reply;
+
+ reply = kzalloc(TU104_GSCI_SIZE, GFP_KERNEL);
+ if (!reply) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GET_GSP_STATIC_INFO reply alloc failed");
+ return;
+ }
+
+ gsp_rm_build_static_info(reply);
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: GET_GSP_STATIC_INFO seq=%u -> %u bytes (fb=%lluMB, engines=0x%x/0x%x)",
+ sequence, TU104_GSCI_SIZE,
+ 0x10000000ULL >> 20, 0x60003E02, 0x362020);
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GET_GSP_STATIC_INFO, sequence, 0,
+ reply, TU104_GSCI_SIZE))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GET_GSP_STATIC_INFO reply send failed");
+ else
+ rm->rpcs_replied++;
+
+ kfree(reply);
+}
+
+/* ============================================================
+ * GSP_RM_CONTROL (function 76) - sub-command dispatch
+ *
+ * rpc_gsp_rm_control_v03_00: hClient(4) + hObject(4) + cmd(4) + status(4)
+ * + paramsSize(4) + flags(4) + params[]
+ * ============================================================ */
+
+struct gsp_rm_control_req {
+ u32 hClient;
+ u32 hObject;
+ u32 cmd;
+ u32 status;
+ u32 paramsSize;
+ u32 flags;
+ /* params[] follows */
+};
+
+#define GSP_RM_CTRL_HDR_SIZE 24
+
+static void gsp_rm_handle_fifo_device_table(struct gsp_emu_context *ctx,
+ u32 sequence,
+ struct gsp_rm_control_req *req,
+ u32 *out_status,
+ u8 *out_params, u32 *out_params_size)
+{
+ struct tu104_fifo_get_device_info_params *fifo_params;
+ u32 base, count, i, copy_count;
+
+ fifo_params = (struct tu104_fifo_get_device_info_params *)out_params;
+ base = 0;
+ if (req->paramsSize >= 4)
+ memcpy(&base, (u8 *)(req + 1), 4);
+
+ /* How many entries to return in this page */
+ count = TU104_ENGINE_TABLE_SIZE - base;
+ if (count > TU104_FIFO_TABLE_MAX_ENTRIES)
+ count = TU104_FIFO_TABLE_MAX_ENTRIES;
+
+ copy_count = count;
+ if (base >= TU104_ENGINE_TABLE_SIZE)
+ copy_count = 0;
+
+ fifo_params->baseIndex = base;
+ fifo_params->numEntries = copy_count;
+ fifo_params->bMore = (base + copy_count) < TU104_ENGINE_TABLE_SIZE;
+
+ for (i = 0; i < copy_count; i++)
+ memcpy(&fifo_params->entries[i], &tu104_engine_table[base + i],
+ sizeof(struct tu104_fifo_device_entry));
+
+ *out_params_size = offsetof(struct tu104_fifo_get_device_info_params,
+ entries) +
+ copy_count * sizeof(struct tu104_fifo_device_entry);
+ *out_status = 0; /* NV_OK */
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: FIFO_GET_DEVICE_INFO_TABLE base=%u count=%u more=%u",
+ base, copy_count, fifo_params->bMore);
+}
+
+static void gsp_rm_handle_intr_kernel_table(struct gsp_emu_context *ctx,
+ u32 sequence,
+ u32 *out_status,
+ u8 *out_params, u32 *out_params_size)
+{
+ struct tu104_intr_get_kernel_table_params *intr_params;
+ u32 copy_size;
+
+ intr_params = (struct tu104_intr_get_kernel_table_params *)out_params;
+
+ intr_params->tableLen = TU104_INTR_KERNEL_TABLE_SIZE;
+ copy_size = TU104_INTR_KERNEL_TABLE_SIZE *
+ sizeof(struct tu104_intr_table_entry);
+ memcpy(intr_params->table, tu104_intr_kernel_table, copy_size);
+
+ /* Subtree map: all entries in DEFAULT category (0) */
+ memset(intr_params->subtreeMap, 0, sizeof(intr_params->subtreeMap));
+
+ *out_params_size = offsetof(struct tu104_intr_get_kernel_table_params,
+ table) +
+ copy_size +
+ sizeof(intr_params->subtreeMap);
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: INTR_GET_KERNEL_TABLE tableLen=%u",
+ TU104_INTR_KERNEL_TABLE_SIZE);
+}
+
+static void gsp_rm_handle_constructed_falcon_info(struct gsp_emu_context *ctx,
+ u32 sequence,
+ u32 *out_status,
+ u8 *out_params,
+ u32 *out_params_size)
+{
+ struct tu104_get_constructed_falcon_info_params *falcon_params;
+
+ falcon_params = (struct tu104_get_constructed_falcon_info_params *)out_params;
+
+ falcon_params->numConstructedFalcons = TU104_FALCON_TABLE_SIZE;
+ memcpy(falcon_params->constructedFalconsTable, tu104_falcon_table,
+ TU104_FALCON_TABLE_SIZE *
+ sizeof(struct tu104_constructed_falcon_info));
+
+ *out_params_size = offsetof(struct tu104_get_constructed_falcon_info_params,
+ constructedFalconsTable) +
+ TU104_FALCON_TABLE_SIZE *
+ sizeof(struct tu104_constructed_falcon_info);
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: GET_CONSTRUCTED_FALCON_INFO numFalcons=%u",
+ TU104_FALCON_TABLE_SIZE);
+}
+
+static void gsp_rm_handle_internal_device_table(struct gsp_emu_context *ctx,
+ u32 sequence,
+ u32 *out_status,
+ u8 *out_params,
+ u32 *out_params_size)
+{
+ struct tu104_internal_get_device_info_params *dev_params;
+ u32 copy_size;
+
+ dev_params = (struct tu104_internal_get_device_info_params *)out_params;
+
+ dev_params->numEntries = TU104_INTERNAL_DEVICE_TABLE_SIZE;
+ copy_size = TU104_INTERNAL_DEVICE_TABLE_SIZE *
+ sizeof(struct tu104_internal_device_info);
+ memcpy(dev_params->deviceInfoTable, tu104_internal_device_table,
+ copy_size);
+
+ *out_params_size = offsetof(struct tu104_internal_get_device_info_params,
+ deviceInfoTable) + copy_size;
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: INTERNAL_GET_DEVICE_INFO_TABLE numEntries=%u",
+ TU104_INTERNAL_DEVICE_TABLE_SIZE);
+}
+
+static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
+ const u8 *elem, u32 sequence)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const u8 *req_data = elem + GSPRM_ELEM_HDR_SIZE +
+ sizeof(struct gsprm_rpc_header);
+ struct gsp_rm_control_req req;
+ u32 reply_data_len;
+ u8 *reply;
+ u32 out_status = 0;
+ u32 out_params_size = 0;
+
+ memcpy(&req, req_data, GSP_RM_CTRL_HDR_SIZE);
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: GSP_RM_CONTROL cmd=0x%x hClient=0x%x hObject=0x%x paramsSize=%u flags=0x%x",
+ req.cmd, req.hClient, req.hObject, req.paramsSize,
+ req.flags);
+
+ /* Allocate reply: control header + max possible params */
+ reply_data_len = GSP_RM_CTRL_HDR_SIZE + max(req.paramsSize, (u32)4096);
+ reply = kzalloc(reply_data_len, GFP_KERNEL);
+ if (!reply) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: RM_CONTROL reply alloc failed");
+ return;
+ }
+
+ switch (req.cmd) {
+ case TU104_CTRL_FIFO_GET_DEVICE_INFO_TABLE:
+ gsp_rm_handle_fifo_device_table(ctx, sequence, &req,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case TU104_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE:
+ gsp_rm_handle_intr_kernel_table(ctx, sequence,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case TU104_CTRL_GPU_GET_CONSTRUCTED_FALCON_INFO:
+ gsp_rm_handle_constructed_falcon_info(ctx, sequence,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ case TU104_CTRL_INTERNAL_GET_DEVICE_INFO_TABLE:
+ gsp_rm_handle_internal_device_table(ctx, sequence,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
+ default:
+ /* Unknown control command - return success with zeroed params */
+ nv_vgpu_info(ctx->vgpu, "GSPRM: RM_CONTROL unknown cmd=0x%x -> generic SUCCESS",
+ req.cmd);
+ out_status = 0;
+ out_params_size = req.paramsSize;
+ break;
+ }
+
+ /* Build the reply header */
+ memcpy(reply + 0, &req.hClient, 4);
+ memcpy(reply + 4, &req.hObject, 4);
+ memcpy(reply + 8, &req.cmd, 4);
+ memcpy(reply + 12, &out_status, 4);
+ memcpy(reply + 16, &out_params_size, 4);
+ memcpy(reply + 20, &req.flags, 4);
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_GSP_RM_CONTROL, sequence, 0,
+ reply, GSP_RM_CTRL_HDR_SIZE + out_params_size))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: RM_CONTROL reply send failed cmd=0x%x",
+ req.cmd);
+ else
+ rm->rpcs_replied++;
+
+ kfree(reply);
+}
+
+/*
+ * Handle one command-queue record (first element). For async init RPCs we
+ * just consume. Synchronous RPCs get a reply; per-function handlers are
+ * implemented iteratively (unknown ones get a generic success reply).
+ */
+static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const struct gsprm_rpc_header *hdr =
+ (const struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE);
+ u32 function = hdr->function;
+ u32 sequence = hdr->sequence;
+
+ rm->rpcs_seen++;
+
+ switch (function) {
+ case GSPRM_RPC_FUNC_GSP_SET_SYSTEM_INFO:
+ case GSPRM_RPC_FUNC_SET_REGISTRY:
+ case GSPRM_RPC_FUNC_CONTINUATION_RECORD:
+ /* Async init RPCs - fire and forget, just consume. */
+ nv_vgpu_info(ctx->vgpu, "GSPRM: async RPC %s(%u) seq=%u len=%u consumed",
+ gsp_rm_rpc_name(function), function, sequence,
+ hdr->length);
+ return;
+
+ case GSPRM_RPC_FUNC_SET_GUEST_SYSTEM_INFO:
+ gsp_rm_handle_set_guest_system_info(ctx, elem, sequence);
+ return;
+
+ case GSPRM_RPC_FUNC_GET_GSP_STATIC_INFO:
+ gsp_rm_handle_get_static_info(ctx, sequence);
+ return;
+
+ case GSPRM_RPC_FUNC_GSP_RM_CONTROL:
+ gsp_rm_handle_rm_control(ctx, elem, sequence);
+ return;
+
+ default:
+ /* Synchronous RPC: echo function+sequence with success so the
+ * driver's rpcRecvPoll completes. */
+ rm->unknown_rpcs++;
+ nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u len=%u -> generic SUCCESS reply (unknown #%llu)",
+ gsp_rm_rpc_name(function), function, sequence,
+ hdr->length, rm->unknown_rpcs);
+ if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: reply send failed fn=%u", function);
+ else
+ rm->rpcs_replied++;
+ return;
+ }
+}
+
+/*
+ * Poll the command queue TX writePtr; consume and handle new records.
+ * A record spans elemCount consecutive queue elements.
+ */
+static void gsp_rm_poll_commands(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ struct gsprm_msgq_tx_header tx;
+ int ret;
+
+ ret = gsp_rm_read(ctx, gsp_rm_off_to_gpa(rm, rm->cmd_off), &tx, sizeof(tx));
+ if (ret) {
+ /* VM may be gone or device closed; stay quiet, retry later */
+ return;
+ }
+
+ if (tx.writePtr == rm->cmd_read)
+ return;
+
+ if (tx.writePtr < rm->cmd_read ||
+ (tx.writePtr - rm->cmd_read) > rm->cmd_msgcount) {
+ nv_vgpu_warn(ctx->vgpu, "GSPRM: cmd writePtr jumped %u -> %u, resyncing",
+ rm->cmd_read, tx.writePtr);
+ rm->cmd_read = tx.writePtr;
+ return;
+ }
+
+ while (rm->cmd_read < tx.writePtr) {
+ u8 *elem;
+ u64 elem_gpa = gsp_rm_off_to_gpa(rm, rm->cmd_off + rm->entry_off +
+ (u64)(rm->cmd_read % rm->cmd_msgcount) * GSPRM_QUEUE_ELEMENT_SIZE);
+ u32 signature, elem_count;
+ const struct gsprm_rpc_header *hdr;
+
+ elem = kmalloc(GSPRM_QUEUE_ELEMENT_SIZE, GFP_KERNEL);
+ if (!elem)
+ return;
+ ret = gsp_rm_read(ctx, elem_gpa, elem, GSPRM_QUEUE_ELEMENT_SIZE);
+ if (ret) {
+ kfree(elem);
+ return;
+ }
+
+ hdr = (const struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE);
+ signature = hdr->signature;
+ memcpy(&elem_count, elem + 0x28, 4);
+
+ if (signature != GSPRM_RPC_SIGNATURE || elem_count == 0 ||
+ elem_count > rm->cmd_msgcount) {
+ nv_vgpu_warn(ctx->vgpu, "GSPRM: bad element idx=%u sig=0x%x elemCount=%u fn=%u, skipping",
+ rm->cmd_read, signature, elem_count,
+ hdr->function);
+ rm->cmd_read++;
+ kfree(elem);
+ continue;
+ }
+
+ gsp_rm_handle_element(ctx, elem);
+ kfree(elem);
+
+ /* consume the whole record (all its elements) */
+ rm->cmd_read += elem_count;
+ }
+
+ /* Publish our command-queue read position into the status queue RX
+ * header (MSGQ_FLAGS_SWAP_RX: read pointers live in the peer's queue). */
+ gsp_rm_write(ctx, gsp_rm_off_to_gpa(rm, rm->stat_off) + 0x20,
+ &rm->cmd_read, sizeof(rm->cmd_read));
+}
+
+/* ============================================================
+ * Public interface
+ * ============================================================ */
+
+int gsp_rm_emu_init(struct nv_vgpu_device *vgpu, struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm;
+
+ rm = kzalloc(sizeof(*rm), GFP_KERNEL);
+ if (!rm)
+ return -ENOMEM;
+
+ rm->vgpu = vgpu;
+ ctx->rm_emu = rm;
+ return 0;
+}
+
+void gsp_rm_emu_fini(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+
+ if (!rm)
+ return;
+ kfree(rm->ptes);
+ kfree(rm);
+ ctx->rm_emu = NULL;
+}
+
+/*
+ * Reset the GSP-RM emulation state on GSP ENGINE reset (guest driver reboots
+ * the GSP on every probe/unload). The queue buffer GPAs from the previous
+ * boot are stale (freed by the guest) - drop all parsed state so the next
+ * Booter completion re-parses the chain.
+ */
+void gsp_rm_emu_reset(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+
+ if (!rm || !rm->parsed)
+ return;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: reset (rpcs=%llu replied=%llu unknown=%llu)",
+ rm->rpcs_seen, rm->rpcs_replied, rm->unknown_rpcs);
+
+ kfree(rm->ptes);
+ rm->ptes = NULL;
+ rm->parsed = false;
+ rm->active = false;
+ rm->init_done_sent = false;
+ rm->libos_args_gpa = 0;
+ rm->gsp_args_gpa = 0;
+ rm->cmd_read = 0;
+ rm->stat_write = 0;
+ rm->stat_seq = 0;
+ memset(&rm->mq, 0, sizeof(rm->mq));
+ rm->cmd_gpa = 0;
+ rm->stat_gpa = 0;
+ rm->cmd_size = 0;
+ rm->stat_size = 0;
+ rm->cmd_msgcount = 0;
+ rm->stat_msgcount = 0;
+}
+
+/*
+ * Called when the guest's Booter Load completes. The GSP MAILBOX0/1 now hold
+ * the libos init args GPA; parse the queue chain, link the status queue and
+ * emit GSP_INIT_DONE so the driver's kgspWaitForRmInitDone completes.
+ */
+void gsp_rm_emu_on_booter_done(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ u32 mb_lo, mb_hi;
+ u32 b_is_d3hot = 0;
+ int ret;
+
+ if (!rm || rm->parsed)
+ return;
+
+ mb_lo = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_MAILBOX0);
+ mb_hi = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_GSP_MAILBOX0 + 4);
+ rm->libos_args_gpa = ((u64)mb_hi << 32) | mb_lo;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: libos args at gpa=0x%llx (mailbox 0x%x:0x%x)",
+ rm->libos_args_gpa, mb_hi, mb_lo);
+
+ if (!rm->libos_args_gpa) {
+ nv_vgpu_warn(ctx->vgpu, "GSPRM: libos args GPA is 0, will retry");
+ return;
+ }
+
+ ret = gsp_rm_parse_queue_chain(ctx);
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: queue chain parse failed: %d", ret);
+ return;
+ }
+
+ ret = gsp_rm_link_status_queue(ctx);
+ if (ret)
+ return;
+
+ rm->parsed = true;
+ rm->active = true;
+
+ /* Emit GSP_INIT_DONE (function 0x1001, sequence 0, init_done_v17_00) */
+ ret = gsp_rm_send(ctx, GSPRM_RPC_EVENT_GSP_INIT_DONE, 0, 0,
+ &b_is_d3hot, sizeof(b_is_d3hot));
+ if (ret) {
+ nv_vgpu_err(ctx->vgpu, "GSPRM: GSP_INIT_DONE send failed: %d", ret);
+ return;
+ }
+ rm->init_done_sent = true;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: GSP_INIT_DONE emitted, RPC loop active");
+}
+
+void gsp_rm_emu_poll(struct gsp_emu_context *ctx)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+
+ if (!rm)
+ return;
+
+ /* Retry chain parsing if the first attempt at BOOTER_DONE failed
+ * (e.g. mailbox not yet visible). Boot stage 2 == BOOTER_DONE. */
+ if (!rm->parsed) {
+ if (ctx->boot_stage == 2)
+ gsp_rm_emu_on_booter_done(ctx);
+ return;
+ }
+
+ if (!rm->active)
+ return;
+
+ gsp_rm_poll_commands(ctx);
+}
diff --git a/kernel/gsp_rm_emulation.h b/kernel/gsp_rm_emulation.h
new file mode 100644
index 0000000..b1a7259
--- /dev/null
+++ b/kernel/gsp_rm_emulation.h
@@ -0,0 +1,151 @@
+/*
+ * GSP-RM Emulation (physical-path) for Host-Side vGPU
+ *
+ * Emulates the GSP-RM firmware's msgq-based RPC channel for a guest driver
+ * that believes it is driving a real physical GPU. The guest driver:
+ * - boots the "GSP" (falcon stages emulated in gsp_emulation.c)
+ * - programs libos init args address into GSP MAILBOX0/1
+ * - expects GSP-RM to msgqInit the status queue in *guest system memory*
+ * (shared command/status queue buffer described by GSP_ARGUMENTS_CACHED)
+ * - polls the status queue for NV_VGPU_MSG_EVENT_GSP_INIT_DONE and then
+ * issues the RM RPC sequence on the command queue.
+ *
+ * Since there is no real GSP engine, this module performs the "virtual DMA"
+ * on behalf of the device using vfio_dma_rw() (CPU copies through the VFIO
+ * container's IOMMU mappings, IOVA == guest physical address).
+ */
+
+#ifndef _GSP_RM_EMULATION_H_
+#define _GSP_RM_EMULATION_H_
+
+#include <linux/types.h>
+
+#include "nvidia-vgpu-vfio.h"
+
+struct gsp_emu_context;
+
+/* Wire constants (from NVIDIA OpenRM 570.144 msgq/rpc headers - the guest
+ * driver is the 570.144 stock runfile, whose GSP_MSG_QUEUE_ELEMENT has NO
+ * MCTP/NVDM transport headers: authTag[16]+aad[16]+checkSum+seqNum+elemCount
+ * +rpc@0x30). */
+#define GSPRM_MSGQ_VERSION 0
+#define GSPRM_MSGQ_FLAGS_SWAP_RX 1
+#define GSPRM_RPC_SIGNATURE 0x43505256 /* NV_VGPU_MSG_SIGNATURE_VALID */
+#define GSPRM_RPC_HEADER_VERSION 0x03000000 /* MAJOR_TOT(3)<<24|MINOR_TOT(0)<<16 */
+
+/* RPC function/event numbers (rpc_global_enums.h) */
+#define GSPRM_RPC_FUNC_SET_GUEST_SYSTEM_INFO 1
+#define GSPRM_RPC_FUNC_ALLOC_ROOT 2
+#define GSPRM_RPC_FUNC_GET_GSP_STATIC_INFO 65
+#define GSPRM_RPC_FUNC_GSP_SET_SYSTEM_INFO 72
+#define GSPRM_RPC_FUNC_SET_REGISTRY 73
+#define GSPRM_RPC_FUNC_CONTINUATION_RECORD 71
+#define GSPRM_RPC_FUNC_GSP_RM_CONTROL 76
+#define GSPRM_RPC_EVENT_GSP_INIT_DONE 0x1001
+
+/* rpc_set_guest_system_info_v03_00: version fields the driver reads back
+ * from the reply to negotiate the RPC interface version (ipVersion). */
+#define GSPRM_SET_GUEST_INFO_SIZE 0x324
+#define GSPRM_VGX_VERSION_MAJOR 0x29
+#define GSPRM_VGX_VERSION_MINOR 0x0C
+
+/* "RMARGS" packed big-endian into u64 by _kgspGenerateInitArgId() */
+#define GSPRM_LIBOS_ID_RMARGS 0x524D41524753ULL
+
+#define GSPRM_MAX_LIBOS_REGIONS 32
+#define GSPRM_QUEUE_ELEMENT_SIZE 0x1000
+#define GSPRM_STATUS_QUEUE_SIZE 0x40000
+
+/* msgqTxHeader (msgq_priv.h) */
+struct gsprm_msgq_tx_header {
+ u32 version;
+ u32 size;
+ u32 msgSize;
+ u32 msgCount;
+ u32 writePtr;
+ u32 flags;
+ u32 rxHdrOff;
+ u32 entryOff;
+};
+
+/* rpc_message_header_v03_00 (g_rpc-message-header.h) */
+struct gsprm_rpc_header {
+ u32 header_version;
+ u32 signature;
+ u32 length;
+ u32 function;
+ u32 rpc_result;
+ u32 rpc_result_private;
+ u32 sequence;
+ u32 spare; /* rpc_message_rpc_union_field_v (spare/cpuRmGfid) */
+};
+
+/* LibosMemoryRegionInitArgument (libos_init_args.h) - natural alignment, 32B */
+struct gsprm_libos_region {
+ u64 id8;
+ u64 pa;
+ u64 size;
+ u8 kind;
+ u8 loc;
+ u8 pad[6];
+};
+
+/* MESSAGE_QUEUE_INIT_ARGUMENTS (gsp_init_args.h), x86_64 NvLength=u64 */
+struct gsprm_mq_init_args {
+ u64 sharedMemPhysAddr;
+ u32 pageTableEntryCount;
+ u32 pad0;
+ u64 cmdQueueOffset;
+ u64 statQueueOffset;
+ u64 queueElementHdrSize;
+ u64 queueElementSizeMin;
+ u64 queueElementSizeMax;
+ u32 queueHeaderAlign;
+ u32 queueElementAlign;
+};
+
+struct gsp_rm_emu {
+ struct nv_vgpu_device *vgpu;
+
+ bool parsed; /* queue chain parsed + status queue linked */
+ bool active;
+
+ u64 libos_args_gpa; /* from GSP MAILBOX0/1 */
+ u64 gsp_args_gpa; /* RMARGS region pa */
+
+ /* queue buffer description (from GSP_ARGUMENTS_CACHED) */
+ struct gsprm_mq_init_args mq;
+ u64 *ptes; /* pageTableEntryCount guest-phys addrs */
+
+ /* computed queue geometry. cmd_off/stat_off are offsets into the shared
+ * buffer (linear); GPAs must be computed per-access via the page table
+ * (buffer is NV_MEMORY_NONCONTIGUOUS). */
+ u64 cmd_off;
+ u64 stat_off;
+ u64 cmd_gpa; /* GPA of command queue base (diagnostic) */
+ u64 stat_gpa; /* GPA of status queue base (diagnostic) */
+ u32 cmd_size; /* bytes */
+ u32 stat_size; /* bytes */
+ u32 cmd_msgcount;
+ u32 stat_msgcount;
+ u32 entry_off; /* 0x1000 (msgq entryOff, both queues) */
+
+ /* GSP-side cursors */
+ u32 cmd_read; /* elements consumed from command queue */
+ u32 stat_write; /* elements written to status queue */
+ u32 stat_seq; /* status record sequence numbers */
+ bool init_done_sent;
+
+ /* stats */
+ u64 rpcs_seen;
+ u64 rpcs_replied;
+ u64 unknown_rpcs;
+};
+
+int gsp_rm_emu_init(struct nv_vgpu_device *vgpu, struct gsp_emu_context *ctx);
+void gsp_rm_emu_fini(struct gsp_emu_context *ctx);
+void gsp_rm_emu_reset(struct gsp_emu_context *ctx);
+void gsp_rm_emu_on_booter_done(struct gsp_emu_context *ctx);
+void gsp_rm_emu_poll(struct gsp_emu_context *ctx);
+
+#endif /* _GSP_RM_EMULATION_H_ */
diff --git a/kernel/gsp_rm_tu104.h b/kernel/gsp_rm_tu104.h
new file mode 100644
index 0000000..dc08b1d
--- /dev/null
+++ b/kernel/gsp_rm_tu104.h
@@ -0,0 +1,694 @@
+/*
+ * TU104 Hardware Tables for GSP-RM Emulation
+ *
+ * Static device/engine/interrupt tables for the emulated TU104 GPU.
+ * Data extracted from NVIDIA driver material (ext-tree) and hardware
+ * reference headers (hwref/turing/tu104).
+ *
+ * These tables replace what the real GSP-RM firmware would read from
+ * PTOP hardware registers during boot.
+ */
+
+#ifndef _GSP_RM_TU104_H_
+#define _GSP_RM_TU104_H_
+
+#include <linux/types.h>
+
+/* ============================================================
+ * Wire structures (from 570.144 reference, ABI-checked)
+ * ============================================================ */
+
+/* NV2080_CTRL_FIFO_DEVICE_ENTRY (ctrl2080fifo.h) - 100 bytes */
+struct tu104_fifo_device_entry {
+ u32 engineData[16]; /* ENGINE_INFO_TYPE_* indexed */
+ u32 pbdmaIds[2];
+ u32 pbdmaFaultIds[2];
+ u32 numPbdmas;
+ char engineName[16];
+};
+
+/* NV2080_CTRL_FIFO_GET_DEVICE_INFO_TABLE_PARAMS - 3212 bytes max */
+#define TU104_FIFO_TABLE_MAX_ENTRIES 32
+#define TU104_FIFO_TABLE_MAX_DEVICES 256
+
+struct tu104_fifo_get_device_info_params {
+ u32 baseIndex;
+ u32 numEntries;
+ u8 bMore;
+ u8 _pad[3];
+ struct tu104_fifo_device_entry entries[TU104_FIFO_TABLE_MAX_ENTRIES];
+};
+
+/* NV2080_CTRL_INTERNAL_DEVICE_INFO (ctrl2080internal.h) - 48 bytes */
+struct tu104_internal_device_info {
+ u32 faultId;
+ u32 instanceId;
+ u32 typeEnum;
+ u32 resetId;
+ u32 devicePriBase;
+ u32 isEngine;
+ u32 rlEngId;
+ u32 runlistPriBase;
+ u32 groupId;
+ u32 ginTargetId;
+ u32 deviceBroadcastPriBase;
+ u32 groupLocalInstanceId;
+};
+
+/* NV2080_CTRL_INTERNAL_GET_DEVICE_INFO_TABLE_PARAMS - up to 512 entries */
+#define TU104_INTERNAL_TABLE_MAX_ENTRIES 512
+
+struct tu104_internal_get_device_info_params {
+ u32 numEntries;
+ struct tu104_internal_device_info deviceInfoTable[TU104_INTERNAL_TABLE_MAX_ENTRIES];
+};
+
+/* NV2080_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE_PARAMS */
+#define TU104_INTR_TABLE_MAX_SIZE 128
+
+struct tu104_intr_table_entry {
+ u16 engineIdx;
+ u16 _pad0;
+ u32 pmcIntrMask;
+ u32 vectorStall;
+ u32 vectorNonStall;
+};
+
+struct tu104_intr_get_kernel_table_params {
+ u32 tableLen;
+ struct tu104_intr_table_entry table[TU104_INTR_TABLE_MAX_SIZE];
+ u32 subtreeMap[7]; /* NV2080_INTR_CATEGORY_* count */
+};
+
+/* NV2080_CTRL_GPU_CONSTRUCTED_FALCON_INFO (ctrl2080gpu.h) - 20 bytes */
+struct tu104_constructed_falcon_info {
+ u32 engDesc;
+ u32 ctxAttr;
+ u32 ctxBufferSize;
+ u32 addrSpaceList;
+ u32 registerBase;
+};
+
+#define TU104_MAX_CONSTRUCTED_FALCONS 0x40
+
+struct tu104_get_constructed_falcon_info_params {
+ u32 numConstructedFalcons;
+ struct tu104_constructed_falcon_info constructedFalconsTable[TU104_MAX_CONSTRUCTED_FALCONS];
+};
+
+/* ============================================================
+ * ENGINE_INFO_TYPE indices (engine_info.h) - frozen vGPU ABI
+ * ============================================================ */
+#define EI_ENG_DESC 0
+#define EI_FIFO_TAG 1
+#define EI_RM_ENGINE_TYPE 2
+#define EI_RUNLIST 3
+#define EI_MMU_FAULT_ID 4
+#define EI_RC_MASK 5
+#define EI_RESET 6
+#define EI_INTR 7
+#define EI_MC 8
+#define EI_DEV_TYPE_ENUM 9
+#define EI_INSTANCE_ID 10
+#define EI_RUNLIST_PRI_BASE 11
+#define EI_IS_HOST_DRIVEN_ENGINE 12
+#define EI_RUNLIST_ENGINE_ID 13
+#define EI_CHRAM_PRI_BASE 14
+#define EI_KERNEL_RM_MAX 15
+
+/* ============================================================
+ * ENG_DESC values (MKENGDESC = classId << 8 | instance)
+ * ============================================================ */
+#define TU104_ENG_GR0 0xd334df00 /* classId(Graphics)=0xd334df, inst 0 */
+#define TU104_ENG_CE0 0x793ceb00 /* classId(OBJCE)=0x793ceb, inst 0 */
+#define TU104_ENG_CE1 0x793ceb01
+#define TU104_ENG_CE2 0x793ceb02
+#define TU104_ENG_CE3 0x793ceb03
+#define TU104_ENG_CE4 0x793ceb04
+#define TU104_ENG_NVDEC0 0x8f99e100 /* classId(OBJBSP)=0x8f99e1, inst 0 */
+#define TU104_ENG_NVDEC1 0x8f99e101
+#define TU104_ENG_NVENC0 0xe97b6c00 /* classId(OBJMSENC)=0xe97b6c, inst 0 */
+#define TU104_ENG_SEC2 0x28c40800 /* classId(OBJSEC2)=0x28c408, inst 0 */
+#define TU104_ENG_NVJPG 0x2b3a5400 /* classId(OBJNVJPG)=0x2b3a54, inst 0 */
+#define TU104_ENG_SW 0x95a6f500 /* classId(OBJSWENG)=0x95a6f5, inst 0 */
+#define TU104_ENG_FBFLCN 0x8a20bf00 /* classId(OBJFBFLCN)=0x8a20bf, inst 0 */
+
+/* ============================================================
+ * RM_ENGINE_TYPE values (gpu_engine_type.h)
+ * ============================================================ */
+#define TU104_RM_ENGINE_GR0 0x01
+#define TU104_RM_ENGINE_CE0 0x09
+#define TU104_RM_ENGINE_CE1 0x0a
+#define TU104_RM_ENGINE_CE2 0x0b
+#define TU104_RM_ENGINE_CE3 0x0c
+#define TU104_RM_ENGINE_CE4 0x0d
+#define TU104_RM_ENGINE_NVDEC0 0x1d
+#define TU104_RM_ENGINE_NVDEC1 0x1e
+#define TU104_RM_ENGINE_NVENC0 0x25
+#define TU104_RM_ENGINE_SW 0x2d
+#define TU104_RM_ENGINE_SEC2 0x31
+#define TU104_RM_ENGINE_HOST 0x32
+#define TU104_RM_ENGINE_PMU 0x34
+#define TU104_RM_ENGINE_FBFLCN 0x35
+#define TU104_RM_ENGINE_NVJPG0 0x36
+
+/* ============================================================
+ * MC_ENGINE_IDX values (engine_idx.h)
+ * ============================================================ */
+#define TU104_MC_ENGINE_GR 84
+#define TU104_MC_ENGINE_CE0 15
+#define TU104_MC_ENGINE_CE1 16
+#define TU104_MC_ENGINE_CE2 17
+#define TU104_MC_ENGINE_CE3 18
+#define TU104_MC_ENGINE_CE4 19
+#define TU104_MC_ENGINE_NVDEC0 65
+#define TU104_MC_ENGINE_NVDEC1 66
+#define TU104_MC_ENGINE_NVENC0 38
+#define TU104_MC_ENGINE_SEC2 47
+#define TU104_MC_ENGINE_NVJPG 51
+
+/* ============================================================
+ * NV_PFAULT_MMU_ENG_ID values (tu104/dev_fault.h)
+ * ============================================================ */
+#define TU104_FAULT_ID_GR 64
+#define TU104_FAULT_ID_CE0 15
+#define TU104_FAULT_ID_CE1 16
+#define TU104_FAULT_ID_CE2 17
+#define TU104_FAULT_ID_CE3 18
+#define TU104_FAULT_ID_CE4 19
+#define TU104_FAULT_ID_NVDEC0 10
+#define TU104_FAULT_ID_NVDEC1 25
+#define TU104_FAULT_ID_NVENC0 11
+#define TU104_FAULT_ID_SEC2 14
+#define TU104_FAULT_ID_NVJPG0 24
+#define TU104_FAULT_ID_GSP 2
+#define TU104_FAULT_ID_PMU 6
+
+/* PBDMA fault ID base (NV_PFIFO_CFG0_PBDMA_FAULT_ID_INIT = 32) */
+#define TU104_PBDMA_FAULT_ID_BASE 32
+#define TU104_NUM_PBDMAS 12
+
+/* ============================================================
+ * ROBUST_CHANNEL values (rc.h)
+ * ============================================================ */
+#define TU104_RC_GRAPHICS 40
+#define TU104_RC_CE0 22
+#define TU104_RC_CE1 23
+#define TU104_RC_CE2 24
+#define TU104_RC_CE3 25
+#define TU104_RC_CE4 26
+#define TU104_RC_NVDEC0 14
+#define TU104_RC_NVDEC1 15
+#define TU104_RC_NVENC0 11
+#define TU104_RC_SEC2 2
+#define TU104_RC_NVJPG0 32
+
+/* ============================================================
+ * NV_PTOP_DEVICE_INFO_TYPE_ENUM (tu104/dev_top.h)
+ * ============================================================ */
+#define TU104_PTOP_TYPE_GRAPHICS 0
+#define TU104_PTOP_TYPE_LCE 19
+#define TU104_PTOP_TYPE_NVDEC 16
+#define TU104_PTOP_TYPE_NVENC 14
+#define TU104_PTOP_TYPE_NVJPG 21
+#define TU104_PTOP_TYPE_SEC 13
+#define TU104_PTOP_TYPE_GSP 20
+#define TU104_PTOP_TYPE_IOCTRL 18
+
+/* ============================================================
+ * FIFO_TAG values (hardware engine enum, 4 bits)
+ * ============================================================ */
+#define TU104_TAG_GR 0
+#define TU104_TAG_CE0 1
+#define TU104_TAG_CE1 2
+#define TU104_TAG_CE2 3
+#define TU104_TAG_CE3 4
+#define TU104_TAG_CE4 5
+#define TU104_TAG_NVDEC0 10
+#define TU104_TAG_NVDEC1 11
+#define TU104_TAG_NVENC0 13
+#define TU104_TAG_NVJPG 14
+#define TU104_TAG_SEC2 15
+
+/* ============================================================
+ * TU104 FIFO Engine Table (11 hardware engines + 1 SW)
+ *
+ * Runlist assignment: each engine gets its own runlist.
+ * Runlist 0=GR, 1-5=CE0-CE4, 6-7=NVDEC0-1, 8=NVENC0, 9=SEC2, 10=NVJPG
+ * PBDMA assignment: 1:1 with runlists (PBDMA N services runlist N).
+ * SW engine shares NVJPG's runlist/PBDMA (per fifoConstructSwEngine).
+ * ============================================================ */
+
+static const struct tu104_fifo_device_entry tu104_engine_table[] = {
+ /* [0] GR0 - Graphics */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_GR0,
+ [EI_FIFO_TAG] = TU104_TAG_GR,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_GR0,
+ [EI_RUNLIST] = 0,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_GR,
+ [EI_RC_MASK] = TU104_RC_GRAPHICS,
+ [EI_RESET] = 12, /* NV_PMC_ENABLE_PGRAPH bit */
+ [EI_INTR] = 0,
+ [EI_MC] = TU104_MC_ENGINE_GR,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_GRAPHICS,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {0, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 0, 0},
+ .numPbdmas = 1,
+ .engineName = "GRAPHICS",
+ },
+ /* [1] CE0 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_CE0,
+ [EI_FIFO_TAG] = TU104_TAG_CE0,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_CE0,
+ [EI_RUNLIST] = 1,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_CE0,
+ [EI_RC_MASK] = TU104_RC_CE0,
+ [EI_RESET] = 1,
+ [EI_INTR] = 1,
+ [EI_MC] = TU104_MC_ENGINE_CE0,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_LCE,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {1, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 1, 0},
+ .numPbdmas = 1,
+ .engineName = "CE0",
+ },
+ /* [2] CE1 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_CE1,
+ [EI_FIFO_TAG] = TU104_TAG_CE1,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_CE1,
+ [EI_RUNLIST] = 2,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_CE1,
+ [EI_RC_MASK] = TU104_RC_CE1,
+ [EI_RESET] = 2,
+ [EI_INTR] = 2,
+ [EI_MC] = TU104_MC_ENGINE_CE1,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_LCE,
+ [EI_INSTANCE_ID] = 1,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {2, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 2, 0},
+ .numPbdmas = 1,
+ .engineName = "CE1",
+ },
+ /* [3] CE2 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_CE2,
+ [EI_FIFO_TAG] = TU104_TAG_CE2,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_CE2,
+ [EI_RUNLIST] = 3,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_CE2,
+ [EI_RC_MASK] = TU104_RC_CE2,
+ [EI_RESET] = 3,
+ [EI_INTR] = 3,
+ [EI_MC] = TU104_MC_ENGINE_CE2,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_LCE,
+ [EI_INSTANCE_ID] = 2,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {3, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 3, 0},
+ .numPbdmas = 1,
+ .engineName = "CE2",
+ },
+ /* [4] CE3 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_CE3,
+ [EI_FIFO_TAG] = TU104_TAG_CE3,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_CE3,
+ [EI_RUNLIST] = 4,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_CE3,
+ [EI_RC_MASK] = TU104_RC_CE3,
+ [EI_RESET] = 4,
+ [EI_INTR] = 4,
+ [EI_MC] = TU104_MC_ENGINE_CE3,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_LCE,
+ [EI_INSTANCE_ID] = 3,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {4, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 4, 0},
+ .numPbdmas = 1,
+ .engineName = "CE3",
+ },
+ /* [5] CE4 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_CE4,
+ [EI_FIFO_TAG] = TU104_TAG_CE4,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_CE4,
+ [EI_RUNLIST] = 5,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_CE4,
+ [EI_RC_MASK] = TU104_RC_CE4,
+ [EI_RESET] = 5,
+ [EI_INTR] = 5,
+ [EI_MC] = TU104_MC_ENGINE_CE4,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_LCE,
+ [EI_INSTANCE_ID] = 4,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {5, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 5, 0},
+ .numPbdmas = 1,
+ .engineName = "CE4",
+ },
+ /* [6] NVDEC0 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_NVDEC0,
+ [EI_FIFO_TAG] = TU104_TAG_NVDEC0,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_NVDEC0,
+ [EI_RUNLIST] = 6,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_NVDEC0,
+ [EI_RC_MASK] = TU104_RC_NVDEC0,
+ [EI_RESET] = 14,
+ [EI_INTR] = 6,
+ [EI_MC] = TU104_MC_ENGINE_NVDEC0,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_NVDEC,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {6, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 6, 0},
+ .numPbdmas = 1,
+ .engineName = "NVDEC0",
+ },
+ /* [7] NVDEC1 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_NVDEC1,
+ [EI_FIFO_TAG] = TU104_TAG_NVDEC1,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_NVDEC1,
+ [EI_RUNLIST] = 7,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_NVDEC1,
+ [EI_RC_MASK] = TU104_RC_NVDEC1,
+ [EI_RESET] = 15,
+ [EI_INTR] = 7,
+ [EI_MC] = TU104_MC_ENGINE_NVDEC1,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_NVDEC,
+ [EI_INSTANCE_ID] = 1,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {7, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 7, 0},
+ .numPbdmas = 1,
+ .engineName = "NVDEC1",
+ },
+ /* [8] NVENC0 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_NVENC0,
+ [EI_FIFO_TAG] = TU104_TAG_NVENC0,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_NVENC0,
+ [EI_RUNLIST] = 8,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_NVENC0,
+ [EI_RC_MASK] = TU104_RC_NVENC0,
+ [EI_RESET] = 17,
+ [EI_INTR] = 8,
+ [EI_MC] = TU104_MC_ENGINE_NVENC0,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_NVENC,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {8, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 8, 0},
+ .numPbdmas = 1,
+ .engineName = "NVENC0",
+ },
+ /* [9] SEC2 */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_SEC2,
+ [EI_FIFO_TAG] = TU104_TAG_SEC2,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_SEC2,
+ [EI_RUNLIST] = 9,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_SEC2,
+ [EI_RC_MASK] = TU104_RC_SEC2,
+ [EI_RESET] = 19,
+ [EI_INTR] = 9,
+ [EI_MC] = TU104_MC_ENGINE_SEC2,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_SEC,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {9, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 9, 0},
+ .numPbdmas = 1,
+ .engineName = "SEC2",
+ },
+ /* [10] NVJPG */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_NVJPG,
+ [EI_FIFO_TAG] = TU104_TAG_NVJPG,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_NVJPG0,
+ [EI_RUNLIST] = 10,
+ [EI_MMU_FAULT_ID] = TU104_FAULT_ID_NVJPG0,
+ [EI_RC_MASK] = TU104_RC_NVJPG0,
+ [EI_RESET] = 20,
+ [EI_INTR] = 10,
+ [EI_MC] = TU104_MC_ENGINE_NVJPG,
+ [EI_DEV_TYPE_ENUM] = TU104_PTOP_TYPE_NVJPG,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 1,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {10, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 10, 0},
+ .numPbdmas = 1,
+ .engineName = "NVJPG",
+ },
+ /* [11] SW (software engine - shares NVJPG's runlist per fifoConstructSwEngine) */
+ {
+ .engineData = {
+ [EI_ENG_DESC] = TU104_ENG_SW,
+ [EI_FIFO_TAG] = 0xffffffff,
+ [EI_RM_ENGINE_TYPE] = TU104_RM_ENGINE_SW,
+ [EI_RUNLIST] = 10, /* NVJPG's runlist */
+ [EI_MMU_FAULT_ID] = 0xffffffff,
+ [EI_RC_MASK] = 0,
+ [EI_RESET] = 0,
+ [EI_INTR] = 0,
+ [EI_MC] = 0,
+ [EI_DEV_TYPE_ENUM] = 0,
+ [EI_INSTANCE_ID] = 0,
+ [EI_RUNLIST_PRI_BASE] = 0,
+ [EI_IS_HOST_DRIVEN_ENGINE] = 0,
+ [EI_RUNLIST_ENGINE_ID] = 0,
+ [EI_CHRAM_PRI_BASE] = 0,
+ [EI_KERNEL_RM_MAX] = 0,
+ },
+ .pbdmaIds = {10, 0},
+ .pbdmaFaultIds = {TU104_PBDMA_FAULT_ID_BASE + 10, 0},
+ .numPbdmas = 1,
+ .engineName = "SOFTWARE",
+ },
+};
+
+#define TU104_ENGINE_TABLE_SIZE 12
+
+/* ============================================================
+ * TU104 Interrupt Table (from intr_phys_tu102.c, kernel table)
+ *
+ * These are the CPU-RM (guest) visible interrupts.
+ * NV_PMC_INTR_INVALID_MASK = 0 (no PMC mask for these).
+ * NV_INTR_VECTOR_INVALID = 0xFFFFFFFF.
+ * ============================================================ */
+
+#define TU104_NV_INTR_VECTOR_INVALID 0xFFFFFFFF
+
+static const struct tu104_intr_table_entry tu104_intr_kernel_table[] = {
+ /* MC_ENGINE_IDX_REPLAYABLE_FAULT = 59 */
+ { .engineIdx = 59, .pmcIntrMask = 0,
+ .vectorStall = 64, /* NV_PFB_PRI_MMU_INT_VECTOR_FAULT_NOTIFY_REPLAYABLE */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_REPLAYABLE_FAULT_ERROR = 62 */
+ { .engineIdx = 62, .pmcIntrMask = 0,
+ .vectorStall = 131, /* NV_PFB_PRI_MMU_INT_VECTOR_FAULT_ERROR_REPLAYABLE */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_ACCESS_CNTR = 60 */
+ { .engineIdx = 60, .pmcIntrMask = 0,
+ .vectorStall = 72, /* HUB_ACCESS_CNTR_INTR_VECTOR */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_CPU_DOORBELL = 73 */
+ { .engineIdx = 73, .pmcIntrMask = 0,
+ .vectorStall = 129, /* NV_CTRL_CPU_DOORBELL_VECTORID_VALUE_CONSTANT */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_PRIV_DOORBELL = 74 */
+ { .engineIdx = 74, .pmcIntrMask = 0,
+ .vectorStall = 130, /* NV_CTRL_VIRTUAL_INTR_VECTORID_VALUE_CONSTANT */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_GR_FECS_LOG = 156 */
+ { .engineIdx = 156, .pmcIntrMask = 0,
+ .vectorStall = TU104_NV_INTR_VECTOR_INVALID,
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_GSP = 50 */
+ { .engineIdx = 50, .pmcIntrMask = 0,
+ .vectorStall = 155, /* GSP_INTR_0_VECTOR */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+ /* MC_ENGINE_IDX_DISP = 2 */
+ { .engineIdx = 2, .pmcIntrMask = 0,
+ .vectorStall = 154, /* PDISP_VECTOR */
+ .vectorNonStall = TU104_NV_INTR_VECTOR_INVALID },
+};
+
+#define TU104_INTR_KERNEL_TABLE_SIZE 8
+
+/* ============================================================
+ * TU104 Constructed Falcon Table
+ *
+ * Only FBFLCN (framebuffer falcon) is a "constructed falcon" on Turing
+ * (SEC2, GSP, PMU are skipped as they have their own kernel objects).
+ * ============================================================ */
+
+static const struct tu104_constructed_falcon_info tu104_falcon_table[] = {
+ {
+ .engDesc = TU104_ENG_FBFLCN,
+ .ctxAttr = 0,
+ .ctxBufferSize = 0x400, /* 1KB context buffer */
+ .addrSpaceList = 0,
+ .registerBase = 0x009a4000, /* NV_PFBFALCON base */
+ },
+};
+
+#define TU104_FALCON_TABLE_SIZE 1
+
+/* ============================================================
+ * TU104 Internal Device Info Table
+ *
+ * Engines + key non-engine devices. The guest driver queries this
+ * by typeEnum to find device register bases and fault IDs.
+ * ============================================================ */
+
+static const struct tu104_internal_device_info tu104_internal_device_table[] = {
+ /* Engines (isEngine=1) */
+ { .faultId = TU104_FAULT_ID_GR, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_GRAPHICS, .resetId = 12, .devicePriBase = 0x00400000, .isEngine = 1, .rlEngId = 0, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+ { .faultId = TU104_FAULT_ID_CE0, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_LCE, .resetId = 1, .devicePriBase = 0x00040000, .isEngine = 1, .rlEngId = 1, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+ { .faultId = TU104_FAULT_ID_CE1, .instanceId = 1, .typeEnum = TU104_PTOP_TYPE_LCE, .resetId = 2, .devicePriBase = 0x00041000, .isEngine = 1, .rlEngId = 2, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 1 },
+ { .faultId = TU104_FAULT_ID_CE2, .instanceId = 2, .typeEnum = TU104_PTOP_TYPE_LCE, .resetId = 3, .devicePriBase = 0x00042000, .isEngine = 1, .rlEngId = 3, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 2 },
+ { .faultId = TU104_FAULT_ID_CE3, .instanceId = 3, .typeEnum = TU104_PTOP_TYPE_LCE, .resetId = 4, .devicePriBase = 0x00043000, .isEngine = 1, .rlEngId = 4, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 3 },
+ { .faultId = TU104_FAULT_ID_CE4, .instanceId = 4, .typeEnum = TU104_PTOP_TYPE_LCE, .resetId = 5, .devicePriBase = 0x00044000, .isEngine = 1, .rlEngId = 5, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 4 },
+ { .faultId = TU104_FAULT_ID_NVDEC0, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_NVDEC, .resetId = 14, .devicePriBase = 0x00060000, .isEngine = 1, .rlEngId = 6, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+ { .faultId = TU104_FAULT_ID_NVDEC1, .instanceId = 1, .typeEnum = TU104_PTOP_TYPE_NVDEC, .resetId = 15, .devicePriBase = 0x00061000, .isEngine = 1, .rlEngId = 7, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 1 },
+ { .faultId = TU104_FAULT_ID_NVENC0, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_NVENC, .resetId = 17, .devicePriBase = 0x00070000, .isEngine = 1, .rlEngId = 8, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+ { .faultId = TU104_FAULT_ID_SEC2, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_SEC, .resetId = 19, .devicePriBase = 0x00840000, .isEngine = 1, .rlEngId = 9, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+ { .faultId = TU104_FAULT_ID_NVJPG0, .instanceId = 0, .typeEnum = TU104_PTOP_TYPE_NVJPG, .resetId = 20, .devicePriBase = 0x00080000, .isEngine = 1, .rlEngId = 10, .runlistPriBase = 0, .groupId = 0, .ginTargetId = 0, .deviceBroadcastPriBase = 0, .groupLocalInstanceId = 0 },
+};
+
+#define TU104_INTERNAL_DEVICE_TABLE_SIZE 11
+
+/* ============================================================
+ * GspStaticConfigInfo field offsets (ABI-verified, 1656 bytes total)
+ * ============================================================ */
+#define TU104_GSCI_OFF_grCapsBits 0 /* 23 bytes */
+#define TU104_GSCI_OFF_gidInfo 24 /* 268 bytes */
+#define TU104_GSCI_OFF_SKUInfo 292 /* 48 bytes */
+#define TU104_GSCI_OFF_fbRegionInfoParams 344 /* 776 bytes */
+#define TU104_GSCI_OFF_sriovCaps 1120 /* 80 bytes */
+#define TU104_GSCI_OFF_sriovMaxGfid 1200 /* 4 bytes */
+#define TU104_GSCI_OFF_engineCaps 1204 /* 12 bytes */
+#define TU104_GSCI_OFF_poisonFuseEnabled 1216 /* 1 byte */
+#define TU104_GSCI_OFF_fb_length 1224 /* 8 bytes */
+#define TU104_GSCI_OFF_fbio_mask 1232 /* 8 bytes */
+#define TU104_GSCI_OFF_fb_bus_width 1240 /* 4 bytes */
+#define TU104_GSCI_OFF_fb_ram_type 1244 /* 4 bytes */
+#define TU104_GSCI_OFF_fbp_mask 1248 /* 8 bytes */
+#define TU104_GSCI_OFF_l2_cache_size 1256 /* 4 bytes */
+#define TU104_GSCI_OFF_gpuNameString 1260 /* 64 bytes */
+#define TU104_GSCI_OFF_gpuShortNameString 1324 /* 64 bytes */
+#define TU104_GSCI_OFF_gpuNameString_Unicode 1388 /* 128 bytes */
+#define TU104_GSCI_OFF_bIsMobile 1524 /* 1 byte */
+#define TU104_GSCI_OFF_bar1PdeBase 1536 /* 8 bytes */
+#define TU104_GSCI_OFF_bar2PdeBase 1544 /* 8 bytes */
+#define TU104_GSCI_OFF_bVbiosValid 1552 /* 1 byte */
+#define TU104_GSCI_OFF_vbiosSubVendor 1556 /* 4 bytes */
+#define TU104_GSCI_OFF_vbiosSubDevice 1560 /* 4 bytes */
+#define TU104_GSCI_OFF_hInternalClient 1600 /* 4 bytes */
+#define TU104_GSCI_OFF_hInternalDevice 1604 /* 4 bytes */
+#define TU104_GSCI_OFF_hInternalSubdevice 1608 /* 4 bytes */
+#define TU104_GSCI_OFF_ecidInfo 1616 /* 24 bytes */
+#define TU104_GSCI_OFF_fwWprLayoutOffset 1640 /* 16 bytes */
+#define TU104_GSCI_SIZE 1656
+
+/* Internal handle values (g_gpu_nvoc.h, g_resserv_nvoc.h) */
+#define TU104_H_INTERNAL_CLIENT 0xC1E00000
+#define TU104_H_INTERNAL_DEVICE 0xABCD0080
+#define TU104_H_INTERNAL_SUBDEVICE 0xABCD2080
+
+/* GSP_RM_CONTROL sub-commands */
+#define TU104_CTRL_INTERNAL_GET_DEVICE_INFO_TABLE 0x20800a40
+#define TU104_CTRL_FIFO_GET_DEVICE_INFO_TABLE 0x20801112
+#define TU104_CTRL_INTERNAL_INTR_GET_KERNEL_TABLE 0x20800a5c
+#define TU104_CTRL_GPU_GET_CONSTRUCTED_FALCON_INFO 0x208001b0
+
+/* FB region info params: numFBRegions(4) + 16 × 48B entries = 772 → aligned 776 */
+struct tu104_fb_region {
+ u64 base;
+ u64 limit;
+ u64 reserved;
+ u32 performance;
+ u8 supportCompressed;
+ u8 supportISO;
+ u8 bProtected;
+ u8 blackList[17];
+ u8 _pad[6];
+};
+
+#endif /* _GSP_RM_TU104_H_ */
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 <linux/module.h>
+#include <linux/pci.h>
+#include <linux/io.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <stdint.h>
+
+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
--- /dev/null
+++ b/tools/rmq/wpr2_reset
Binary files 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/<dev>/resource0 while no driver is bound.
+ *
+ * Usage: sudo ./wpr2_reset [pci.bdf] (default 0000:01:00.0)
+ */
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/mman.h>
+
+#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;
+}