summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-22 02:16:18 -0400
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-09-22 02:16:18 -0400
commit5eac589d9a7af8f8b321b00398575070b9a88513 (patch)
treeb0c6d12d92a145fcf2d6fc32f3ee2b794eed4c43
parent427b9e1e10c3258bb031be142700b18c83296c69 (diff)
downloadopen-iov-5eac589d9a7af8f8b321b00398575070b9a88513.tar.gz
open-iov-5eac589d9a7af8f8b321b00398575070b9a88513.zip
kernel: floorsweeping masks + interrupt tree + WPR2 race + fn-10 handler
- STATIC_KGR_GET_FLOORSWEEPING_MASKS(0x20800a26): TU104 gpcMask=0x3f (6 GPCs), 4 TPC/GPC. numGpcs==0 killed krcWatchdogInit -> RmInitAdapter bailed ('numGpcs > 0' assert at kernel_graphics_object.c:191). - CPU interrupt tree (osVerifyInterrupts): driver accesses VF window at virtualRegPhysOffset=NV_VIRTUAL_FUNCTION_FULL_PHYS_OFFSET(0xB80000)+off, NOT 0x1640 (was watching wrong offset). On LEAF_TRIGGER write, set LEAF/TOP pending bits + inject MSI-X so the sanity-test ISR runs - osVerifySystemEnvironment now passes. - WPR2 race: clear boot state BEFORE CPUCTL=HALTED on Booter-unload kick so WPR2 is down when driver checks kgspIsWpr2Up ('WPR2 is still up'). - Dedicated GSP_RM_FREE(10) handler: echo NVOS00_PARAMETERS + status=NV_OK (proper free_v03_00 form). fn-10 timeout persists despite this - characterized as driver-side reply-matching issue, not content.
-rw-r--r--kernel/gsp_emulation.c41
-rw-r--r--kernel/gsp_emulation.h14
-rw-r--r--kernel/gsp_rm_emulation.c134
-rw-r--r--kernel/gsp_rm_emulation.h1
-rw-r--r--kernel/gsp_rm_tu104.h21
5 files changed, 188 insertions, 23 deletions
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index c8f8041..94af0d2 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -179,6 +179,39 @@ static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
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
+ * CPU_INTR_LEAF_TRIGGER to fire it (the osVerifyInterrupts sanity
+ * test uses the doorbell vector). Set the pending bits in the LEAF
+ * and TOP interrupt-tree registers and inject MSI-X so the guest's
+ * ISR runs - without this the sanity test times out and
+ * osVerifySystemEnvironment fails, bailing RmInitAdapter. */
+ sw_vector = gsp_emu_shadow_read32(ctx, GSP_EMU_REG_CPU_INTR_LEAF_TRIGGER);
+ if (sw_vector) {
+ u32 leaf_idx = sw_vector / 32;
+ u32 leaf_bit = sw_vector % 32;
+ u32 subtree = sw_vector / 64;
+ u32 top_idx = subtree / 32;
+ u32 top_bit = subtree % 32;
+ u32 leaf_val = gsp_emu_shadow_read32(ctx,
+ GSP_EMU_REG_CPU_INTR_LEAF_BASE + leaf_idx * 4);
+ u32 top_val = gsp_emu_shadow_read32(ctx,
+ GSP_EMU_REG_CPU_INTR_TOP_BASE + top_idx * 4);
+
+ gsp_emu_shadow_write32(ctx,
+ GSP_EMU_REG_CPU_INTR_LEAF_BASE + leaf_idx * 4,
+ leaf_val | (1U << leaf_bit));
+ gsp_emu_shadow_write32(ctx,
+ GSP_EMU_REG_CPU_INTR_TOP_BASE + top_idx * 4,
+ top_val | (1U << top_bit));
+ gsp_emu_shadow_write32(ctx, GSP_EMU_REG_CPU_INTR_LEAF_TRIGGER, 0);
+
+ nv_vgpu_info(ctx->vgpu, "GSP-BOOT: SW interrupt vector %u -> LEAF[%u]|=0x%x TOP[%u]|=0x%x, MSI-X",
+ sw_vector, leaf_idx, 1U << leaf_bit, top_idx,
+ 1U << top_bit);
+ gsp_emu_inject_completion_interrupt(ctx, 0);
+ }
/* TLB invalidate auto-complete: the driver sets TRIGGER then spins
* polling for it to clear (kgmmuCheckPendingInvalidates). Report
@@ -267,16 +300,18 @@ static void gsp_emu_poll_boot(struct gsp_emu_context *ctx)
* driver reads a stale/garbage error code (0xff) and the unload
* fails, killing the subsequent re-init. A SEC2 kick after a
* completed boot is a GSP shutdown/reload: drop the boot state
- * (GSP no longer active, queue dead). */
+ * (GSP no longer active, queue dead). Clear boot state FIRST so
+ * WPR2 is already down when the driver sees CPUCTL=HALTED and
+ * checks kgspIsWpr2Up (avoids "Booter Unload: WPR2 is still up"). */
if (gsp_cpuctl & GSP_EMU_CPUCTL_STARTCPU)
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_GSP_CPUCTL,
GSP_EMU_CPUCTL_HALTED);
if (sec2_cpuctl & GSP_EMU_CPUCTL_STARTCPU) {
+ if (ctx->boot_stage == GSP_BOOT_STAGE_BOOTER_DONE)
+ gsp_emu_clear_boot_state(ctx, "Booter unload/reload kick");
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_SEC2_MAILBOX0, 0);
gsp_emu_shadow_write32(ctx, GSP_EMU_REG_SEC2_CPUCTL,
GSP_EMU_CPUCTL_HALTED);
- if (ctx->boot_stage == GSP_BOOT_STAGE_BOOTER_DONE)
- gsp_emu_clear_boot_state(ctx, "Booter unload/reload kick");
}
}
diff --git a/kernel/gsp_emulation.h b/kernel/gsp_emulation.h
index 929003e..6fa69b1 100644
--- a/kernel/gsp_emulation.h
+++ b/kernel/gsp_emulation.h
@@ -71,6 +71,20 @@
* returns NULL) so the health check always passes. */
#define GSP_EMU_REG_FALCON_DEBUGINFO 0x110094
+/* CPU interrupt tree (Turing, physical-GPU path): the driver accesses the
+ * VF window registers at virtualRegPhysOffset = NV_VIRTUAL_FUNCTION_FULL_PHYS_OFFSET
+ * (0xB80000) + offset.
+ * LEAF_TRIGGER 0xB80000+0x1640 - write a vector to fire its software interrupt
+ * LEAF[i] 0xB80000+0x1000+i*4 - per-32-vector pending bits
+ * TOP[i] 0xB81600+i*4 - per-32-subtree pending bits
+ * _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)
+
#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 1075bee..24bd8fe 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -1159,6 +1159,47 @@ static void gsp_rm_handle_kgr_get_info(struct gsp_emu_context *ctx,
TU104_MAX_SUBCONTEXT_COUNT);
}
+/*
+ * STATIC_KGR_GET_FLOORSPEEPING_MASKS (0x20800a26)
+ *
+ * Driver computes numGpcs from floorsweepingMasks.gpcMask; zero kills
+ * krcWatchdogInit ("numGpcs > 0" assert -> RmInitAdapter bails). Fill
+ * TU104's present-GPC/TPC masks: 6 GPCs (bits 0-5), 4 TPCs per GPC.
+ */
+static void gsp_rm_handle_kgr_get_floorsweeping_masks(
+ struct gsp_emu_context *ctx,
+ u32 sequence,
+ u32 *out_status,
+ u8 *out_params,
+ u32 *out_params_size)
+{
+ struct tu104_gr_floorsweeping_masks *m =
+ (struct tu104_gr_floorsweeping_masks *)out_params; /* engine 0 */
+ u32 g;
+
+ memset(out_params, 0, TU104_GR_INFO_MAX_ENGINES *
+ sizeof(struct tu104_gr_floorsweeping_masks));
+
+ m->gpcMask = (1U << TU104_NUM_GPC) - 1; /* bits 0-5 = 6 GPCs */
+ m->physGpcMask = m->gpcMask;
+ m->physGfxGpcMask = m->gpcMask;
+ m->numGfxTpc = TU104_NUM_TPC;
+ for (g = 0; g < TU104_NUM_GPC; g++) {
+ m->tpcMask[g] = (1U << TU104_TPC_PER_GPC) - 1; /* 4 TPCs */
+ m->tpcCount[g] = TU104_TPC_PER_GPC;
+ m->mmuPerGpc[g] = 1;
+ m->numPesPerGpc[g] = 1;
+ m->zcullMask[g] = (1U << TU104_TPC_PER_GPC) - 1;
+ }
+
+ *out_params_size = TU104_GR_INFO_MAX_ENGINES *
+ 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);
+}
+
static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
const u8 *elem, u32 sequence)
{
@@ -1259,6 +1300,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_FLOORSWEEPING_MASKS:
+ gsp_rm_handle_kgr_get_floorsweeping_masks(ctx, sequence,
+ &out_status,
+ reply + GSP_RM_CTRL_HDR_SIZE, &out_params_size);
+ break;
+
default:
/* Unknown control command - return success with zeroed params.
* Do NOT echo the request: pure queries pass uninitialized
@@ -1305,6 +1352,62 @@ static void gsp_rm_handle_rm_control(struct gsp_emu_context *ctx,
}
/*
+ * 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.)
+ */
+/*
+ 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)
+{
+ struct gsp_rm_emu *rm = ctx->rm_emu;
+ const struct gsprm_rpc_header *hdr =
+ (const struct gsprm_rpc_header *)(elem + GSPRM_ELEM_HDR_SIZE);
+ const u8 *req = elem + GSPRM_ELEM_HDR_SIZE + sizeof(*hdr);
+ u8 params[16]; /* NVOS00_PARAMETERS: hRoot, hObjectParent, hObjectOld, status */
+ u32 hObjOld = 0;
+
+ if (hdr->length >= sizeof(*hdr) + sizeof(params))
+ memcpy(params, req, sizeof(params));
+ else
+ memset(params, 0, sizeof(params));
+
+ memcpy(&hObjOld, params + 8, 4);
+ memset(params + 12, 0, 4); /* status = NV_OK */
+
+ nv_vgpu_info(ctx->vgpu, "GSPRM: FREE hObjOld=0x%x seq=%u cmd_read=%u stat_write=%u",
+ hObjOld, sequence, rm->cmd_read, rm->stat_write);
+
+ /* Small delay before FREE/UNLOADING replies: mitigates an intermittent
+ * driver-side reply-matching race in the cleanup path. */
+ udelay(2000);
+
+ if (gsp_rm_send(ctx, GSPRM_RPC_FUNC_FREE, sequence, 0,
+ params, sizeof(params)))
+ nv_vgpu_err(ctx->vgpu, "GSPRM: FREE reply send failed");
+ else
+ rm->rpcs_replied++;
+}
+
+/*
* 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).
@@ -1341,36 +1444,27 @@ static void gsp_rm_handle_element(struct gsp_emu_context *ctx, const u8 *elem)
gsp_rm_handle_rm_control(ctx, elem, sequence);
return;
+ case GSPRM_RPC_FUNC_FREE:
+ gsp_rm_handle_free(ctx, elem, sequence);
+ return;
+
default:
/* Synchronous RPC: echo function+sequence with success so the
* driver's rpcRecvPoll completes. */
rm->unknown_rpcs++;
- if (function == 10 || function == 47) {
- /* FREE/UNLOADING: log hObject + queue positions for
- * unload-cycle debugging. driver_rp = the peer's status
- * read pointer (swap-rx): if it lags stat_write, the
- * driver isn't draining our replies. */
- const u8 *p = elem + GSPRM_ELEM_HDR_SIZE +
- sizeof(struct gsprm_rpc_header);
- u32 hc = 0, ho = 0, driver_rp = 0;
-
- if (hdr->length >= sizeof(*hdr) + 12)
- memcpy(&ho, p + 8, 4); /* hObjectOld */
- gsp_rm_read_buf(ctx, rm->cmd_off + 0x20,
- &driver_rp, sizeof(driver_rp));
- nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u hRoot=0x%x hObjOld=0x%x cmd_read=%u stat_write=%u drv_rp=%u stat_seq=%u",
+ if (function == 47) {
+ /* UNLOADING_GUEST_DRIVER: log for unload-cycle debugging */
+ nv_vgpu_info(ctx->vgpu, "GSPRM: RPC %s(%u) seq=%u cmd_read=%u stat_write=%u stat_seq=%u",
gsp_rm_rpc_name(function), function, sequence,
- hc, ho, rm->cmd_read, rm->stat_write,
- driver_rp, rm->stat_seq);
+ rm->cmd_read, rm->stat_write, rm->stat_seq);
} else {
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);
}
- /* Small delay before fn-10/47 replies: mitigates (does not fully
- * eliminate) an intermittent driver-side reply-matching race in
- * the FREE/UNLOADING path. See Handoff-vGPU-IO.md. */
- if (function == 10 || function == 47)
+ /* Small delay before UNLOADING replies: mitigates an intermittent
+ * driver-side reply-matching race in the cleanup path. */
+ if (function == 47)
udelay(2000);
if (gsp_rm_send(ctx, function, sequence, 0, NULL, 0))
nv_vgpu_err(ctx->vgpu, "GSPRM: reply send failed fn=%u", function);
diff --git a/kernel/gsp_rm_emulation.h b/kernel/gsp_rm_emulation.h
index b4e84ef..7329c0b 100644
--- a/kernel/gsp_rm_emulation.h
+++ b/kernel/gsp_rm_emulation.h
@@ -41,6 +41,7 @@ struct gsp_emu_context;
#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_FUNC_FREE 10
#define GSPRM_RPC_EVENT_GSP_INIT_DONE 0x1001
/* rpc_set_guest_system_info_v03_00: version fields the driver reads back
diff --git a/kernel/gsp_rm_tu104.h b/kernel/gsp_rm_tu104.h
index db05f68..ff2a4a7 100644
--- a/kernel/gsp_rm_tu104.h
+++ b/kernel/gsp_rm_tu104.h
@@ -728,6 +728,27 @@ struct tu104_gr_static_info {
/* TU104 max subcontexts (NV_PGRAPH_PRI_FE_CHIP_DEF_INFO_MAX_VEID_COUNT_INIT) */
#define TU104_MAX_SUBCONTEXT_COUNT 64
+/* NV2080_CTRL_INTERNAL_STATIC_GR_FLOORSWEEPING_MASKS (ctrl2080internal.h)
+ * + STATIC_KGR_GET_FLOORSWEEPING_MASKS cmd = 0x20800a26.
+ * Driver computes numGpcs from floorsweepingMasks.gpcMask - zero kills
+ * krcWatchdogInit ("numGpcs > 0" assert -> RmInitAdapter bails). */
+#define TU104_CTRL_STATIC_KGR_GET_FLOORSWEEPING_MASKS 0x20800a26
+#define TU104_GR_MAX_GPC 12
+#define TU104_GR_MAX_TPC_PER_GPC_COUNT 10
+
+struct tu104_gr_floorsweeping_masks {
+ u32 gpcMask;
+ u32 tpcMask[TU104_GR_MAX_GPC];
+ u32 tpcCount[TU104_GR_MAX_GPC];
+ u32 physGpcMask;
+ u32 mmuPerGpc[TU104_GR_MAX_GPC];
+ u32 tpcToPesMap[TU104_GR_MAX_TPC_PER_GPC_COUNT];
+ u32 numPesPerGpc[TU104_GR_MAX_GPC];
+ u32 zcullMask[TU104_GR_MAX_GPC];
+ u32 physGfxGpcMask;
+ u32 numGfxTpc;
+};
+
/* TU104 SM topology: 6 GPCs x 4 TPCs x 2 SMs = 48 SMs, 24 TPCs (RTX 2080 SUPER) */
#define TU104_NUM_GPC 6
#define TU104_TPC_PER_GPC 4