summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--README.md28
-rw-r--r--STATE.md44
-rw-r--r--docs/DEBUGGING.md190
-rw-r--r--docs/DEVELOPMENT_WORKFLOW.md39
-rw-r--r--docs/GSP_EMULATION_ARCHITECTURE.md192
-rw-r--r--docs/ONBOARDING.md166
-rw-r--r--docs/PERFORMANCE.md98
-rw-r--r--docs/VERSION_UPGRADE.md137
-rw-r--r--kernel/Makefile9
-rw-r--r--kernel/gsp_comm.c5
-rw-r--r--kernel/gsp_emulation.c71
-rw-r--r--kernel/gsp_rpc_client.c40
-rw-r--r--kernel/include/nvidia-vgpu-vfio.h8
-rw-r--r--kernel/main.c85
-rw-r--r--legacy-driver-guest/Makefile (renamed from driver/Makefile)0
-rw-r--r--legacy-driver-guest/README.md21
-rw-r--r--legacy-driver-guest/bar/gsp_bar.c (renamed from driver/bar/gsp_bar.c)0
-rw-r--r--legacy-driver-guest/core/gsp_core.c (renamed from driver/core/gsp_core.c)0
-rw-r--r--legacy-driver-guest/debugfs/gsp_debugfs.c (renamed from driver/debugfs/gsp_debugfs.c)0
-rw-r--r--legacy-driver-guest/include/gsp_core.h (renamed from driver/include/gsp_core.h)0
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c (renamed from kernel/tests/gsp_vgpu_comprehensive_test.c)0
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c (renamed from kernel/tests/gsp_vgpu_kunit_test.c)0
-rw-r--r--legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c (renamed from kernel/tests/gsp_vgpu_mock_test.c)0
-rw-r--r--legacy-driver-guest/migration/gsp_migration.c (renamed from driver/migration/gsp_migration.c)0
-rw-r--r--legacy-driver-guest/rpc/gsp_rpc.c (renamed from driver/rpc/gsp_rpc.c)0
-rw-r--r--legacy-driver-guest/vf/gsp_vf.c (renamed from driver/vf/gsp_vf.c)0
-rw-r--r--legacy-driver-guest/wpr/gsp_wpr.c (renamed from driver/wpr/gsp_wpr.c)0
-rwxr-xr-xscripts/stack_manager.sh30
-rwxr-xr-xuserspace/tests/test_vgpu_userspacebin0 -> 16584 bytes
30 files changed, 1060 insertions, 105 deletions
diff --git a/Makefile b/Makefile
index b4c0654..ae7a8e2 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,6 @@ SHELL := /bin/bash
# Directories
KERNEL_DIR := kernel
-DRIVER_DIR := driver
USERSPACE_DIR := userspace
FIRMWARE_DIR := firmware/57X-GSP/570.144
@@ -77,7 +76,6 @@ firmware-check:
# Clean
clean:
$(MAKE) -C $(KERNEL_DIR) clean
- $(MAKE) -C $(DRIVER_DIR) clean
$(MAKE) -C $(USERSPACE_DIR)/lib clean
$(MAKE) -C $(USERSPACE_DIR)/examples clean
$(MAKE) -C $(USERSPACE_DIR)/tests clean
diff --git a/README.md b/README.md
index 17bf93d..36b66ca 100644
--- a/README.md
+++ b/README.md
@@ -75,7 +75,8 @@ No Open-IOV component depends on a specific wrapper.
kernel/ open-iov-vfio.ko host driver (mdev, VFIO, GSP emulation,
firmware analyzer, dynamic RPC dispatcher, arch/version layers)
userspace/ libiov.so management library, examples, tests
-driver/ legacy guest driver (being removed in the host-only refactor)
+legacy-driver-guest/ historical guest-side driver, preserved for reference
+ only - NOT built (see its README)
firmware/ GSP analysis: scripts, docs, address maps (blobs NOT in git -
fetched by install.sh from linux-firmware.git)
patches/nvidia/ patch series applied to NVIDIA open-gpu-kernel-modules
@@ -103,15 +104,30 @@ images/ISOs, Ghidra project databases, and any local VM-wrapper stacks.
## Documentation
+Start here: **[Developer onboarding](docs/ONBOARDING.md)** — repo tour,
+coding standards, first build & test.
+
- [STATE.md](STATE.md) — current state, limitations, bug tracker
-- [Portable architecture](docs/PORTABLE_ARCHITECTURE.md)
-- [Host-only GSP emulation design](docs/refactored-architecture.md)
+- [GSP emulation engine architecture](docs/GSP_EMULATION_ARCHITECTURE.md) —
+ how the host-side emulator works (shadow BAR0, doorbell protocol, dispatch,
+ MSI-X injection)
+- [Debugging guide](docs/DEBUGGING.md) — QEMU GDB stub, serial console,
+ failure taxonomy, bring-up validation checklist
+- [Version upgrade guide](docs/VERSION_UPGRADE.md) — supporting a new NVIDIA
+ driver/GSP version end-to-end (Ghidra workflow)
+- [Performance testing](docs/PERFORMANCE.md) — bare-metal A/B methodology
+- [Portable architecture](docs/PORTABLE_ARCHITECTURE.md) — multi-version,
+ multi-hardware design
+- [Host-only GSP emulation design](docs/refactored-architecture.md) —
+ original refactor plan
- [Implementation TODO](docs/refactored-implementation-to-do.md)
-- [Firmware analysis](wiki/GSP-570.144W-Firmware-Analysis.md) —
+- [Development workflow reference](docs/DEVELOPMENT_WORKFLOW.md) — exhaustive
+ command reference
+- Firmware canon: [analysis](wiki/GSP-570.144W-Firmware-Analysis.md) —
[RPC format](wiki/RPC-Message-Format.md) —
[BAR emulation](wiki/BAR-Emulation-Specification.md) —
- [Migration protocol](wiki/Migration-Protocol.md) —
- [Address reference](wiki/Address-Reference.md)
+ [migration protocol](wiki/Migration-Protocol.md) —
+ [address reference](wiki/Address-Reference.md)
## License
diff --git a/STATE.md b/STATE.md
index dc81f80..73a8654 100644
--- a/STATE.md
+++ b/STATE.md
@@ -1,6 +1,6 @@
# Open-IOV Project State
-*Last updated: 2026-09-21*
+*Last updated: 2026-09-21 (PM)*
## Mission
@@ -21,11 +21,14 @@ proprietary GRID licenses — via host-side GSP emulation in
| Refactor 5: Guest driver integration (mdev creation, VFIO registration) | ✅ Unblocked, in test |
| Refactor 6-11: CUDA / graphics / benchmarks / migration / Windows / hardening | ⏳ Pending |
-**Right now**: host stack (nvidia.ko 610.57.04-base + open-iov-vfio.ko) loads
-cleanly, mdev device creates and removes cleanly, IOMMU group assignment
-works, `/dev/vfio/<n>` appears. Guest VM (Ubuntu 24.04) is installed; guest
-NVIDIA driver installation is in progress. Next: guest `nvidia.ko` probe
-against the emulated vGPU, then `nvidia-smi`, then CUDA validation.
+**Right now**: the **guest-probe hang root cause is found and fixed** (VFIO
+mmap garbage PFN → KVM EPT livelock — see below) plus a sweep of correctness
+fixes (mdev remove leaks, eventfd wiring, GSP shadow binding, RPC livelock).
+Guest driver **570.144 stock runfile installed** in the VM. The fixed module
+is built and pushed; it loads after the next host reboot (a leaked
+`gsp_emu` kthread from the pre-fix remove path pins the old module — rmmod
+would oops). Post-reboot: guest `nvidia.ko` probe → `nvidia-smi` → teardown
+regression → unit test → CUDA validation (`docs/DEBUGGING.md` §5 checklist).
## Recently solved bugs
@@ -38,24 +41,43 @@ against the emulated vGPU, then `nvidia-smi`, then CUDA validation.
| `Unknown symbol nvidia_vgpu_vfio_probe` | NVIDIA OpenRM built without `KBUILD_EXTRA_SYMBOLS` | Build order automated in `stack_manager.sh` (kernel → nvidia → userspace) |
| `vfio 1: No such file or directory` | VFIO device registration is NOT automatic for mdev | `vfio_register_emulated_iommu_dev()` in probe, unregister in remove (Intel kvmgt pattern) |
| Guest-visible PCI config space wrong | BAR types/offsets, subsystem ID offsets, MSI-X table size | Config space rewritten to match physical TU104 (BAR0 16MB 32-bit non-prefetch, BAR1 256MB 64-bit prefetch VRAM, BAR3 32MB; 32 MSI-X vectors; subsystem IDs at 0x2C/0x2E) |
+| **Guest driver probe hard-hangs whole VM (580 & 570.144)** | VFIO `mmap` used `virt_to_phys()` on `vzalloc`'d BAR0 shadow + ioremap'd BAR2/3 → garbage PFN → KVM `make_spte` reserved-bit WARN → EPT-violation livelock (diagnosed via QEMU GDB stub: one vCPU frozen on a single MMIO instruction) | BAR0 mapped per-page via `vmalloc_to_pfn()`; BAR2/3/4/5 map `bars[N].gpa` directly; `nv_vgpu_mmap_virt_to_pfn()` helper |
+| **GSP emulation thread never saw guest RPCs** | Engine polled the *host physical* BAR0 doorbell + a local `dma_alloc_wc` buffer; guest writes land in the vzalloc `bar0_shadow` | `gsp_emu_init` binds exclusively to `bar0_shadow` (doorbell/mailbox/PBA aliases); also stopped PBA writes corrupting the host GPU's physical BAR0+0x3000 |
+| **Guest never received MSI-X completions** | `SET_IRQS DATA_EVENTFD` handler discarded eventfds (`TODO` + `kfree`); `msix_trigger[]` all NULL; inject used an init-time (stale) copy | Full eventfd wiring: `eventfd_ctx_fdget` per vector, `-1` clears, close releases refs; inject looks up `vgpu->irqs[0].trigger` live |
+| **BAR_TRAP/MIGRATE RPC livelock** | dispatch → `gsp_rpc_submit_raw` rings the same doorbell the thread polls → self-triggered infinite loop + 2–30 s stalls | emulation-mode guards return `-ENOTSUPP` with `pr_warn_ratelimited` |
+| **mdev remove leaked everything** | remove never `list_del`'d (available_instances stuck), never ran cleanup (leaked `gsp_emu` kthread), never unregistered VFIO (leaked `/dev/vfio/N` + iommu group), never put pdev/freed struct | kvmgt-style remove: wait fds → `vfio_unregister_group_dev` → cleanup work → `list_del` → `pci_dev_put` → `vfio_put_device` |
+| `dma_map_page` IOVA corruption | 64-bit `dma_addr_t` stored in `int ret` (truncates ≥4 GB IOVAs under IOMMU) | dedicated `dma_addr_t iova` in `gsp_rpc_alloc_emul` |
+| config-space write returned `-1` (`SIZE_MAX`) | `while(count--)` wrap + bogus return expression; function was dead code while the live path did unfiltered byte copies | return fixed; VFIO write handler now routes through the per-register filter (guest can't rewrite vendor ID/BARs) |
+| Misleading `MODULE_SOFTDEP("pre: nvidia")` | module has zero nvidia imports (it *exports* `nvidia_vgpu_vfio_*`) | removed; load order is open-iov-vfio **before** nvidia.ko |
+| Kernel unit tests didn't build | written against a removed struct-based RPC API | rewritten to drive `gsp_emu_process_rpc()` with crafted mailbox messages; `depends: open-iov-vfio` |
## Known open bugs / limitations
-- Guest `nvidia.ko` end-to-end probe not yet validated (in progress)
-- Kernel test modules (`kernel/tests/`) need API updates to match final
- driver headers (low priority)
+- Guest `nvidia.ko` end-to-end probe not yet validated (fixed module loads
+ post-reboot; checklist in `docs/DEBUGGING.md` §5)
+- **Memory-model gap**: guest VRAM (BAR1) maps directly to physical BAR1 gpa,
+ but `gsp_rpc_alloc_emul` services ALLOC with host system pages — the two
+ are not unified; must be resolved before CUDA/DMA-heavy workloads
+ (`docs/GSP_EMULATION_ARCHITECTURE.md` §3)
+- BAR_TRAP / MIGRATE RPCs fail fast with `-ENOTSUPP` in emulation mode
+ (guest's normal BAR access is direct MMIO; real handlers TODO)
+- `gsp_emu_process_rpc()` doesn't bump stats counters (only the kthread
+ path does) — cosmetic
- WPR callback registration lacks kernel eventfd support
- (`TODO` in driver/wpr)
+ (`TODO` in driver/wpr — legacy guest-driver tree)
- Migration WPR state save/restore incomplete
- BAR `force_access` path not fully wired (unused-function warning)
- Kernel `SCHEDULE` emulation returns success without real work submission
(userspace emulation works)
- Async `NOTIFY` events not emulated in kernel (no eventfd/poll yet)
-- `open-iov-vfio.ko` requires `nvidia.ko` loaded first (`MODULE_SOFTDEP`)
- Single GPU validated so far: TU104 (RTX 2080 SUPER Mobile), GSP 570.144,
kernel 6.8.0-139-generic
- Live migration protocol implemented (4-phase) but not yet validated
- Windows guest not yet validated (automation scaffolding present)
+- MSI-X emulation has dual stores (guest-visible `bar0_shadow` table vs
+ `bar_emul_ctx.msix_table[]` used by the currently-unused trap path) —
+ documented in `docs/GSP_EMULATION_ARCHITECTURE.md`; harmless with the
+ direct-mmap design
## Reproducibility notes
diff --git a/docs/DEBUGGING.md b/docs/DEBUGGING.md
new file mode 100644
index 0000000..dc95ed7
--- /dev/null
+++ b/docs/DEBUGGING.md
@@ -0,0 +1,190 @@
+# Open-IOV Debugging Guide
+
+> Battle-tested procedures for bringing up the vGPU stack, diagnosing guest
+> driver failures, and validating a fully working setup. Built from real
+> debugging sessions (including the 2026-09-21 EPT-livelock hunt).
+
+---
+
+## 1. Debug surfaces (know them all)
+
+| Surface | Where | What it shows |
+|---------|-------|---------------|
+| Host dmesg | `dmesg -T \| grep -iE 'gsp\|vgpu\|open-iov\|mdev\|vfio'` | mdev lifecycle, VFIO ioctls, guest BAR accesses, emulation thread, KVM warns |
+| Guest serial | `/tmp/vgpu-guest-serial.log` ( yaml `serialLog`) | guest boot, kernel oops, driver prints — even when agent is dead |
+| QEMU GDB stub | `tcp::1234` ( yaml `gdbPort`) | vCPU state, registers, disassembly — **works on a hard-wedged guest** |
+| QMP | `/tmp/qemu-qmp.sock` | NMI injection, VM control, status |
+| Guest agent | `/tmp/qemu-guest-agent.sock` (via `vgpu_vm.py exec`) | in-guest commands — **only while guest userspace is alive** |
+| Guest SSH | `ssh -p 2222 <user>@localhost` | same, richer |
+| GDB on host kernel | (optional) | host-side driver state |
+
+The VM config (`~/.local/libvf.io/vms/vgpu-linux.yaml`) should contain:
+
+```yaml
+serialLog: "/tmp/vgpu-guest-serial.log"
+gdbPort: 1234
+gdbFreeze: false # true = halt at reset vector (boot debugging)
+```
+
+---
+
+## 2. The golden rule of failure triage
+
+**When the guest agent dies mid-test, the guest is wedged in kernel mode.
+Do not keep retrying `exec` — go straight to the GDB stub and host dmesg.**
+
+Triage order:
+1. `sudo dmesg -T | tail -50` — KVM warnings (`make_spte`, EPT), our driver's last messages
+2. `tail -100 /tmp/vgpu-guest-serial.log` — guest kernel's last words
+3. GDB stub vCPU sampling (below) — *where* is each vCPU stuck?
+
+---
+
+## 3. QEMU GDB stub workflow
+
+Attach (no symbols needed for triage):
+
+```bash
+gdb -q -ex 'set pagination off' -ex 'target remote localhost:1234' \
+ -ex 'info threads' -ex 'thread 1' -ex 'p/x $rip' -ex 'x/8i $rip-8' \
+ -ex 'thread 2' -ex 'p/x $rip' -ex 'x/8i $rip-8' -ex 'detach' -ex 'quit'
+```
+
+Reading the result:
+
+| Observation | Meaning |
+|-------------|---------|
+| vCPU RIP = `0xffffffff85......`, disasm shows `hlt` | CPU idle (kernel text range) — fine |
+| vCPU RIP = `0xffffffffc0......` (module range), **same RIP+RSP across samples** | **hard spin in a kernel module** (e.g. nvidia.ko polling loop) — this is your hung driver |
+| RIPs moving between samples | guest alive and progressing |
+
+For precise diagnosis, sample the stuck vCPU 3× a few seconds apart. Identical
+`$rip` **and** `$rsp` every time = the *same instruction* faults/retries
+(EPT-violation livelock) rather than a loop making progress.
+
+With guest symbols (`vmlinux` + module `.ko` with debug info), add
+`add-symbol-file` and get real function names.
+
+### QMP NMI (soft alternative)
+
+```bash
+(echo '{"execute":"qmp_capabilities"}'; sleep 0.3; \
+ echo '{"execute":"human-monitor-command","arguments":{"command-line":"nmi 0"}}') \
+ | sudo socat - UNIX-CONNECT:/tmp/qemu-qmp.sock
+```
+
+Watch the serial log afterwards for backtraces. Note: a truly hard-wedged vCPU
+(spinning with a faulting MMIO instruction) may not print anything — use the
+GDB stub instead.
+
+---
+
+## 4. Case study: the 2026-09-21 EPT livelock
+
+Symptoms: guest `modprobe nvidia` hangs; guest agent dies; QEMU alive.
+
+1. **Host dmesg**: `WARNING: ... at arch/x86/kvm/mmu/spte.c:245 make_spte` with
+ `spte = 0x600404aa8001b07, rsvd bits = 0xfff8000000000` — a bogus PFN.
+2. **GDB stub**: vCPU2 frozen at one address in module text
+ (`0xffffffffc0d968ef`), identical RIP/RSP across samples; other vCPUs in
+ `hlt`. The faulting instruction: `mov 0x4(%rax),%r13d` — a memory read that
+ can never complete.
+3. **Root cause**: VFIO `mmap` used `virt_to_phys()` on the `vzalloc`'d BAR0
+ shadow → garbage PFN in the EPT → every guest BAR0 read faulted → KVM
+ retried forever.
+4. **Fix**: per-page `vmalloc_to_pfn()` mapping; see
+ `docs/GSP_EMULATION_ARCHITECTURE.md` §2.
+
+Lesson: **a KVM `make_spte` reserved-bits WARN + one frozen vCPU = bad PFN from
+your VFIO mmap handler.** Always question `virt_to_phys()` on anything that
+isn't lowmem.
+
+---
+
+## 5. What a *successful* driver bring-up looks like (validation checklist)
+
+After `stack_manager.sh load-stack` on the host:
+
+- [ ] `lsmod | grep -E 'open_iov_vfio|nvidia '` — both present, no `~` (stale) markers
+- [ ] `cat /sys/class/mdev_bus/0000:01:00.0/mdev_supported_types/nvidia-nvidia-vgpu-turing/available_instances` → **1**
+- [ ] No `Unknown symbol`, `make_spte`, or `cut here` lines in dmesg
+
+After `vgpu_vm.py start vgpu-linux`:
+
+- [ ] `pgrep -af qemu-system` shows `-device vfio-pci,...,sysfsdev=/sys/bus/mdev/devices/<uuid>`
+- [ ] Host dmesg: `gsp_emu_init: GSP emulation engine initialized successfully`, `GSP emulation thread started`
+- [ ] Host dmesg: VFIO `SET_IRQS wired N eventfds` (eventfds actually stored)
+- [ ] Host dmesg: `VFIO device mmap offset=0x0 size=0x1000000` (BAR0 shadow mapped)
+- [ ] Guest serial reaches login prompt; `vgpu_vm.py exec "whoami"` → `root`
+
+When the guest nvidia driver probes (auto via udev, or `modprobe nvidia`):
+
+- [ ] Host dmesg shows **BAR0 doorbell writes being consumed** (no `Invalid seq_id` spam after the first)
+- [ ] Host dmesg: GSP RPCs processed (dispatch logs; stats increment)
+- [ ] **No** KVM `make_spte` WARN, **no** `pr_warn_ratelimited ... not supported in emulation mode` spam
+- [ ] Guest: `nvidia-smi` lists the TU104 vGPU (name, memory, no "No devices found")
+- [ ] Guest: `dmesg | grep -i nvidia` shows successful probe, GSP-RM init done
+- [ ] Guest: `cat /proc/driver/nvidia/version` → **570.144**
+
+Teardown regression:
+
+- [ ] `vgpu_vm.py stop <uuid>`; remove mdev → `available_instances` returns to **1**
+- [ ] `ps -e | grep gsp_emu` → **empty** (no leaked thread)
+- [ ] `/dev/vfio/<N>` node gone; matching `/sys/kernel/iommu_groups/<N>` gone
+
+Kernel unit test:
+
+- [ ] `sudo insmod kernel/tests/gsp_emulation_test.ko` → dmesg `GSP_EMU_TEST: ALL TESTS PASSED`
+- [ ] `sudo rmmod gsp_emulation_test` → clean
+
+---
+
+## 6. Failure taxonomy (symptom → likely cause)
+
+| Symptom | Likely cause | First check |
+|---------|--------------|-------------|
+| `insmod open-iov-vfio.ko: Unknown symbol` | missing prereq module (`mdev`) | `modprobe mdev vfio vfio_iommu_type1 vfio_pci` |
+| mdev create `-EUSERS` | `available_instances` = 0 (leak) | `cat .../available_instances` |
+| Guest: no PCI device | mdev not attached | QEMU cmdline for `sysfsdev=` |
+| Guest: sees device, probe hangs instantly | BAR0 mmap PFN bug (fixed) | host `make_spte` WARN |
+| Guest: probe hangs, no KVM warn | doorbell/RPC never consumed | host dmesg for thread + doorbell writes |
+| Guest: RPC processed but driver still waits | MSI-X never delivered | `SET_IRQS wired` line; `irqs[0].trigger` non-NULL |
+| Guest agent dead, QEMU alive | guest kernel wedge | GDB stub vCPU sampling (§3) |
+| `rmmod open_iov_vfio` hangs | leaked kthread / open fds | `ps -e \| grep gsp_emu`; mdevs still present? |
+| Guest Code 43 / probe fail (Windows) | config space / MSI-X details | config read/write logs |
+| `pr_warn_ratelimited(...not supported in emulation mode)` | guest used BAR_TRAP/MIGRATE RPC | expected fail-fast; wire real handler if hot |
+
+---
+
+## 7. Host-guest version matrix discipline
+
+Probe failures caused by **version mismatch** look identical to emulation bugs.
+Before deep debugging, verify the matrix:
+
+| Component | Must be | Check |
+|-----------|---------|-------|
+| Guest driver | 570.144 | `cat /proc/driver/nvidia/version` (guest) |
+| Host OpenRM nvidia.ko | 570.144 | `cat /sys/module/nvidia/version` (host) |
+| Host GSP firmware | 570.144 | `/lib/firmware/gsp-570.144.fwimage.bin` MD5 `13ce12a392a31eeb79a2e6ebd3a4a93a` |
+| Emulation target | 570.144W | decompiled firmware basis |
+
+Rule: **match versions first, debug second.** (2026-09-21: 580.178 guest vs
+570.144 emulation cost a full session before the real mmap bug was found —
+the mismatch masked the signal.)
+
+---
+
+## 8. Quick reference
+
+```bash
+# Stack
+./scripts/stack_manager.sh status|load-stack|unload|dmesg
+# VM (harness ONLY - never raw /qemu)
+./scripts/vgpu_vm.py start vgpu-linux|stop <uuid>|exec [--timeout N] <cmd>|shell
+# Debug
+tail -f /tmp/vgpu-guest-serial.log &
+gdb -ex 'target remote localhost:1234'
+sudo dmesg -Tw | grep -iE 'gsp|vgpu|open-iov'
+# Recover wedged VM
+./scripts/vgpu_vm.py stop <uuid> # or: sudo kill -9 <qemu pid>
+```
diff --git a/docs/DEVELOPMENT_WORKFLOW.md b/docs/DEVELOPMENT_WORKFLOW.md
index 2263af6..9019ac1 100644
--- a/docs/DEVELOPMENT_WORKFLOW.md
+++ b/docs/DEVELOPMENT_WORKFLOW.md
@@ -17,7 +17,7 @@ $OPEN_IOV_ROOT/ # Main monorepo (git + LFS)
| Directory | Purpose |
|-----------|---------|
| `kernel/` | Host VFIO mdev driver (`open-iov-vfio.ko`) |
-| `driver/` | Guest vGPU driver (`gsp-vgpu.ko`) - **to be removed** |
+| `legacy-driver-guest/` | Historical guest-side driver (reference only, **not built**) |
| `userspace/lib/` | `libiov.so` / `libvgpu.so` + `rpc_emulator.c` |
| `userspace/examples/` | `example_vgpu` test app |
| `firmware/57X-GSP/570.144/` | GSP firmware binary + 132 decompiled C files |
@@ -212,8 +212,7 @@ oiv-clean
| Artifact | Location | Size |
|----------|----------|------|
| `nvidia.ko` | `nvidia-open-gpu-kernel-modules/kernel-open/` | 35MB |
-| `open-iov-vfio.ko` | `kernel/` | 2.8MB |
-| `gsp-vgpu.ko` | `driver/` | 1.8MB |
+| `open-iov-vfio.ko` | `kernel/` | ~3.8MB |
| `libiov.so` / `libvgpu.so` | `userspace/lib/` | 76KB |
| `example_vgpu` | `userspace/examples/` | 27KB |
@@ -222,32 +221,44 @@ oiv-clean
## 4. Kernel Module Loading
### Prerequisites
-- **REBOOT REQUIRED** if kernel cmdline has `module_blacklist=nvidia,...`
-- Check: `cat /proc/cmdline | grep module_blacklist`
-- GRUB must be updated: `/etc/default/grub` → `update-grub` → `update-initramfs -u` → reboot
+- The **stock Ubuntu nvidia driver must not be loaded** when you want our
+ OpenRM nvidia.ko: both are named `nvidia`. The stock driver auto-loads on
+ desktop boots (udev) and is held open by the GUI.
+ `/etc/modprobe.d/blacklist-nvidia-open-iov.conf` (installed on this
+ machine) prevents that auto-load after reboot; `insmod` is unaffected.
+- `stack_manager.sh load-nvidia` detects a foreign loaded nvidia
+ (compares `/sys/module/nvidia/version` with our build) and refuses with
+ guidance instead of silently skipping.
### Load Order (Critical)
```bash
-# 1. NVIDIA OpenRM host driver (exports vGPU VFIO symbols)
-sudo insmod $OPEN_IOV_ROOT/nvidia-open-gpu-kernel-modules/kernel-open/nvidia.ko
+# 1. VFIO/mdev prerequisites
+sudo modprobe vfio vfio_iommu_type1 vfio_pci mdev
-# 2. Open-IOV VFIO mdev driver (depends on nvidia.ko symbols)
+# 2. Open-IOV VFIO mdev driver FIRST - it is fully standalone (zero nvidia
+# imports) and EXPORTS the nvidia_vgpu_vfio_* interface + registers the
+# mdev parent on the physical GPU.
sudo insmod $OPEN_IOV_ROOT/kernel/open-iov-vfio.ko
-# 3. Guest vGPU driver (standalone, works without above)
-sudo insmod $OPEN_IOV_ROOT/driver/gsp-vgpu.ko firmware_name=gsp-570.144.fwimage.bin
+# 3. NVIDIA OpenRM host driver - imports our nvidia_vgpu_vfio_* symbols
+# (must be built with KBUILD_EXTRA_SYMBOLS=kernel/Module.symvers)
+sudo insmod $OPEN_IOV_ROOT/nvidia-open-gpu-kernel-modules/kernel-open/nvidia.ko
+
+# 4. GSP firmware for the host GPU
+sudo cp firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin /lib/firmware/
```
### Using Helper Script
```bash
-oiv-load # Runs all three in order
+./scripts/stack_manager.sh load-stack # prereqs → open-iov-vfio → nvidia → firmware
```
### Verify Load
```bash
-lsmod | grep -E "nvidia|open_iov|gsp_vgpu"
+lsmod | grep -E "nvidia|open_iov"
dmesg -T | grep -i -e nvidia -e gsp -e vgpu -e open-iov
-ls -la /dev/gsp-vgpu /dev/nvidia*
+ls /sys/class/mdev_bus/0000:01:00.0/mdev_supported_types/
+cat /sys/class/mdev_bus/0000:01:00.0/mdev_supported_types/nvidia-nvidia-vgpu-turing/available_instances
```
### Unload
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) |
diff --git a/docs/ONBOARDING.md b/docs/ONBOARDING.md
new file mode 100644
index 0000000..1d0a4f7
--- /dev/null
+++ b/docs/ONBOARDING.md
@@ -0,0 +1,166 @@
+# Open-IOV Developer Onboarding
+
+> Welcome. This guide gets you from zero to a productive contributor in ~30
+> minutes: what the project is, how the repo is laid out, how to build and
+> test, and the standards we hold the code to.
+
+---
+
+## 1. What this project is (60 seconds)
+
+**Open-IOV** reimplements NVIDIA vGPU so an **unmodified NVIDIA guest driver**
+runs in a VM with full GPU functionality — no GRID license. The trick: the
+host kernel module (`open-iov-vfio.ko`) emulates the GPU's **GSP** (the
+firmware processor the driver talks to) in software, so the guest's stock
+`nvidia.ko` can't tell it isn't running on real hardware.
+
+Everything guest-facing is **host-side only**. There is no Open-IOV code in
+the guest.
+
+How we got the protocol: full reverse engineering of NVIDIA's GSP 570.144W
+firmware (28.5 MB RV64IMAC ELF → 132 decompiled C files → 12 RPC types
+mapped). See `wiki/` for the firmware analysis and `docs/GSP_EMULATION_ARCHITECTURE.md`
+for how the emulator works.
+
+## 2. Repository tour
+
+```
+open-iov/
+├── Makefile # top-level: all/kernel/userspace/examples/tests/clean
+├── install.sh # fresh-machine reproduction (deps→nvidia→firmware→build→load)
+├── README.md # project intro + quick start
+├── STATE.md # live project state (works/limitations/bugs)
+├── kernel/ # ★ the host driver (open-iov-vfio.ko)
+│ ├── main.c # module init, mdev parent/driver, VFIO ops, PCI config
+│ ├── gsp_emulation.c/h # GSP emulation engine (thread, dispatch, MSI-X)
+│ ├── gsp_rpc_client.c/h # RPC API + emulation implementations + msg structs
+│ ├── bar_emulation.c/h # BAR0 shadow + MSI-X shadow state
+│ ├── bar_init.c # physical BAR mapping (host side)
+│ ├── memory.c # simple mapping/pin tracking
+│ ├── irq.c # IRQ/eventfd plumbing
+│ ├── ioctl.c # host management ioctls (libiov consumers)
+│ ├── migration.c # 4-phase live-migration skeleton
+│ ├── firmware_analyzer.c/h # runtime firmware signature/pattern analysis
+│ ├── dynamic_rpc_dispatcher.c/h # version-aware RPC dispatch
+│ ├── architecture/ # hw abstraction (Turing/Ampere/Blackwell)
+│ ├── version/ # driver-version adaptation (550/560/570/580)
+│ ├── include/ # kernel headers (nvidia-vgpu-vfio.h is the main one)
+│ └── tests/ # kernel unit tests (gsp_emulation_test)
+├── userspace/
+│ ├── lib/ # libiov.so (+ libvgpu.so compat) management API
+│ ├── examples/ # example_vgpu test app
+│ └── tests/
+├── legacy-driver-guest/ # historical guest-side driver (reference only, NOT built)
+├── firmware/57X-GSP/570.144/ # GSP blob + analysis scripts (per-version dirs)
+├── nvidia-open-gpu-kernel-modules/ # NVIDIA OpenRM source (patched, builds nvidia.ko)
+├── patches/nvidia/ # our delta vs upstream OpenRM tag
+├── scripts/ # stack_manager.sh (THE tool), vgpu_vm.py (THE harness), helpers
+├── tools/ # guest monitor, failure analyzer, benchmarks, ghidra
+├── config/ # declarative YAML: hardware/, versions/
+├── docs/ # ★ all developer documentation (start here)
+└── wiki/ # firmware analysis pages (RPC format, addresses, ...)
+```
+
+## 3. Your first build & test
+
+```bash
+cd open-iov
+./scripts/stack_manager.sh build-all # kernel → nvidia → userspace (ORDER MATTERS)
+sudo ./scripts/stack_manager.sh load-stack
+sudo ./scripts/stack_manager.sh mdev-create
+./scripts/stack_manager.sh status
+```
+
+Rules that will save your week (also enforced in `stack_manager.sh`):
+
+1. **Build order**: `build-kernel` → `build-nvidia` → `build-userspace`.
+ nvidia.ko resolves our symbols via `KBUILD_EXTRA_SYMBOLS=kernel/Module.symvers`.
+2. **Load order**: prereqs → `open-iov-vfio.ko` → `nvidia.ko` → firmware.
+3. **Unload order**: remove mdevs → `rmmod nvidia` → `rmmod open_iov_vfio`.
+4. **Version matrix**: guest driver == host OpenRM == host GSP firmware ==
+ emulation target (all **570.144** today). Mismatches look exactly like
+ emulation bugs — check versions first (`docs/DEBUGGING.md` §7).
+5. **VM access**: ONLY through `scripts/vgpu_vm.py` (the QEMU harness).
+ Never raw `` or `qemu-system-*` commands.
+
+Run the unit tests (host, needs the module loaded):
+
+```bash
+make tests
+sudo insmod kernel/tests/gsp_emulation_test.ko # dmesg → ALL TESTS PASSED
+sudo rmmod gsp_emulation_test
+```
+
+## 4. Coding standards
+
+We follow the **Linux kernel coding style** for kernel code and common-sense
+C for userspace. The practical checklist:
+
+### Must
+- **Tabs** for indentation in kernel code (8-wide). Userspace/scripts: match the
+ surrounding file.
+- **Guard every allocation** (`kmalloc`/`vzalloc`/`kzalloc`) and propagate
+ `-ENOMEM` with clean unwinding (`goto err_...` in reverse init order).
+- **Never `virt_to_phys()` on vmalloc/ioremap memory.** Use
+ `is_vmalloc_addr() ? vmalloc_to_pfn() : virt_to_phys()>>PAGE_SHIFT`
+ (see `nv_vgpu_mmap_virt_to_pfn`). This class of bug wedged our guest for a
+ whole session — it is called out in code review.
+- **`dma_addr_t` for DMA addresses.** Never truncate into `int`/`u32`
+ (IOMMU IOVAs can exceed 32 bits).
+- **Every `get` needs a `put`**: `pci_dev_get/pci_dev_put`,
+ `eventfd_ctx_fdget/eventfd_ctx_put`, `vfio_alloc_device/vfio_put_device`,
+ module refs. Unbalanced gets become leaks that block unload.
+- **Remove paths must fully unwind init paths** (we had a remove that forgot
+ `list_del` + kthread stop + VFIO unregister — leaked everything).
+- **Fail fast in emulation mode**: never `gsp_rpc_submit_raw()` from emulation
+ (it rings the doorbell our own thread polls → self-trigger livelock).
+ Return `-ENOTSUPP` with `pr_warn_ratelimited` until a real handler exists.
+- **`pr_*`/`dev_*` logging** with the `nv_vgpu_*` wrappers (NULL-tolerant);
+ rate-limit anything a guest can spam.
+- Comments explain **why**, not what — especially around hardware/firmware
+ behavior discovered by reverse engineering (cite the wiki/address).
+- **Signed-off-by** style commit messages: short imperative summary line,
+ then the *why* (root cause), the *what*, and the user-visible effect.
+ Reference real dmesg/GDB evidence where relevant.
+
+### Must not
+- No guest-side Open-IOV code. Ever. The guest is stock.
+- No reboots/system-affecting commands from scripts without explicit operator
+ action (see the project's hard rule).
+- No new `MODULE_SOFTDEP`/`depends` on `nvidia` — open-iov-vfio is standalone;
+ nvidia.ko imports *our* symbols, not vice versa.
+- No direct ``/QEMU invocations in tooling — go through `vgpu_vm.py`.
+- Don't widen an API's hardware assumptions silently: if a function needs
+ `vgpu->pdev`, say so in the comment (unit tests can't call those paths).
+
+### Kernel module specifics
+- One module: `open-iov-vfio.ko` (`obj-m` in `kernel/Makefile`).
+- Exported symbols: only what other components genuinely need
+ (`EXPORT_SYMBOL` right after the definition).
+- New per-device resources belong in `struct nv_vgpu_device` with matching
+ init/cleanup in the `nv_vgpu_device_init_work` / cleanup-work chain.
+
+## 5. Documentation expectations
+
+- New subsystem → new doc in `docs/` + a link from `README.md` and this file.
+- Changed behavior → update the affected doc in the **same commit**.
+- Debugging discoveries → add to `docs/DEBUGGING.md` taxonomy table.
+- Firmware addresses/structures → `wiki/Address-Reference.md` and friends.
+
+## 6. Where to read next (in order)
+
+1. `docs/GSP_EMULATION_ARCHITECTURE.md` — how the emulator works
+2. `docs/DEBUGGING.md` — gdb stub/serial workflow + validation checklist
+3. `docs/PORTABLE_ARCHITECTURE.md` — multi-version/multi-hardware design
+4. `docs/VERSION_UPGRADE.md` — supporting a new driver version end-to-end
+5. `docs/PERFORMANCE.md` — benchmarking and A/B vs bare metal
+6. `docs/DEVELOPMENT_WORKFLOW.md` — exhaustive command reference
+7. `wiki/` — firmware reverse-engineering canon
+
+## 7. Getting help / escalating
+
+- Stuck on firmware analysis? `tools/ghidra/` scripts + `wiki/`.
+- Guest driver won't probe? `docs/DEBUGGING.md` §5-6 first.
+- GSP emulation questions? `docs/GSP_EMULATION_ARCHITECTURE.md` + the unit test.
+- Found a new failure mode? Debug it per the guide, then **write it down** in
+ the taxonomy table so the next person doesn't rediscover it.
diff --git a/docs/PERFORMANCE.md b/docs/PERFORMANCE.md
new file mode 100644
index 0000000..6077920
--- /dev/null
+++ b/docs/PERFORMANCE.md
@@ -0,0 +1,98 @@
+# Open-IOV Performance Testing & A/B Methodology
+
+> How we measure vGPU performance against bare metal, interpret the results,
+> and keep overhead on the critical path honest. **Run correctness first**
+> (`docs/DEBUGGING.md` §5) — numbers from a half-working stack are noise.
+
+---
+
+## 1. What overhead is acceptable (targets)
+
+| Path | Target | Why |
+|------|--------|-----|
+| GSP RPC round-trip (doorbell→response→MSI-X) | < 50 µs (stretch: < 10 µs) | every driver op goes through here |
+| MSI-X injection (eventfd → guest handler) | < 5 µs | completion latency dominates |
+| Guest BAR0 MMIO read/write | < 2 µs (shadow RAM) | hot path for register polling |
+| CUDA kernel launch (added overhead vs bare metal) | < 10% | user-visible |
+| Memory alloc/map RPCs | < 100 µs | setup-time, amortized |
+| CUDA compute throughput | ≥ 95% of bare metal | GPU-bound, should be ~free |
+| Graphics (frame rate) | ≥ 90% of bare metal | swapchain + submission overhead |
+
+## 2. The A/B rule
+
+Every measurement runs **twice on the same machine, same boot if possible**:
+
+- **A (bare metal)**: workload on the physical GPU via the host's stock
+ driver (or our OpenRM nvidia.ko directly on the hardware).
+- **B (vGPU)**: identical workload in the guest on the emulated vGPU.
+
+Same binary, same input sizes, same environment (governor, clocks, hugepages).
+Report **B/A ratio %** — never absolute numbers alone.
+
+One variable at a time. If B regresses, you changed the stack, the workload,
+or the machine — find which before optimizing.
+
+## 3. Microbenchmarks (build these first)
+
+1. **RPC round-trip**: from the guest, submit a DEBUG/NOOP-ish RPC in a loop;
+ time doorbell-write → response-visible. Host side: `gsp_emu_get_stats()`
+ gives `total_latency_ns` and counts — divide for per-RPC host cost.
+ (Add `trace_printk` in `gsp_emu_dispatch_rpc` entry/exit for distribution;
+ read via `/sys/kernel/debug/tracing/trace`.)
+2. **MSI-X latency**: timestamp (guest TSC) at RPC submit → in the guest
+ driver's interrupt handler. Compare with the host eventfd_signal timestamp.
+3. **BAR0 shadow read latency**: guest loops reading a shadow register;
+ expect ~memory speed (uncached, so ~100-300 ns) — anything ≥ 1 µs means
+ you're trapping somewhere you shouldn't.
+4. **Doorbell polling cost**: host `top`/`perf` on `gsp_emu_N` — the adaptive
+ poll (busy → 50-100 µs sleep) should sit at ~0% CPU when idle, and react
+ within one sleep quantum under load. Tune `GSP_EMU_POLL_*` only with data.
+
+## 4. Workload benchmarks
+
+Runner: `tools/benchmark_infrastructure.sh` (extend it; keep the A/B pattern).
+In-guest execution goes through the harness (`vgpu_vm.py exec`) or the guest
+test agent (`tools/guest_agent/`).
+
+| Suite | What | Pass criterion |
+|-------|------|----------------|
+| `cuda-samples vectorAdd` | smoke + element-wise throughput | correct result; ≥ 95% B/A |
+| `simpleCUBLAS` | SGEMM TFLOPS | ≥ 95% B/A |
+| `nvbandwidth` (if available) | H2D/D2H/device copy GB/s | report; DMA path validation |
+| `glxgears -info` / a real GL app | frame rate | ≥ 90% B/A |
+| `nvenc`/`nvdec` sample | transcode fps | functional (perf later) |
+| PyTorch CUDA op (matmul loop) | end-to-end framework | ≥ 90% B/A |
+
+Always log: guest/host driver versions, GPU clocks (`nvidia-smi -q -d CLOCK`
+both sides — throttling skews everything), and the command lines.
+
+## 5. Where the overhead lives (profile in this order)
+
+1. **RPC dispatch** — the single biggest lever. Everything the guest driver
+ does funnels through the emulation thread.
+2. **Doorbell polling cadence** — too slow = latency; too hot = host CPU burn.
+3. **eventfd/MSI-X delivery** — measure, don't assume.
+4. **BAR0 uncached semantics** — shadow is UC for the guest; that's
+ MMIO-correct but slow for large reads. If the driver bulk-reads shadow,
+ consider caching-safe sub-regions.
+5. **Memory model gap** (ALLOC host-pages vs BAR1 direct VRAM) — see
+ `docs/GSP_EMULATION_ARCHITECTURE.md` §3. Until unified, DMA-heavy
+ workloads are not representative.
+
+## 6. Optimizing safely
+
+- Change one thing, re-run the A/B pair, keep the numbers with the commit.
+- Never optimize off bare-metal-only data: the goal is B/A, not A.
+- If a change makes B faster but breaks the §5 validation checklist in
+ `docs/DEBUGGING.md`, it doesn't count.
+- Record results in `STATE.md` (date, versions, B/A ratios) so regressions
+ are visible across commits.
+
+## 7. Reporting template
+
+```
+Date / commit / versions (guest=host=firmware=emulation):
+Machine / GPU / clocks locked?:
+Test, unit, A (bare), B (vGPU), B/A %:
+Notes (what changed vs last run):
+```
diff --git a/docs/VERSION_UPGRADE.md b/docs/VERSION_UPGRADE.md
new file mode 100644
index 0000000..94c7d68
--- /dev/null
+++ b/docs/VERSION_UPGRADE.md
@@ -0,0 +1,137 @@
+# Open-IOV Version Upgrade Guide
+
+> How to support a new NVIDIA driver version (e.g. moving from 570.144 to a
+> 610-series release) end-to-end: firmware acquisition → reverse engineering →
+> config → host driver → guest validation. The goal is that a version bump is
+> **configuration + analysis**, not a rewrite.
+
+---
+
+## 0. Version discipline (read first)
+
+Four components must stay in lock-step; a mismatch between any two produces
+failures that are indistinguishable from emulation bugs:
+
+```
+guest nvidia driver == host OpenRM nvidia.ko == host GSP firmware == emulation target (decompiled GSP)
+```
+
+Today that anchor is **570.144** everywhere. When you bump one, you bump all
+four. Verify with `docs/DEBUGGING.md` §7 before any deep debugging.
+
+---
+
+## 1. Acquire the new firmware + matching OpenRM tag
+
+1. Download the Linux x86_64 runfile for the target version:
+ `https://us.download.nvidia.com/XFree86/Linux-x86_64/<VER>/NVIDIA-Linux-x86_64-<VER>.run`
+ (the `tesla/` path 404s for consumer releases).
+2. Extract the GSP firmware (`gsp_tu10x.bin` / arch-specific) — or fetch the
+ versioned blob from `linux-firmware.git`:
+ `nvidia/<VER>/gsp_tu10x.bin`.
+3. Extract the `.fwimage` section (host boot image) and record the MD5:
+ ```bash
+ objcopy --only-section=.fwimage gsp_tu10x.bin gsp-<VER>.fwimage.bin
+ md5sum gsp-<VER>.fwimage.bin
+ ```
+4. Note the matching OpenRM tag at
+ `github.com/NVIDIA/open-gpu-kernel-modules` (e.g. `570.144`).
+
+## 2. Lay down versioned directories (never overwrite a working version)
+
+```
+firmware/<SERIES>-GSP/<VER>/ # e.g. firmware/61X-GSP/610.XX/
+ gsp-<VER>.fwimage.bin
+ decompiled/ # ghidra output lands here
+config/versions/<MAJOR>.yaml # e.g. config/versions/610.yaml
+```
+
+Copy the analysis scripts from the previous version dir
+(`firmware/57X-GSP/570.144/*.py`) into the new one — they are script-relative.
+
+## 3. Reverse-engineer the new firmware (Ghidra headless)
+
+```bash
+cd tools/ghidra
+python3 analyze_fwimage.py \
+ ../../firmware/<SERIES>-GSP/<VER>/gsp-<VER>.fwimage.bin \
+ /tmp/ghidra_project_<VER> "gsp_<VER>"
+cd ../../firmware/<SERIES>-GSP/<VER>
+python3 analyze_firmware.py gsp-<VER>.fwimage.bin
+```
+
+What to extract (compare against `wiki/Address-Reference.md` for 570.144W):
+
+| Artifact | Why it matters |
+|----------|----------------|
+| Primary RPC dispatcher + jump table | RPC types/subcommands may shift |
+| BAR trap / WPR handlers | BAR emulation behavior |
+| Entry/boot sequence | GSP boot RPC ordering the guest driver expects |
+| Migration tables | migration protocol (if enabled) |
+| RPC message layouts | struct changes break the emulator silently |
+| Version string/signature | runtime firmware matching |
+
+Sanity checks: still RV64IMAC? still the same 12 top-level RPC types? new
+subcommands? Confidential-computing/TEE additions?
+
+## 4. Produce the version config
+
+Update `config/versions/<MAJOR>.yaml` with:
+- `version.name/major/minor` and `firmware.gsp_version`
+- discovered dispatcher/handler addresses (if tracked in yaml)
+- `features` toggles per what the firmware actually has
+- `symbols` (the nvidia.ko vGPU VFIO interface — verify against the new
+ OpenRM tag; renamed exports break the nvidia build)
+
+## 5. Update the runtime signature database
+
+`kernel/firmware_analyzer.c` matches firmware by CRC32/SHA256 + patterns.
+Add the new entry to `known_signatures[]` (version string, hashes, discovered
+addresses). This is what lets one `open-iov-vfio.ko` serve multiple firmware
+versions safely.
+
+## 6. Rebuild host + guest against the new anchor
+
+1. Point `patches/nvidia/` at the new OpenRM tag (rebase our delta; verify
+ `modinfo nvidia.ko | grep depends` still shows `open-iov-vfio`).
+2. `./scripts/stack_manager.sh build-all` (kernel → nvidia → userspace).
+3. Install the new host firmware to `/lib/firmware/`
+ (`stack_manager.sh install-firmware`).
+4. **Guest**: install the *matching* stock driver in the VM (runfile for the
+ exact version), replacing the old one. Keep the guest able to boot without
+ nvidia auto-loading during the swap (temporarily move dkms-built modules
+ aside — see `docs/DEBUGGING.md`).
+
+## 7. Validate (same checklist every version)
+
+Run the full bring-up checklist in `docs/DEBUGGING.md` §5:
+module load → mdev → VM boot → guest probe → `nvidia-smi` → teardown
+regression → unit test. Then the workload tests (CUDA vectorAdd, graphics).
+Only after the checklist is green do performance comparisons
+(`docs/PERFORMANCE.md`).
+
+## 8. Rollback plan
+
+Keep the previous version's directory + config untouched. Rollback =
+`git checkout` the repo tag/commit for the old anchor + reinstall the old
+guest driver. Because versions live in parallel directories, the working
+setup is never destroyed by a forward attempt.
+
+## 9. Multi-version testing matrix (target state)
+
+For each supported anchor version × each supported GPU family
+(`config/hardware/*.yaml`), the CI matrix should run the §7 checklist. When
+adding a version, extend the matrix rather than replacing the old column —
+regressions across versions are the cheapest bugs to catch.
+
+## 10. Common pitfalls (learned the hard way)
+
+- **Runfile version drift**: Ubuntu's `nvidia-driver-XXX-open` metapackages
+ can resolve to a *newer* major (570-open pulled 580). Always check the
+ candidate version before installing; prefer the exact-version runfile.
+- **GSP firmware filename**: guest driver requests `nvidia/<VER>/gsp_tu10x.bin`;
+ a wrong-version firmware file = probe failure that looks like an emulation bug.
+- **Kernel text addresses differ per build**: re-derive Ghidra addresses per
+ firmware; never carry 570.144 addresses into a 610 config.
+- **Symbol renames across OpenRM tags**: `nvidia_vgpu_vfio_*` renames break
+ the nvidia build with `Unknown symbol` — check `kernel-open` diffs.
diff --git a/kernel/Makefile b/kernel/Makefile
index aef7129..98adfd4 100644
--- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -25,14 +25,17 @@ KBUILD_MODPOST_WARN := 1
# Use symbols from host nvidia.ko (path resolved relative to this repo)
KBUILD_EXTRA_SYMBOLS := $(abspath $(src)/../nvidia-open-gpu-kernel-modules/kernel-open/Module.symvers)
+# Use $(CURDIR), NOT $(PWD): when invoked via the top-level Makefile
+# (make -C kernel), $(PWD) still points at the caller's directory and kbuild
+# silently builds nothing (the "stale .ko" trap).
all:
- $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
+ $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) modules
clean:
- $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
+ $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) clean
install:
- $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules_install
+ $(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(CURDIR) modules_install
depmod -a
.PHONY: all clean install \ No newline at end of file
diff --git a/kernel/gsp_comm.c b/kernel/gsp_comm.c
index a8d0664..094fcfb 100644
--- a/kernel/gsp_comm.c
+++ b/kernel/gsp_comm.c
@@ -37,7 +37,6 @@ static void nv_vgpu_gsp_rpc_work(struct work_struct *work)
static void nv_vgpu_gsp_poll_work(struct work_struct *work)
{
struct nv_vgpu_gsp_comm_ctx *ctx = container_of(work, struct nv_vgpu_gsp_comm_ctx, poll_work.work);
- struct nv_vgpu_device *vgpu = ctx->vgpu;
u32 status;
if (!ctx || !ctx->irq_enabled || !ctx->gsp_doorbell)
@@ -221,7 +220,7 @@ int nv_vgpu_gsp_boot(struct nv_vgpu_device *vgpu)
nv_vgpu_info(vgpu, "DRY-RUN: Would write GSP binary (%zu bytes) to offset 0x200000",
ctx->firmware.gsp_size);
nv_vgpu_info(vgpu, "DRY-RUN: Would kick GSP doorbell at offset 0x%llx",
- NV_VGPU_GSP_DOORBELL_OFFSET);
+ (u64)NV_VGPU_GSP_DOORBELL_OFFSET);
return 0;
}
@@ -267,7 +266,7 @@ int nv_vgpu_gsp_init(struct nv_vgpu_device *vgpu)
return -ENODEV;
}
nv_vgpu_info(vgpu, "DRY-RUN: BAR0 mapped at %p, GSP doorbell offset 0x%llx, shared mem offset 0x%llx",
- bar->vaddr, NV_VGPU_GSP_DOORBELL_OFFSET, NV_VGPU_GSP_SHARED_MEM_OFFSET);
+ bar->vaddr, (u64)NV_VGPU_GSP_DOORBELL_OFFSET, (u64)NV_VGPU_GSP_SHARED_MEM_OFFSET);
/* Validate firmware files exist */
ret = request_firmware(NULL, NV_VGPU_GSP_BOOTLOADER_FILE, &vgpu->pdev->dev);
if (ret) {
diff --git a/kernel/gsp_emulation.c b/kernel/gsp_emulation.c
index 60805eb..bfd8be7 100644
--- a/kernel/gsp_emulation.c
+++ b/kernel/gsp_emulation.c
@@ -239,16 +239,23 @@ static int gsp_emu_dispatch_rpc(struct gsp_emu_context *ctx,
void gsp_emu_inject_completion_interrupt(struct gsp_emu_context *ctx, u32 seq_id)
{
+ struct eventfd_ctx *trigger = NULL;
+
/* Write to MSI-X PBA (Pending Bit Array) at BAR0+0x3000 */
if (ctx->bar0_vaddr) {
u32 *pba = (u32 *)(ctx->bar0_vaddr + GSP_EMU_MSIX_PBA_OFFSET);
writel(1, pba); /* Set pending bit for vector 0 */
}
+ /* Look up the vector-0 trigger LIVE: QEMU wires eventfds via
+ * VFIO_DEVICE_SET_IRQS after gsp_emu_init runs, so an init-time copy
+ * would be stale (NULL). */
+ if (ctx->vgpu)
+ trigger = ctx->vgpu->irqs[0].trigger;
+
/* Signal eventfd for VFIO interrupt delivery */
- if (ctx->msix_trigger[0]) {
- eventfd_signal(ctx->msix_trigger[0]);
- }
+ if (trigger)
+ eventfd_signal(trigger);
}
/* ============================================================
@@ -321,6 +328,7 @@ static int gsp_emu_thread_fn(void *data)
int gsp_emu_init(struct nv_vgpu_device *vgpu)
{
+ struct nv_vgpu_bar_emul_ctx *bar_ctx;
struct gsp_emu_context *ctx;
int ret = 0;
@@ -337,46 +345,47 @@ int gsp_emu_init(struct nv_vgpu_device *vgpu)
init_completion(&ctx->thread_stopped);
spin_lock_init(&ctx->lock);
- /* Use already-mapped BAR0 virtual address (mapped by nv_vgpu_bar_init) */
- if (!vgpu->bars[0].vaddr) {
- nv_vgpu_err(vgpu, "BAR0 virtual address not available");
+ /* 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
+ * and the host nvidia driver). All guest doorbell writes, RPC messages,
+ * MSI-X table writes and PBA reads land in the shadow, so the emulation
+ * engine must operate on the shadow exclusively. Polling the physical
+ * BAR0 would never see guest traffic and would corrupt host GPU state. */
+ bar_ctx = (struct nv_vgpu_bar_emul_ctx *)vgpu->bar_emul_ctx;
+ if (!bar_ctx || !bar_ctx->bar0_shadow) {
+ nv_vgpu_err(vgpu, "BAR0 emulation shadow not available");
ret = -ENODEV;
goto err_free_ctx;
}
- ctx->bar0_vaddr = vgpu->bars[0].vaddr;
+ ctx->bar0_vaddr = (void __iomem *)bar_ctx->bar0_shadow;
- /* Locate doorbell register at BAR0+0x110C00 */
+ /* Doorbell register at BAR0+0x110C00 (guest-visible shadow) */
ctx->doorbell_reg = ctx->bar0_vaddr + GSP_EMU_DOORBELL_OFFSET;
- /* Allocate shared memory (4KB, page-aligned, WC mapping) */
+ /* RPC shared memory at BAR0+0x1000. This ALIASES the guest-visible
+ * shadow - it is NOT a separate allocation. shared_mem_dma stays 0 to
+ * mark it as aliased so cleanup does not dma_free it. */
ctx->shared_mem_size = GSP_EMU_SHARED_MEM_SIZE;
- ctx->shared_mem_virt = dma_alloc_wc(&vgpu->pdev->dev, ctx->shared_mem_size,
- &ctx->shared_mem_dma, GFP_KERNEL);
- if (!ctx->shared_mem_virt) {
- nv_vgpu_err(vgpu, "Failed to allocate shared memory");
- ret = -ENOMEM;
- goto err_free_ctx;
- }
-
- /* Map shared memory into guest BAR0 at offset 0x1000 */
ctx->shared_mem = ctx->bar0_vaddr + GSP_EMU_SHARED_MEM_OFFSET;
+ ctx->shared_mem_virt = (void *)ctx->shared_mem;
+ ctx->shared_mem_dma = 0;
- /* Initialize RPC client with already-mapped BAR0 virtual address and doorbell */
+ /* Initialize RPC client with the shadow BAR0 base and doorbell */
ctx->rpc_client = gsp_client_init_emulation(&vgpu->dev, ctx->bar0_vaddr, ctx->doorbell_reg);
if (IS_ERR(ctx->rpc_client)) {
ret = PTR_ERR(ctx->rpc_client);
nv_vgpu_err(vgpu, "Failed to init GSP RPC client: %d", ret);
- goto err_free_shared_mem;
+ goto err_free_ctx;
}
/* Enable emulation mode in RPC client */
gsp_rpc_emulation_set(true);
- /* Get MSI-X eventfd triggers from IRQ context */
- for (int i = 0; i < NV_VGPU_MAX_IRQS; i++) {
- ctx->msix_trigger[i] = vgpu->irqs[i].trigger;
- }
+ /* 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[]. */
/* Start emulation thread */
ctx->thread = kthread_run(gsp_emu_thread_fn, ctx, "gsp_emu_%u", vgpu->vgpu_id);
@@ -395,10 +404,9 @@ int gsp_emu_init(struct nv_vgpu_device *vgpu)
err_fini_client:
gsp_client_fini(ctx->rpc_client);
-err_free_shared_mem:
- dma_free_wc(&vgpu->pdev->dev, ctx->shared_mem_size,
- ctx->shared_mem_virt, ctx->shared_mem_dma);
err_free_ctx:
+ /* shared_mem_virt aliases the BAR0 shadow (owned by bar_emulation),
+ * nothing to free here */
kfree(ctx);
return ret;
}
@@ -433,14 +441,15 @@ void gsp_emu_cleanup(struct nv_vgpu_device *vgpu)
ctx->rpc_client = NULL;
}
- /* Free shared memory */
- if (ctx->shared_mem_virt) {
+ /* Shared memory aliases the BAR0 shadow (owned by bar_emulation) -
+ * only free it if it was a standalone DMA allocation (dma != 0) */
+ if (ctx->shared_mem_virt && ctx->shared_mem_dma) {
dma_free_wc(&vgpu->pdev->dev, ctx->shared_mem_size,
ctx->shared_mem_virt, ctx->shared_mem_dma);
- ctx->shared_mem_virt = NULL;
}
+ ctx->shared_mem_virt = NULL;
- /* BAR0 is managed by nv_vgpu_bar_init/cleanup, don't unmap here */
+ /* BAR0 shadow is managed by nv_vgpu_bar_emul_init/cleanup */
ctx->bar0_vaddr = NULL;
vgpu->gsp_comm_ctx = NULL;
diff --git a/kernel/gsp_rpc_client.c b/kernel/gsp_rpc_client.c
index ae45513..93c1876 100644
--- a/kernel/gsp_rpc_client.c
+++ b/kernel/gsp_rpc_client.c
@@ -375,8 +375,8 @@ int gsp_rpc_unpin(struct gsp_client *client, u64 handle, u64 offset, u64 size)
/* BAR Trap Emulation */
int gsp_rpc_bar_trap(struct gsp_client *client,
- u64 bar_addr, u32 access_type, u32 size,
- u64 value, u32 bar_num, u64 *ret_value)
+ u64 bar_addr, u32 access_type, u32 size,
+ u64 value, u32 bar_num, u64 *ret_value)
{
struct gsp_bar_trap_req req = {0};
struct gsp_bar_trap_rsp rsp = {0};
@@ -384,6 +384,17 @@ int gsp_rpc_bar_trap(struct gsp_client *client,
u32 seq = gsp_next_seq(client);
int ret;
+ /* In emulation mode there is no real firmware to submit to, and
+ * submit_raw would ring the very doorbell the emulation thread polls
+ * (self-triggered RPC livelock + timeout stall). Guest BAR access is
+ * synchronous MMIO via the shadow mapping and never needs this RPC.
+ * Real shadow-register handlers are TODO; fail fast and loudly. */
+ if (gsp_rpc_emulation_is_enabled()) {
+ pr_warn_ratelimited("gsp_rpc_bar_trap: not supported in emulation mode (bar=%u addr=0x%llx)\n",
+ bar_num, bar_addr);
+ return -ENOTSUPP;
+ }
+
req.bar_addr = bar_addr;
req.access_type = access_type;
req.size = size;
@@ -413,9 +424,19 @@ int gsp_rpc_migrate_save(struct gsp_client *client,
struct gsp_migrate_req req = {0};
struct gsp_migrate_rsp rsp = {0};
struct gsp_rpc_response response = {0};
- u32 seq = gsp_next_seq(client);
+ u32 seq;
int ret;
+ /* Emulation mode: no real firmware; submit_raw would ring the doorbell
+ * the emulation thread polls (self-triggered livelock + 30s stall).
+ * Live migration over the emulated vGPU is not yet implemented;
+ * see kernel/migration.c for the 4-phase host-side protocol work. */
+ if (gsp_rpc_emulation_is_enabled()) {
+ pr_warn_ratelimited("gsp_rpc_migrate_save: not supported in emulation mode\n");
+ return -ENOTSUPP;
+ }
+
+ seq = gsp_next_seq(client);
req.src_gpu_id = src_gpu_id;
req.dst_gpu_id = dst_gpu_id;
req.memory_bitmap = memory_bitmap;
@@ -444,9 +465,16 @@ int gsp_rpc_migrate_restore(struct gsp_client *client,
{
struct gsp_migrate_req req = {0};
struct gsp_rpc_response response = {0};
- u32 seq = gsp_next_seq(client);
+ u32 seq;
int ret;
+ /* Emulation mode: no real firmware; see gsp_rpc_migrate_save note. */
+ if (gsp_rpc_emulation_is_enabled()) {
+ pr_warn_ratelimited("gsp_rpc_migrate_restore: not supported in emulation mode\n");
+ return -ENOTSUPP;
+ }
+
+ seq = gsp_next_seq(client);
req.src_gpu_id = src_gpu_id;
req.dst_gpu_id = dst_gpu_id;
req.memory_bitmap = memory_bitmap;
@@ -777,7 +805,7 @@ int gsp_rpc_schedule(struct gsp_client *client,
u32 seq = gsp_next_seq(client);
int ret;
- if (gsp_emulation_enabled) {
+ if (gsp_rpc_emulation_is_enabled()) {
struct gsp_sched_work *sched = kzalloc(sizeof(*sched), GFP_KERNEL);
if (!sched)
return -ENOMEM;
@@ -827,7 +855,7 @@ int gsp_rpc_yield(struct gsp_client *client)
u32 seq = gsp_next_seq(client);
int ret;
- if (gsp_emulation_enabled) {
+ if (gsp_rpc_emulation_is_enabled()) {
/* In emulation, yield is a no-op success */
return 0;
}
diff --git a/kernel/include/nvidia-vgpu-vfio.h b/kernel/include/nvidia-vgpu-vfio.h
index 02c2c13..961bba2 100644
--- a/kernel/include/nvidia-vgpu-vfio.h
+++ b/kernel/include/nvidia-vgpu-vfio.h
@@ -177,6 +177,14 @@ int nv_vgpu_gsp_load_firmware(struct nv_vgpu_device *vgpu);
void nv_vgpu_gsp_release_firmware(struct nv_vgpu_device *vgpu);
int nv_vgpu_gsp_boot(struct nv_vgpu_device *vgpu);
+/* mdev driver/parent registration (main.c) */
+int nv_vgpu_create_mdev_types(void);
+void nv_vgpu_free_type_info(void);
+int nv_vgpu_mdev_driver_register(void);
+void nv_vgpu_mdev_driver_unregister(void);
+int nv_vgpu_mdev_register_parent(struct pci_dev *pdev);
+void nv_vgpu_mdev_unregister_parent(struct pci_dev *pdev);
+
int nv_vgpu_migration_init(struct nv_vgpu_device *vgpu);
void nv_vgpu_migration_cleanup(struct nv_vgpu_device *vgpu);
int nv_vgpu_migration_save_state(struct nv_vgpu_device *vgpu, u8 **data, size_t *size);
diff --git a/kernel/main.c b/kernel/main.c
index e0e823b..3da7dc6 100644
--- a/kernel/main.c
+++ b/kernel/main.c
@@ -671,8 +671,19 @@ static int nv_vgpu_vfio_dev_open(struct vfio_device *vdev)
static void nv_vgpu_vfio_dev_close(struct vfio_device *vdev)
{
struct nv_vgpu_device *vgpu = container_of(vdev, struct nv_vgpu_device, vfio_dev);
-
+ int i;
+
pr_info("nvidia-vgpu-vfio: VFIO device close\n");
+
+ /* Release eventfd trigger references wired via SET_IRQS; the next
+ * opener re-wires its own. Prevents fd/ctx leaks across VM restarts. */
+ for (i = 0; i < NV_VGPU_MAX_IRQS; i++) {
+ if (vgpu->irqs[i].trigger) {
+ eventfd_ctx_put(vgpu->irqs[i].trigger);
+ vgpu->irqs[i].trigger = NULL;
+ }
+ }
+
atomic_dec(&vgpu->vfio_dev_open_count);
wake_up(&vgpu->vfio_dev_close_wait);
}
@@ -712,14 +723,17 @@ static ssize_t nv_vgpu_vfio_dev_write(struct vfio_device *vdev, const char __use
pr_info("nvidia-vgpu-vfio: VFIO device write count=%zu pos=%lld (0x%llx)\n", count, pos, pos);
- /* Config space region */
+ /* Config space region - route through the per-register filter so the
+ * guest can only modify writable registers (not vendor/device IDs,
+ * BAR addresses, capabilities structures, etc.) */
if (pos >= 0x70000000000ULL && pos < 0x70000000000ULL + 4096) {
loff_t cfg_pos = pos - 0x70000000000ULL;
- size_t to_write = min(count, (size_t)(4096 - cfg_pos));
- if (copy_from_user(vgpu->pci_config + cfg_pos, buf, to_write))
- return -EFAULT;
- *ppos += to_write;
- return to_write;
+ ssize_t ret;
+
+ ret = nv_vgpu_config_write(vgpu, buf, count, &cfg_pos);
+ if (ret > 0)
+ *ppos += ret;
+ return ret;
}
return 0;
@@ -961,11 +975,21 @@ static long nv_vgpu_vfio_dev_ioctl(struct vfio_device *vdev, unsigned int cmd,
/* Handle DATA_EVENTFD */
if (irq_set_ptr->flags & VFIO_IRQ_SET_DATA_EVENTFD) {
+ int i, idx, wired = 0;
+
if (irq_set_ptr->count == 0) {
kfree(irq_set_ptr);
return -EINVAL;
}
+ if (irq_set_ptr->index > VFIO_PCI_REQ_IRQ_INDEX ||
+ irq_set_ptr->start + irq_set_ptr->count > NV_VGPU_MAX_IRQS) {
+ pr_err("nvidia-vgpu-vfio: SET_IRQS out of range: index=%u start=%u count=%u\n",
+ irq_set_ptr->index, irq_set_ptr->start, irq_set_ptr->count);
+ kfree(irq_set_ptr);
+ return -EINVAL;
+ }
+
fds = kcalloc(irq_set_ptr->count, sizeof(int), GFP_KERNEL);
if (!fds) {
kfree(irq_set_ptr);
@@ -980,21 +1004,38 @@ static long nv_vgpu_vfio_dev_ioctl(struct vfio_device *vdev, unsigned int cmd,
return -EFAULT;
}
- /* For now, just store the eventfds for INTx and MSI-X */
- if (irq_set_ptr->index == VFIO_PCI_INTX_IRQ_INDEX) {
- pr_info("nvidia-vgpu-vfio: SET_IRQS INTx trigger fds[%d]\n", irq_set_ptr->count);
- /* TODO: Store eventfds for INTx */
- } else if (irq_set_ptr->index == VFIO_PCI_MSIX_IRQ_INDEX) {
- pr_info("nvidia-vgpu-vfio: SET_IRQS MSI-X trigger fds[%d] start=%d\n", irq_set_ptr->count, irq_set_ptr->start);
- /* TODO: Store eventfds for MSI-X vectors */
- } else if (irq_set_ptr->index == VFIO_PCI_MSI_IRQ_INDEX) {
- pr_info("nvidia-vgpu-vfio: SET_IRQS MSI trigger fds[%d]\n", irq_set_ptr->count);
- /* TODO: Store eventfds for MSI */
- } else if (irq_set_ptr->index == VFIO_PCI_ERR_IRQ_INDEX) {
- pr_info("nvidia-vgpu-vfio: SET_IRQS ERR trigger fds[%d]\n", irq_set_ptr->count);
- /* TODO: Store eventfds for ERR */
+ /* Wire each eventfd to its interrupt vector trigger.
+ * fd >= 0: take a reference and store; fd == -1: clear.
+ * The GSP emulation engine fires vector 0 via
+ * gsp_emu_inject_completion_interrupt(). */
+ for (i = 0; i < irq_set_ptr->count; i++) {
+ idx = irq_set_ptr->start + i;
+
+ if (fds[i] >= 0) {
+ struct eventfd_ctx *trigger;
+
+ trigger = eventfd_ctx_fdget(fds[i]);
+ if (IS_ERR(trigger)) {
+ pr_err("nvidia-vgpu-vfio: SET_IRQS bad eventfd %d for vector %d\n",
+ fds[i], idx);
+ continue;
+ }
+ if (vgpu->irqs[idx].trigger)
+ eventfd_ctx_put(vgpu->irqs[idx].trigger);
+ vgpu->irqs[idx].trigger = trigger;
+ vgpu->irqs[idx].type = irq_set_ptr->index;
+ wired++;
+ } else if (fds[i] == -1) {
+ if (vgpu->irqs[idx].trigger) {
+ eventfd_ctx_put(vgpu->irqs[idx].trigger);
+ vgpu->irqs[idx].trigger = NULL;
+ }
+ }
}
+ pr_info("nvidia-vgpu-vfio: SET_IRQS wired %d/%d eventfds (index=%d start=%d)\n",
+ wired, irq_set_ptr->count, irq_set_ptr->index, irq_set_ptr->start);
+
kfree(fds);
kfree(irq_set_ptr);
return 0;
@@ -1685,6 +1726,7 @@ static ssize_t nv_vgpu_config_read(struct nv_vgpu_device *vgpu, char __user *buf
static ssize_t nv_vgpu_config_write(struct nv_vgpu_device *vgpu, const char __user *buf, size_t count, loff_t *ppos)
{
loff_t pos = *ppos;
+ size_t written = 0;
u8 val;
if (pos >= 256)
@@ -1737,11 +1779,12 @@ static ssize_t nv_vgpu_config_write(struct nv_vgpu_device *vgpu, const char __us
break;
}
pos++;
+ written++;
}
vgpu->config_space_dirty = true;
*ppos = pos;
- return pos - *ppos + count;
+ return written;
}
/* ============================================================
diff --git a/driver/Makefile b/legacy-driver-guest/Makefile
index 4675cbf..4675cbf 100644
--- a/driver/Makefile
+++ b/legacy-driver-guest/Makefile
diff --git a/legacy-driver-guest/README.md b/legacy-driver-guest/README.md
new file mode 100644
index 0000000..309613a
--- /dev/null
+++ b/legacy-driver-guest/README.md
@@ -0,0 +1,21 @@
+# legacy-driver-guest — historical guest-side driver (NOT BUILT)
+
+This directory preserves the **old guest-side vGPU driver** (`gsp-vgpu.ko`)
+from the pre-refactor architecture, where GSP RPC handling lived in a kernel
+module *inside the guest*.
+
+It is kept for reference only:
+
+- The current architecture is **host-only GSP emulation** — the guest runs
+ the stock, unmodified `nvidia.ko`, and there is no Open-IOV code in the
+ guest. See `docs/GSP_EMULATION_ARCHITECTURE.md`.
+- Nothing here is part of any build target (the top-level Makefile no longer
+ references it).
+- `kernel-tests/` holds the old test modules written against this driver's
+ internal API (`gsp_core.h`); they are superseded by
+ `kernel/tests/gsp_emulation_test.c`, which tests the host-side emulation
+ engine through `gsp_emu_process_rpc()`.
+
+Do not add new code here. If you need behavior that only exists in this tree
+(e.g. WPR eventfd work, migration WPR state), port it into the host-side
+`kernel/` modules rather than reviving the guest driver.
diff --git a/driver/bar/gsp_bar.c b/legacy-driver-guest/bar/gsp_bar.c
index aa3ed62..aa3ed62 100644
--- a/driver/bar/gsp_bar.c
+++ b/legacy-driver-guest/bar/gsp_bar.c
diff --git a/driver/core/gsp_core.c b/legacy-driver-guest/core/gsp_core.c
index cd41bd2..cd41bd2 100644
--- a/driver/core/gsp_core.c
+++ b/legacy-driver-guest/core/gsp_core.c
diff --git a/driver/debugfs/gsp_debugfs.c b/legacy-driver-guest/debugfs/gsp_debugfs.c
index f3df124..f3df124 100644
--- a/driver/debugfs/gsp_debugfs.c
+++ b/legacy-driver-guest/debugfs/gsp_debugfs.c
diff --git a/driver/include/gsp_core.h b/legacy-driver-guest/include/gsp_core.h
index 569d6f8..569d6f8 100644
--- a/driver/include/gsp_core.h
+++ b/legacy-driver-guest/include/gsp_core.h
diff --git a/kernel/tests/gsp_vgpu_comprehensive_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c
index 7918beb..7918beb 100644
--- a/kernel/tests/gsp_vgpu_comprehensive_test.c
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c
diff --git a/kernel/tests/gsp_vgpu_kunit_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c
index c661f91..c661f91 100644
--- a/kernel/tests/gsp_vgpu_kunit_test.c
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c
diff --git a/kernel/tests/gsp_vgpu_mock_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
index b9a46df..b9a46df 100644
--- a/kernel/tests/gsp_vgpu_mock_test.c
+++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c
diff --git a/driver/migration/gsp_migration.c b/legacy-driver-guest/migration/gsp_migration.c
index 52d1931..52d1931 100644
--- a/driver/migration/gsp_migration.c
+++ b/legacy-driver-guest/migration/gsp_migration.c
diff --git a/driver/rpc/gsp_rpc.c b/legacy-driver-guest/rpc/gsp_rpc.c
index 6bdcdb2..6bdcdb2 100644
--- a/driver/rpc/gsp_rpc.c
+++ b/legacy-driver-guest/rpc/gsp_rpc.c
diff --git a/driver/vf/gsp_vf.c b/legacy-driver-guest/vf/gsp_vf.c
index 951c84d..951c84d 100644
--- a/driver/vf/gsp_vf.c
+++ b/legacy-driver-guest/vf/gsp_vf.c
diff --git a/driver/wpr/gsp_wpr.c b/legacy-driver-guest/wpr/gsp_wpr.c
index 91c5550..91c5550 100644
--- a/driver/wpr/gsp_wpr.c
+++ b/legacy-driver-guest/wpr/gsp_wpr.c
diff --git a/scripts/stack_manager.sh b/scripts/stack_manager.sh
index 0b62170..ef8385a 100755
--- a/scripts/stack_manager.sh
+++ b/scripts/stack_manager.sh
@@ -11,6 +11,8 @@ FIRMWARE_PATH="$BASE_DIR/firmware/57X-GSP/570.144/gsp-570.144.fwimage.bin"
FIRMWARE_DEST="/lib/firmware/gsp-570.144.fwimage.bin"
MDEV_SCRIPT="$BASE_DIR/scripts/create_mdev.sh"
+# Legacy direct-QEMU script retained for reference only - VM launch MUST go
+# through the harness (vgpu_vm.py). See cmd_launch_qemu.
QEMU_SCRIPT="$BASE_DIR/scripts/launch_qemu_vgpu.sh"
TEST_SCRIPT="$BASE_DIR/scripts/test_vgpu_stack.sh"
@@ -226,12 +228,16 @@ cmd_list_mdev() {
# ============================================================
cmd_launch_qemu() {
- log_info "Launching QEMU with vGPU..."
- if [[ ! -f "$QEMU_SCRIPT" ]]; then
- log_err "launch_qemu_vgpu.sh not found"
+ # WORKFLOW RULE: always use the QEMU harness (vgpu_vm.py), never
+ # or raw QEMU directly - the harness owns , mdev lifecycle, QMP,
+ # guest-agent and serial logging.
+ local harness="$BASE_DIR/scripts/vgpu_vm.py"
+ if [[ ! -f "$harness" ]]; then
+ log_err "vgpu_vm.py harness not found at $harness"
return 1
fi
- "$QEMU_SCRIPT" "$@"
+ log_info "Launching QEMU with vGPU via harness (vgpu_vm.py)..."
+ "$harness" start "${1:-vgpu-linux}"
}
# ============================================================
@@ -273,12 +279,20 @@ cmd_dmesg() {
}
cmd_debug_qemu() {
- log_info "Starting debug QEMU session..."
- if [[ ! -f "$BASE_DIR/scripts/debug_guest_init.sh" ]]; then
- log_err "debug_guest_init.sh not found"
+ # WORKFLOW RULE: harness only. The vgpu-linux.yaml already sets
+ # serialLog=/tmp/vgpu-guest-serial.log and gdbPort=1234, so a normal
+ # harness start IS the debug session: attach GDB with
+ # gdb -ex 'target remote localhost:1234'
+ # and follow the guest serial log plus host dmesg.
+ local harness="$BASE_DIR/scripts/vgpu_vm.py"
+ if [[ ! -f "$harness" ]]; then
+ log_err "vgpu_vm.py harness not found at $harness"
return 1
fi
- "$BASE_DIR/scripts/debug_guest_init.sh" "$@"
+ log_info "Starting debug session via harness (GDB stub tcp::1234, serial /tmp/vgpu-guest-serial.log)..."
+ "$harness" start "${1:-vgpu-linux}"
+ log_info "Attach GDB: gdb -ex 'target remote localhost:1234'"
+ log_info "Serial log: tail -f /tmp/vgpu-guest-serial.log"
}
cmd_cleanup_crash() {
diff --git a/userspace/tests/test_vgpu_userspace b/userspace/tests/test_vgpu_userspace
new file mode 100755
index 0000000..592266c
--- /dev/null
+++ b/userspace/tests/test_vgpu_userspace
Binary files differ