summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-09 19:27:48 -0500
committerArthur Hanson Rasmusson <arthur@vgpu.io>2026-11-09 19:27:48 -0500
commita478f0b418bd3dd5fbcc6260979865160ca25cf0 (patch)
treee29fb7ec56741b9be1d8e747c108cb661b89d6c2
parent4a2cf8292ad37767de22a26e7e35d0a8a06edad3 (diff)
downloadopen-iov-a478f0b418bd3dd5fbcc6260979865160ca25cf0.tar.gz
open-iov-a478f0b418bd3dd5fbcc6260979865160ca25cf0.zip
vgsp: raise the GSP engine event interrupt so guest ISR drains events and wakes poll() waiters
The emulated GSP only signalled the MSI-X eventfd when it queued a status record. The guest's primary ISR (nvidia_isr) decides pending from the CPU_INTR leaf registers, which we never set for the GSP stall vector, so it returned IRQ_NONE: IRQ 33 counted thousands of interrupts while the nvidia event thread sat idle and a client blocked in poll() on /dev/nvidia0 (a GLX swap) waited forever even though the event was already in the status queue. Mirror what a real GSP-RM does when it queues an event: - program RISC-V IRQMASK/IRQDEST for SWGEN0 (read-only to the guest; the GSP firmware owns them), - set SWGEN0 in the falcon IRQSTAT, - latch GSP stall vector 155 in the CPU interrupt tree (LEAF[4] bit 27, UVM_SHARED subtree 2 -> TOP[0] bit 2), - signal MSI-X. Raise it only for POST_EVENT records (replies are polled by the client). Verified on r1680: the guest now writes IRQSCLR SWGEN0, kgspService_TU102 drains events, and glxgears renders (was a hard hang).
-rw-r--r--kernel/gsp_emulation.c57
-rw-r--r--kernel/gsp_emulation.h26
-rw-r--r--kernel/gsp_rm_emulation.c14
3 files changed, 92 insertions, 5 deletions
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index 47c8151..38f6284 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -644,6 +644,63 @@ void gsp_emu_inject_completion_interrupt(struct gsp_emu_context *ctx, u32 seq_id
eventfd_signal(trigger);
}
+/*
+ * Raise the GSP engine event interrupt.
+ *
+ * Mirrors what a real GSP-RM does when it queues an event for the host:
+ * - program the RISC-V interrupt mask/destination so SWGEN0 is routed to
+ * the CPU (read-only to the guest; the GSP firmware owns them),
+ * - set SWGEN0 in the falcon IRQSTAT,
+ * - latch the GSP stall vector (155) in the host CPU interrupt tree:
+ * LEAF[4] bit 27 and TOP[0] bit 2 (UVM_SHARED subtree 2),
+ * - signal MSI-X.
+ *
+ * Without the leaf bit the guest's primary ISR returns IRQ_NONE and the
+ * threaded handler (kgspService_TU102 -> kgspRpcRecvEvents -> nv_post_event)
+ * never runs, so a client blocked in poll() on /dev/nvidia0 (a GLX swap)
+ * waits forever even though the event is sitting in the status queue.
+ */
+void gsp_emu_raise_gsp_event_irq(struct gsp_emu_context *ctx)
+{
+ u32 reg, val;
+ u32 leaf_idx = GSP_EMU_GSP_EVENT_VECTOR / 32;
+ u32 leaf_bit = GSP_EMU_GSP_EVENT_VECTOR % 32;
+ u32 subtree = GSP_EMU_GSP_EVENT_VECTOR / 64;
+ u32 top_idx = subtree / 32;
+ u32 top_bit = subtree % 32;
+
+ if (!ctx || !ctx->bar0_vaddr)
+ return;
+
+ /* IRQMASK / IRQDEST: route SWGEN0 to the CPU (idempotent). */
+ reg = GSP_EMU_REG_RISCV_IRQMASK;
+ val = gsp_emu_shadow_read32(ctx, reg);
+ if (!(val & GSP_EMU_FALCON_IRQSTAT_SWGEN0))
+ gsp_emu_shadow_write32(ctx, reg,
+ val | GSP_EMU_FALCON_IRQSTAT_SWGEN0);
+ reg = GSP_EMU_REG_RISCV_IRQDEST;
+ val = gsp_emu_shadow_read32(ctx, reg);
+ if (!(val & GSP_EMU_FALCON_IRQSTAT_SWGEN0))
+ gsp_emu_shadow_write32(ctx, reg,
+ val | GSP_EMU_FALCON_IRQSTAT_SWGEN0);
+
+ /* Pending source: SWGEN0. */
+ reg = GSP_EMU_REG_FALCON_IRQSTAT;
+ val = gsp_emu_shadow_read32(ctx, reg);
+ gsp_emu_shadow_write32(ctx, reg,
+ val | GSP_EMU_FALCON_IRQSTAT_SWGEN0);
+
+ /* CPU interrupt tree: LEAF[leaf_idx] |= bit, TOP[top_idx] |= bit. */
+ reg = GSP_EMU_REG_CPU_INTR_LEAF_BASE + leaf_idx * 4;
+ val = gsp_emu_shadow_read32(ctx, reg);
+ gsp_emu_shadow_write32(ctx, reg, val | (1U << leaf_bit));
+ reg = GSP_EMU_REG_CPU_INTR_TOP_BASE + top_idx * 4;
+ val = gsp_emu_shadow_read32(ctx, reg);
+ gsp_emu_shadow_write32(ctx, reg, val | (1U << top_bit));
+
+ gsp_emu_inject_completion_interrupt(ctx, 0);
+}
+
/* ============================================================
* Emulation Thread
* ============================================================ */
diff --git a/kernel/gsp_emulation.h b/kernel/gsp_emulation.h
index 9a8f241..d9a7f01 100644
--- a/kernel/gsp_emulation.h
+++ b/kernel/gsp_emulation.h
@@ -116,6 +116,29 @@
#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)
+/* GSP event interrupt (Turing, physical-GPU path).
+ *
+ * A real GSP-RM posts an event into the status queue and raises the GSP
+ * engine interrupt: it sets SWGEN0 in the falcon IRQSTAT and the host CPU's
+ * interrupt tree latches the GSP stall vector (155 = LEAF[4] bit 27, UVM_SHARED
+ * subtree 2 -> TOP[0] bit 2). The guest's primary ISR (nvidia_isr) then
+ * returns IRQ_WAKE_THREAD, kgspService_TU102 reads
+ * IRQSTAT & RISCV_IRQMASK & RISCV_IRQDEST, sees SWGEN0, calls
+ * kgspRpcRecvEvents -> nv_post_event -> wake_up_interruptible, which is what
+ * releases a client blocked in poll() on /dev/nvidia0 (e.g. a GLX swap).
+ *
+ * We were only signalling the MSI-X eventfd, so the primary ISR returned
+ * IRQ_NONE (no pending leaf bit) and the event thread never ran: IRQ 33
+ * counted thousands of interrupts while the guest's nvidia worker threads sat
+ * idle and the client polled forever. IRQMASK/IRQDEST are read-only to the
+ * guest (the GSP firmware owns them), so the emulated GSP must program them. */
+#define GSP_EMU_REG_FALCON_IRQSCLR 0x110004 /* -W- : write 1 to clear */
+#define GSP_EMU_REG_FALCON_IRQSTAT 0x110008 /* R-- : pending sources */
+#define GSP_EMU_REG_RISCV_IRQMASK 0x1112b4 /* R-I : enabled sources */
+#define GSP_EMU_REG_RISCV_IRQDEST 0x1112b8 /* RW : routed to CPU */
+#define GSP_EMU_FALCON_IRQSTAT_SWGEN0 (1U << 6)
+#define GSP_EMU_GSP_EVENT_VECTOR 155 /* MC_ENGINE_IDX_GSP stall */
+
/* 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)
@@ -195,6 +218,9 @@ void gsp_emu_cleanup(struct nv_vgpu_device *vgpu);
void gsp_emu_clear_boot_state(struct gsp_emu_context *ctx, const char *reason);
int gsp_emu_process_rpc(struct gsp_emu_context *ctx);
void gsp_emu_inject_completion_interrupt(struct gsp_emu_context *ctx, u32 seq_id);
+/* Raise the GSP event interrupt (falcon SWGEN0 + CPU_INTR leaf/top + MSI-X)
+ * so the guest's ISR drains the status queue and wakes poll() waiters. */
+void gsp_emu_raise_gsp_event_irq(struct gsp_emu_context *ctx);
/* ============================================================
* Debug/Statistics
diff --git a/kernel/gsp_rm_emulation.c b/kernel/gsp_rm_emulation.c
index c14a767..f395ce6 100644
--- a/kernel/gsp_rm_emulation.c
+++ b/kernel/gsp_rm_emulation.c
@@ -737,11 +737,15 @@ static int gsp_rm_send_flags(struct gsp_emu_context *ctx, u32 function,
return ret;
}
- /* Notify the guest (MSI-X vector 0). The driver's bottom-half drains
- * events under the GPU lock; the rpcRecvPoll poller holds the same
- * lock from send to poll, so a reply can't be consumed by the event
- * drain while a poller waits for it (same as real GSP-RM). */
- gsp_emu_inject_completion_interrupt(ctx, rm->stat_seq);
+ /* Notify the guest. An event must also raise the GSP engine interrupt
+ * (falcon SWGEN0 + CPU_INTR leaf/top) so the guest's ISR drains the
+ * status queue and nv_post_event() wakes a client blocked in poll() on
+ * /dev/nvidia0 (e.g. a GLX swap). Replies only need the MSI-X ping
+ * because the client polls the queue for them. */
+ if (function == GSPRM_RPC_EVENT_POST_EVENT)
+ gsp_emu_raise_gsp_event_irq(ctx);
+ else
+ gsp_emu_inject_completion_interrupt(ctx, rm->stat_seq);
mutex_unlock(&rm->stat_lock);
return 0;