From c3a295efd1b1132bbaa5d422cd05b3fa8e89a8bf Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Mon, 21 Sep 2026 13:06:51 -0400 Subject: kernel: Fix GSP emulation shadow binding, eventfd wiring, RPC livelocks + docs suite + repo reorg CRITICAL FIXES (guest driver bring-up): - gsp_emulation.c: emulation engine bound to the WRONG BAR0 - it polled the host physical BAR0 (bars[0].vaddr ioremap) for the doorbell and read RPCs from a local dma_alloc_wc buffer, while the guest writes land in the vzalloc'd bar0_shadow. The RPC path could never work; PBA writes even corrupted the host GPU's physical BAR0+0x3000. gsp_emu_init now binds exclusively to bar0_shadow (doorbell/mailbox/PBA aliases; shared_mem_dma==0 marks the alias so cleanup never dma_free's it). - main.c SET_IRQS: DATA_EVENTFD eventfds were DISCARDED ('TODO' + kfree) - the guest never received MSI-X completions. Full wiring now: eventfd_ctx_fdget per vector, fd -1 clears, dev_close releases refs. gsp_emu_inject_completion_interrupt looks up vgpu->irqs[0].trigger LIVE (init-time copy was permanently NULL since QEMU wires after gsp_emu_init). - gsp_rpc_client.c: BAR_TRAP/MIGRATE in emulation mode called gsp_rpc_submit_raw which rings the SAME doorbell the thread polls - self-triggered RPC livelock + multi-second stalls. Now fail fast with -ENOTSUPP + pr_warn_ratelimited (guest BAR access is direct MMIO). MORE FIXES: - main.c: nv_vgpu_config_write returned SIZE_MAX (while(count--) wrap) and was dead code while the VFIO write handler did unfiltered byte copies (guest could rewrite vendor ID/BARs). Return fixed; VFIO write handler now routes through the per-register filter. - kernel/Makefile: M=$(PWD) used the env var which is stale when invoked via the top-level make (pointed at repo root -> kbuild silently built NOTHING; explains every 'stale .ko' incident). Now M=$(CURDIR). - Warnings sweep: header prototypes for mdev/type functions, format casts in gsp_comm.c DRY-RUN logs, unused variable. ROBUSTNESS (harness/tooling): - vgpu_vm.py: run had no timeout (start could block forever); guest_shell recv could block forever on a wedged guest. Both now bounded; guest_shell pre-flights guest-ping and reports wedge diagnostics. - stack_manager.sh: qemu/qemu-debug now launch via the vgpu_vm.py harness (workflow rule: never raw QEMU). REPO REORGANIZATION: - driver/ (legacy guest-side gsp-vgpu.ko) moved to legacy-driver-guest/ (reference only, not built; README explains). 3 legacy un-buildable test files moved to legacy-driver-guest/kernel-tests/. Top-level Makefile cleaned of the dead DRIVER_DIR reference. DOCUMENTATION SUITE (new): - docs/GSP_EMULATION_ARCHITECTURE.md - emulator deep-dive: shadow BAR0 model, doorbell protocol, dispatch table, MSI-X path, lifecycle, memory- model caveats - docs/DEBUGGING.md - QEMU GDB stub workflow, serial console, QMP NMI, failure taxonomy, full bring-up validation checklist, EPT-livelock case study, version-matrix discipline - docs/ONBOARDING.md - repo tour, coding standards (incl. virt_to_phys / dma_addr_t / get-put rules), first build & test - docs/VERSION_UPGRADE.md - end-to-end new-driver-version procedure (firmware fetch, Ghidra analysis, configs, signatures, validation) - docs/PERFORMANCE.md - bare-metal A/B methodology + targets - README.md doc index; STATE.md refreshed; DEVELOPMENT_WORKFLOW.md corrected (load order: open-iov-vfio BEFORE nvidia; stock-driver blacklist note) --- docs/GSP_EMULATION_ARCHITECTURE.md | 192 +++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 docs/GSP_EMULATION_ARCHITECTURE.md (limited to 'docs/GSP_EMULATION_ARCHITECTURE.md') diff --git a/docs/GSP_EMULATION_ARCHITECTURE.md b/docs/GSP_EMULATION_ARCHITECTURE.md new file mode 100644 index 0000000..eb7f697 --- /dev/null +++ b/docs/GSP_EMULATION_ARCHITECTURE.md @@ -0,0 +1,192 @@ +# Open-IOV GSP Emulation Engine — Architecture + +> **Audience**: developers working on `kernel/gsp_emulation.c`, `kernel/gsp_rpc_client.c`, +> `kernel/bar_emulation.c`, or debugging guest driver bring-up. +> +> **Scope**: how the host-side GSP emulator actually works today (post-2026-09-21 +> fixes). For multi-version/portability design see `PORTABLE_ARCHITECTURE.md`; +> for the original refactor plan see `refactored-architecture.md`. + +--- + +## 1. Purpose + +An NVIDIA guest driver (stock `nvidia.ko`, no Open-IOV code in the guest) expects +to talk to a **GSP (GPU System Processor)** running on the GPU: it writes RPC +messages to a mailbox in BAR0, rings a doorbell, and waits for a completion +interrupt plus a response written back into the mailbox. + +Open-IOV has no real GSP for the guest (the physical GPU's GSP belongs to the +host driver). Instead, `open-iov-vfio.ko` **emulates the entire GSP on the +host**: a per-mdev kernel thread watches the guest's emulated BAR0, consumes +RPCs, performs them in software (or declines them), writes responses back, and +injects MSI-X interrupts. + +``` +┌──────────────────────────── Guest VM ────────────────────────────┐ +│ stock nvidia.ko (570.144) │ +│ │ writel(doorbell), memcpy(RPC → BAR0+0x1000) │ +│ ▼ │ +│ vGPU BAR0 (guest phys) ── VFIO mmap ──► host bar0_shadow │ +└──────────────────────────────────────────────────────────────────┘ + │ (16 MB vzalloc'd shadow, uncached) +┌──────────────────────────── Host ────────────────────────────────┐ +│ gsp_emu thread (open-iov-vfio.ko) │ +│ poll shadow+0x110C00 (doorbell) │ +│ → read RPC msg from shadow+0x1000 │ +│ → dispatch (12 RPC types, emulation implementations) │ +│ → write response to shadow+0x1080 │ +│ → set PBA bit at shadow+0x3000 │ +│ → eventfd_signal(msix_trigger[0]) → guest MSI-X │ +└──────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 2. The single source of truth: `bar0_shadow` + +The guest's BAR0 is **not** the physical GPU's BAR0. It is a 16 MB software +shadow allocated with `vzalloc()` in `nv_vgpu_bar_emul_init()` +(`bar_emulation.c`) and mapped into the guest via the VFIO `mmap` handler +(`main.c: nv_vgpu_vfio_dev_dev_mmap`, per-page `vmalloc_to_pfn`). + +Everything the guest driver does with BAR0 lands in this shadow: + +| BAR0 offset | Region | Guest writes go to | Emulation reads from | +|------------------|-----------------------------------|--------------------|----------------------| +| `0x0000-0x0FFF` | (shadow register area) | shadow | shadow | +| `0x1000` | RPC request mailbox (72 B msg) | shadow | `shared_mem_virt` | +| `0x1080` | RPC response mailbox (60 B) | shadow (written by emu) | guest reads | +| `0x2000` | MSI-X table (32 × 16 B) | shadow | (guest-managed) | +| `0x3000` | MSI-X PBA (pending bits) | shadow (written by emu) | guest reads | +| `0x110C00` | **GSP doorbell** | shadow | thread polls this | +| rest | emulated register space | shadow | shadow | + +> **Historical bug (fixed 2026-09-21)**: the emulation engine previously polled +> the *host physical* BAR0 (`vgpu->bars[0].vaddr`, an `ioremap` of the real +> GPU) for the doorbell and read RPCs from a separate `dma_alloc_wc` buffer — +> it could never see guest traffic and even wrote PBA bits into the real GPU's +> BAR0+0x3000. `gsp_emu_init()` now binds exclusively to `bar0_shadow`. +> `ctx->shared_mem_dma == 0` marks the shared buffer as a shadow alias so +> cleanup never `dma_free`s it. + +### 2.1 Transport protocol (as emulated) + +1. Guest writes the 72-byte `struct gsp_rpc_msg` to `BAR0+0x1000` + (`type`, `subcmd`, `payload[6]`, `seq_id`, `flags`). +2. Guest writes a non-zero value to the doorbell at `BAR0+0x110C00`. +3. The emulation thread sees the doorbell, ACKs it (writes 0), validates + `seq_id != 0`, and calls `gsp_emu_dispatch_rpc()`. +4. Dispatch fills a 60-byte `struct gsp_rpc_response` (`status`, `type`, + `result[6]`, `seq_id`) written to `BAR0+0x1080`. +5. Thread sets the vector-0 PBA bit at `BAR0+0x3000` and signals the wired + eventfd → QEMU delivers MSI-X to the guest. +6. Stats: the **thread path** increments `processed_rpcs`/`failed_rpcs` and + accumulates `total_latency_ns` (read via `gsp_emu_get_stats()`; note the + public `gsp_emu_process_rpc()` entry does not bump counters). + +--- + +## 3. RPC dispatch + +`gsp_emu_dispatch_rpc()` (`gsp_emulation.c`) switches on `msg->type` and calls +the `gsp_rpc_*()` API (`gsp_rpc_client.c`), which routes to an **emulation +implementation** when `gsp_rpc_emulation_is_enabled()` (set once per mdev by +`gsp_emu_init`). + +| Type (val) | Handler path in emulation | Notes | +|----------------|--------------------------------------------------------|-------| +| ALLOC (0x01) | `gsp_rpc_alloc_emul` — host pages + `dma_map_page` | tracks `gsp_vram_alloc` list | +| FREE (0x02) | `gsp_rpc_free_emul` | | +| MAP (0x03) | `gsp_rpc_map_emul` | | +| UNMAP (0x04) | `gsp_rpc_unmap_emul` | | +| PIN (0x05) | `gsp_rpc_pin_emul` | | +| UNPIN (0x06) | `gsp_rpc_unpin_emul` | | +| SCHEDULE (0x07)| local workqueue (`gsp_sched_wq`), completes immediately| no real work submission | +| YIELD (0x08) | no-op success | | +| NOTIFY (0x09) | log-only in dispatch | no eventfd delivery yet | +| BAR_TRAP (0x0A)| **`-ENOTSUPP`** (fail-fast) | guest BAR access is direct MMIO, no RPC needed; real shadow-register handler TODO | +| MIGRATE (0x0B) | **`-ENOTSUPP`** (fail-fast) | 4-phase host protocol lives in `migration.c`; RPC wiring TODO | +| DEBUG (0xFF) | log-only in dispatch | | +| unknown | `-ENOTSUPP` | | + +> **Livelock fixed (2026-09-21)**: BAR_TRAP/MIGRATE previously fell through to +> `gsp_rpc_submit_raw()`, which writes the request into the same shadow mailbox +> and **rings the same doorbell the thread polls** — a self-triggered infinite +> RPC loop plus multi-second stalls. Emulation mode now fails these fast with +> `pr_warn_ratelimited`. + +### Memory model caveat (design note) + +Guest VRAM (BAR1) is mapped **directly to the host GPU's physical BAR1** (gpa). +`gsp_rpc_alloc_emul`, however, services ALLOC with *host system pages* DMA-mapped +to the physical device. These two models are not yet unified: a guest allocation +returning `gpu_va=0x100000000+` is not visible through the guest's BAR1 window. +This is a known design gap to resolve before CUDA workloads; it does not affect +driver probe/GSP boot, which is the current milestone. + +--- + +## 4. Interrupt delivery (MSI-X) + +1. QEMU wires vectors with `VFIO_DEVICE_SET_IRQS` + `DATA_EVENTFD`. + Our ioctl handler takes `eventfd_ctx_fdget()` references into + `vgpu->irqs[vector].trigger` (fd `-1` clears; close releases all refs). +2. On RPC completion the thread calls `gsp_emu_inject_completion_interrupt()`: + sets the PBA bit in the shadow and `eventfd_signal()`s the **live** trigger + from `vgpu->irqs[0]` (looked up per injection — QEMU wires eventfds *after* + `gsp_emu_init`, so an init-time copy would be permanently NULL; this was a + real bug fixed 2026-09-21). + +INTx/MSI/ERR vectors are wired the same way (stored per-vector); the emulator +currently fires vector 0 for GSP completion. + +--- + +## 5. Lifecycle + +``` +mdev create (nv_vgpu_mdev_create) + └─ schedule init_work + └─ nv_vgpu_bar_init → nv_vgpu_bar_emul_init (bar0_shadow) + └─ nv_vgpu_irq_init / nv_vgpu_memory_init + └─ gsp_emu_init + ├─ bind ctx to bar0_shadow (doorbell/shared/mem aliases) + ├─ gsp_client_init_emulation (RPC client, emulation_mode=true) + ├─ gsp_rpc_emulation_set(true) + └─ kthread_run(gsp_emu_thread_fn) + +mdev remove (nv_vgpu_mdev_remove) + └─ cancel init_work, wait for VFIO fds to close + └─ vfio_unregister_group_dev + └─ cleanup work: migration → gsp_emu (kthread_stop w/ 5 s completion) + → memory → irq → BAR (frees bar0_shadow) + └─ list_del, pci_dev_put, vfio_put_device +``` + +--- + +## 6. Debugging the emulator + +- Host dmesg: `dmesg -T | grep -iE 'gsp|vgpu|open-iov'` +- Stats: `gsp_emu_get_stats()` (or add a debugfs readout; counters update on + the thread path only) +- Guest side: serial log `/tmp/vgpu-guest-serial.log`, QEMU GDB stub + `tcp::1234` — see `DEBUGGING.md` for the full workflow and the validation + checklist. +- Unit test: `kernel/tests/gsp_emulation_test.ko` drives + `gsp_emu_process_rpc()` with crafted mailbox messages (requires + `open-iov-vfio.ko` loaded; `depends:` is set accordingly). + +--- + +## 7. Key files + +| File | Role | +|------|------| +| `kernel/gsp_emulation.c/h` | thread, doorbell protocol, dispatch, MSI-X injection | +| `kernel/gsp_rpc_client.c/h` | RPC API + emulation implementations + message structs | +| `kernel/bar_emulation.c/h` | `bar0_shadow` allocation, MSI-X shadow state | +| `kernel/main.c` | VFIO ops: mmap (shadow mapping), SET_IRQS (eventfd wiring), config space | +| `kernel/memory.c` | simple mapping/pin tracking (host management API) | +| `kernel/migration.c` | 4-phase live-migration skeleton (not RPC-wired yet) | -- cgit v1.2.3