summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-22 22:40:26 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-22 22:40:26 -0400
commitf6d450a8be075fdc19a874826e7649ae0149b099 (patch)
tree9f68bdb6ce1f120fb80531895ad4f265c001d65a
parentfcd603db457c0fb035df882abc55397bc854a1cd (diff)
downloadopen-iov-f6d450a8be075fdc19a874826e7649ae0149b099.tar.gz
open-iov-f6d450a8be075fdc19a874826e7649ae0149b099.zip
kernel: guest probe completes - nvidia-smi enumerates the emulated TU104
Root cause of the golden-image NV_ERR_NO_MEMORY: STATIC_KGR_GET_CONTEXT_ BUFFERS_INFO (0x20800a32) fell through to the generic zeroed reply, so kgraphicsInitializeDeferredStaticData() stored a 0-byte main ctx buffer size and kgrctxAllocMainCtxBuffer() did a 0-page allocation (mem_desc.c:1353). Serve the TU104 per-engine ctx buffer table (26 engine IDs x 8 GR engines; NV_U32_MAX = unsupported, which the client skips). Second blocker after that: L2 cache-op and TLB-invalidate completion registers. Turing reaches them through the virtual-register window (GPU_VREG_* = BAR0 + sriovState.virtualRegPhysOffset + offset, 0xB80000 for a non-SRIOV virtual GPU): NV_VIRTUAL_FUNCTION_PRIV_MMU_INVALIDATE (0xB830B0, TRIGGER bit31) and the L2 invalidate/dirty-flush set (0x70000-0x70014 and 0xB80F00/0xB80F04). The driver soft-locked spinning in osDevReadReg032 on 0xB830B4 (kgmmuCheckPendingInvalidates_TU102) and timed out in kmemsysDoCacheOp_GM107; auto-clear all of them (both the plain and windowed aliases). Third: re-probe failed with GspStatusQueueInit msgqRxLink timeout because gsp_rm_emu_reset() was not called before a fresh boot, leaving rm->parsed set so the new libos args were never parsed. Reset GSP-RM state on the fresh FWSEC kick. Verified: guest nvidia-smi and nvidia-smi -L both exit 0 (full unload/reload cycle), GPU enumerated as "NVIDIA GeForce RTX 2080 SUPER" with 256MB, no NO_MEMORY / timeouts / soft lockups in the guest. Also add tools/rmq/grctx_oracle.c documenting that the host RM does not expose the ctx-buffer table to userspace (the real values are probed from the ctxsw ucode by GSP-RM), so the emulation serves synthesized sizes.
-rw-r--r--kernel/gsp_emulation.c72
-rw-r--r--kernel/gsp_emulation.h39
-rw-r--r--kernel/gsp_rm_emulation.c90
-rw-r--r--kernel/gsp_rm_tu104.h78
-rw-r--r--tools/rmq/grctx_oracle.c189
5 files changed, 442 insertions, 26 deletions
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index 44facc1..0b6bba0 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -195,7 +195,6 @@ static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
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);
- u32 mmu_inv = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_MMU_INVALIDATE);
u32 sw_vector;
/* Software-interrupt trigger: the driver writes a vector to
@@ -231,13 +230,63 @@ static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
}
/* TLB invalidate auto-complete: the driver sets TRIGGER then spins
- * polling for it to clear (kgmmuCheckPendingInvalidates). Report
- * instant completion or the driver wedges the open/init path. */
- if (mmu_inv & GSP_EMU_MMU_INVALIDATE_TRIGGER) {
- gsp_emu_shadow_write32(ctx, GSP_EMU_REG_MMU_INVALIDATE,
- mmu_inv & ~GSP_EMU_MMU_INVALIDATE_TRIGGER);
- pr_info_ratelimited("GSP-BOOT: MMU_INVALIDATE trigger auto-cleared (val=0x%x)\n",
- mmu_inv);
+ * polling for it to clear (kgmmuCheckPendingInvalidates_TU102 reads
+ * GPU_VREG_RD32(NV_VIRTUAL_FUNCTION_PRIV_MMU_INVALIDATE) =
+ * BAR0 + 0xB80000 + 0x30B0; the physical path uses the plain alias).
+ * Report instant completion or the driver soft-locks spinning inside
+ * osDevReadReg032. */
+ {
+ static const u32 tlb_inv_regs[] = {
+ GSP_EMU_REG_MMU_INVALIDATE,
+ GSP_EMU_REG_MMU_INVALIDATE_WIN,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(tlb_inv_regs); i++) {
+ u32 val = gsp_emu_shadow_read32(ctx, tlb_inv_regs[i]);
+
+ if (!(val & GSP_EMU_MMU_INVALIDATE_TRIGGER))
+ continue;
+ gsp_emu_shadow_write32(ctx, tlb_inv_regs[i],
+ val & ~GSP_EMU_MMU_INVALIDATE_TRIGGER);
+ pr_info_ratelimited("GSP-BOOT: MMU_INVALIDATE[0x%x] trigger auto-cleared (val=0x%x)\n",
+ tlb_inv_regs[i], val);
+ }
+ }
+
+ /* L2 cache-op auto-complete: kmemsysDoCacheOp_GM107 writes PENDING
+ * (bit 0) into the NV_UFLUSH_* / VF L2 invalidate registers and spins
+ * until the polled bits read back 0 (FB flush, L2 sysmem/peermem
+ * invalidate, L2 clean, L2 flush-dirty, L2 wait-for-sys-reads). On
+ * Turing the sysmem/peermem invalidate registers go through the
+ * GPU_VREG_* window (BAR0 + 0xB80000 + NV_VIRTUAL_FUNCTION_PRIV_L2_*),
+ * so cover both the plain and windowed aliases. The emulated L2 has
+ * nothing to do, so clear them as soon as they are observed. */
+ {
+ static const u32 uflush_regs[] = {
+ GSP_EMU_REG_UFLUSH_FB_FLUSH,
+ GSP_EMU_REG_UFLUSH_L2_SYSMEM_INV,
+ GSP_EMU_REG_UFLUSH_L2_PEERMEM_INV,
+ GSP_EMU_REG_UFLUSH_L2_CLEAN_COMPTAGS,
+ GSP_EMU_REG_UFLUSH_L2_FLUSH_DIRTY,
+ GSP_EMU_REG_UFLUSH_L2_WAIT_SYS_READS,
+ GSP_EMU_REG_VF_L2_SYSMEM_INV,
+ GSP_EMU_REG_VF_L2_PEERMEM_INV,
+ GSP_EMU_REG_VF_L2_SYSMEM_INV_WIN,
+ GSP_EMU_REG_VF_L2_PEERMEM_INV_WIN,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(uflush_regs); i++) {
+ u32 val = gsp_emu_shadow_read32(ctx,
+ uflush_regs[i]);
+
+ if (!val)
+ continue;
+ gsp_emu_shadow_write32(ctx, uflush_regs[i], 0);
+ pr_info_ratelimited("GSP-BOOT: UFLUSH[0x%x] 0x%x auto-cleared\n",
+ uflush_regs[i], val);
+ }
}
/* CrashCat wayfinder suppression: if the GSP falcon's DEBUGINFO
@@ -312,6 +361,13 @@ static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
*/
if ((gsp_cpuctl & GSP_EMU_CPUCTL_STARTCPU) &&
ctx->boot_stage == GSP_BOOT_STAGE_IDLE) {
+ /* A fresh FWSEC kick means a new GSP boot: drop any GSP-RM
+ * queue state left from the previous probe (the driver has
+ * already torn down its old queue). Without this the next
+ * Booter completion would skip the chain re-parse
+ * (rm->parsed still set) and GspStatusQueueInit would time
+ * out on re-probe. */
+ gsp_rm_emu_reset(ctx);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_VBIOS_SCRATCH_0E, 0);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_HI, 0x100000);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_WPR2_ADDR_LO,
diff --git a/kernel/gsp_emulation.h b/kernel/gsp_emulation.h
index 801ea78..9a8f241 100644
--- a/kernel/gsp_emulation.h
+++ b/kernel/gsp_emulation.h
@@ -58,10 +58,42 @@
#define GSP_EMU_REG_SEC2_MAILBOX0 0x840040
#define GSP_EMU_REG_SEC2_ENGINE 0x8403c0
+/* Base of the virtual-function register window (NV_VIRTUAL_FUNCTION_FULL_PHYS_
+ * OFFSET). The client RM reaches per-VF registers through GPU_VREG_* =
+ * BAR0 + sriovState.virtualRegPhysOffset + offset; for a non-SRIOV virtual GPU
+ * that offset is 0xB80000, and 0 for a VF, hence the plain + windowed aliases
+ * below. */
+#define GSP_EMU_REG_FULL_PHYS_OFFSET 0xB80000
+
/* NV_VIRTUAL_FUNCTION_PRIV_MMU_INVALIDATE: driver sets TRIGGER(bit31) to
- * start a TLB invalidate, then polls for bit31==0 (done). We auto-clear it. */
+ * start a TLB invalidate, then polls for bit31==0 (done). We auto-clear it.
+ * The client RM reaches it through the GPU_VREG_* window (BAR0 + 0xB80000 +
+ * 0x30B0) and the physical path pokes the plain 0x30B0 alias, so watch both. */
#define GSP_EMU_REG_MMU_INVALIDATE 0x0030b0
#define GSP_EMU_MMU_INVALIDATE_TRIGGER (1U << 31)
+#define GSP_EMU_REG_MMU_INVALIDATE_WIN (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x0030b0)
+
+/* L2 cache-op completion registers (NV_UFLUSH_*): the driver writes PENDING
+ * (bit 0) to issue FB flush / L2 invalidate / L2 clean+dirty flush and then
+ * spins in kmemsysDoCacheOp_GM107 until the polled bits read back 0. On real
+ * hardware the L2 clears them when the operation completes; the emulated L2
+ * completes instantly, so auto-clear any pending bits.
+ *
+ * On Turing the sysmem/peermem invalidate registers are reached through the
+ * virtual-register window (GPU_VREG_*: BAR0 + sriovState.virtualRegPhysOffset
+ * + NV_VIRTUAL_FUNCTION_PRIV_L2_*); the offset is 0xB80000 for a non-SRIOV
+ * virtual GPU and 0 for a VF, so watch both the plain and windowed aliases. */
+#define GSP_EMU_REG_UFLUSH_BASE 0x070000
+#define GSP_EMU_REG_UFLUSH_FB_FLUSH (GSP_EMU_REG_UFLUSH_BASE + 0x00)
+#define GSP_EMU_REG_UFLUSH_L2_SYSMEM_INV (GSP_EMU_REG_UFLUSH_BASE + 0x04)
+#define GSP_EMU_REG_UFLUSH_L2_PEERMEM_INV (GSP_EMU_REG_UFLUSH_BASE + 0x08)
+#define GSP_EMU_REG_UFLUSH_L2_CLEAN_COMPTAGS (GSP_EMU_REG_UFLUSH_BASE + 0x0c)
+#define GSP_EMU_REG_UFLUSH_L2_FLUSH_DIRTY (GSP_EMU_REG_UFLUSH_BASE + 0x10)
+#define GSP_EMU_REG_UFLUSH_L2_WAIT_SYS_READS (GSP_EMU_REG_UFLUSH_BASE + 0x14)
+
+/* NV_VIRTUAL_FUNCTION_PRIV_L2_{SYSMEM,PEERMEM}_INVALIDATE (dev_vm.h) */
+#define GSP_EMU_REG_VF_L2_SYSMEM_INV 0x000f00
+#define GSP_EMU_REG_VF_L2_PEERMEM_INV 0x000f04
/* NV_PFALCON_FALCON_DEBUGINFO (GSP falcon BAR0+0x110094): holds the CrashCat
* wayfinder-L0 descriptor. If its low 16 bits == 0xdead (NV_CRASHCAT_SIGNATURE),
@@ -80,11 +112,14 @@
* _osVerifyInterrupts (osVerifySystemEnvironment) fires a doorbell software
* interrupt and waits for its ISR; if we don't set the pending state and
* inject MSI-X, the ISR never runs and RmInitAdapter bails. */
-#define GSP_EMU_REG_FULL_PHYS_OFFSET 0xB80000
#define GSP_EMU_REG_CPU_INTR_LEAF_TRIGGER (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x1640)
#define GSP_EMU_REG_CPU_INTR_LEAF_BASE (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x1000)
#define GSP_EMU_REG_CPU_INTR_TOP_BASE (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x1600)
+/* Windowed aliases of the VF L2 invalidate registers (GPU_VREG_* path). */
+#define GSP_EMU_REG_VF_L2_SYSMEM_INV_WIN (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x000f00)
+#define GSP_EMU_REG_VF_L2_PEERMEM_INV_WIN (GSP_EMU_REG_FULL_PHYS_OFFSET + 0x000f04)
+
#define GSP_EMU_CPUCTL_STARTCPU 0x2
#define GSP_EMU_CPUCTL_HALTED 0x10
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index 07ba807..f18d17c 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -1201,6 +1201,74 @@ static void gsp_rm_handle_kgr_get_floorsweeping_masks(
m->gpcMask, TU104_TPC_PER_GPC);
}
+/*
+ * STATIC_KGR_GET_CONTEXT_BUFFERS_INFO (0x20800a32)
+ *
+ * kgraphicsInitializeDeferredStaticData() asks the physical RM (GSP-RM) for
+ * the per-engine context buffer sizes; kgrctxAllocMainCtxBuffer() then sizes
+ * the golden image ctx buffer from engine[GRAPHICS].size. The zeroed generic
+ * reply left size == 0 -> a 0-page allocation -> NV_ERR_NO_MEMORY at
+ * mem_desc.c:1353 (kgrctxAllocMainCtxBuffer -> kgraphicsCreateGoldenImageChannel).
+ *
+ * The real physical RM probes these from the ctxsw ucode
+ * (grSetupInitialCtxState -> grDiscoverCtxBufferSize) and reports NV_U32_MAX
+ * for unsupported engine IDs. The client skips NV_U32_MAX entries in
+ * kgraphicsCreateGoldenImageChannel's VA reservation, so the emulated layout
+ * below mirrors that: page-aligned scratch sizes for the buffers the client
+ * allocates, NV_U32_MAX for the engine IDs Turing does not support.
+ */
+static void gsp_rm_handle_kgr_get_context_buffers_info(
+ struct gsp_emu_context *ctx,
+ u32 sequence,
+ u32 *out_status,
+ u8 *out_params,
+ u32 *out_params_size)
+{
+ static const struct tu104_ctx_buffer_info sizes[TU104_CTX_ENGINE_ID_COUNT] = {
+ [TU104_CTX_ENG_GRAPHICS] = { TU104_CTX_SIZE_MAIN, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_ZCULL] = { TU104_CTX_SIZE_ZCULL, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PM] = { TU104_CTX_SIZE_PM, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PREEMPT] = { TU104_CTX_SIZE_PREEMPT, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_SPILL] = { TU104_CTX_SIZE_SPILL, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PAGEPOOL] = { TU104_CTX_SIZE_PAGEPOOL, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_BETACB] = { TU104_CTX_SIZE_BETACB, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_RTV] = { TU104_CTX_SIZE_RTV, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PATCH] = { TU104_CTX_SIZE_PATCH, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_BUNDLE_CB] = { TU104_CTX_SIZE_BUNDLE_CB, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PAGEPOOL_GLOBAL] = { TU104_CTX_SIZE_PAGEPOOL, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_ATTRIBUTE_CB] = { TU104_CTX_SIZE_ATTRIBUTE_CB, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_RTV_CB_GLOBAL] = { TU104_CTX_SIZE_RTV_CB_GLOBAL, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_FECS_EVENT] = { TU104_CTX_SIZE_FECS_EVENT, 0x1000 },
+ [TU104_CTX_ENG_GRAPHICS_PRIV_ACCESS_MAP] = { TU104_CTX_SIZE_PRIV_ACCESS_MAP, 0x1000 },
+ };
+ struct tu104_gr_get_context_buffers_info_params *info =
+ (struct tu104_gr_get_context_buffers_info_params *)out_params;
+ u32 e, i;
+
+ memset(out_params, 0, sizeof(*info));
+
+ for (e = 0; e < TU104_CTX_GPU_MAX_ENGINES; e++) {
+ for (i = 0; i < TU104_CTX_ENGINE_ID_COUNT; i++) {
+ /* engine IDs the chip does not support report
+ * NV_U32_MAX (the client skips those entries) */
+ if (sizes[i].size == 0) {
+ info->engineContextBuffersInfo[e].engine[i].size = 0xFFFFFFFF;
+ info->engineContextBuffersInfo[e].engine[i].alignment = 0xFFFFFFFF;
+ continue;
+ }
+ info->engineContextBuffersInfo[e].engine[i].size = sizes[i].size;
+ info->engineContextBuffersInfo[e].engine[i].alignment = sizes[i].alignment;
+ }
+ }
+
+ *out_params_size = sizeof(*info);
+ *out_status = 0;
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: KGR_GET_CONTEXT_BUFFERS_INFO -> main ctx 0x%x, patch 0x%x, pm 0x%x (engines=%u x %u)",
+ TU104_CTX_SIZE_MAIN, TU104_CTX_SIZE_PATCH, TU104_CTX_SIZE_PM,
+ TU104_CTX_GPU_MAX_ENGINES, TU104_CTX_ENGINE_ID_COUNT);
+}
+
static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
const u8 *elem, u32 sequence)
{
@@ -1301,6 +1369,12 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
break;
+ case TU104_CTRL_STATIC_KGR_GET_CONTEXT_BUFFERS_INFO:
+ gsp_rm_handle_kgr_get_context_buffers_info(ctx, sequence,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
case TU104_CTRL_STATIC_KGR_GET_FLOORSWEEPING_MASKS:
gsp_rm_handle_kgr_get_floorsweeping_masks(ctx, sequence,
&out_status,
@@ -1360,22 +1434,6 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
* complete well-formed record. (The poll match is function-only; the echo
* keeps the post-poll result struct self-consistent.)
*/
-/*
- sizeof(struct tu104_gr_floorsweeping_masks);
- *out_status = 0;
-
- nv_vgpu_info(ctx->vgpu, "GSPRM: KGR_GET_FLOORSWEEPING_MASKS -> gpcMask=0x%x tpc/gpc=%u",
- m->gpcMask, TU104_TPC_PER_GPC);
-}
-
-/*
- * GSP_RM_FREE (function 10) - NVOS00_PARAMETERS echo with status=NV_OK.
- *
- * rpc_gsp_free: { hRoot, hObjectParent, hObjectOld, status }. Echo the
- * request's params back with status=0 so the driver's free rpc_message is a
- * complete well-formed record. (The poll match is function-only; the echo
- * keeps the post-poll result struct self-consistent.)
- */
static void gsp_rm_handle_free(struct gsp_emu_context *ctx, const u8 *elem,
u32 sequence)
{
diff --git a/kernel/gsp_rm_tu104.h b/kernel/gsp_rm_tu104.h
index ff2a4a7..f40c77d 100644
--- a/kernel/gsp_rm_tu104.h
+++ b/kernel/gsp_rm_tu104.h
@@ -702,6 +702,84 @@ static const struct tu104_internal_device_info tu104_internal_device_table[] = {
#define TU104_CTRL_CE_GET_FAULT_METHOD_BUFFER_SIZE 0x20802a08
#define TU104_CTRL_STATIC_KGR_GET_GLOBAL_SM_ORDER 0x20800a22
#define TU104_CTRL_STATIC_KGR_GET_INFO 0x20800a2a
+#define TU104_CTRL_STATIC_KGR_GET_CONTEXT_BUFFERS_INFO 0x20800a32
+
+/* NV2080_CTRL_INTERNAL_ENGINE_CONTEXT_BUFFER_INFO (engine context buffer info)
+ * NV2080_CTRL_INTERNAL_STATIC_GR_CONTEXT_BUFFERS_INFO: engine[ENGINE_ID_COUNT]
+ * NV2080_CTRL_INTERNAL_STATIC_GR_GET_CONTEXT_BUFFERS_INFO_PARAMS:
+ * engineContextBuffersInfo[GR_MAX_ENGINES]
+ *
+ * The driver's kgraphicsInitializeDeferredStaticData() asks the physical RM
+ * (GSP-RM) for this table; kgrctxAllocMainCtxBuffer() then sizes the golden
+ * image ctx buffer from engine[GRAPHICS].size. A zeroed reply produces a
+ * 0-byte (0-page) allocation -> NV_ERR_NO_MEMORY (mem_desc.c:1353).
+ *
+ * The physical RM probes these sizes from the ctxsw ucode at GR init
+ * (grSetupInitialCtxState -> grDiscoverCtxBufferSize) and reports
+ * NV_U32_MAX for engine IDs the chip does not support. The values below are
+ * the emulated TU104 layout (own buffers, no real GR engine behind them):
+ * sizes are page-aligned scratch regions, NV_U32_MAX = "not supported". */
+#define TU104_CTX_ENGINE_ID_COUNT 26 /* _ENGINE_ID_COUNT (0x1a) */
+#define TU104_CTX_GPU_MAX_ENGINES 8 /* INTERNAL_GR_MAX_ENGINES */
+
+/* NV0080_CTRL_FIFO_GET_ENGINE_CONTEXT_PROPERTIES_ENGINE_ID_* */
+#define TU104_CTX_ENG_GRAPHICS 0x00
+#define TU104_CTX_ENG_VLD 0x01
+#define TU104_CTX_ENG_VIDEO 0x02
+#define TU104_CTX_ENG_MPEG 0x03
+#define TU104_CTX_ENG_CAPTURE 0x04
+#define TU104_CTX_ENG_DISPLAY 0x05
+#define TU104_CTX_ENG_ENCRYPTION 0x06
+#define TU104_CTX_ENG_POSTPROCESS 0x07
+#define TU104_CTX_ENG_GRAPHICS_ZCULL 0x08
+#define TU104_CTX_ENG_GRAPHICS_PM 0x09
+#define TU104_CTX_ENG_COMPUTE_PREEMPT 0x0a
+#define TU104_CTX_ENG_GRAPHICS_PREEMPT 0x0b
+#define TU104_CTX_ENG_GRAPHICS_SPILL 0x0c
+#define TU104_CTX_ENG_GRAPHICS_PAGEPOOL 0x0d
+#define TU104_CTX_ENG_GRAPHICS_BETACB 0x0e
+#define TU104_CTX_ENG_GRAPHICS_RTV 0x0f
+#define TU104_CTX_ENG_GRAPHICS_PATCH 0x10
+#define TU104_CTX_ENG_GRAPHICS_BUNDLE_CB 0x11
+#define TU104_CTX_ENG_GRAPHICS_PAGEPOOL_GLOBAL 0x12
+#define TU104_CTX_ENG_GRAPHICS_ATTRIBUTE_CB 0x13
+#define TU104_CTX_ENG_GRAPHICS_RTV_CB_GLOBAL 0x14
+#define TU104_CTX_ENG_GRAPHICS_GFXP_POOL 0x15
+#define TU104_CTX_ENG_GRAPHICS_GFXP_CTRL_BLK 0x16
+#define TU104_CTX_ENG_GRAPHICS_FECS_EVENT 0x17
+#define TU104_CTX_ENG_GRAPHICS_PRIV_ACCESS_MAP 0x18
+#define TU104_CTX_ENG_GRAPHICS_SETUP 0x19
+
+/* grabbag: emulated ctx buffer sizes (bytes) */
+#define TU104_CTX_SIZE_MAIN 0x200000
+#define TU104_CTX_SIZE_ZCULL 0x100000
+#define TU104_CTX_SIZE_PM 0x8000
+#define TU104_CTX_SIZE_PREEMPT 0x1000
+#define TU104_CTX_SIZE_SPILL 0x1000
+#define TU104_CTX_SIZE_PAGEPOOL 0x10000
+#define TU104_CTX_SIZE_BETACB 0x1000
+#define TU104_CTX_SIZE_RTV 0x1000
+#define TU104_CTX_SIZE_PATCH 0x2000
+#define TU104_CTX_SIZE_BUNDLE_CB 0x1000
+#define TU104_CTX_SIZE_ATTRIBUTE_CB 0x1000
+#define TU104_CTX_SIZE_RTV_CB_GLOBAL 0x1000
+#define TU104_CTX_SIZE_FECS_EVENT 0x1000
+#define TU104_CTX_SIZE_PRIV_ACCESS_MAP 0x1000
+
+struct tu104_ctx_buffer_info {
+ u32 size;
+ u32 alignment;
+};
+
+struct tu104_gr_context_buffers_info {
+ struct tu104_ctx_buffer_info engine[TU104_CTX_ENGINE_ID_COUNT];
+};
+
+struct tu104_gr_get_context_buffers_info_params {
+ struct tu104_gr_context_buffers_info
+ engineContextBuffersInfo[TU104_CTX_GPU_MAX_ENGINES];
+};
+
/* NV0080_CTRL_GR_INFO entry + infoList indices (ctrl0080gr.h) */
struct tu104_gr_info_entry {
diff --git a/tools/rmq/grctx_oracle.c b/tools/rmq/grctx_oracle.c
new file mode 100644
index 0000000..a2fc5fa
--- /dev/null
+++ b/tools/rmq/grctx_oracle.c
@@ -0,0 +1,189 @@
+/* grctx_oracle - query the host NVIDIA RM for the engine context buffer sizes.
+ *
+ * Goal: recover the table that GSP-RM serves for
+ * NV2080_CTRL_CMD_INTERNAL_STATIC_KGR_GET_CONTEXT_BUFFERS_INFO (0x20800a32),
+ * so the emulation layer does not have to guess the per-engine ctx buffer
+ * sizes (kernel/gsp_rm_tu104.h: TU104_CTX_SIZE_*).
+ *
+ * Outcome (2026-09-22): NOT REACHABLE from userspace on a bare-metal PF:
+ * - 0x801707 NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES
+ * hObject=hDevice -> 0x57 NV_ERR_OBJECT_NOT_FOUND (not exported)
+ * hObject=hClient -> 0x56 NV_ERR_NOT_SUPPORTED (bound to the
+ * _92bfc3 hal: the client RM does not manage ctx
+ * buffers on a PF, only a VF's VGPU_STATIC_INFO)
+ * - 0x2080122d NV2080_CTRL_CMD_GR_GET_ENGINE_CONTEXT_PROPERTIES
+ * (subdevice) -> 0x57 / 0x56, never returns sizes
+ * - The producing control (0x20800a32) is internal-only.
+ * The real values are probed from the ctxsw ucode by the physical RM
+ * (grSetupInitialCtxState -> grDiscoverCtxBufferSize) and are internal to
+ * GSP-RM, so the emulation currently serves synthesized page-aligned sizes
+ * (see tu104_ctx_buffer_info in kernel/gsp_rm_tu104.h).
+ *
+ * Kept as a reference for the RM client bring-up sequence and for a future
+ * attempt via a kernel-side RM client.
+ */
+#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 uint64_t NvU64;
+typedef uint32_t NvHandle;
+typedef uint32_t NvV32;
+typedef uint64_t NvP64;
+
+#define NV_IOCTL_MAGIC 'F'
+#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 hRoot;
+ NvHandle hObjectParent;
+ NvHandle hObjectNew;
+ NvV32 hClass;
+ NvP64 pAllocParms __attribute__((aligned(8)));
+ NvP64 pRightsRequested __attribute__((aligned(8)));
+ NvU32 paramsSize;
+ NvU32 flags;
+ NvV32 status;
+} NVOS64_PARAMETERS;
+
+typedef struct {
+ NvHandle hClient;
+ NvHandle hObject;
+ NvV32 cmd;
+ NvU32 flags;
+ NvP64 params __attribute__((aligned(8)));
+ NvU32 paramsSize;
+ NvV32 status;
+} NVOS54_PARAMETERS;
+
+#define NV_IOCTL_RM_ALLOC _IOWR(NV_IOCTL_MAGIC, NV_ESC_RM_ALLOC, NVOS21_PARAMETERS)
+#define NV_IOCTL_RM_CONTROL _IOWR(NV_IOCTL_MAGIC, NV_ESC_RM_CONTROL, NVOS54_PARAMETERS)
+
+#define NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES 0x801707U
+#define NV2080_CTRL_CMD_GR_GET_ENGINE_CONTEXT_PROPERTIES 0x2080122dU
+#define ENGINE_ID_COUNT 0x1a
+
+typedef struct {
+ NvU32 engineId;
+ NvU32 alignment;
+ NvU32 size;
+} FIFO_GET_ENGINE_CONTEXT_PROPERTIES_PARAMS;
+
+typedef struct {
+ NvU32 flags;
+ NvU32 pad;
+ NvU64 route;
+} GR_ROUTE_INFO;
+
+typedef struct {
+ GR_ROUTE_INFO grRouteInfo;
+ NvU32 engineId;
+ NvU32 alignment;
+ NvU32 size;
+ NvU32 bInfoPopulated;
+} GR_GET_ENGINE_CONTEXT_PROPERTIES_PARAMS;
+
+static int fd;
+
+static NvU32 rm_alloc(NvHandle parent, NvHandle client, NvU32 hClass, NvHandle *pOut)
+{
+ NVOS21_PARAMETERS p = {0};
+ p.hRoot = client;
+ p.hObjectParent = parent;
+ p.hClass = hClass;
+ if (ioctl(fd, NV_IOCTL_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_alloc64(NvHandle parent, NvHandle client, NvU32 hClass,
+ NvHandle newHandle, void *parms, NvU32 parmsSize, NvHandle *pOut)
+{
+ NVOS64_PARAMETERS p = {0};
+ p.hRoot = client;
+ p.hObjectParent = parent;
+ p.hObjectNew = newHandle; /* 0 = let RM generate (device); subdevices take an explicit handle */
+ p.hClass = hClass;
+ p.pAllocParms = (NvP64)parms;
+ p.paramsSize = parmsSize;
+ p.flags = 0;
+ if (ioctl(fd, NV_IOCTL_RM_ALLOC, &p) < 0) { perror("alloc64 ioctl"); return 1; }
+ if (p.status) {
+ fprintf(stderr, "alloc64 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_IOCTL_RM_CONTROL, &p) < 0) { perror("control ioctl"); return 1; }
+ return p.status;
+}
+
+int main(void)
+{
+ NvHandle hClient = 0, hDevice = 0, hSubdevice = 0;
+ NvU32 deviceParams = 0; /* deviceId 0 */
+ NvU32 subdeviceParams = 0; /* subDeviceId 0 */
+ NvU32 st, i;
+
+ fd = open("/dev/nvidiactl", O_RDWR);
+ if (fd < 0) { perror("open /dev/nvidiactl"); return 1; }
+
+ if ((st = rm_alloc(0, 0, NV01_ROOT, &hClient))) return 1;
+ if ((st = rm_alloc64(hClient, hClient, NV01_DEVICE_0, 0,
+ &deviceParams, sizeof(deviceParams), &hDevice))) return 1;
+ if ((st = rm_alloc64(hDevice, hClient, NV20_SUBDEVICE_0, 0xcaf00001,
+ &subdeviceParams, sizeof(subdeviceParams), &hSubdevice))) return 1;
+ printf("hClient=0x%x hDevice=0x%x hSubdevice=0x%x\n", hClient, hDevice, hSubdevice);
+
+ printf("=== device-level FIFO_GET_ENGINE_CONTEXT_PROPERTIES (0x801707) ===\n");
+ for (i = 0; i < ENGINE_ID_COUNT; i++) {
+ FIFO_GET_ENGINE_CONTEXT_PROPERTIES_PARAMS p;
+ memset(&p, 0, sizeof(p));
+ p.engineId = i;
+ st = rm_control(hClient, hDevice, NV0080_CTRL_CMD_FIFO_GET_ENGINE_CONTEXT_PROPERTIES,
+ &p, sizeof(p));
+ printf("engine[%2u] status=0x%x size=0x%08x alignment=0x%08x\n",
+ i, st, p.size, p.alignment);
+ }
+
+ printf("=== subdevice-level GR_GET_ENGINE_CONTEXT_PROPERTIES (0x2080122d) ===\n");
+ for (i = 0; i < ENGINE_ID_COUNT; i++) {
+ GR_GET_ENGINE_CONTEXT_PROPERTIES_PARAMS p;
+ memset(&p, 0, sizeof(p));
+ p.engineId = i;
+ st = rm_control(hClient, hSubdevice, NV2080_CTRL_CMD_GR_GET_ENGINE_CONTEXT_PROPERTIES,
+ &p, sizeof(p));
+ printf("engine[%2u] status=0x%x size=0x%08x alignment=0x%08x\n",
+ i, st, p.size, p.alignment);
+ }
+
+ return 0;
+}