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) --- Makefile | 2 - README.md | 28 +- STATE.md | 44 +- docs/DEBUGGING.md | 190 ++++++ docs/DEVELOPMENT_WORKFLOW.md | 39 +- docs/GSP_EMULATION_ARCHITECTURE.md | 192 ++++++ docs/ONBOARDING.md | 166 ++++++ docs/PERFORMANCE.md | 98 +++ docs/VERSION_UPGRADE.md | 137 +++++ driver/Makefile | 44 -- driver/bar/gsp_bar.c | 242 -------- driver/core/gsp_core.c | 654 --------------------- driver/debugfs/gsp_debugfs.c | 273 --------- driver/include/gsp_core.h | 613 ------------------- driver/migration/gsp_migration.c | 339 ----------- driver/rpc/gsp_rpc.c | 331 ----------- driver/vf/gsp_vf.c | 312 ---------- driver/wpr/gsp_wpr.c | 285 --------- kernel/Makefile | 9 +- kernel/gsp_comm.c | 5 +- kernel/gsp_emulation.c | 71 ++- kernel/gsp_rpc_client.c | 40 +- kernel/include/nvidia-vgpu-vfio.h | 8 + kernel/main.c | 85 ++- kernel/tests/gsp_vgpu_comprehensive_test.c | 432 -------------- kernel/tests/gsp_vgpu_kunit_test.c | 327 ----------- kernel/tests/gsp_vgpu_mock_test.c | 624 -------------------- legacy-driver-guest/Makefile | 44 ++ legacy-driver-guest/README.md | 21 + legacy-driver-guest/bar/gsp_bar.c | 242 ++++++++ legacy-driver-guest/core/gsp_core.c | 654 +++++++++++++++++++++ legacy-driver-guest/debugfs/gsp_debugfs.c | 273 +++++++++ legacy-driver-guest/include/gsp_core.h | 613 +++++++++++++++++++ .../kernel-tests/gsp_vgpu_comprehensive_test.c | 432 ++++++++++++++ .../kernel-tests/gsp_vgpu_kunit_test.c | 327 +++++++++++ .../kernel-tests/gsp_vgpu_mock_test.c | 624 ++++++++++++++++++++ legacy-driver-guest/migration/gsp_migration.c | 339 +++++++++++ legacy-driver-guest/rpc/gsp_rpc.c | 331 +++++++++++ legacy-driver-guest/vf/gsp_vf.c | 312 ++++++++++ legacy-driver-guest/wpr/gsp_wpr.c | 285 +++++++++ scripts/stack_manager.sh | 30 +- userspace/tests/test_vgpu_userspace | Bin 0 -> 16584 bytes 42 files changed, 5536 insertions(+), 4581 deletions(-) create mode 100644 docs/DEBUGGING.md create mode 100644 docs/GSP_EMULATION_ARCHITECTURE.md create mode 100644 docs/ONBOARDING.md create mode 100644 docs/PERFORMANCE.md create mode 100644 docs/VERSION_UPGRADE.md delete mode 100644 driver/Makefile delete mode 100644 driver/bar/gsp_bar.c delete mode 100644 driver/core/gsp_core.c delete mode 100644 driver/debugfs/gsp_debugfs.c delete mode 100644 driver/include/gsp_core.h delete mode 100644 driver/migration/gsp_migration.c delete mode 100644 driver/rpc/gsp_rpc.c delete mode 100644 driver/vf/gsp_vf.c delete mode 100644 driver/wpr/gsp_wpr.c delete mode 100644 kernel/tests/gsp_vgpu_comprehensive_test.c delete mode 100644 kernel/tests/gsp_vgpu_kunit_test.c delete mode 100644 kernel/tests/gsp_vgpu_mock_test.c create mode 100644 legacy-driver-guest/Makefile create mode 100644 legacy-driver-guest/README.md create mode 100644 legacy-driver-guest/bar/gsp_bar.c create mode 100644 legacy-driver-guest/core/gsp_core.c create mode 100644 legacy-driver-guest/debugfs/gsp_debugfs.c create mode 100644 legacy-driver-guest/include/gsp_core.h create mode 100644 legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c create mode 100644 legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c create mode 100644 legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c create mode 100644 legacy-driver-guest/migration/gsp_migration.c create mode 100644 legacy-driver-guest/rpc/gsp_rpc.c create mode 100644 legacy-driver-guest/vf/gsp_vf.c create mode 100644 legacy-driver-guest/wpr/gsp_wpr.c create mode 100755 userspace/tests/test_vgpu_userspace 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/` 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 @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/` +- [ ] 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 `; remove mdev → `available_instances` returns to **1** +- [ ] `ps -e | grep gsp_emu` → **empty** (no leaked thread) +- [ ] `/dev/vfio/` node gone; matching `/sys/kernel/iommu_groups/` 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 |exec [--timeout N] |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 # or: sudo kill -9 +``` 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//NVIDIA-Linux-x86_64-.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//gsp_tu10x.bin`. +3. Extract the `.fwimage` section (host boot image) and record the MD5: + ```bash + objcopy --only-section=.fwimage gsp_tu10x.bin gsp-.fwimage.bin + md5sum gsp-.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/-GSP// # e.g. firmware/61X-GSP/610.XX/ + gsp-.fwimage.bin + decompiled/ # ghidra output lands here +config/versions/.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/-GSP//gsp-.fwimage.bin \ + /tmp/ghidra_project_ "gsp_" +cd ../../firmware/-GSP/ +python3 analyze_firmware.py gsp-.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/.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//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/driver/Makefile b/driver/Makefile deleted file mode 100644 index 4675cbf..0000000 --- a/driver/Makefile +++ /dev/null @@ -1,44 +0,0 @@ -# GSP vGPU-IO Driver Makefile - -obj-m += gsp-vgpu.o - -gsp-vgpu-y := \ - core/gsp_core.o \ - rpc/gsp_rpc.o \ - vf/gsp_vf.o \ - bar/gsp_bar.o \ - wpr/gsp_wpr.o \ - migration/gsp_migration.o \ - debugfs/gsp_debugfs.o - -ccflags-y += -I$(src)/include -ccflags-y += -I$(src)/../kernel/include - -# Build against the kernel source -KDIR ?= /lib/modules/$(shell uname -r)/build - -all: - $(MAKE) -C $(KDIR) M=$(PWD) modules - -clean: - $(MAKE) -C $(KDIR) M=$(PWD) clean - -install: - $(MAKE) -C $(KDIR) M=$(PWD) modules_install - -load: all - sudo insmod gsp-vgpu.ko firmware_name=gsp-570.144.fwimage.bin - -unload: - sudo rmmod gsp-vgpu - -test: all load - @echo "Driver loaded, checking status..." - sleep 2 - dmesg -T | tail -20 - @echo "Checking debugfs..." - ls -la /sys/kernel/debug/gsp-vgpu/ 2>/dev/null || echo "Debugfs not available" - @echo "Checking device nodes..." - ls -la /dev/gsp-vgpu* 2>/dev/null || echo "No device nodes" - -.PHONY: all clean install load unload test \ No newline at end of file diff --git a/driver/bar/gsp_bar.c b/driver/bar/gsp_bar.c deleted file mode 100644 index aa3ed62..0000000 --- a/driver/bar/gsp_bar.c +++ /dev/null @@ -1,242 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - BAR Emulation - */ - -#include "gsp_core.h" -#include -#include - -/* BAR Emulation Ops */ -static int gsp_bar_trap_handler(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap) -{ - struct gsp_bar_state *bar = &vf->bar_state; - struct gsp_bar_trap_response resp = {0}; - size_t resp_size = sizeof(resp); - int ret; - - /* Validate BAR index (we only support BAR1) */ - if (trap->bar_index != 1) - return -EINVAL; - - /* Validate offset */ - if (trap->offset >= bar->aperture_size) - return -ERANGE; - - /* Check ForceBarAccess */ - if (bar->force_access && vf->id != bar->force_vf) - return -EPERM; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - /* Simulate BAR trap handling */ - if (trap->trap_type == GSP_BAR_WRITE) { - /* Config write - update aperture */ - bar->aperture_offset = trap->offset; - bar->aperture_size = trap->value; - } else if (trap->trap_type == GSP_BAR_ATOMIC) { - /* Force access control */ - bar->force_access = trap->offset ? true : false; - bar->force_vf = trap->offset ? vf->id : 0; - } - resp.value = 0; - return 0; - } - - /* Forward to GSP BAR trap handler (0x04CB8188) */ - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, - trap, sizeof(*trap), &resp, &resp_size); - - return ret; -} - -/* Aperture Configuration */ -static int gsp_bar_config_aperture(struct gsp_vf *vf, uint64_t offset, uint64_t size) -{ - struct gsp_bar_state *bar = &vf->bar_state; - struct gsp_bar_trap_payload payload = {0}; - struct gsp_bar_trap_response resp = {0}; - size_t resp_size = sizeof(resp); - int ret; - - /* Validate alignment (4KB) */ - if (offset & 0xFFF || size & 0xFFF) - return -EINVAL; - - /* Validate max size (256KB per firmware) */ - if (size > 0x40000) - return -EINVAL; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - bar->aperture_offset = offset; - bar->aperture_size = size; - return 0; - } - - payload.bar_index = 1; - payload.trap_type = GSP_BAR_WRITE; /* Config write */ - payload.offset = offset; - payload.value = size; - payload.size = 8; - payload.vf_id = vf->id; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, - &payload, sizeof(payload), &resp, &resp_size); - if (!ret) { - bar->aperture_offset = offset; - bar->aperture_size = size; - } - - return ret; -} - -/* ForceBarAccess Control */ -int gsp_bar_force_access(struct gsp_vf *vf, bool enable) -{ - struct gsp_bar_state *bar = &vf->bar_state; - struct gsp_bar_trap_payload payload = {0}; - struct gsp_bar_trap_response resp = {0}; - size_t resp_size = sizeof(resp); - int ret; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - bar->force_access = enable; - bar->force_vf = enable ? vf->id : 0; - return 0; - } - - payload.bar_index = 1; - payload.trap_type = GSP_BAR_ATOMIC; /* Force access control */ - payload.offset = enable ? 1 : 0; - payload.value = enable ? vf->id : 0; - payload.size = 8; - payload.vf_id = vf->id; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, - &payload, sizeof(payload), &resp, &resp_size); - if (!ret) { - bar->force_access = enable; - bar->force_vf = enable ? vf->id : 0; - } - - return ret; -} - -/* State Save for Migration */ -static int gsp_bar_save_state(struct gsp_vf *vf, void *buf, size_t *size) -{ - struct gsp_bar_state *bar = &vf->bar_state; - size_t needed = sizeof(*bar) + bar->backing_size; - - if (!buf || *size < needed) - return -ENOSPC; - - memcpy(buf, bar, sizeof(*bar)); - if (bar->backing_store && bar->backing_size > 0) - memcpy(buf + sizeof(*bar), bar->backing_store, bar->backing_size); - - *size = needed; - return 0; -} - -/* State Restore for Migration */ -static int gsp_bar_restore_state(struct gsp_vf *vf, void *buf, size_t size) -{ - struct gsp_bar_state *bar = &vf->bar_state; - size_t min_size = sizeof(*bar); - - if (!buf || size < min_size) - return -EINVAL; - - memcpy(bar, buf, sizeof(*bar)); - - if (size > min_size && bar->backing_size > 0) { - if (!bar->backing_store) { - bar->backing_store = vzalloc(bar->backing_size); - if (!bar->backing_store) - return -ENOMEM; - } - memcpy(bar->backing_store, buf + min_size, - min(size - min_size, bar->backing_size)); - } - - /* Reconfigure BAR in GSP */ - return gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size); -} - -/* VF BAR Initialization */ -int gsp_vf_bar_init(struct gsp_vf *vf) -{ - struct gsp_bar_state *bar = &vf->bar_state; - int ret; - - /* Initialize with config values */ - bar->aperture_offset = vf->bar1_offset; - bar->aperture_size = vf->bar1_size; - bar->force_access = false; - bar->force_vf = 0; - bar->backing_store = NULL; - bar->backing_size = 0; - - /* Allocate backing store (max 256KB) */ - if (bar->aperture_size > 0) { - bar->backing_size = min(bar->aperture_size, (uint64_t)0x40000); - bar->backing_store = vzalloc(bar->backing_size); - if (!bar->backing_store) - return -ENOMEM; - } - - /* Configure aperture in GSP (skip in emulation mode) */ - if (!emulation_mode) { - ret = gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size); - if (ret) { - dev_err(vf->gsp->dev, "VF %u: BAR aperture config failed: %d\n", vf->id, ret); - goto err_backing; - } - } else { - dev_info(vf->gsp->dev, "VF %u: BAR1 initialized in emulation mode [0x%llx, 0x%llx)\n", - vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size); - } - - dev_info(vf->gsp->dev, "VF %u: BAR1 initialized [0x%llx, 0x%llx)\n", - vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size); - - return 0; - -err_backing: - vfree(bar->backing_store); - bar->backing_store = NULL; - bar->backing_size = 0; - return ret; -} - -/* VF BAR Cleanup */ -void gsp_vf_bar_fini(struct gsp_vf *vf) -{ - struct gsp_bar_state *bar = &vf->bar_state; - - vfree(bar->backing_store); - bar->backing_store = NULL; - bar->backing_size = 0; - - dev_info(vf->gsp->dev, "VF %u: BAR1 cleaned up\n", vf->id); -} - -/* BAR Trap Entry Point (called from ISR/notification) */ -int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap) -{ - return gsp_bar_trap_handler(vf, trap); -} - -/* Migration Interface */ -int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size) -{ - return gsp_bar_save_state(vf, buf, size); -} - -int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size) -{ - return gsp_bar_restore_state(vf, buf, size); -} \ No newline at end of file diff --git a/driver/core/gsp_core.c b/driver/core/gsp_core.c deleted file mode 100644 index cd41bd2..0000000 --- a/driver/core/gsp_core.c +++ /dev/null @@ -1,654 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - Core Implementation - * Guest vGPU driver - communicates with GSP already booted by host GRID driver - */ - -#include "gsp_core.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define DRIVER_NAME "gsp-vgpu" -#define DRIVER_VERSION "0.1.0" - -/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */ -bool emulation_mode = true; /* ON by default */ -static bool emulation_override = false; /* true = force emulation, false = force native */ -module_param(emulation_mode, bool, 0444); -MODULE_PARM_DESC(emulation_mode, "Enable emulation mode for BAR/WPR (skip GSP RPC) - default ON, auto-detects if GSP has vGPU handlers"); -module_param(emulation_override, bool, 0444); -MODULE_PARM_DESC(emulation_override, "Override auto-detection: 1=force emulation, 0=force native (default: auto-detect)"); - -/* Forward declarations */ -int gsp_firmware_load(struct gsp_device *gsp); -int gsp_setup_irq(struct gsp_device *gsp); -int gsp_rpc_init(struct gsp_device *gsp); -void gsp_shutdown(struct gsp_device *gsp); -static void gsp_detect_emulation_mode(struct gsp_device *gsp); -static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg); -static int gsp_open(struct inode *inode, struct file *file); -static int gsp_release(struct inode *inode, struct file *file); - -/* Char device file operations */ -static const struct file_operations gsp_fops = { - .owner = THIS_MODULE, - .open = gsp_open, - .release = gsp_release, - .unlocked_ioctl = gsp_ioctl, - .compat_ioctl = gsp_ioctl, -}; - -/* Char device open */ -static int gsp_open(struct inode *inode, struct file *file) -{ - struct gsp_device *gsp = container_of(file->private_data, struct gsp_device, miscdev); - file->private_data = gsp; - return 0; -} - -/* Char device release */ -static int gsp_release(struct inode *inode, struct file *file) -{ - return 0; -} - -/* Char device ioctl */ -static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct gsp_device *gsp = file->private_data; - void __user *argp = (void __user *)arg; - int ret = 0; - - mutex_lock(&gsp->chardev_lock); - - switch (cmd) { - case GSP_IOCTL_CREATE_VF: { - struct gsp_vf_config config; - if (copy_from_user(&config, argp, sizeof(config))) { - ret = -EFAULT; - break; - } - ret = gsp_vf_create(gsp, &config); - break; - } - case GSP_IOCTL_DESTROY_VF: { - uint32_t vf_id; - if (copy_from_user(&vf_id, argp, sizeof(vf_id))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - gsp_vf_destroy(vf); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_ALLOC: { - struct gsp_alloc_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_alloc(vf, args.size, args.flags, &args.handle, &args.va); - if (!ret) - ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_FREE: { - struct gsp_free_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_free(vf, args.handle); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_PIN: { - struct gsp_pin_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_pin(vf, args.handle, args.offset, args.size, &args.dma_addr); - if (!ret) - ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_UNPIN: { - struct gsp_unpin_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_unpin(vf, args.handle); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_MAP: { - struct gsp_map_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_map(vf, args.handle, args.gpu_va, args.size, - args.prot, args.flags, args.offset, &args.mapped_va); - if (!ret) - ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_UNMAP: { - struct gsp_unmap_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_mem_unmap(vf, args.handle, args.gpu_va, args.size); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_BAR_CONFIG: { - struct gsp_bar_config_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_bar_init(vf); /* Re-init with new aperture */ - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_BAR_FORCE: { - struct gsp_bar_force_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_bar_force_access(vf, args.enable); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_BAR_SAVE: { - struct gsp_bar_save_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_migration_bar_save(vf, args.buf, args.size); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_BAR_RESTORE: { - struct gsp_bar_restore_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_migration_bar_restore(vf, args.buf, args.size); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_WPR_PROTECT: { - struct gsp_wpr_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_wpr_protect(vf, args.base, args.size, args.flags); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_WPR_UNPROTECT: { - struct gsp_wpr_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_wpr_unprotect(vf, args.base); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_WPR_TIMEOUT: { - struct gsp_wpr_timeout_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_wpr_set_timeout(vf, args.timeout_ns); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_SCHEDULE: { - struct gsp_schedule_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_schedule(vf, args.priority, args.time_slice_ns); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_YIELD: { - uint32_t vf_id; - if (copy_from_user(&vf_id, argp, sizeof(vf_id))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_vf_yield(vf); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_MIGRATE: { - struct gsp_migrate_args args; - if (copy_from_user(&args, argp, sizeof(args))) { - ret = -EFAULT; - break; - } - struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); - if (!vf) { - ret = -ENOENT; - break; - } - ret = gsp_migration_start(vf, args.dst_vf); - gsp_vf_put(vf); - break; - } - case GSP_IOCTL_GET_INFO: { - char info[256]; - snprintf(info, sizeof(info), "GSP vGPU-IO Driver v%s (FW: 570.144W)", DRIVER_VERSION); - ret = copy_to_user(argp, info, sizeof(info)) ? -EFAULT : 0; - break; - } - default: - ret = -ENOTTY; - } - - mutex_unlock(&gsp->chardev_lock); - return ret; -} - -/* PCI Device IDs */ -static const struct pci_device_id gsp_pci_ids[] = { - { PCI_DEVICE(0x10DE, 0x1E07), .driver_data = 0 }, /* TU102 */ - { PCI_DEVICE(0x10DE, 0x1E08), .driver_data = 0 }, /* TU104 */ - { PCI_DEVICE(0x10DE, 0x1ED3), .driver_data = 0 }, /* TU104 RTX 2080 Super Mobile */ - { 0 } -}; -MODULE_DEVICE_TABLE(pci, gsp_pci_ids); - -/* Module Parameters */ -static int max_vfs = 7; -module_param(max_vfs, int, 0444); -MODULE_PARM_DESC(max_vfs, "Maximum number of VFs (default: 7)"); - -static char *firmware_name = "gsp-570.144.fwimage.bin"; -module_param(firmware_name, charp, 0444); -MODULE_PARM_DESC(firmware_name, "GSP firmware filename (for version verification only)"); - -/* PCI Probe */ -int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id) -{ - struct gsp_device *gsp; - int ret; - - gsp = devm_kzalloc(&pdev->dev, sizeof(*gsp), GFP_KERNEL); - if (!gsp) - return -ENOMEM; - - gsp->pdev = pdev; - gsp->dev = &pdev->dev; - gsp->max_vfs = max_vfs; - pci_set_drvdata(pdev, gsp); - - mutex_init(&gsp->vf_lock); - idr_init(&gsp->vf_idr); - INIT_LIST_HEAD(&gsp->vf_list); - - /* Enable PCI device */ - ret = pcim_enable_device(pdev); - if (ret) { - dev_err(&pdev->dev, "Failed to enable PCI device: %d\n", ret); - return ret; - } - - pci_set_master(pdev); - - /* Map BARs */ - gsp->bar0 = pcim_iomap(pdev, 0, 0); - gsp->bar1 = pcim_iomap(pdev, 1, 0); - if (!gsp->bar0 || !gsp->bar1) { - dev_err(&pdev->dev, "Failed to map BARs\n"); - return -ENOMEM; - } - - dev_info(&pdev->dev, "BAR0: %p, BAR1: %p\n", gsp->bar0, gsp->bar1); - - /* Load Firmware (for version verification only - GSP already booted by host) */ - ret = gsp_firmware_load(gsp); - if (ret) { - dev_err(&pdev->dev, "Firmware load failed: %d\n", ret); - return ret; - } - - /* Initialize RPC client (sets up rings, doorbells) */ - gsp->rpc = gsp_rpc_client_init(gsp); - if (IS_ERR(gsp->rpc)) { - ret = PTR_ERR(gsp->rpc); - dev_err(&pdev->dev, "RPC client init failed: %d\n", ret); - return ret; - } - - /* Initialize RPC communication with GSP */ - ret = gsp_rpc_init(gsp); - if (ret) { - dev_err(&pdev->dev, "RPC init failed: %d\n", ret); - return ret; - } - - /* Setup IRQ */ - ret = gsp_setup_irq(gsp); - if (ret) { - dev_err(&pdev->dev, "IRQ setup failed: %d\n", ret); - return ret; - } - - /* Initialize char device */ - mutex_init(&gsp->chardev_lock); - gsp->miscdev.minor = MISC_DYNAMIC_MINOR; - gsp->miscdev.name = "gsp-vgpu"; - gsp->miscdev.fops = &gsp_fops; - gsp->miscdev.parent = &pdev->dev; - gsp->miscdev.mode = 0660; - - ret = misc_register(&gsp->miscdev); - if (ret) { - dev_err(&pdev->dev, "Failed to register misc device: %d\n", ret); - return ret; - } - - /* Debugfs */ - gsp_debugfs_init(gsp); - - dev_info(&pdev->dev, "GSP vGPU-IO driver loaded (FW: 570.144W)\n"); - return 0; -} - -/* PCI Remove */ -void gsp_remove(struct pci_dev *pdev) -{ - struct gsp_device *gsp = pci_get_drvdata(pdev); - struct gsp_vf *vf, *tmp; - - if (!gsp) - return; - - /* Destroy all VFs */ - list_for_each_entry_safe(vf, tmp, &gsp->vf_list, list) - gsp_vf_destroy(vf); - - /* Unregister char device */ - misc_deregister(&gsp->miscdev); - - gsp_debugfs_fini(gsp); - gsp_shutdown(gsp); - gsp_rpc_client_fini(gsp->rpc); - - if (gsp->fw) - release_firmware(gsp->fw); - - idr_destroy(&gsp->vf_idr); - - dev_info(&pdev->dev, "GSP vGPU-IO driver unloaded\n"); -} - -/* Firmware Load - only for version verification */ -int gsp_firmware_load(struct gsp_device *gsp) -{ - const struct firmware *fw; - int ret; - - ret = request_firmware(&fw, firmware_name, gsp->dev); - if (ret) { - dev_err(gsp->dev, "Failed to load firmware '%s': %d\n", firmware_name, ret); - return ret; - } - - /* Verify MD5: 13ce12a392a31eeb79a2e6ebd3a4a93a */ - if (fw->size != 0x1C50000) { /* ~28.5 MB */ - dev_warn(gsp->dev, "Firmware size unexpected: %zu (expected ~28.5MB)\n", fw->size); - } - - dev_info(gsp->dev, "Firmware verified: %zu bytes\n", fw->size); - gsp->fw = fw; - return 0; -} - -/* Initialize RPC communication with already-running GSP */ -int gsp_rpc_init(struct gsp_device *gsp) -{ - /* - * The GSP is already running (booted by host GRID driver). - * We just need to verify communication works by reading mailbox registers - * and optionally sending a test RPC. - */ - - /* Read mailbox registers to verify GSP is responsive */ - uint32_t mbox0 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX0); - uint32_t mbox1 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX1); - - dev_info(gsp->dev, "GSP mailbox0: 0x%08x, mailbox1: 0x%08x\n", mbox0, mbox1); - - /* Check if GSP appears to be running (non-zero mailbox or queue heads) */ - uint32_t qhead0 = readl(gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); - dev_info(gsp->dev, "GSP queue head 0: 0x%08x\n", qhead0); - - /* Ring doorbell to signal we're ready - write to queue head */ - writel(1, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); - - gsp->fw_loaded = true; /* Indicates GSP communication established */ - dev_info(gsp->dev, "GSP RPC communication initialized\n"); - - /* Auto-detect emulation mode if not overridden */ - if (!emulation_override) { - gsp_detect_emulation_mode(gsp); - } else if (emulation_mode) { - dev_info(gsp->dev, "Emulation mode FORCED ON by module parameter\n"); - } else { - dev_info(gsp->dev, "Emulation mode FORCED OFF by module parameter\n"); - } - - return 0; -} - -/* Auto-detect if GSP has vGPU RPC handlers */ -static void gsp_detect_emulation_mode(struct gsp_device *gsp) -{ - struct gsp_bar_trap_payload test_payload = {0}; - struct gsp_bar_trap_response test_resp = {0}; - size_t resp_size = sizeof(test_resp); - int ret; - - dev_info(gsp->dev, "Auto-detecting GSP vGPU RPC handler support...\n"); - - /* Send a test BAR_TRAP RPC to check if GSP has vGPU handlers */ - test_payload.bar_index = 1; - test_payload.trap_type = GSP_BAR_READ; /* Non-destructive read */ - test_payload.offset = 0; - test_payload.value = 0; - test_payload.size = 8; - test_payload.vf_id = 0; - - /* Try synchronous RPC with short timeout (1 second) */ - ret = gsp_rpc_send_sync(gsp->rpc, GSP_RPC_BAR_TRAP, - &test_payload, sizeof(test_payload), - &test_resp, &resp_size); - - if (ret == -ETIMEDOUT || ret == -ENOTTY || ret == -EIO || ret == -ENODEV) { - /* GSP doesn't have vGPU handlers - enable emulation */ - emulation_mode = true; - dev_info(gsp->dev, "GSP lacks vGPU RPC handlers (ret=%d) - ENABLING emulation mode\n", ret); - } else if (ret == 0) { - /* GSP responded - has vGPU handlers */ - emulation_mode = false; - dev_info(gsp->dev, "GSP has vGPU RPC handlers - DISABLING emulation mode\n"); - } else { - /* Other error - default to emulation for safety */ - emulation_mode = true; - dev_info(gsp->dev, "GSP RPC test returned %d - ENABLING emulation mode (safe default)\n", ret); - } -} - -/* GSP Shutdown - just clean up our side */ -void gsp_shutdown(struct gsp_device *gsp) -{ - if (gsp->fw_loaded) { - /* Don't reset GSP - host driver owns it */ - gsp->fw_loaded = false; - } -} - -/* IRQ Setup */ -int gsp_setup_irq(struct gsp_device *gsp) -{ - int ret; - struct msix_entry entries[1]; - - /* Try MSI-X first */ - ret = pci_enable_msix_range(gsp->pdev, entries, 1, 1); - if (ret > 0) { - gsp->msix_enabled = true; - gsp->irq = entries[0].vector; - gsp->msix_entries[0] = entries[0]; - } else { - gsp->irq = gsp->pdev->irq; - } - - ret = devm_request_irq(gsp->dev, gsp->irq, gsp_isr, - gsp->msix_enabled ? 0 : IRQF_SHARED, - DRIVER_NAME, gsp); - if (ret) { - dev_err(gsp->dev, "Failed to request IRQ %d: %d\n", gsp->irq, ret); - return ret; - } - - dev_info(gsp->dev, "IRQ %d registered (MSI-X: %s)\n", - gsp->irq, gsp->msix_enabled ? "yes" : "no"); - return 0; -} - -/* PCI Driver Structure */ -static struct pci_driver gsp_pci_driver = { - .name = DRIVER_NAME, - .id_table = gsp_pci_ids, - .probe = gsp_probe, - .remove = gsp_remove, -}; - -/* Module Init/Exit */ -static int __init gsp_module_init(void) -{ - return pci_register_driver(&gsp_pci_driver); -} - -static void __exit gsp_module_exit(void) -{ - pci_unregister_driver(&gsp_pci_driver); -} - -module_init(gsp_module_init); -module_exit(gsp_module_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("NVIDIA vGPU-IO Team"); -MODULE_DESCRIPTION("NVIDIA GSP 570.144W vGPU-IO Driver"); -MODULE_VERSION(DRIVER_VERSION); -MODULE_FIRMWARE("gsp-570.144.fwimage.bin"); diff --git a/driver/debugfs/gsp_debugfs.c b/driver/debugfs/gsp_debugfs.c deleted file mode 100644 index f3df124..0000000 --- a/driver/debugfs/gsp_debugfs.c +++ /dev/null @@ -1,273 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - Debugfs Interface - */ - -#include "gsp_core.h" -#include -#include - -static int gsp_debugfs_vfs_show(struct seq_file *s, void *unused) -{ - struct gsp_device *gsp = s->private; - struct gsp_vf *vf; - - seq_printf(s, "%-4s %-12s %-10s %-18s %-18s\n", - "ID", "STATE", "REFCNT", "BAR1", "PAGE_TABLE"); - - mutex_lock(&gsp->vf_lock); - list_for_each_entry(vf, &gsp->vf_list, list) { - seq_printf(s, "%-4u %-12d %-10d 0x%016llx-0x%016llx 0x%016llx-0x%016llx\n", - vf->id, vf->state, atomic_read(&vf->refcount), - vf->bar1_offset, vf->bar1_offset + vf->bar1_size, - vf->pt_base, vf->pt_base + vf->pt_size); - } - mutex_unlock(&gsp->vf_lock); - - return 0; -} - -static int gsp_debugfs_vfs_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_vfs_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_vfs_fops = { - .open = gsp_debugfs_vfs_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int gsp_debugfs_vf_show(struct seq_file *s, void *unused) -{ - struct gsp_vf *vf = s->private; - struct gsp_wpr_region *rgn; - - seq_printf(s, "VF %u\n", vf->id); - seq_printf(s, " State: %d\n", vf->state); - seq_printf(s, " Refcount: %d\n", atomic_read(&vf->refcount)); - seq_printf(s, " BAR1: 0x%llx - 0x%llx (size: 0x%llx)\n", - vf->bar1_offset, vf->bar1_offset + vf->bar1_size, vf->bar1_size); - seq_printf(s, " Page Table: 0x%llx - 0x%llx (size: 0x%llx)\n", - vf->pt_base, vf->pt_base + vf->pt_size, vf->pt_size); - seq_printf(s, " Cmd Buffer: 0x%llx - 0x%llx (size: 0x%llx)\n", - vf->cmd_base, vf->cmd_base + vf->cmd_size, vf->cmd_size); - seq_printf(s, " BAR State:\n"); - seq_printf(s, " Aperture: 0x%llx - 0x%llx\n", - vf->bar_state.aperture_offset, - vf->bar_state.aperture_offset + vf->bar_state.aperture_size); - seq_printf(s, " ForceAcc: %s (VF: %u)\n", - vf->bar_state.force_access ? "enabled" : "disabled", - vf->bar_state.force_vf); - seq_printf(s, " WPR Regions:\n"); - - mutex_lock(&vf->wpr_lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - seq_printf(s, " [0x%llx, 0x%llx) flags=0x%x\n", - rgn->base, rgn->base + rgn->size, rgn->flags); - } - mutex_unlock(&vf->wpr_lock); - - if (vf->migration) { - seq_printf(s, " Migration:\n"); - seq_printf(s, " State: %d\n", vf->migration->state); - seq_printf(s, " Dst VF: %llu\n", vf->migration->dst_vf); - seq_printf(s, " Token: 0x%llx\n", vf->migration->token); - seq_printf(s, " Progress: %llu pages, %llu bytes\n", - vf->migration->progress.pages_transferred, - vf->migration->progress.bytes_transferred); - } - - return 0; -} - -static int gsp_debugfs_vf_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_vf_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_vf_fops = { - .open = gsp_debugfs_vf_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int gsp_debugfs_rpc_show(struct seq_file *s, void *unused) -{ - struct gsp_device *gsp = s->private; - - seq_printf(s, "RPC Statistics:\n"); - seq_printf(s, " Total: %lld\n", atomic64_read(&gsp->rpc_total)); - seq_printf(s, " Errors: %lld\n", atomic64_read(&gsp->rpc_errors)); - seq_printf(s, " BAR Traps: %lld\n", atomic64_read(&gsp->bar_traps)); - seq_printf(s, " WPR Events: %lld\n", atomic64_read(&gsp->wpr_events)); - seq_printf(s, " Migrations: %lld\n", atomic64_read(&gsp->migrations)); - - return 0; -} - -static int gsp_debugfs_rpc_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_rpc_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_rpc_fops = { - .open = gsp_debugfs_rpc_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int gsp_debugfs_bar_show(struct seq_file *s, void *unused) -{ - struct gsp_vf *vf = s->private; - - seq_printf(s, "VF %u BAR State:\n", vf->id); - seq_printf(s, " Aperture Offset: 0x%llx\n", vf->bar_state.aperture_offset); - seq_printf(s, " Aperture Size: 0x%llx\n", vf->bar_state.aperture_size); - seq_printf(s, " Force Access: %s\n", vf->bar_state.force_access ? "enabled" : "disabled"); - seq_printf(s, " Force VF: %u\n", vf->bar_state.force_vf); - seq_printf(s, " Backing Store: %p\n", vf->bar_state.backing_store); - seq_printf(s, " Backing Size: 0x%zx\n", vf->bar_state.backing_size); - - return 0; -} - -static int gsp_debugfs_bar_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_bar_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_bar_fops = { - .open = gsp_debugfs_bar_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int gsp_debugfs_wpr_show(struct seq_file *s, void *unused) -{ - struct gsp_vf *vf = s->private; - struct gsp_wpr_region *rgn; - - seq_printf(s, "VF %u WPR Regions:\n", vf->id); - - mutex_lock(&vf->wpr_lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - seq_printf(s, " [0x%llx, 0x%llx) size=0x%llx flags=0x%x\n", - rgn->base, rgn->base + rgn->size, rgn->size, rgn->flags); - } - mutex_unlock(&vf->wpr_lock); - - return 0; -} - -static int gsp_debugfs_wpr_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_wpr_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_wpr_fops = { - .open = gsp_debugfs_wpr_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -static int gsp_debugfs_migration_show(struct seq_file *s, void *unused) -{ - struct gsp_vf *vf = s->private; - - if (!vf->migration) { - seq_printf(s, "No active migration\n"); - return 0; - } - - seq_printf(s, "VF %u Migration:\n", vf->id); - seq_printf(s, " State: %d\n", vf->migration->state); - seq_printf(s, " Destination VF: %llu\n", vf->migration->dst_vf); - seq_printf(s, " Token: 0x%llx\n", vf->migration->token); - seq_printf(s, " Flags: 0x%x\n", vf->migration->flags); - seq_printf(s, " Pages Xfer: %llu\n", vf->migration->progress.pages_transferred); - seq_printf(s, " Bytes Xfer: %llu\n", vf->migration->progress.bytes_transferred); - seq_printf(s, " Dirty Pages: %llu\n", vf->migration->progress.dirty_pages); - seq_printf(s, " Cancelled: %s\n", vf->migration->cancelled ? "yes" : "no"); - - return 0; -} - -static int gsp_debugfs_migration_open(struct inode *inode, struct file *file) -{ - return single_open(file, gsp_debugfs_migration_show, inode->i_private); -} - -static const struct file_operations gsp_debugfs_migration_fops = { - .open = gsp_debugfs_migration_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -/* VF Debugfs Directory Creation */ -static void gsp_debugfs_vf_create(struct gsp_vf *vf) -{ - char name[16]; - struct dentry *vf_dir; - - snprintf(name, sizeof(name), "vf%u", vf->id); - vf_dir = debugfs_create_dir(name, vf->gsp->debugfs_vfs_dir); - if (!vf_dir) - return; - - debugfs_create_file("status", 0444, vf_dir, vf, &gsp_debugfs_vf_fops); - debugfs_create_file("bar", 0444, vf_dir, vf, &gsp_debugfs_bar_fops); - debugfs_create_file("wpr", 0444, vf_dir, vf, &gsp_debugfs_wpr_fops); - debugfs_create_file("migration", 0444, vf_dir, vf, &gsp_debugfs_migration_fops); -} - -/* Main Debugfs Init */ -int gsp_debugfs_init(struct gsp_device *gsp) -{ - gsp->debugfs_root = debugfs_create_dir("gsp-vgpu", NULL); - if (!gsp->debugfs_root) - return -ENOMEM; - - debugfs_create_file("vfs", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_vfs_fops); - debugfs_create_file("rpc", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_rpc_fops); - - gsp->debugfs_vfs_dir = debugfs_create_dir("vfs", gsp->debugfs_root); - if (!gsp->debugfs_vfs_dir) { - debugfs_remove_recursive(gsp->debugfs_root); - return -ENOMEM; - } - - /* Create entries for existing VFs */ - mutex_lock(&gsp->vf_lock); - struct gsp_vf *vf; - list_for_each_entry(vf, &gsp->vf_list, list) - gsp_debugfs_vf_create(vf); - mutex_unlock(&gsp->vf_lock); - - return 0; -} - -void gsp_debugfs_fini(struct gsp_device *gsp) -{ - if (gsp->debugfs_root) - debugfs_remove_recursive(gsp->debugfs_root); -} - -/* Export for VF creation/destruction */ -void gsp_debugfs_vf_add(struct gsp_vf *vf) -{ - gsp_debugfs_vf_create(vf); -} - -void gsp_debugfs_vf_remove(struct gsp_vf *vf) -{ - char name[16]; - snprintf(name, sizeof(name), "vf%u", vf->id); - debugfs_remove_recursive(debugfs_lookup(name, vf->gsp->debugfs_vfs_dir)); -} \ No newline at end of file diff --git a/driver/include/gsp_core.h b/driver/include/gsp_core.h deleted file mode 100644 index 569d6f8..0000000 --- a/driver/include/gsp_core.h +++ /dev/null @@ -1,613 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - Core Definitions - * NVIDIA GSP 570.144W (TU102/TU104) Firmware Interface - */ - -#ifndef _GSP_CORE_H_ -#define _GSP_CORE_H_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* Firmware Constants */ -#define GSP_FW_BASE_ADDR 0x04000000ULL -#define GSP_FW_SIZE 0x02000000ULL -#define GSP_ENTRY_POINT 0x04009000ULL -#define GSP_PRIMARY_DISPATCHER 0x057A1AC0ULL -#define GSP_SECONDARY_DISPATCHER 0x04ED39DCULL -#define GSP_RPC_JUMP_TABLE 0x059BFEB0ULL - -/* PCIe BAR Offsets */ -#define GSP_BAR0_FALCON_ENGINE 0x1103c0 -#define GSP_BAR0_FALCON_MAILBOX0 0x110040 -#define GSP_BAR0_FALCON_MAILBOX1 0x110044 -#define GSP_BAR0_MAILBOX(i) (0x110804 + (i) * 4) -#define GSP_BAR0_QUEUE_HEAD(i) (0x110c00 + (i) * 8) - -/* IOCTL definitions (must match userspace libvgpu.h) */ -#define GSP_IOCTL_MAGIC 'G' -#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config) -#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t) -#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args) -#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args) -#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args) -#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args) -#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args) -#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args) -#define GSP_IOCTL_BAR_CONFIG _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_config_args) -#define GSP_IOCTL_BAR_FORCE _IOW(GSP_IOCTL_MAGIC, 0x17, struct gsp_bar_force_args) -#define GSP_IOCTL_BAR_SAVE _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_bar_save_args) -#define GSP_IOCTL_BAR_RESTORE _IOWR(GSP_IOCTL_MAGIC, 0x19, struct gsp_bar_restore_args) -#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x1A, struct gsp_wpr_args) -#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x1B, struct gsp_wpr_args) -#define GSP_IOCTL_WPR_TIMEOUT _IOW(GSP_IOCTL_MAGIC, 0x1C, struct gsp_wpr_timeout_args) -#define GSP_IOCTL_SCHEDULE _IOW(GSP_IOCTL_MAGIC, 0x1D, struct gsp_schedule_args) -#define GSP_IOCTL_YIELD _IOW(GSP_IOCTL_MAGIC, 0x1E, uint32_t) -#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x1F, struct gsp_migrate_args) -#define GSP_IOCTL_GET_INFO _IOR(GSP_IOCTL_MAGIC, 0x20, char[256]) - -/* Kernel argument structures (matching userspace) */ -struct gsp_vf_config { - uint32_t id; - uint64_t bar1_offset; - uint64_t bar1_size; - uint64_t pt_base; - uint64_t pt_size; - uint64_t cmd_base; - uint64_t cmd_size; - uint32_t max_memory_mb; - uint32_t num_engines; -}; - -struct gsp_alloc_args { - uint32_t vf_id; - uint64_t size; - uint32_t type; - uint64_t alignment; - uint64_t va_hint; - uint32_t flags; - uint32_t pid; - uint64_t handle; - uint64_t va; - uint64_t actual_size; -}; - -struct gsp_free_args { - uint32_t vf_id; - uint64_t handle; -}; - -struct gsp_map_args { - uint32_t vf_id; - uint64_t handle; - uint64_t gpu_va; - uint64_t size; - uint32_t prot; - uint32_t flags; - uint64_t offset; - uint64_t mapped_va; -}; - -struct gsp_unmap_args { - uint32_t vf_id; - uint64_t handle; - uint64_t gpu_va; - uint64_t size; -}; - -struct gsp_pin_args { - uint32_t vf_id; - uint64_t handle; - uint64_t offset; - uint64_t size; - uint32_t flags; - uint64_t dma_addr; - uint32_t num_pages; -}; - -struct gsp_unpin_args { - uint32_t vf_id; - uint64_t handle; - uint64_t offset; - uint64_t size; -}; - -struct gsp_bar_config_args { - uint32_t vf_id; - uint64_t aperture_offset; - uint64_t aperture_size; -}; - -struct gsp_bar_force_args { - uint32_t vf_id; - uint32_t enable; -}; - -struct gsp_bar_save_args { - uint32_t vf_id; - void *buf; - size_t *size; -}; - -struct gsp_bar_restore_args { - uint32_t vf_id; - void *buf; - size_t size; -}; - -struct gsp_wpr_args { - uint32_t vf_id; - uint64_t base; - uint64_t size; - uint32_t flags; -}; - -struct gsp_wpr_timeout_args { - uint32_t vf_id; - uint64_t timeout_ns; -}; - -struct gsp_schedule_args { - uint32_t vf_id; - uint32_t priority; - uint64_t time_slice_ns; -}; - -struct gsp_migrate_args { - uint32_t vf_id; - uint64_t dst_vf; - uint32_t phase; - uint32_t flags; - uint64_t token; -}; - -/* Message Ring */ -#define GSP_MSG_RING_SIZE 256 -#define GSP_MSG_MAX_SIZE 256 -#define GSP_MAX_PENDING 64 - -/* RPC Types */ -enum gsp_rpc_type { - GSP_RPC_ALLOC = 0x01, - GSP_RPC_FREE = 0x02, - GSP_RPC_MAP = 0x03, - GSP_RPC_UNMAP = 0x04, - GSP_RPC_PIN = 0x05, - GSP_RPC_UNPIN = 0x06, - GSP_RPC_SCHEDULE = 0x07, - GSP_RPC_YIELD = 0x08, - GSP_RPC_NOTIFY = 0x09, - GSP_RPC_BAR_TRAP = 0x0A, - GSP_RPC_MIGRATE = 0x0B, - GSP_RPC_DEBUG = 0x0C, -}; - -/* RPC Message Flags */ -#define GSP_MSG_SYNC (1 << 0) -#define GSP_MSG_ASYNC (1 << 1) -#define GSP_MSG_NOREPLY (1 << 2) - -/* Memory Types */ -enum gsp_mem_type { - GSP_MEM_VRAM = 0, - GSP_MEM_SYSMEM = 1, - GSP_MEM_UNCACHED = 2, -}; - -/* Pin Flags */ -#define GSP_PIN_READ (1 << 0) -#define GSP_PIN_WRITE (1 << 1) -#define GSP_PIN_LONGTERM (1 << 2) - -/* BAR Trap Types */ -#define GSP_BAR_READ 0 -#define GSP_BAR_WRITE 1 -#define GSP_BAR_ATOMIC 2 - -/* Migration Phases */ -enum gsp_migrate_phase { - GSP_MIG_QUERY = 0, - GSP_MIG_PREPARE = 1, - GSP_MIG_TRANSFER = 2, - GSP_MIG_COMMIT = 3, - GSP_MIG_CLEANUP = 4, -}; - -/* Migration Flags */ -#define GSP_MIG_LIVE (1 << 0) -#define GSP_MIG_POSTCOPY (1 << 1) -#define GSP_MIG_AUTO_COMMIT (1 << 2) -#define GSP_MIG_VERIFY (1 << 3) - -/* Error Codes (from firmware) */ -#define GSP_OK 0 -#define GSP_EPERM 0xFFFFFFFF -#define GSP_ENOENT 0xFFFFFFFE -#define GSP_EINVAL 0xFFFFFFFD -#define GSP_ENODEV 0xFFFFFFFA -#define GSP_ENOMEM 0xFFFFFFF4 -#define GSP_EBUSY 0xFFFFFFEE -#define GSP_ENOSPC 0xFFFFFFE4 -#define GSP_ETIMEDOUT 0x0000001E -#define GSP_EOVERFLOW 0x00000025 -#define GSP_EDEV 0x0000001F - -/* Forward Declarations */ -struct gsp_device; -struct gsp_vf; -struct gsp_rpc_client; -struct gsp_migration; -struct gsp_wpr_region; - -/* WPR Region */ -struct gsp_wpr_region { - struct list_head list; - uint64_t base; - uint64_t size; - uint32_t flags; - uint32_t vf_id; - void (*completion_cb)(struct gsp_vf *, uint64_t, uint32_t); - void (*timeout_cb)(struct gsp_vf *, uint64_t, uint32_t); - uint64_t timeout_ns; -}; - -/* WPR Flags */ -#define GSP_WPR_WRITE (1 << 0) -#define GSP_WPR_READ (1 << 1) -#define GSP_WPR_EXECUTE (1 << 2) - -/* WPR Event Types */ -enum gsp_wpr_event { - GSP_WPR_WRITE_COMPLETE = 0, - GSP_WPR_READ_COMPLETE = 1, - GSP_WPR_WRITE_TIMEOUT = 2, - GSP_WPR_READ_TIMEOUT = 3, -}; - -/* Migration State */ -enum gsp_mig_state { - GSP_MIG_S_IDLE = 0, - GSP_MIG_S_QUERY = 1, - GSP_MIG_S_PREPARE = 2, - GSP_MIG_S_TRANSFER = 3, - GSP_MIG_S_COMMIT = 4, - GSP_MIG_S_CLEANUP = 5, - GSP_MIG_S_FAILED = 6, - GSP_MIG_S_COMPLETED = 7, -}; - -/* Migration Context */ -struct gsp_migration { - struct gsp_vf *vf; - struct mutex lock; - enum gsp_mig_state state; - uint64_t token; - uint64_t dst_vf; - uint32_t flags; - - struct { - uint64_t pages_transferred; - uint64_t bytes_transferred; - uint64_t dirty_pages; - uint64_t *page_list; - uint32_t page_count; - } progress; - - struct { - void *buf; - size_t size; - } bar_state; - - struct task_struct *thread; - struct completion comp; - bool cancelled; - int error; -}; - -/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */ -extern bool emulation_mode; - -/* RPC Message Header (64 bytes) */ -struct gsp_rpc_header { - uint32_t magic; /* 0x47535052 "GSPR" */ - uint32_t version; /* Protocol version */ - uint32_t rpc_type; /* enum gsp_rpc_type */ - uint32_t sequence; /* Monotonic sequence */ - uint64_t timestamp_ns; /* Submission timestamp */ - uint32_t payload_size; /* Payload size in bytes */ - uint32_t flags; /* GSP_MSG_* */ - uint64_t correlation_id; /* Async matching */ - uint64_t reserved[3]; /* Padding to 64 bytes */ -} __attribute__((packed)); - -/* Complete RPC Message */ -struct gsp_rpc_message { - struct gsp_rpc_header hdr; - uint8_t payload[GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)]; -} __attribute__((packed)); - -/* RPC Response Header */ -struct gsp_rpc_response { - int64_t status; /* 0 = success, negative = error */ - uint32_t rpc_type; /* Echo of request */ - uint32_t sequence; /* Echo of request */ - uint64_t timestamp_ns; /* Completion timestamp */ - uint32_t payload_size; /* Response payload size */ - uint32_t reserved; - uint8_t payload[]; -} __attribute__((packed)); - -/* Primary Dispatcher Sub-Command Descriptor */ -struct gsp_subcmd_desc { - uint32_t subcmd_id; /* Constant ID (e.g., 0x10, 0x14, 0x40) */ - uint8_t echo_flags_b; /* Copy of input flags_b */ - uint8_t echo_flags_a; /* Copy of input flags_a */ - uint8_t echo_flags_c; /* Copy of input flags_c */ - uint8_t reserved; -} __attribute__((packed)); - -/* ALLOC Payload */ -struct gsp_alloc_payload { - uint64_t size; - uint32_t type; /* enum gsp_mem_type */ - uint32_t alignment; - uint64_t va_hint; - uint32_t flags; - uint32_t pid; - uint32_t vf_id; - uint32_t reserved; -} __attribute__((packed)); - -struct gsp_alloc_response { - uint64_t handle; - uint64_t va; - uint64_t pa; -} __attribute__((packed)); - -/* PIN Payload */ -struct gsp_pin_payload { - uint64_t handle; - uint64_t offset; - uint64_t size; - uint32_t vf_id; - uint32_t flags; /* GSP_PIN_* */ - uint32_t reserved[2]; -} __attribute__((packed)); - -struct gsp_pin_response { - uint64_t dma_addr; -} __attribute__((packed)); - -/* BAR Trap Payload */ -struct gsp_bar_trap_payload { - uint32_t bar_index; - uint32_t trap_type; /* GSP_BAR_* */ - uint64_t offset; - uint64_t value; - uint32_t size; - uint32_t vf_id; - uint32_t reserved; -} __attribute__((packed)); - -struct gsp_bar_trap_response { - uint64_t value; -} __attribute__((packed)); - -/* Migration Payload */ -struct gsp_migrate_payload { - uint32_t phase; /* enum gsp_migrate_phase */ - uint32_t subcmd; /* Sub-command index */ - uint64_t src_vf; - uint64_t dst_vf; - uint64_t data_ptr; - uint64_t data_size; - uint32_t flags; /* GSP_MIG_* */ - uint32_t reserved; - uint64_t token; -} __attribute__((packed)); - -/* Message Ring Buffer */ -struct gsp_msg_ring { - void *base; - size_t size; - uint32_t head; - uint32_t tail; - spinlock_t lock; -}; - -/* RPC Client */ -struct gsp_rpc_client { - struct gsp_device *gsp; - struct mutex lock; - atomic_t seq; - wait_queue_head_t wq; - struct gsp_msg_ring *tx_ring; - struct gsp_msg_ring *rx_ring; - dma_addr_t tx_ring_dma; - dma_addr_t rx_ring_dma; - struct completion *pending[GSP_MAX_PENDING]; - struct gsp_rpc_response *resp_buf[GSP_MAX_PENDING]; -}; - -/* BAR State per VF */ -struct gsp_bar_state { - uint64_t aperture_offset; - uint64_t aperture_size; - bool force_access; - uint32_t force_vf; - void *backing_store; - size_t backing_size; -}; - -/* VF State */ -enum gsp_vf_state { - GSP_VF_CREATING = 0, - GSP_VF_ACTIVE, - GSP_VF_MIGRATING, - GSP_VF_SUSPENDED, - GSP_VF_DESTROYING, -}; - -/* GSP Virtual Function */ -struct gsp_vf { - struct gsp_device *gsp; - uint32_t id; - struct kref ref; - struct mutex lock; - enum gsp_vf_state state; - struct gsp_migration *migration; - - /* Resource configuration */ - uint64_t bar1_offset; - uint64_t bar1_size; - uint64_t pt_base; - uint64_t pt_size; - uint64_t cmd_base; - uint64_t cmd_size; - - /* WPR regions */ - struct list_head wpr_list; - struct mutex wpr_lock; - - /* BAR state */ - struct gsp_bar_state bar_state; - - atomic_t refcount; - bool active; - struct list_head list; -}; - -/* GSP Device */ -struct gsp_device { - struct pci_dev *pdev; - struct device *dev; - void __iomem *bar0; - void __iomem *bar1; - - /* Firmware */ - const struct firmware *fw; - bool fw_loaded; - - /* RPC */ - struct gsp_rpc_client *rpc; - - /* VF Management */ - struct mutex vf_lock; - struct idr vf_idr; - struct list_head vf_list; - uint32_t max_vfs; - - /* IRQ */ - int irq; - bool msix_enabled; - struct msix_entry msix_entries[1]; - - /* Debug */ - struct dentry *debugfs_root; - struct dentry *debugfs_vfs_dir; - - /* Char device */ - struct miscdevice miscdev; - struct mutex chardev_lock; - - /* Statistics */ - atomic64_t rpc_total; - atomic64_t rpc_errors; - atomic64_t bar_traps; - atomic64_t wpr_events; - atomic64_t migrations; -}; - -/* Function Prototypes */ - -/* Core */ -int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id); -void gsp_remove(struct pci_dev *pdev); -int gsp_firmware_load(struct gsp_device *gsp); -int gsp_rpc_init(struct gsp_device *gsp); -void gsp_shutdown(struct gsp_device *gsp); - -/* RPC Client */ -struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp); -void gsp_rpc_client_fini(struct gsp_rpc_client *rpc); -int gsp_rpc_send_sync(struct gsp_rpc_client *rpc, - enum gsp_rpc_type type, - void *payload, size_t payload_size, - void *response, size_t *response_size); -int gsp_rpc_send_async(struct gsp_rpc_client *rpc, - enum gsp_rpc_type type, - void *payload, size_t payload_size, - void (*callback)(struct gsp_rpc_client *, - struct gsp_rpc_response *, - void *), - void *ctx); -irqreturn_t gsp_isr(int irq, void *data); -void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg); -int gsp_errno_to_errno(int gsp_err); - -/* VF Management */ -int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config); -void gsp_vf_destroy(struct gsp_vf *vf); -struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id); -void gsp_vf_put(struct gsp_vf *vf); - -/* Memory Management */ -int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags, - uint64_t *handle, uint64_t *va); -int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle); -int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle, - uint64_t offset, uint64_t size, - uint64_t *dma_addr); -int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle); -int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, - uint64_t size, uint32_t prot, uint32_t flags, - uint64_t offset, uint64_t *mapped_va); -int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size); - -/* BAR Emulation */ -int gsp_vf_bar_init(struct gsp_vf *vf); -void gsp_vf_bar_fini(struct gsp_vf *vf); -int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap); -int gsp_bar_force_access(struct gsp_vf *vf, bool enable); -int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size); -int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size); - -/* WPR Management */ -int gsp_vf_wpr_init(struct gsp_vf *vf); -void gsp_vf_wpr_fini(struct gsp_vf *vf); -int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size, - uint32_t flags); -int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base); -int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size); -int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size); -int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns); -int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type, - void (*cb)(struct gsp_vf *, uint64_t, uint32_t), - void *ctx); -void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg); - -/* Scheduling */ -int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns); -int gsp_vf_yield(struct gsp_vf *vf); - -/* Migration */ -int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf); -void gsp_migration_cancel(struct gsp_migration *mig); -int gsp_migration_status(struct gsp_migration *mig); - -/* Debugfs */ -int gsp_debugfs_init(struct gsp_device *gsp); -void gsp_debugfs_fini(struct gsp_device *gsp); -void gsp_debugfs_vf_add(struct gsp_vf *vf); -void gsp_debugfs_vf_remove(struct gsp_vf *vf); - -#endif /* _GSP_CORE_H_ */ \ No newline at end of file diff --git a/driver/migration/gsp_migration.c b/driver/migration/gsp_migration.c deleted file mode 100644 index 52d1931..0000000 --- a/driver/migration/gsp_migration.c +++ /dev/null @@ -1,339 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - Live Migration - */ - -#include "gsp_core.h" -#include -#include -#include -#include - -/* Forward declarations */ -static int gsp_migration_prepare(struct gsp_migration *mig); -static int gsp_migration_transfer_iter(struct gsp_migration *mig); -static int gsp_migration_commit(struct gsp_migration *mig); -static void gsp_migration_cleanup(struct gsp_migration *mig); - -/* Migration Thread */ -static int gsp_migration_thread(void *data) -{ - struct gsp_migration *mig = data; - int ret = 0; - - while (!kthread_should_stop() && !mig->cancelled) { - mutex_lock(&mig->lock); - - switch (mig->state) { - case GSP_MIG_S_PREPARE: - ret = gsp_migration_prepare(mig); - if (!ret) - mig->state = GSP_MIG_S_TRANSFER; - else - mig->state = GSP_MIG_S_FAILED; - break; - - case GSP_MIG_S_TRANSFER: - ret = gsp_migration_transfer_iter(mig); - if (ret > 0) { /* Transfer complete */ - mig->state = GSP_MIG_S_COMMIT; - } else if (ret < 0) { - mig->state = GSP_MIG_S_FAILED; - } - break; - - case GSP_MIG_S_COMMIT: - ret = gsp_migration_commit(mig); - if (!ret) - mig->state = GSP_MIG_S_CLEANUP; - else - mig->state = GSP_MIG_S_FAILED; - break; - - case GSP_MIG_S_CLEANUP: - gsp_migration_cleanup(mig); - mig->state = GSP_MIG_S_COMPLETED; - complete(&mig->comp); - mutex_unlock(&mig->lock); - return 0; - - case GSP_MIG_S_FAILED: - mig->error = ret; - complete(&mig->comp); - mutex_unlock(&mig->lock); - return ret; - - default: - break; - } - - mutex_unlock(&mig->lock); - - if (mig->state == GSP_MIG_S_TRANSFER) - msleep(10); /* Throttle transfer iterations */ - } - - return 0; -} - -/* Migration Query */ -static int gsp_migration_query(struct gsp_vf *vf, bool *migratable) -{ - struct gsp_migrate_payload payload = { - .phase = GSP_MIG_QUERY, - .subcmd = 0, - .src_vf = vf->id, - .dst_vf = 0, - }; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - *migratable = (desc.subcmd_id != 0); - return 0; -} - -/* Phase 1: Prepare */ -static int gsp_migration_prepare(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - /* Quiesce VF (subcmd 3 -> returns ID 0x10) */ - payload.phase = GSP_MIG_PREPARE; - payload.subcmd = 3; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.flags = mig->flags; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - mig->token = desc.subcmd_id; - - /* Snapshot BAR state */ - ret = gsp_migration_bar_save(vf, mig->bar_state.buf, &mig->bar_state.size); - if (ret) - return ret; - - /* Snapshot WPR regions */ - /* TODO: WPR snapshot via migration RPC */ - - dev_info(vf->gsp->dev, "VF %u: Migration prepare done, token=0x%llx\n", - vf->id, mig->token); - - return 0; -} - -/* Phase 2: Transfer (Iterative) */ -static int gsp_migration_transfer_iter(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - if (!mig->progress.page_list || mig->progress.page_count == 0) - return 1; /* Nothing to transfer */ - - payload.phase = GSP_MIG_TRANSFER; - payload.subcmd = 0; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.data_ptr = (uint64_t)mig->progress.page_list; - payload.data_size = mig->progress.page_count * sizeof(uint64_t); - payload.token = mig->token; - payload.flags = mig->flags; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - /* Update progress from response */ - /* Note: Actual progress parsing depends on firmware response format */ - mig->progress.pages_transferred += 100; /* Placeholder */ - mig->progress.bytes_transferred += 100 * 4096; - - /* Check for dirty pages */ - if (mig->progress.dirty_pages > 0 && !(mig->flags & GSP_MIG_POSTCOPY)) - return 0; /* Continue iterating */ - - return 1; /* Transfer complete */ -} - -/* Phase 3: Commit */ -static int gsp_migration_commit(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - struct gsp_migrate_payload payload = {0}; - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - /* Final dirty page transfer */ - if (mig->progress.dirty_pages > 0) { - payload.phase = GSP_MIG_TRANSFER; - payload.subcmd = 11; /* Dirty tracking */ - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.token = mig->token; - payload.flags = GSP_MIG_POSTCOPY; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - } - - /* Commit (subcmd 12) */ - payload.phase = GSP_MIG_COMMIT; - payload.subcmd = 12; - payload.src_vf = vf->id; - payload.dst_vf = mig->dst_vf; - payload.token = mig->token; - payload.flags = mig->flags | GSP_MIG_VERIFY; - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, - &payload, sizeof(payload), &desc, &desc_size); - if (ret) - return ret; - - /* Restore BAR on destination */ - ret = gsp_migration_bar_restore(vf, mig->bar_state.buf, mig->bar_state.size); - if (ret) - return ret; - - /* Restore WPR on destination */ - /* TODO: WPR restore */ - - dev_info(vf->gsp->dev, "VF %u: Migration commit done\n", vf->id); - - return 0; -} - -/* Phase 4: Cleanup */ -static void gsp_migration_cleanup(struct gsp_migration *mig) -{ - struct gsp_vf *vf = mig->vf; - - /* Free page list */ - kfree(mig->progress.page_list); - mig->progress.page_list = NULL; - mig->progress.page_count = 0; - - /* Free BAR state buffer */ - kfree(mig->bar_state.buf); - mig->bar_state.buf = NULL; - mig->bar_state.size = 0; - - dev_info(vf->gsp->dev, "VF %u: Migration cleanup done\n", vf->id); -} - -/* Public Migration API */ -int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf) -{ - struct gsp_migration *mig; - bool migratable; - int ret; - - if (vf->migration) - return -EBUSY; - - /* Query migratability */ - ret = gsp_migration_query(vf, &migratable); - if (ret || !migratable) - return ret ? ret : -EINVAL; - - mig = kzalloc(sizeof(*mig), GFP_KERNEL); - if (!mig) - return -ENOMEM; - - mig->vf = vf; - mig->dst_vf = dst_vf; - mig->flags = GSP_MIG_LIVE | GSP_MIG_VERIFY; - mig->state = GSP_MIG_S_PREPARE; - mutex_init(&mig->lock); - init_completion(&mig->comp); - - /* Allocate page list (placeholder) */ - mig->progress.page_count = vf->pt_size / 4096; - mig->progress.page_list = kcalloc(mig->progress.page_count, sizeof(uint64_t), GFP_KERNEL); - if (!mig->progress.page_list) { - kfree(mig); - return -ENOMEM; - } - - /* Allocate BAR state buffer */ - mig->bar_state.buf = kmalloc(65536, GFP_KERNEL); /* 64KB max */ - if (!mig->bar_state.buf) { - kfree(mig->progress.page_list); - kfree(mig); - return -ENOMEM; - } - - vf->migration = mig; - - /* Start migration thread */ - mig->thread = kthread_run(gsp_migration_thread, mig, "gsp-mig-%u", vf->id); - if (IS_ERR(mig->thread)) { - ret = PTR_ERR(mig->thread); - mig->thread = NULL; - goto err_free; - } - - return 0; - -err_free: - kfree(mig->progress.page_list); - kfree(mig->bar_state.buf); - kfree(mig); - vf->migration = NULL; - return ret; -} - -void gsp_migration_cancel(struct gsp_migration *mig) -{ - if (!mig) - return; - - mutex_lock(&mig->lock); - mig->cancelled = true; - mig->state = GSP_MIG_S_FAILED; - mutex_unlock(&mig->lock); - - if (mig->thread) - kthread_stop(mig->thread); - - wait_for_completion(&mig->comp); - - gsp_migration_cleanup(mig); - kfree(mig); -} - -int gsp_migration_status(struct gsp_migration *mig) -{ - if (!mig) - return -EINVAL; - - mutex_lock(&mig->lock); - int state = mig->state; - int error = mig->error; - mutex_unlock(&mig->lock); - - if (state == GSP_MIG_S_FAILED) - return error; - - return state; -} \ No newline at end of file diff --git a/driver/rpc/gsp_rpc.c b/driver/rpc/gsp_rpc.c deleted file mode 100644 index 6bdcdb2..0000000 --- a/driver/rpc/gsp_rpc.c +++ /dev/null @@ -1,331 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - RPC Client Implementation - */ - -#include "gsp_core.h" -#include -#include -#include -#include - -/* Ring Buffer Operations */ -static int gsp_ring_init(struct gsp_msg_ring *ring, void *base, size_t size) -{ - ring->base = base; - ring->size = size; - ring->head = 0; - ring->tail = 0; - spin_lock_init(&ring->lock); - return 0; -} - -static bool gsp_ring_empty(struct gsp_msg_ring *ring) -{ - return ring->head == ring->tail; -} - -static bool gsp_ring_full(struct gsp_msg_ring *ring) -{ - return ((ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE)) == ring->tail; -} - -static int gsp_ring_write(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg, size_t len) -{ - unsigned long flags; - uint32_t next_head; - void *dst; - - if (len > GSP_MSG_MAX_SIZE) - return -EINVAL; - - spin_lock_irqsave(&ring->lock, flags); - - if (gsp_ring_full(ring)) { - spin_unlock_irqrestore(&ring->lock, flags); - return -EBUSY; - } - - dst = ring->base + (ring->head * GSP_MSG_MAX_SIZE); - memcpy(dst, msg, len); - - next_head = (ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE); - ring->head = next_head; - - spin_unlock_irqrestore(&ring->lock, flags); - return 0; -} - -static int gsp_ring_read(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg) -{ - unsigned long flags; - void *src; - - spin_lock_irqsave(&ring->lock, flags); - - if (gsp_ring_empty(ring)) { - spin_unlock_irqrestore(&ring->lock, flags); - return 0; - } - - src = ring->base + (ring->tail * GSP_MSG_MAX_SIZE); - memcpy(msg, src, GSP_MSG_MAX_SIZE); - - ring->tail = (ring->tail + 1) % (ring->size / GSP_MSG_MAX_SIZE); - - spin_unlock_irqrestore(&ring->lock, flags); - return 1; -} - -/* RPC Client Initialization */ -struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp) -{ - struct gsp_rpc_client *rpc; - void *ring_mem; - - rpc = kzalloc(sizeof(*rpc), GFP_KERNEL); - if (!rpc) - return ERR_PTR(-ENOMEM); - - rpc->gsp = gsp; - mutex_init(&rpc->lock); - atomic_set(&rpc->seq, 0); - init_waitqueue_head(&rpc->wq); - - /* Allocate shared memory for message rings (2 pages each) */ - ring_mem = dma_alloc_coherent(gsp->dev, 8 * PAGE_SIZE, - &rpc->tx_ring_dma, GFP_KERNEL); - if (!ring_mem) { - kfree(rpc); - return ERR_PTR(-ENOMEM); - } - - rpc->tx_ring = kzalloc(sizeof(*rpc->tx_ring), GFP_KERNEL); - rpc->rx_ring = kzalloc(sizeof(*rpc->rx_ring), GFP_KERNEL); - if (!rpc->tx_ring || !rpc->rx_ring) { - dma_free_coherent(gsp->dev, 8 * PAGE_SIZE, ring_mem, rpc->tx_ring_dma); - kfree(rpc->tx_ring); - kfree(rpc->rx_ring); - kfree(rpc); - return ERR_PTR(-ENOMEM); - } - - gsp_ring_init(rpc->tx_ring, ring_mem, 4 * PAGE_SIZE); - gsp_ring_init(rpc->rx_ring, ring_mem + 4 * PAGE_SIZE, 4 * PAGE_SIZE); - - /* Map rings to GSP via queue head registers (BAR0) */ - writel(rpc->tx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); - writel(rpc->tx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0) + 4); - writel(rpc->rx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1)); - writel(rpc->rx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1) + 4); - - /* Allocate response buffers */ - for (int i = 0; i < GSP_MAX_PENDING; i++) { - rpc->resp_buf[i] = kzalloc(GSP_MSG_MAX_SIZE, GFP_KERNEL); - if (!rpc->resp_buf[i]) { - /* Cleanup on failure */ - for (int j = 0; j < i; j++) - kfree(rpc->resp_buf[j]); - gsp_rpc_client_fini(rpc); - return ERR_PTR(-ENOMEM); - } - } - - dev_info(gsp->dev, "RPC client initialized\n"); - return rpc; -} - -void gsp_rpc_client_fini(struct gsp_rpc_client *rpc) -{ - if (!rpc) - return; - - for (int i = 0; i < GSP_MAX_PENDING; i++) - kfree(rpc->resp_buf[i]); - - if (rpc->tx_ring) { - dma_free_coherent(rpc->gsp->dev, 8 * PAGE_SIZE, - rpc->tx_ring->base, rpc->tx_ring_dma); - kfree(rpc->tx_ring); - } - kfree(rpc->rx_ring); - kfree(rpc); -} - -/* Synchronous RPC Send */ -int gsp_rpc_send_sync(struct gsp_rpc_client *rpc, - enum gsp_rpc_type type, - void *payload, size_t payload_size, - void *response, size_t *response_size) -{ - struct gsp_rpc_message msg = {0}; - struct completion done; - uint32_t seq; - int ret; - - if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)) - return -EINVAL; - - seq = atomic_inc_return(&rpc->seq); - init_completion(&done); - - msg.hdr.magic = 0x47535052; /* "GSPR" */ - msg.hdr.version = 1; - msg.hdr.rpc_type = type; - msg.hdr.sequence = seq; - msg.hdr.timestamp_ns = ktime_get_ns(); - msg.hdr.payload_size = payload_size; - msg.hdr.flags = GSP_MSG_SYNC; - msg.hdr.correlation_id = seq; - - if (payload && payload_size > 0) - memcpy(msg.payload, payload, payload_size); - - mutex_lock(&rpc->lock); - - rpc->pending[seq % GSP_MAX_PENDING] = &done; - - ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE); - if (ret) { - rpc->pending[seq % GSP_MAX_PENDING] = NULL; - mutex_unlock(&rpc->lock); - return ret; - } - - /* Ring doorbell - write to queue head to signal GSP */ - writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); - - mutex_unlock(&rpc->lock); - - /* Wait for completion (5 second timeout) */ - if (!wait_for_completion_timeout(&done, msecs_to_jiffies(5000))) { - mutex_lock(&rpc->lock); - rpc->pending[seq % GSP_MAX_PENDING] = NULL; - mutex_unlock(&rpc->lock); - atomic64_inc(&rpc->gsp->rpc_errors); - return -ETIMEDOUT; - } - - /* Get response */ - struct gsp_rpc_response *resp = rpc->resp_buf[seq % GSP_MAX_PENDING]; - - atomic64_inc(&rpc->gsp->rpc_total); - - if (resp->status != 0) { - atomic64_inc(&rpc->gsp->rpc_errors); - return gsp_errno_to_errno(resp->status); - } - - if (response && response_size) { - size_t copy_size = min(*response_size, (size_t)resp->payload_size); - memcpy(response, resp->payload, copy_size); - *response_size = copy_size; - } - - return 0; -} - -/* Async RPC Send */ -int gsp_rpc_send_async(struct gsp_rpc_client *rpc, - enum gsp_rpc_type type, - void *payload, size_t payload_size, - void (*callback)(struct gsp_rpc_client *, - struct gsp_rpc_response *, - void *), - void *ctx) -{ - struct gsp_rpc_message msg = {0}; - uint32_t seq; - int ret; - - if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)) - return -EINVAL; - - seq = atomic_inc_return(&rpc->seq); - - msg.hdr.magic = 0x47535052; - msg.hdr.version = 1; - msg.hdr.rpc_type = type; - msg.hdr.sequence = seq; - msg.hdr.timestamp_ns = ktime_get_ns(); - msg.hdr.payload_size = payload_size; - msg.hdr.flags = GSP_MSG_ASYNC; - msg.hdr.correlation_id = (uint64_t)callback | ((uint64_t)ctx << 32); - - if (payload && payload_size > 0) - memcpy(msg.payload, payload, payload_size); - - mutex_lock(&rpc->lock); - ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE); - if (!ret) - writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); - mutex_unlock(&rpc->lock); - - return ret; -} - -/* ISR - Handle Completions */ -irqreturn_t gsp_isr(int irq, void *data) -{ - struct gsp_device *gsp = data; - struct gsp_rpc_message msg; - int handled = 0; - - while (gsp_ring_read(gsp->rpc->rx_ring, &msg)) { - handled = 1; - - if (msg.hdr.flags & GSP_MSG_SYNC) { - uint32_t seq = msg.hdr.sequence; - struct completion *done = gsp->rpc->pending[seq % GSP_MAX_PENDING]; - struct gsp_rpc_response *resp = gsp->rpc->resp_buf[seq % GSP_MAX_PENDING]; - - if (done && resp) { - resp->status = ((struct gsp_rpc_response *)msg.payload)->status; - resp->payload_size = msg.hdr.payload_size; - memcpy(resp->payload, ((struct gsp_rpc_response *)msg.payload)->payload, - msg.hdr.payload_size); - complete(done); - } - gsp->rpc->pending[seq % GSP_MAX_PENDING] = NULL; - } else { - /* Async notification - handle via callback */ - gsp_handle_notification(gsp, &msg); - } - } - - return handled ? IRQ_HANDLED : IRQ_NONE; -} - -/* Notification Handler */ -void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg) -{ - switch (msg->hdr.rpc_type) { - case GSP_RPC_NOTIFY: - /* Handle VF notifications (WPR, migration, etc.) */ - break; - case GSP_RPC_BAR_TRAP: - atomic64_inc(&gsp->bar_traps); - break; - default: - dev_dbg(gsp->dev, "Unhandled notification type 0x%02x\n", msg->hdr.rpc_type); - } -} - -/* Error Code Translation */ -int gsp_errno_to_errno(int gsp_err) -{ - switch (gsp_err) { - case GSP_OK: return 0; - case GSP_EPERM: return -EPERM; - case GSP_ENOENT: return -ENOENT; - case GSP_EINVAL: return -EINVAL; - case GSP_ENODEV: return -ENODEV; - case GSP_ENOMEM: return -ENOMEM; - case GSP_EBUSY: return -EBUSY; - case GSP_ENOSPC: return -ENOSPC; - case GSP_ETIMEDOUT: return -ETIMEDOUT; - case GSP_EOVERFLOW: return -EOVERFLOW; - case GSP_EDEV: return -EIO; - default: return -EIO; - } -} \ No newline at end of file diff --git a/driver/vf/gsp_vf.c b/driver/vf/gsp_vf.c deleted file mode 100644 index 951c84d..0000000 --- a/driver/vf/gsp_vf.c +++ /dev/null @@ -1,312 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - VF Management - */ - -#include "gsp_core.h" -#include -#include -#include - -/* VF Reference Counting */ -static void gsp_vf_release(struct kref *ref) -{ - struct gsp_vf *vf = container_of(ref, struct gsp_vf, ref); - kfree(vf); -} - -struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id) -{ - struct gsp_vf *vf; - - mutex_lock(&gsp->vf_lock); - vf = idr_find(&gsp->vf_idr, id); - if (vf && vf->active) - kref_get(&vf->ref); - else - vf = NULL; - mutex_unlock(&gsp->vf_lock); - - return vf; -} - -void gsp_vf_put(struct gsp_vf *vf) -{ - if (vf) - kref_put(&vf->ref, gsp_vf_release); -} - -/* VF Creation */ -int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config) -{ - struct gsp_vf *vf; - int ret, id; - - if (!config || config->id == 0 || config->id > gsp->max_vfs) - return -EINVAL; - - vf = kzalloc(sizeof(*vf), GFP_KERNEL); - if (!vf) - return -ENOMEM; - - vf->gsp = gsp; - vf->id = config->id; - kref_init(&vf->ref); - mutex_init(&vf->lock); - mutex_init(&vf->wpr_lock); - INIT_LIST_HEAD(&vf->wpr_list); - INIT_LIST_HEAD(&vf->list); - atomic_set(&vf->refcount, 1); - - /* Copy configuration */ - vf->bar1_offset = config->bar1_offset; - vf->bar1_size = config->bar1_size; - vf->pt_base = config->pt_base; - vf->pt_size = config->pt_size; - vf->cmd_base = config->cmd_base; - vf->cmd_size = config->cmd_size; - - /* Allocate VF ID */ - mutex_lock(&gsp->vf_lock); - id = idr_alloc(&gsp->vf_idr, vf, config->id, config->id + 1, GFP_KERNEL); - if (id < 0) { - ret = id; - mutex_unlock(&gsp->vf_lock); - goto err_free; - } - - list_add(&vf->list, &gsp->vf_list); - vf->active = true; - vf->state = GSP_VF_ACTIVE; - mutex_unlock(&gsp->vf_lock); - - /* Initialize subsystems */ - ret = gsp_vf_wpr_init(vf); - if (ret) { - dev_err(gsp->dev, "VF %u: WPR init failed: %d\n", vf->id, ret); - goto err_wpr; - } - - ret = gsp_vf_bar_init(vf); - if (ret) { - dev_err(gsp->dev, "VF %u: BAR init failed: %d\n", vf->id, ret); - goto err_bar; - } - - dev_info(gsp->dev, "VF %u created (bar1=0x%llx-0x%llx, pt=0x%llx-0x%llx)\n", - vf->id, vf->bar1_offset, vf->bar1_offset + vf->bar1_size, - vf->pt_base, vf->pt_base + vf->pt_size); - - return 0; - -err_bar: - gsp_vf_wpr_fini(vf); -err_wpr: - mutex_lock(&gsp->vf_lock); - list_del(&vf->list); - idr_remove(&gsp->vf_idr, vf->id); - vf->active = false; - mutex_unlock(&gsp->vf_lock); -err_free: - kfree(vf); - return ret; -} - -/* VF Destruction */ -void gsp_vf_destroy(struct gsp_vf *vf) -{ - struct gsp_device *gsp = vf->gsp; - uint32_t id = vf->id; - - if (!vf->active) - return; - - /* Cancel any ongoing migration */ - mutex_lock(&vf->lock); - if (vf->migration) { - gsp_migration_cancel(vf->migration); - vf->migration = NULL; - } - vf->active = false; - vf->state = GSP_VF_DESTROYING; - mutex_unlock(&vf->lock); - - /* Remove from device lists */ - mutex_lock(&gsp->vf_lock); - list_del(&vf->list); - idr_remove(&gsp->vf_idr, id); - mutex_unlock(&gsp->vf_lock); - - /* Cleanup subsystems */ - gsp_vf_bar_fini(vf); - gsp_vf_wpr_fini(vf); - - dev_info(gsp->dev, "VF %u destroyed\n", id); - - gsp_vf_put(vf); -} - -/* Memory Allocation */ -int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags, - uint64_t *handle, uint64_t *va) -{ - struct gsp_alloc_payload payload = { - .size = size, - .type = flags & 0x3, /* Low bits: mem type */ - .alignment = 0x1000, - .va_hint = 0, - .flags = flags >> 2, - .pid = current->pid, - .vf_id = vf->id, - }; - struct gsp_alloc_response resp; - size_t resp_size = sizeof(resp); - int ret; - - if (!handle || !va) - return -EINVAL; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - static uint64_t next_handle = 0x1000; - static uint64_t next_va = 0x100000000ULL; /* 4GB base */ - - *handle = next_handle++; - *va = next_va; - next_va += (size + 0xFFF) & ~0xFFF; /* Align to 4KB */ - - dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx (emulation)\n", - vf->id, *handle, *va, size); - - return 0; - } - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_ALLOC, - &payload, sizeof(payload), - &resp, &resp_size); - if (ret) - return ret; - - *handle = resp.handle; - *va = resp.va; - - dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx\n", - vf->id, *handle, *va, size); - - return 0; -} - -int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle) -{ - /* FREE uses primary dispatcher sub-command */ - struct gsp_subcmd_desc desc; - size_t desc_size = sizeof(desc); - int ret; - - /* Subcmd for FREE: need to determine which subcmd corresponds to FREE */ - /* For now, use a generic FREE RPC if implemented */ - - /* Placeholder - actual implementation depends on FREE subcmd mapping */ - dev_dbg(vf->gsp->dev, "VF %u: free handle=0x%llx\n", vf->id, handle); - - return 0; -} - -/* Memory Pinning */ -int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle, - uint64_t offset, uint64_t size, - uint64_t *dma_addr) -{ - struct gsp_pin_payload payload = { - .handle = handle, - .offset = offset, - .size = size, - .vf_id = vf->id, - .flags = GSP_PIN_READ | GSP_PIN_WRITE, - }; - struct gsp_pin_response resp; - size_t resp_size = sizeof(resp); - int ret; - - if (!dma_addr) - return -EINVAL; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - static uint64_t next_dma = 0x200000000ULL; /* 8GB base */ - *dma_addr = next_dma; - next_dma += (size + 0xFFF) & ~0xFFF; - - dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx (emulation)\n", - vf->id, handle, offset, size, *dma_addr); - - return 0; - } - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_PIN, - &payload, sizeof(payload), - &resp, &resp_size); - if (ret) - return ret; - - *dma_addr = resp.dma_addr; - - dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx\n", - vf->id, handle, offset, size, *dma_addr); - - return 0; -} - -int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle) -{ - dev_dbg(vf->gsp->dev, "VF %u: unpin handle=0x%llx\n", vf->id, handle); - return 0; -} - -/* Memory Mapping */ -int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, - uint64_t size, uint32_t prot, uint32_t flags, - uint64_t offset, uint64_t *mapped_va) -{ - /* In emulation mode, just return the requested VA */ - if (emulation_mode) { - if (mapped_va) - *mapped_va = gpu_va; - - dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx -> mapped_va=0x%llx (emulation)\n", - vf->id, handle, gpu_va, size, gpu_va); - - return 0; - } - - /* TODO: Implement GSP RPC for MAP */ - dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx\n", - vf->id, handle, gpu_va, size); - - if (mapped_va) - *mapped_va = gpu_va; - - return 0; -} - -int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size) -{ - dev_dbg(vf->gsp->dev, "VF %u: unmap handle=0x%llx gpu_va=0x%llx size=0x%llx\n", - vf->id, handle, gpu_va, size); - return 0; -} - -/* Scheduling */ -int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns) -{ - /* Use primary dispatcher SCHEDULE subcmd or secondary dispatcher */ - dev_dbg(vf->gsp->dev, "VF %u: schedule prio=%u slice=%llu ns\n", - vf->id, priority, time_slice_ns); - return 0; -} - -int gsp_vf_yield(struct gsp_vf *vf) -{ - dev_dbg(vf->gsp->dev, "VF %u: yield\n", vf->id); - return 0; -} \ No newline at end of file diff --git a/driver/wpr/gsp_wpr.c b/driver/wpr/gsp_wpr.c deleted file mode 100644 index 91c5550..0000000 --- a/driver/wpr/gsp_wpr.c +++ /dev/null @@ -1,285 +0,0 @@ -/* SPDX-License-Identifier: GPL-2.0-only */ -/* - * GSP vGPU-IO Driver - WPR (Write/Read Protect Region) Management - */ - -#include "gsp_core.h" -#include -#include - -/* WPR Payload (for RPC) */ -struct gsp_wpr_payload { - uint32_t vf_id; - uint32_t op; /* 0=protect_write, 1=protect_read, 2=unprotect */ - uint64_t base; - uint64_t size; - uint32_t flags; - uint64_t timeout_ns; -} __attribute__((packed)); - -struct gsp_wpr_response { - int64_t status; -} __attribute__((packed)); - -/* WPR Initialization */ -int gsp_vf_wpr_init(struct gsp_vf *vf) -{ - INIT_LIST_HEAD(&vf->wpr_list); - mutex_init(&vf->wpr_lock); - return 0; -} - -void gsp_vf_wpr_fini(struct gsp_vf *vf) -{ - struct gsp_wpr_region *rgn, *tmp; - - mutex_lock(&vf->wpr_lock); - list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { - list_del(&rgn->list); - kfree(rgn); - } - mutex_unlock(&vf->wpr_lock); -} - -/* Protect Region (Write) */ -int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size) -{ - return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_WRITE); -} - -/* Protect Region (Read) */ -int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size) -{ - return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_READ); -} - -/* Generic Protect */ -int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size, uint32_t flags) -{ - struct gsp_wpr_payload payload = { - .vf_id = vf->id, - .op = (flags & GSP_WPR_READ) ? 1 : 0, - .base = base, - .size = size, - .flags = flags, - .timeout_ns = 10000000, /* 10ms default */ - }; - struct gsp_wpr_response resp; - size_t resp_size = sizeof(resp); - struct gsp_wpr_region *rgn; - int ret; - - /* Validate alignment */ - if (base & 0xFFF || size & 0xFFF) - return -EINVAL; - - rgn = kzalloc(sizeof(*rgn), GFP_KERNEL); - if (!rgn) - return -ENOMEM; - - rgn->base = base; - rgn->size = size; - rgn->flags = flags; - rgn->vf_id = vf->id; - rgn->timeout_ns = payload.timeout_ns; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - mutex_lock(&vf->wpr_lock); - list_add(&rgn->list, &vf->wpr_list); - mutex_unlock(&vf->wpr_lock); - - dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x (emulation)\n", - vf->id, base, base + size, flags); - - return 0; - } - - /* Send to GSP WPR handler (0x04F00B0C/0x04F00C78) */ - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MAP, /* Uses MAP RPC for WPR */ - &payload, sizeof(payload), &resp, &resp_size); - if (ret) { - kfree(rgn); - return ret; - } - - if (resp.status != 0) { - kfree(rgn); - return gsp_errno_to_errno(resp.status); - } - - mutex_lock(&vf->wpr_lock); - list_add(&rgn->list, &vf->wpr_list); - mutex_unlock(&vf->wpr_lock); - - dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x\n", - vf->id, base, base + size, flags); - - return 0; -} - -/* Unprotect Region */ -int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base) -{ - struct gsp_wpr_payload payload = { - .vf_id = vf->id, - .op = 2, /* unprotect */ - .base = base, - .size = 0, - .flags = 0, - }; - struct gsp_wpr_response resp; - size_t resp_size = sizeof(resp); - struct gsp_wpr_region *rgn, *tmp; - bool found = false; - int ret; - - /* In emulation mode, handle locally */ - if (emulation_mode) { - mutex_lock(&vf->wpr_lock); - list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { - if (rgn->base == base) { - list_del(&rgn->list); - kfree(rgn); - found = true; - break; - } - } - mutex_unlock(&vf->wpr_lock); - - dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx (emulation)\n", vf->id, base); - - return found ? 0 : -ENOENT; - } - - ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_UNMAP, /* Uses UNMAP for WPR */ - &payload, sizeof(payload), &resp, &resp_size); - if (ret) - return ret; - - if (resp.status != 0) - return gsp_errno_to_errno(resp.status); - - mutex_lock(&vf->wpr_lock); - list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { - if (rgn->base == base) { - list_del(&rgn->list); - kfree(rgn); - found = true; - break; - } - } - mutex_unlock(&vf->wpr_lock); - - if (!found) - return -ENOENT; - - dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx\n", vf->id, base); - - return 0; -} - -/* Set Timeout */ -int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns) -{ - /* Update timeout for all WPR regions */ - struct gsp_wpr_region *rgn; - - mutex_lock(&vf->wpr_lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - rgn->timeout_ns = timeout_ns; - } - mutex_unlock(&vf->wpr_lock); - - return 0; -} - -/* Register Completion Callback */ -int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type, - void (*cb)(struct gsp_vf *, uint64_t, uint32_t), - void *ctx) -{ - struct gsp_wpr_region *rgn; - - mutex_lock(&vf->wpr_lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - switch (type) { - case GSP_WPR_WRITE_COMPLETE: - case GSP_WPR_WRITE_TIMEOUT: - rgn->completion_cb = cb; - break; - case GSP_WPR_READ_COMPLETE: - case GSP_WPR_READ_TIMEOUT: - rgn->timeout_cb = cb; - break; - } - } - mutex_unlock(&vf->wpr_lock); - - return 0; -} - -/* WPR Event Handler (called from notification) */ -void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg) -{ - struct gsp_rpc_response *resp = (struct gsp_rpc_response *)msg->payload; - uint32_t event_type = resp->payload[0]; - uint32_t vf_id = resp->payload[1]; - uint64_t address = *(uint64_t *)&resp->payload[2]; - struct gsp_vf *vf; - struct gsp_wpr_region *rgn; - - vf = gsp_vf_get(gsp, vf_id); - if (!vf) - return; - - atomic64_inc(&gsp->wpr_events); - - mutex_lock(&vf->wpr_lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - if (address >= rgn->base && address < rgn->base + rgn->size) { - switch (event_type) { - case GSP_WPR_WRITE_COMPLETE: - case GSP_WPR_READ_COMPLETE: - if (rgn->completion_cb) - rgn->completion_cb(vf, address, event_type); - break; - case GSP_WPR_WRITE_TIMEOUT: - case GSP_WPR_READ_TIMEOUT: - if (rgn->timeout_cb) - rgn->timeout_cb(vf, address, event_type); - break; - } - break; - } - } - mutex_unlock(&vf->wpr_lock); - - gsp_vf_put(vf); -} - -/* Default Completion Callback */ -void gsp_wpr_default_complete_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type) -{ - dev_dbg(vf->gsp->dev, "VF %u: WPR %s complete at 0x%llx\n", - vf->id, type == GSP_WPR_WRITE_COMPLETE ? "write" : "read", addr); - - /* If this was a page table write, invalidate TLB */ - if (addr >= vf->pt_base && addr < vf->pt_base + vf->pt_size) { - /* TODO: TLB invalidate */ - } -} - -/* Default Timeout Callback */ -void gsp_wpr_default_timeout_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type) -{ - dev_warn(vf->gsp->dev, "VF %u: WPR %s timeout at 0x%llx\n", - vf->id, type == GSP_WPR_WRITE_TIMEOUT ? "write" : "read", addr); - - /* Throttle or reset VF */ - if (type == GSP_WPR_WRITE_TIMEOUT) { - /* TODO: VF throttle */ - } else { - /* TODO: VF reset */ - } -} \ No newline at end of file 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/kernel/tests/gsp_vgpu_comprehensive_test.c b/kernel/tests/gsp_vgpu_comprehensive_test.c deleted file mode 100644 index 7918beb..0000000 --- a/kernel/tests/gsp_vgpu_comprehensive_test.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - * GSP vGPU-IO Driver - Comprehensive Test Suite - * Tests all driver components: RPC, VF, BAR, WPR, Migration - * Compiles against kernel headers only (no driver dependency) - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -static int test_count = 0; -static int pass_count = 0; -static int fail_count = 0; - -#define TEST_ASSERT(cond, msg) \ - do { \ - test_count++; \ - if (cond) { \ - pass_count++; \ - pr_info("PASS: %s\n", msg); \ - } else { \ - fail_count++; \ - pr_err("FAIL: %s\n", msg); \ - } \ - } while (0) - -#define TEST_ASSERT_EQ(a, b, msg) \ - TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")") - -#define TEST_ASSERT_NE(a, b, msg) \ - TEST_ASSERT((a) != (b), msg " (expected not " #b ", got " #a ")") - -/* ============================================================ - * Test: Kernel API Availability - * ============================================================ */ - -static int test_kernel_apis(void) -{ - pr_info("=== Testing Kernel API Availability ===\n"); - - /* Test kref */ - struct kref *ref = kmalloc(sizeof(*ref), GFP_KERNEL); - TEST_ASSERT(ref != NULL, "kref allocation"); - kref_init(ref); - TEST_ASSERT(kref_read(ref) == 1, "kref_init sets refcount to 1"); - - kref_get(ref); - TEST_ASSERT(kref_read(ref) == 2, "kref_get increments refcount"); - - kref_put(ref, (void (*)(struct kref *))kfree); - TEST_ASSERT(kref_read(ref) == 1, "kref_put decrements refcount"); - - kref_put(ref, (void (*)(struct kref *))kfree); - TEST_ASSERT(kref_read(ref) == 0, "kref_put reaches zero"); - - /* Test completion */ - struct completion comp; - init_completion(&comp); - TEST_ASSERT(!completion_done(&comp), "completion initially not done"); - complete(&comp); - TEST_ASSERT(completion_done(&comp), "completion marked done"); - - init_completion(&comp); - TEST_ASSERT(wait_for_completion_timeout(&comp, msecs_to_jiffies(100)) > 0, - "wait_for_completion_timeout works"); - - /* Test atomic64 */ - atomic64_t a64 = ATOMIC64_INIT(0); - atomic64_inc(&a64); - TEST_ASSERT_EQ(atomic64_read(&a64), 1, "atomic64_inc"); - atomic64_add(5, &a64); - TEST_ASSERT_EQ(atomic64_read(&a64), 6, "atomic64_add"); - atomic64_sub(3, &a64); - TEST_ASSERT_EQ(atomic64_read(&a64), 3, "atomic64_sub"); - - /* Test mutex */ - struct mutex mtx; - mutex_init(&mtx); - mutex_lock(&mtx); - TEST_ASSERT(mutex_is_locked(&mtx), "mutex_is_locked after lock"); - mutex_unlock(&mtx); - TEST_ASSERT(!mutex_is_locked(&mtx), "mutex_is_locked after unlock"); - - /* Test spinlock */ - spinlock_t sl = __SPIN_LOCK_UNLOCKED(sl); - unsigned long flags; - spin_lock_irqsave(&sl, flags); - TEST_ASSERT(spin_is_locked(&sl), "spin_is_locked after lock"); - spin_unlock_irqrestore(&sl, flags); - TEST_ASSERT(!spin_is_locked(&sl), "spin_is_locked after unlock"); - - /* Test idr */ - struct idr idr; - idr_init(&idr); - int id = idr_alloc(&idr, (void *)0xdeadbeef, 1, 100, GFP_KERNEL); - TEST_ASSERT(id >= 1 && id < 100, "idr_alloc returns valid id"); - void *ptr = idr_find(&idr, id); - TEST_ASSERT(ptr == (void *)0xdeadbeef, "idr_find returns correct pointer"); - idr_remove(&idr, id); - TEST_ASSERT(idr_find(&idr, id) == NULL, "idr_remove works"); - idr_destroy(&idr); - - /* Test list */ - struct list_head head; - INIT_LIST_HEAD(&head); - TEST_ASSERT(list_empty(&head), "list_empty on new list"); - - struct list_head item; - INIT_LIST_HEAD(&item); - list_add(&item, &head); - TEST_ASSERT(!list_empty(&head), "list not empty after add"); - list_del(&item); - TEST_ASSERT(list_empty(&head), "list empty after del"); - - /* Test ktime */ - ktime_t start = ktime_get(); - ktime_t end = ktime_get(); - s64 diff = ktime_to_ns(ktime_sub(end, start)); - TEST_ASSERT(diff >= 0, "ktime_get and ktime_to_ns work"); - - return 0; -} - -/* ============================================================ - * Test: Structure Sizes (from driver header) - * ============================================================ */ - -static int test_structure_sizes(void) -{ - pr_info("=== Testing Structure Sizes ===\n"); - - /* These sizes must match driver/include/gsp_core.h */ - TEST_ASSERT_EQ(sizeof(struct gsp_rpc_header), 64, "gsp_rpc_header size"); - TEST_ASSERT_EQ(sizeof(struct gsp_rpc_message), 256, "gsp_rpc_message size"); - TEST_ASSERT_EQ(sizeof(struct gsp_rpc_response), 32, "gsp_rpc_response size"); - TEST_ASSERT_EQ(sizeof(struct gsp_alloc_payload), 40, "gsp_alloc_payload size"); - TEST_ASSERT_EQ(sizeof(struct gsp_alloc_response), 24, "gsp_alloc_response size"); - TEST_ASSERT_EQ(sizeof(struct gsp_pin_payload), 40, "gsp_pin_payload size"); - TEST_ASSERT_EQ(sizeof(struct gsp_pin_response), 8, "gsp_pin_response size"); - TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_payload), 40, "gsp_bar_trap_payload size"); - TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_response), 8, "gsp_bar_trap_response size"); - TEST_ASSERT_EQ(sizeof(struct gsp_migrate_payload), 56, "gsp_migrate_payload size"); - TEST_ASSERT_EQ(sizeof(struct gsp_subcmd_desc), 8, "gsp_subcmd_desc size"); - TEST_ASSERT_EQ(sizeof(struct gsp_vf_config), 56, "gsp_vf_config size"); - - return 0; -} - -/* ============================================================ - * Test: Enum Values (from driver header) - * ============================================================ */ - -static int test_enum_values(void) -{ - pr_info("=== Testing Enum Values ===\n"); - - TEST_ASSERT_EQ(GSP_RPC_ALLOC, 0x01, "RPC_ALLOC"); - TEST_ASSERT_EQ(GSP_RPC_FREE, 0x02, "RPC_FREE"); - TEST_ASSERT_EQ(GSP_RPC_MAP, 0x03, "RPC_MAP"); - TEST_ASSERT_EQ(GSP_RPC_UNMAP, 0x04, "RPC_UNMAP"); - TEST_ASSERT_EQ(GSP_RPC_PIN, 0x05, "RPC_PIN"); - TEST_ASSERT_EQ(GSP_RPC_UNPIN, 0x06, "RPC_UNPIN"); - TEST_ASSERT_EQ(GSP_RPC_SCHEDULE, 0x07, "RPC_SCHEDULE"); - TEST_ASSERT_EQ(GSP_RPC_YIELD, 0x08, "RPC_YIELD"); - TEST_ASSERT_EQ(GSP_RPC_NOTIFY, 0x09, "RPC_NOTIFY"); - TEST_ASSERT_EQ(GSP_RPC_BAR_TRAP, 0x0A, "RPC_BAR_TRAP"); - TEST_ASSERT_EQ(GSP_RPC_MIGRATE, 0x0B, "RPC_MIGRATE"); - TEST_ASSERT_EQ(GSP_RPC_DEBUG, 0x0C, "RPC_DEBUG"); - - TEST_ASSERT_EQ(GSP_MEM_VRAM, 0, "MEM_VRAM"); - TEST_ASSERT_EQ(GSP_MEM_SYSMEM, 1, "MEM_SYSMEM"); - TEST_ASSERT_EQ(GSP_MEM_UNCACHED, 2, "MEM_UNCACHED"); - - TEST_ASSERT_EQ(GSP_PIN_READ, (1 << 0), "PIN_READ"); - TEST_ASSERT_EQ(GSP_PIN_WRITE, (1 << 1), "PIN_WRITE"); - TEST_ASSERT_EQ(GSP_PIN_LONGTERM, (1 << 2), "PIN_LONGTERM"); - - TEST_ASSERT_EQ(GSP_MIG_QUERY, 0, "MIG_QUERY"); - TEST_ASSERT_EQ(GSP_MIG_PREPARE, 1, "MIG_PREPARE"); - TEST_ASSERT_EQ(GSP_MIG_TRANSFER, 2, "MIG_TRANSFER"); - TEST_ASSERT_EQ(GSP_MIG_COMMIT, 3, "MIG_COMMIT"); - TEST_ASSERT_EQ(GSP_MIG_CLEANUP, 4, "MIG_CLEANUP"); - - TEST_ASSERT_EQ(GSP_MIG_LIVE, (1 << 0), "MIG_LIVE"); - TEST_ASSERT_EQ(GSP_MIG_POSTCOPY, (1 << 1), "MIG_POSTCOPY"); - TEST_ASSERT_EQ(GSP_MIG_AUTO_COMMIT, (1 << 2), "MIG_AUTO_COMMIT"); - TEST_ASSERT_EQ(GSP_MIG_VERIFY, (1 << 3), "MIG_VERIFY"); - - TEST_ASSERT_EQ(GSP_VF_CREATING, 0, "VF_CREATING"); - TEST_ASSERT_EQ(GSP_VF_ACTIVE, 1, "VF_ACTIVE"); - TEST_ASSERT_EQ(GSP_VF_MIGRATING, 2, "VF_MIGRATING"); - TEST_ASSERT_EQ(GSP_VF_SUSPENDED, 3, "VF_SUSPENDED"); - TEST_ASSERT_EQ(GSP_VF_DESTROYING, 4, "VF_DESTROYING"); - - return 0; -} - -/* ============================================================ - * Test: Error Code Mapping - * ============================================================ */ - -static int test_error_codes(void) -{ - pr_info("=== Testing Error Codes ===\n"); - - TEST_ASSERT_EQ(GSP_OK, 0, "GSP_OK"); - TEST_ASSERT_EQ(GSP_EPERM, 0xFFFFFFFF, "GSP_EPERM"); - TEST_ASSERT_EQ(GSP_ENOENT, 0xFFFFFFFE, "GSP_ENOENT"); - TEST_ASSERT_EQ(GSP_EINVAL, 0xFFFFFFFD, "GSP_EINVAL"); - TEST_ASSERT_EQ(GSP_ENODEV, 0xFFFFFFFA, "GSP_ENODEV"); - TEST_ASSERT_EQ(GSP_ENOMEM, 0xFFFFFFF4, "GSP_ENOMEM"); - TEST_ASSERT_EQ(GSP_EBUSY, 0xFFFFFFEE, "GSP_EBUSY"); - TEST_ASSERT_EQ(GSP_ENOSPC, 0xFFFFFFE4, "GSP_ENOSPC"); - TEST_ASSERT_EQ(GSP_ETIMEDOUT, 0x0000001E, "GSP_ETIMEDOUT"); - TEST_ASSERT_EQ(GSP_EOVERFLOW, 0x00000025, "GSP_EOVERFLOW"); - TEST_ASSERT_EQ(GSP_EDEV, 0x0000001F, "GSP_EDEV"); - - return 0; -} - -/* ============================================================ - * Test: Firmware Constants - * ============================================================ */ - -static int test_firmware_constants(void) -{ - pr_info("=== Testing Firmware Constants ===\n"); - - TEST_ASSERT_EQ(GSP_FW_BASE_ADDR, 0x04000000ULL, "FW_BASE_ADDR"); - TEST_ASSERT_EQ(GSP_FW_SIZE, 0x02000000ULL, "FW_SIZE"); - TEST_ASSERT_EQ(GSP_ENTRY_POINT, 0x04009000ULL, "ENTRY_POINT"); - TEST_ASSERT_EQ(GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL, "PRIMARY_DISPATCHER"); - TEST_ASSERT_EQ(GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL, "SECONDARY_DISPATCHER"); - TEST_ASSERT_EQ(GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL, "RPC_JUMP_TABLE"); - TEST_ASSERT_EQ(GSP_BAR0_DOORBELL, 0x1000, "BAR0_DOORBELL"); - TEST_ASSERT_EQ(GSP_BAR0_MSIX, 0x2000, "BAR0_MSIX"); - TEST_ASSERT_EQ(GSP_MSG_RING_SIZE, 256, "MSG_RING_SIZE"); - TEST_ASSERT_EQ(GSP_MSG_MAX_SIZE, 256, "MSG_MAX_SIZE"); - TEST_ASSERT_EQ(GSP_MAX_PENDING, 64, "MAX_PENDING"); - - return 0; -} - -/* ============================================================ - * Test: Migration State Enum (internal) - * ============================================================ */ - -static int test_migration_states(void) -{ - pr_info("=== Testing Migration States ===\n"); - - TEST_ASSERT_EQ(GSP_MIG_S_IDLE, 0, "MIG_S_IDLE"); - TEST_ASSERT_EQ(GSP_MIG_S_QUERY, 1, "MIG_S_QUERY"); - TEST_ASSERT_EQ(GSP_MIG_S_PREPARE, 2, "MIG_S_PREPARE"); - TEST_ASSERT_EQ(GSP_MIG_S_TRANSFER, 3, "MIG_S_TRANSFER"); - TEST_ASSERT_EQ(GSP_MIG_S_COMMIT, 4, "MIG_S_COMMIT"); - TEST_ASSERT_EQ(GSP_MIG_S_CLEANUP, 5, "MIG_S_CLEANUP"); - TEST_ASSERT_EQ(GSP_MIG_S_FAILED, 6, "MIG_S_FAILED"); - TEST_ASSERT_EQ(GSP_MIG_S_COMPLETED, 7, "MIG_S_COMPLETED"); - - return 0; -} - -/* ============================================================ - * Test: WPR Flags and Events - * ============================================================ */ - -static int test_wpr_flags(void) -{ - pr_info("=== Testing WPR Flags ===\n"); - - TEST_ASSERT_EQ(GSP_WPR_WRITE, (1 << 0), "WPR_WRITE"); - TEST_ASSERT_EQ(GSP_WPR_READ, (1 << 1), "WPR_READ"); - TEST_ASSERT_EQ(GSP_WPR_EXECUTE, (1 << 2), "WPR_EXECUTE"); - - TEST_ASSERT_EQ(GSP_WPR_WRITE_COMPLETE, 0, "WPR_WRITE_COMPLETE"); - TEST_ASSERT_EQ(GSP_WPR_READ_COMPLETE, 1, "WPR_READ_COMPLETE"); - TEST_ASSERT_EQ(GSP_WPR_WRITE_TIMEOUT, 2, "WPR_WRITE_TIMEOUT"); - TEST_ASSERT_EQ(GSP_WPR_READ_TIMEOUT, 3, "WPR_READ_TIMEOUT"); - - return 0; -} - -/* ============================================================ - * Test: Message Flags - * ============================================================ */ - -static int test_message_flags(void) -{ - pr_info("=== Testing Message Flags ===\n"); - - TEST_ASSERT_EQ(GSP_MSG_SYNC, (1 << 0), "MSG_SYNC"); - TEST_ASSERT_EQ(GSP_MSG_ASYNC, (1 << 1), "MSG_ASYNC"); - TEST_ASSERT_EQ(GSP_MSG_NOREPLY, (1 << 2), "MSG_NOREPLY"); - - return 0; -} - -/* ============================================================ - * Test: BAR Trap Types - * ============================================================ */ - -static int test_bar_trap_types(void) -{ - pr_info("=== Testing BAR Trap Types ===\n"); - - TEST_ASSERT_EQ(GSP_BAR_READ, 0, "BAR_READ"); - TEST_ASSERT_EQ(GSP_BAR_WRITE, 1, "BAR_WRITE"); - TEST_ASSERT_EQ(GSP_BAR_ATOMIC, 2, "BAR_ATOMIC"); - - return 0; -} - -/* ============================================================ - * Test: Memory Allocation Patterns - * ============================================================ */ - -static int test_memory_patterns(void) -{ - pr_info("=== Testing Memory Allocation Patterns ===\n"); - - /* Test vzalloc/vfree */ - void *ptr = vzalloc(4096); - TEST_ASSERT(ptr != NULL, "vzalloc allocates memory"); - vfree(ptr); - - /* Test kmalloc/kfree */ - ptr = kmalloc(1024, GFP_KERNEL); - TEST_ASSERT(ptr != NULL, "kmalloc allocates memory"); - kfree(ptr); - - /* Test kcalloc */ - ptr = kcalloc(10, 64, GFP_KERNEL); - TEST_ASSERT(ptr != NULL, "kcalloc allocates zeroed memory"); - kfree(ptr); - - return 0; -} - -/* ============================================================ - * Test: String Operations - * ============================================================ */ - -static int test_string_ops(void) -{ - pr_info("=== Testing String Operations ===\n"); - - char buf[64]; - int len = snprintf(buf, sizeof(buf), "test_%d", 42); - TEST_ASSERT(len > 0 && len < sizeof(buf), "snprintf works"); - TEST_ASSERT(strcmp(buf, "test_42") == 0, "snprintf formats correctly"); - - char *dup = kstrdup("test_string", GFP_KERNEL); - TEST_ASSERT(dup != NULL, "kstrdup works"); - TEST_ASSERT(strcmp(dup, "test_string") == 0, "kstrdup copies correctly"); - kfree(dup); - - return 0; -} - -/* ============================================================ - * Test Suite Runner - * ============================================================ */ - -static int run_all_tests(void) -{ - pr_info("\n============================================================\n"); - pr_info("GSP vGPU-IO Comprehensive Test Suite Starting\n"); - pr_info("============================================================\n\n"); - - test_kernel_apis(); - test_structure_sizes(); - test_enum_values(); - test_error_codes(); - test_firmware_constants(); - test_migration_states(); - test_wpr_flags(); - test_message_flags(); - test_bar_trap_types(); - test_memory_patterns(); - test_string_ops(); - - pr_info("\n============================================================\n"); - pr_info("TEST RESULTS: %d total, %d passed, %d failed\n", - test_count, pass_count, fail_count); - pr_info("============================================================\n"); - - if (fail_count == 0) { - pr_info("TEST SUITE PASSED\n"); - return 0; - } else { - pr_err("TEST SUITE FAILED\n"); - return -1; - } -} - -/* ============================================================ - * Module Init/Exit - * ============================================================ */ - -static int __init gsp_test_init(void) -{ - return run_all_tests(); -} - -static void __exit gsp_test_exit(void) -{ - pr_info("GSP vGPU-IO test module unloaded\n"); -} - -module_init(gsp_test_init); -module_exit(gsp_test_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("NVIDIA vGPU-IO Team"); -MODULE_DESCRIPTION("GSP vGPU-IO Comprehensive Test Suite"); -MODULE_VERSION("1.0.0"); \ No newline at end of file diff --git a/kernel/tests/gsp_vgpu_kunit_test.c b/kernel/tests/gsp_vgpu_kunit_test.c deleted file mode 100644 index c661f91..0000000 --- a/kernel/tests/gsp_vgpu_kunit_test.c +++ /dev/null @@ -1,327 +0,0 @@ -/* - * GSP vGPU-IO KUnit Tests - * In-kernel unit tests for the driver - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* ============================================================ - * Test: Structure Sizes and Alignments - * ============================================================ */ - -static void test_structure_sizes(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_header), 64); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_message), 256); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_response), 32); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_subcmd_desc), 8); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_vf_config), 64); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_payload), 48); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_response), 24); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_payload), 40); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_response), 8); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_payload), 40); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_response), 8); - KUNIT_EXPECT_EQ(test, sizeof(struct gsp_migrate_payload), 64); -} - -static void test_structure_alignments(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_header), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_message), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_response), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_subcmd_desc), 4); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_vf_config), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_payload), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_response), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_payload), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_response), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_payload), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_response), 8); - KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_migrate_payload), 8); -} - -/* ============================================================ - * Test: Payload Encoding/Decoding - * ============================================================ */ - -static void test_alloc_payload_encoding(struct kunit *test) -{ - struct gsp_alloc_payload payload = {0}; - - payload.size = cpu_to_le64(0x100000); - payload.type = cpu_to_le32(GSP_MEM_VRAM); - payload.alignment = cpu_to_le32(0x1000); - payload.va_hint = cpu_to_le64(0x0); - payload.flags = cpu_to_le32(0x3); - payload.pid = cpu_to_le32(0x456); - payload.vf_id = cpu_to_le32(0x1); - - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x100000); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.type), GSP_MEM_VRAM); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.alignment), 0x1000); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.va_hint), 0x0); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), 0x3); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.pid), 0x456); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1); -} - -static void test_pin_payload_encoding(struct kunit *test) -{ - struct gsp_pin_payload payload = {0}; - - payload.handle = cpu_to_le64(0xCAFEBABE); - payload.offset = cpu_to_le64(0x1000); - payload.size = cpu_to_le64(0x10000); - payload.vf_id = cpu_to_le32(0x2); - payload.flags = cpu_to_le32(GSP_PIN_READ | GSP_PIN_WRITE); - - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.handle), 0xCAFEBABE); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x10000); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x2); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_PIN_READ | GSP_PIN_WRITE); -} - -static void test_bar_trap_payload_encoding(struct kunit *test) -{ - struct gsp_bar_trap_payload payload = {0}; - - payload.bar_index = cpu_to_le32(1); - payload.trap_type = cpu_to_le32(GSP_BAR_WRITE); - payload.offset = cpu_to_le64(0x1000); - payload.value = cpu_to_le64(0xDEADBEEF); - payload.size = cpu_to_le32(4); - payload.vf_id = cpu_to_le32(0x1); - - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.bar_index), 1); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.trap_type), GSP_BAR_WRITE); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.value), 0xDEADBEEF); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.size), 4); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1); -} - -static void test_migrate_payload_encoding(struct kunit *test) -{ - struct gsp_migrate_payload payload = {0}; - - payload.phase = cpu_to_le32(GSP_MIG_PREPARE); - payload.subcmd = cpu_to_le32(3); - payload.src_vf = cpu_to_le64(0x1); - payload.dst_vf = cpu_to_le64(0x2); - payload.data_ptr = cpu_to_le64(0x80000000); - payload.data_size = cpu_to_le64(0x1000); - payload.flags = cpu_to_le32(GSP_MIG_LIVE | GSP_MIG_VERIFY); - payload.token = cpu_to_le64(0xABCDEF1234567890); - - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.phase), GSP_MIG_PREPARE); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.subcmd), 3); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.src_vf), 0x1); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.dst_vf), 0x2); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_ptr), 0x80000000); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_size), 0x1000); - KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_MIG_LIVE | GSP_MIG_VERIFY); - KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.token), 0xABCDEF1234567890); -} - -static void test_subcmd_desc_encoding(struct kunit *test) -{ - struct gsp_subcmd_desc desc = {0}; - - desc.subcmd_id = cpu_to_le32(0x14); - desc.echo_flags_b = 0xAA; - desc.echo_flags_a = 0xBB; - desc.echo_flags_c = 0xCC; - - KUNIT_EXPECT_EQ(test, le32_to_cpu(desc.subcmd_id), 0x14); - KUNIT_EXPECT_EQ(test, desc.echo_flags_b, 0xAA); - KUNIT_EXPECT_EQ(test, desc.echo_flags_a, 0xBB); - KUNIT_EXPECT_EQ(test, desc.echo_flags_c, 0xCC); -} - -/* ============================================================ - * Test: Firmware Address Constants - * ============================================================ */ - -static void test_firmware_addresses(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_FW_BASE_ADDR, 0x04000000ULL); - KUNIT_EXPECT_EQ(test, GSP_ENTRY_POINT, 0x04009000ULL); - KUNIT_EXPECT_EQ(test, GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL); - KUNIT_EXPECT_EQ(test, GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL); - KUNIT_EXPECT_EQ(test, GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL); - KUNIT_EXPECT_EQ(test, 0x04179ED8ULL, 0x04179ED8ULL); /* Migration Table 1 */ - KUNIT_EXPECT_EQ(test, 0x04179FBCULL, 0x04179FBCULL); /* Migration Table 2 */ - KUNIT_EXPECT_EQ(test, 0x04CB8188ULL, 0x04CB8188ULL); /* BAR Handler */ - KUNIT_EXPECT_EQ(test, 0x04F00B0CULL, 0x04F00B0CULL); /* WPR Handler 1 */ - KUNIT_EXPECT_EQ(test, 0x04F00C78ULL, 0x04F00C78ULL); /* WPR Handler 2 */ - KUNIT_EXPECT_EQ(test, 0x057E94ECULL, 0x057E94ECULL); /* ALLOC Main */ - KUNIT_EXPECT_EQ(test, 0x051EFBF4ULL, 0x051EFBF4ULL); /* SCHEDULE Main */ -} - -/* ============================================================ - * Test: RPC Type Constants - * ============================================================ */ - -static void test_rpc_types(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_RPC_ALLOC, 0x01); - KUNIT_EXPECT_EQ(test, GSP_RPC_FREE, 0x02); - KUNIT_EXPECT_EQ(test, GSP_RPC_MAP, 0x03); - KUNIT_EXPECT_EQ(test, GSP_RPC_UNMAP, 0x04); - KUNIT_EXPECT_EQ(test, GSP_RPC_PIN, 0x05); - KUNIT_EXPECT_EQ(test, GSP_RPC_UNPIN, 0x06); - KUNIT_EXPECT_EQ(test, GSP_RPC_SCHEDULE, 0x07); - KUNIT_EXPECT_EQ(test, GSP_RPC_YIELD, 0x08); - KUNIT_EXPECT_EQ(test, GSP_RPC_NOTIFY, 0x09); - KUNIT_EXPECT_EQ(test, GSP_RPC_BAR_TRAP, 0x0A); - KUNIT_EXPECT_EQ(test, GSP_RPC_MIGRATE, 0x0B); - KUNIT_EXPECT_EQ(test, GSP_RPC_DEBUG, 0x0C); -} - -static void test_migration_constants(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_MIG_QUERY, 0); - KUNIT_EXPECT_EQ(test, GSP_MIG_PREPARE, 1); - KUNIT_EXPECT_EQ(test, GSP_MIG_TRANSFER, 2); - KUNIT_EXPECT_EQ(test, GSP_MIG_COMMIT, 3); - KUNIT_EXPECT_EQ(test, GSP_MIG_CLEANUP, 4); - - KUNIT_EXPECT_EQ(test, GSP_MIG_LIVE, (1 << 0)); - KUNIT_EXPECT_EQ(test, GSP_MIG_POSTCOPY, (1 << 1)); - KUNIT_EXPECT_EQ(test, GSP_MIG_AUTO_COMMIT, (1 << 2)); - KUNIT_EXPECT_EQ(test, GSP_MIG_VERIFY, (1 << 3)); -} - -static void test_bar_trap_types(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_BAR_READ, 0); - KUNIT_EXPECT_EQ(test, GSP_BAR_WRITE, 1); - KUNIT_EXPECT_EQ(test, GSP_BAR_ATOMIC, 2); -} - -static void test_pin_flags(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_PIN_READ, (1 << 0)); - KUNIT_EXPECT_EQ(test, GSP_PIN_WRITE, (1 << 1)); - KUNIT_EXPECT_EQ(test, GSP_PIN_LONGTERM, (1 << 2)); -} - -static void test_wpr_flags(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_WPR_WRITE, (1 << 0)); - KUNIT_EXPECT_EQ(test, GSP_WPR_READ, (1 << 1)); - KUNIT_EXPECT_EQ(test, GSP_WPR_EXECUTE, (1 << 2)); -} - -static void test_vf_states(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_VF_CREATING, 0); - KUNIT_EXPECT_EQ(test, GSP_VF_ACTIVE, 1); - KUNIT_EXPECT_EQ(test, GSP_VF_MIGRATING, 2); - KUNIT_EXPECT_EQ(test, GSP_VF_SUSPENDED, 3); - KUNIT_EXPECT_EQ(test, GSP_VF_DESTROYING, 4); -} - -static void test_msg_flags(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_MSG_SYNC, (1 << 0)); - KUNIT_EXPECT_EQ(test, GSP_MSG_ASYNC, (1 << 1)); - KUNIT_EXPECT_EQ(test, GSP_MSG_NOREPLY, (1 << 2)); -} - -/* ============================================================ - * Test: Error Code Translation - * ============================================================ */ - -static void test_error_codes(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_OK), 0); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EPERM), -EPERM); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOENT), -ENOENT); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EINVAL), -EINVAL); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENODEV), -ENODEV); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOMEM), -ENOMEM); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EBUSY), -EBUSY); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOSPC), -ENOSPC); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ETIMEDOUT), -ETIMEDOUT); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EOVERFLOW), -EOVERFLOW); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EDEV), -EIO); - KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(0xDEADBEEF), -EIO); -} - -/* ============================================================ - * Test: Memory Types - * ============================================================ */ - -static void test_memory_types(struct kunit *test) -{ - KUNIT_EXPECT_EQ(test, GSP_MEM_VRAM, 0); - KUNIT_EXPECT_EQ(test, GSP_MEM_SYSMEM, 1); - KUNIT_EXPECT_EQ(test, GSP_MEM_UNCACHED, 2); -} - -/* ============================================================ - * Test: Payload Capacity - * ============================================================ */ - -static void test_payload_capacity(struct kunit *test) -{ - KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_alloc_payload) <= 192); - KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_pin_payload) <= 192); - KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_bar_trap_payload) <= 192); - KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_migrate_payload) <= 192); - KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_subcmd_desc) <= 192); -} - -/* ============================================================ - * Test Suite Registration - * ============================================================ */ - -static struct kunit_case gsp_vgpu_test_cases[] = { - KUNIT_CASE(test_structure_sizes), - KUNIT_CASE(test_structure_alignments), - KUNIT_CASE(test_alloc_payload_encoding), - KUNIT_CASE(test_pin_payload_encoding), - KUNIT_CASE(test_bar_trap_payload_encoding), - KUNIT_CASE(test_migrate_payload_encoding), - KUNIT_CASE(test_subcmd_desc_encoding), - KUNIT_CASE(test_firmware_addresses), - KUNIT_CASE(test_rpc_types), - KUNIT_CASE(test_migration_constants), - KUNIT_CASE(test_bar_trap_types), - KUNIT_CASE(test_pin_flags), - KUNIT_CASE(test_wpr_flags), - KUNIT_CASE(test_vf_states), - KUNIT_CASE(test_msg_flags), - KUNIT_CASE(test_error_codes), - KUNIT_CASE(test_memory_types), - KUNIT_CASE(test_payload_capacity), - {} -}; - -static struct kunit_suite gsp_vgpu_test_suite = { - .name = "gsp-vgpu-io", - .test_cases = gsp_vgpu_test_cases, -}; - -kunit_test_suites(&gsp_vgpu_test_suite); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Open-IOV Project"); -MODULE_DESCRIPTION("GSP vGPU-IO KUnit Tests"); \ No newline at end of file diff --git a/kernel/tests/gsp_vgpu_mock_test.c b/kernel/tests/gsp_vgpu_mock_test.c deleted file mode 100644 index b9a46df..0000000 --- a/kernel/tests/gsp_vgpu_mock_test.c +++ /dev/null @@ -1,624 +0,0 @@ -/* - * GSP vGPU-IO Driver Mock Test - * Tests driver logic with mocked hardware interfaces - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define TEST_PASS 0 -#define TEST_FAIL -1 - -static int test_count = 0; -static int pass_count = 0; -static int fail_count = 0; - -#define TEST_ASSERT(cond, msg) \ - do { \ - test_count++; \ - if (cond) { \ - pass_count++; \ - pr_info("PASS: %s\n", msg); \ - } else { \ - fail_count++; \ - pr_err("FAIL: %s\n", msg); \ - } \ - } while (0) - -#define TEST_ASSERT_EQ(a, b, msg) \ - TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")") - -/* ============================================================ - * Mock Hardware Abstraction - * ============================================================ */ - -struct mock_hw { - u64 bar0_regs[256]; - u64 bar1_regs[256]; - u8 *fw_memory; - size_t fw_size; - bool doorbell_triggered; - u32 doorbell_count; - spinlock_t lock; -}; - -static struct mock_hw *mock_hw_create(void) -{ - struct mock_hw *hw = kzalloc(sizeof(*hw), GFP_KERNEL); - if (!hw) - return NULL; - - hw->fw_memory = vmalloc(32 * 1024 * 1024); // 32MB - if (!hw->fw_memory) { - kfree(hw); - return NULL; - } - hw->fw_size = 32 * 1024 * 1024; - spin_lock_init(&hw->lock); - return hw; -} - -static void mock_hw_destroy(struct mock_hw *hw) -{ - if (!hw) - return; - vfree(hw->fw_memory); - kfree(hw); -} - -static void mock_hw_write_bar0(struct mock_hw *hw, u32 offset, u32 value) -{ - unsigned long flags; - spin_lock_irqsave(&hw->lock, flags); - if (offset / 4 < 256) - hw->bar0_regs[offset / 4] = value; - if (offset == 0x1000) { // Doorbell - hw->doorbell_triggered = true; - hw->doorbell_count++; - } - spin_unlock_irqrestore(&hw->lock, flags); -} - -static u32 mock_hw_read_bar0(struct mock_hw *hw, u32 offset) -{ - unsigned long flags; - u32 value = 0; - spin_lock_irqsave(&hw->lock, flags); - if (offset / 4 < 256) - value = hw->bar0_regs[offset / 4]; - spin_unlock_irqrestore(&hw->lock, flags); - return value; -} - -static void mock_hw_write_bar1(struct mock_hw *hw, u64 offset, u64 value) -{ - unsigned long flags; - spin_lock_irqsave(&hw->lock, flags); - if (offset < hw->fw_size) - *(u64 *)(hw->fw_memory + offset) = value; - spin_unlock_irqrestore(&hw->lock, flags); -} - -static u64 mock_hw_read_bar1(struct mock_hw *hw, u64 offset) -{ - unsigned long flags; - u64 value = 0; - spin_lock_irqsave(&hw->lock, flags); - if (offset < hw->fw_size) - value = *(u64 *)(hw->fw_memory + offset); - spin_unlock_irqrestore(&hw->lock, flags); - return value; -} - -/* ============================================================ - * Mock VF Operations - * ============================================================ */ - -struct mock_vf { - u32 id; - u64 bar1_offset; - u64 bar1_size; - u64 pt_base; - u64 pt_size; - u64 cmd_base; - u64 cmd_size; - bool active; - struct { - u64 aperture_offset; - u64 aperture_size; - bool force_access; - u32 force_vf; - } bar_state; - struct list_head wpr_list; - struct mutex lock; -}; - -struct mock_wpr_region { - struct list_head list; - u64 base; - u64 size; - u32 flags; -}; - -static struct mock_vf *mock_vf_create(u32 id, u64 bar1_offset, u64 bar1_size, - u64 pt_base, u64 pt_size, - u64 cmd_base, u64 cmd_size) -{ - struct mock_vf *vf = kzalloc(sizeof(*vf), GFP_KERNEL); - if (!vf) - return NULL; - - vf->id = id; - vf->bar1_offset = bar1_offset; - vf->bar1_size = bar1_size; - vf->pt_base = pt_base; - vf->pt_size = pt_size; - vf->cmd_base = cmd_base; - vf->cmd_size = cmd_size; - vf->active = true; - INIT_LIST_HEAD(&vf->wpr_list); - mutex_init(&vf->lock); - - vf->bar_state.aperture_offset = bar1_offset; - vf->bar_state.aperture_size = bar1_size; - vf->bar_state.force_access = false; - vf->bar_state.force_vf = 0; - - return vf; -} - -static void mock_vf_destroy(struct mock_vf *vf) -{ - if (!vf) - return; - - struct mock_wpr_region *rgn, *tmp; - mutex_lock(&vf->lock); - list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { - list_del(&rgn->list); - kfree(rgn); - } - mutex_unlock(&vf->lock); - - kfree(vf); -} - -static int mock_vf_wpr_add(struct mock_vf *vf, u64 base, u64 size, u32 flags) -{ - struct mock_wpr_region *rgn = kzalloc(sizeof(*rgn), GFP_KERNEL); - if (!rgn) - return -ENOMEM; - - rgn->base = base; - rgn->size = size; - rgn->flags = flags; - - mutex_lock(&vf->lock); - list_add(&rgn->list, &vf->wpr_list); - mutex_unlock(&vf->lock); - - return 0; -} - -static int mock_vf_wpr_remove(struct mock_vf *vf, u64 base) -{ - struct mock_wpr_region *rgn, *tmp; - bool found = false; - - mutex_lock(&vf->lock); - list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { - if (rgn->base == base) { - list_del(&rgn->list); - kfree(rgn); - found = true; - break; - } - } - mutex_unlock(&vf->lock); - - return found ? 0 : -ENOENT; -} - -/* ============================================================ - * Mock RPC Client - * ============================================================ */ - -struct mock_rpc_client { - u32 seq; - u32 pending_seq[64]; - u32 pending_count; - struct mutex lock; -}; - -static struct mock_rpc_client *mock_rpc_create(void) -{ - struct mock_rpc_client *rpc = kzalloc(sizeof(*rpc), GFP_KERNEL); - if (!rpc) - return NULL; - mutex_init(&rpc->lock); - return rpc; -} - -static void mock_rpc_destroy(struct mock_rpc_client *rpc) -{ - if (!rpc) - return; - kfree(rpc); -} - -static int mock_rpc_send(struct mock_rpc_client *rpc, u32 rpc_type, - void *payload, size_t payload_size, - void *response, size_t *response_size) -{ - if (!rpc) - return -EINVAL; - - mutex_lock(&rpc->lock); - rpc->seq++; - rpc->pending_seq[rpc->pending_count++] = rpc->seq; - mutex_unlock(&rpc->lock); - - /* Simulate successful response */ - if (response && response_size) { - struct gsp_rpc_response *resp = response; - resp->status = 0; - resp->rpc_type = rpc_type; - resp->sequence = rpc->seq; - *response_size = sizeof(*resp); - } - - return 0; -} - -/* ============================================================ - * Driver Logic Tests with Mocks - * ============================================================ */ - -static int test_mock_hardware(void) -{ - struct mock_hw *hw = mock_hw_create(); - TEST_ASSERT(hw != NULL, "Mock HW create"); - - /* Test BAR0 write/read */ - mock_hw_write_bar0(hw, 0x1000, 0x1); - TEST_ASSERT(hw->doorbell_triggered, "Doorbell triggered"); - TEST_ASSERT_EQ(hw->doorbell_count, 1, "Doorbell count"); - - u32 val = mock_hw_read_bar0(hw, 0x1000); - TEST_ASSERT_EQ(val, 0x1, "BAR0 read back"); - - /* Test BAR1 write/read */ - mock_hw_write_bar1(hw, 0x1000, 0xDEADBEEFCAFEBABE); - u64 val64 = mock_hw_read_bar1(hw, 0x1000); - TEST_ASSERT_EQ(val64, 0xDEADBEEFCAFEBABE, "BAR1 read back"); - - mock_hw_destroy(hw); - return TEST_PASS; -} - -static int test_mock_vf_lifecycle(void) -{ - struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000, - 0x20000000, 0x100000, - 0x30000000, 0x10000); - TEST_ASSERT(vf != NULL, "Mock VF create"); - - TEST_ASSERT(vf->active, "VF active after create"); - TEST_ASSERT_EQ(vf->id, 1, "VF ID"); - TEST_ASSERT_EQ(vf->bar1_offset, 0x10000000, "BAR1 offset"); - TEST_ASSERT_EQ(vf->bar1_size, 0x100000, "BAR1 size"); - TEST_ASSERT_EQ(vf->pt_base, 0x20000000, "Page table base"); - TEST_ASSERT_EQ(vf->pt_size, 0x100000, "Page table size"); - - /* Test BAR state */ - TEST_ASSERT_EQ(vf->bar_state.aperture_offset, 0x10000000, "Aperture offset"); - TEST_ASSERT_EQ(vf->bar_state.aperture_size, 0x100000, "Aperture size"); - TEST_ASSERT(!vf->bar_state.force_access, "Force access initially false"); - - /* Test WPR list empty */ - TEST_ASSERT(list_empty(&vf->wpr_list), "WPR list empty initially"); - - mock_vf_destroy(vf); - return TEST_PASS; -} - -static int test_mock_vf_wpr(void) -{ - struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000, - 0x20000000, 0x100000, - 0x30000000, 0x10000); - TEST_ASSERT(vf != NULL, "Mock VF create"); - - /* Add WPR region */ - int ret = mock_vf_wpr_add(vf, 0x10000000, 0x10000, 3); - TEST_ASSERT_EQ(ret, 0, "WPR add"); - - ret = mock_vf_wpr_add(vf, 0x20000000, 0x20000, 1); - TEST_ASSERT_EQ(ret, 0, "WPR add second"); - - /* Verify list */ - int count = 0; - struct mock_wpr_region *rgn; - mutex_lock(&vf->lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - count++; - } - mutex_unlock(&vf->lock); - TEST_ASSERT_EQ(count, 2, "Two WPR regions"); - - /* Remove one */ - ret = mock_vf_wpr_remove(vf, 0x10000000); - TEST_ASSERT_EQ(ret, 0, "WPR remove"); - - count = 0; - mutex_lock(&vf->lock); - list_for_each_entry(rgn, &vf->wpr_list, list) { - count++; - } - mutex_unlock(&vf->lock); - TEST_ASSERT_EQ(count, 1, "One WPR region remaining"); - - /* Remove non-existent */ - ret = mock_vf_wpr_remove(vf, 0x30000000); - TEST_ASSERT_EQ(ret, -ENOENT, "WPR remove non-existent returns ENOENT"); - - mock_vf_destroy(vf); - return TEST_PASS; -} - -static int test_mock_rpc_client(void) -{ - struct mock_rpc_client *rpc = mock_rpc_create(); - TEST_ASSERT(rpc != NULL, "Mock RPC create"); - - struct gsp_rpc_response resp; - size_t resp_size = sizeof(resp); - struct gsp_alloc_payload payload = { - .size = cpu_to_le64(0x100000), - .type = cpu_to_le32(GSP_MEM_VRAM), - }; - - int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "RPC send"); - - TEST_ASSERT_EQ(resp.status, 0, "RPC response status"); - TEST_ASSERT_EQ(resp.rpc_type, GSP_RPC_ALLOC, "RPC response type"); - TEST_ASSERT_EQ(resp.sequence, 1, "RPC response sequence"); - - /* Second call */ - ret = mock_rpc_send(rpc, GSP_RPC_PIN, &payload, sizeof(payload), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "RPC send second"); - TEST_ASSERT_EQ(resp.sequence, 2, "RPC response sequence 2"); - - mock_rpc_destroy(rpc); - return TEST_PASS; -} - -static int test_driver_vf_creation_logic(void) -{ - /* Simulate driver VF creation logic */ - struct mock_hw *hw = mock_hw_create(); - struct mock_rpc_client *rpc = mock_rpc_create(); - struct mock_vf *vfs[8] = {0}; - - /* Create VFs 1-4 */ - for (int i = 1; i <= 4; i++) { - vfs[i] = mock_vf_create(i, 0x10000000 * i, 0x100000, - 0x20000000 * i, 0x100000, - 0x30000000 * i, 0x10000); - TEST_ASSERT(vfs[i] != NULL, "VF creation in loop"); - TEST_ASSERT(vfs[i]->active, "VF active in loop"); - } - - /* Initialize BAR for each VF */ - for (int i = 1; i <= 4; i++) { - vfs[i]->bar_state.aperture_offset = vfs[i]->bar1_offset; - vfs[i]->bar_state.aperture_size = vfs[i]->bar1_size; - TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_offset, 0x10000000 * i, "VF BAR offset"); - TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_size, 0x100000, "VF BAR size"); - } - - /* Add WPR for each VF */ - for (int i = 1; i <= 4; i++) { - int ret = mock_vf_wpr_add(vfs[i], vfs[i]->pt_base, vfs[i]->pt_size, 3); - TEST_ASSERT_EQ(ret, 0, "VF WPR add in loop"); - } - - /* Simulate RPC calls for each VF */ - for (int i = 1; i <= 4; i++) { - struct gsp_rpc_response resp; - size_t resp_size = sizeof(resp); - struct gsp_alloc_payload payload = { - .size = cpu_to_le64(0x100000), - .type = cpu_to_le32(GSP_MEM_VRAM), - .vf_id = cpu_to_le32(i), - }; - int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "VF RPC alloc"); - TEST_ASSERT_EQ(resp.status, 0, "VF RPC response OK"); - } - - /* Cleanup */ - for (int i = 1; i <= 4; i++) { - mock_vf_destroy(vfs[i]); - } - mock_rpc_destroy(rpc); - mock_hw_destroy(hw); - - return TEST_PASS; -} - -static int test_migration_progress_tracking(void) -{ - struct { - u64 pages_transferred; - u64 bytes_transferred; - u64 dirty_pages; - u64 *page_list; - u32 page_count; - } progress = {0}; - - progress.page_count = 1000; - progress.page_list = kcalloc(progress.page_count, sizeof(u64), GFP_KERNEL); - TEST_ASSERT(progress.page_list != NULL, "Progress page list alloc"); - - /* Simulate transfer loop */ - for (int iter = 0; iter < 10; iter++) { - progress.pages_transferred += 100; - progress.bytes_transferred += 100 * 4096; - progress.dirty_pages = 50 - (iter * 5); - - TEST_ASSERT(progress.pages_transferred <= 1000, "Pages not exceeded"); - TEST_ASSERT(progress.bytes_transferred <= 1000 * 4096, "Bytes not exceeded"); - } - - TEST_ASSERT_EQ(progress.pages_transferred, 1000, "Final pages"); - TEST_ASSERT_EQ(progress.bytes_transferred, 1000 * 4096, "Final bytes"); - TEST_ASSERT_EQ(progress.dirty_pages, 0, "Final dirty pages"); - - kfree(progress.page_list); - return TEST_PASS; -} - -static int test_bar_state_machine(void) -{ - struct { - u64 aperture_offset; - u64 aperture_size; - bool force_access; - u32 force_vf; - } bar_state = {0}; - - /* Initial state */ - bar_state.aperture_offset = 0x10000000; - bar_state.aperture_size = 0x100000; - TEST_ASSERT_EQ(bar_state.aperture_offset, 0x10000000, "Initial aperture offset"); - TEST_ASSERT(!bar_state.force_access, "Force access initially false"); - - /* Enable force access */ - bar_state.force_access = true; - bar_state.force_vf = 1; - TEST_ASSERT(bar_state.force_access, "Force access enabled"); - TEST_ASSERT_EQ(bar_state.force_vf, 1, "Force VF set"); - - /* Disable force access */ - bar_state.force_access = false; - bar_state.force_vf = 0; - TEST_ASSERT(!bar_state.force_access, "Force access disabled"); - TEST_ASSERT_EQ(bar_state.force_vf, 0, "Force VF cleared"); - - return TEST_PASS; -} - -static int test_concurrent_vf_operations(void) -{ - struct mock_vf *vf1 = mock_vf_create(1, 0x10000000, 0x100000, 0x20000000, 0x100000, 0x30000000, 0x10000); - struct mock_vf *vf2 = mock_vf_create(2, 0x20000000, 0x100000, 0x40000000, 0x100000, 0x60000000, 0x10000); - struct mock_rpc_client *rpc = mock_rpc_create(); - - TEST_ASSERT(vf1 && vf2 && rpc, "Create two VFs and RPC"); - - /* Simulate concurrent operations */ - struct gsp_rpc_response resp; - size_t resp_size = sizeof(resp); - - /* VF1 alloc */ - struct gsp_alloc_payload payload1 = { - .size = cpu_to_le64(0x100000), - .type = cpu_to_le32(GSP_MEM_VRAM), - .vf_id = cpu_to_le32(1), - }; - int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload1, sizeof(payload1), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "VF1 alloc"); - TEST_ASSERT_EQ(resp.status, 0, "VF1 alloc response"); - - /* VF2 alloc */ - struct gsp_alloc_payload payload2 = { - .size = cpu_to_le64(0x200000), - .type = cpu_to_le32(GSP_MEM_VRAM), - .vf_id = cpu_to_le32(2), - }; - ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload2, sizeof(payload2), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "VF2 alloc"); - TEST_ASSERT_EQ(resp.status, 0, "VF2 alloc response"); - - /* VF1 pin */ - struct gsp_pin_payload pin_payload = { - .handle = cpu_to_le64(0x1000), - .vf_id = cpu_to_le32(1), - }; - ret = mock_rpc_send(rpc, GSP_RPC_PIN, &pin_payload, sizeof(pin_payload), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "VF1 pin"); - - /* VF2 BAR trap */ - struct gsp_bar_trap_payload bar_payload = { - .vf_id = cpu_to_le32(2), - .bar_index = cpu_to_le32(1), - }; - ret = mock_rpc_send(rpc, GSP_RPC_BAR_TRAP, &bar_payload, sizeof(bar_payload), &resp, &resp_size); - TEST_ASSERT_EQ(ret, 0, "VF2 BAR trap"); - - /* Verify sequence numbers */ - TEST_ASSERT_EQ(resp.sequence, 4, "Sequence number 4"); - - mock_vf_destroy(vf1); - mock_vf_destroy(vf2); - mock_rpc_destroy(rpc); - - return TEST_PASS; -} - -/* ============================================================ - * Module Init/Exit - * ============================================================ */ - -static int __init gsp_mock_test_init(void) -{ - pr_info("========================================\n"); - pr_info("GSP vGPU-IO Mock Driver Test Suite\n"); - pr_info("========================================\n"); - - test_mock_hardware(); - test_mock_vf_lifecycle(); - test_mock_vf_wpr(); - test_mock_rpc_client(); - test_driver_vf_creation_logic(); - test_migration_progress_tracking(); - test_bar_state_machine(); - test_concurrent_vf_operations(); - - pr_info("========================================\n"); - pr_info("Mock Test Results: %d total, %d passed, %d failed\n", - test_count, pass_count, fail_count); - pr_info("========================================\n"); - - if (fail_count > 0) { - pr_err("MOCK TEST SUITE FAILED\n"); - return -EINVAL; - } - - pr_info("MOCK TEST SUITE PASSED\n"); - return 0; -} - -static void __exit gsp_mock_test_exit(void) -{ - pr_info("GSP vGPU-IO Mock Test Suite Exited\n"); -} - -module_init(gsp_mock_test_init); -module_exit(gsp_mock_test_exit); - -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Open-IOV Project"); -MODULE_DESCRIPTION("GSP vGPU-IO Mock Driver Test"); -MODULE_VERSION("1.0"); \ No newline at end of file diff --git a/legacy-driver-guest/Makefile b/legacy-driver-guest/Makefile new file mode 100644 index 0000000..4675cbf --- /dev/null +++ b/legacy-driver-guest/Makefile @@ -0,0 +1,44 @@ +# GSP vGPU-IO Driver Makefile + +obj-m += gsp-vgpu.o + +gsp-vgpu-y := \ + core/gsp_core.o \ + rpc/gsp_rpc.o \ + vf/gsp_vf.o \ + bar/gsp_bar.o \ + wpr/gsp_wpr.o \ + migration/gsp_migration.o \ + debugfs/gsp_debugfs.o + +ccflags-y += -I$(src)/include +ccflags-y += -I$(src)/../kernel/include + +# Build against the kernel source +KDIR ?= /lib/modules/$(shell uname -r)/build + +all: + $(MAKE) -C $(KDIR) M=$(PWD) modules + +clean: + $(MAKE) -C $(KDIR) M=$(PWD) clean + +install: + $(MAKE) -C $(KDIR) M=$(PWD) modules_install + +load: all + sudo insmod gsp-vgpu.ko firmware_name=gsp-570.144.fwimage.bin + +unload: + sudo rmmod gsp-vgpu + +test: all load + @echo "Driver loaded, checking status..." + sleep 2 + dmesg -T | tail -20 + @echo "Checking debugfs..." + ls -la /sys/kernel/debug/gsp-vgpu/ 2>/dev/null || echo "Debugfs not available" + @echo "Checking device nodes..." + ls -la /dev/gsp-vgpu* 2>/dev/null || echo "No device nodes" + +.PHONY: all clean install load unload test \ No newline at end of file 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/legacy-driver-guest/bar/gsp_bar.c b/legacy-driver-guest/bar/gsp_bar.c new file mode 100644 index 0000000..aa3ed62 --- /dev/null +++ b/legacy-driver-guest/bar/gsp_bar.c @@ -0,0 +1,242 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - BAR Emulation + */ + +#include "gsp_core.h" +#include +#include + +/* BAR Emulation Ops */ +static int gsp_bar_trap_handler(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap) +{ + struct gsp_bar_state *bar = &vf->bar_state; + struct gsp_bar_trap_response resp = {0}; + size_t resp_size = sizeof(resp); + int ret; + + /* Validate BAR index (we only support BAR1) */ + if (trap->bar_index != 1) + return -EINVAL; + + /* Validate offset */ + if (trap->offset >= bar->aperture_size) + return -ERANGE; + + /* Check ForceBarAccess */ + if (bar->force_access && vf->id != bar->force_vf) + return -EPERM; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + /* Simulate BAR trap handling */ + if (trap->trap_type == GSP_BAR_WRITE) { + /* Config write - update aperture */ + bar->aperture_offset = trap->offset; + bar->aperture_size = trap->value; + } else if (trap->trap_type == GSP_BAR_ATOMIC) { + /* Force access control */ + bar->force_access = trap->offset ? true : false; + bar->force_vf = trap->offset ? vf->id : 0; + } + resp.value = 0; + return 0; + } + + /* Forward to GSP BAR trap handler (0x04CB8188) */ + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, + trap, sizeof(*trap), &resp, &resp_size); + + return ret; +} + +/* Aperture Configuration */ +static int gsp_bar_config_aperture(struct gsp_vf *vf, uint64_t offset, uint64_t size) +{ + struct gsp_bar_state *bar = &vf->bar_state; + struct gsp_bar_trap_payload payload = {0}; + struct gsp_bar_trap_response resp = {0}; + size_t resp_size = sizeof(resp); + int ret; + + /* Validate alignment (4KB) */ + if (offset & 0xFFF || size & 0xFFF) + return -EINVAL; + + /* Validate max size (256KB per firmware) */ + if (size > 0x40000) + return -EINVAL; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + bar->aperture_offset = offset; + bar->aperture_size = size; + return 0; + } + + payload.bar_index = 1; + payload.trap_type = GSP_BAR_WRITE; /* Config write */ + payload.offset = offset; + payload.value = size; + payload.size = 8; + payload.vf_id = vf->id; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, + &payload, sizeof(payload), &resp, &resp_size); + if (!ret) { + bar->aperture_offset = offset; + bar->aperture_size = size; + } + + return ret; +} + +/* ForceBarAccess Control */ +int gsp_bar_force_access(struct gsp_vf *vf, bool enable) +{ + struct gsp_bar_state *bar = &vf->bar_state; + struct gsp_bar_trap_payload payload = {0}; + struct gsp_bar_trap_response resp = {0}; + size_t resp_size = sizeof(resp); + int ret; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + bar->force_access = enable; + bar->force_vf = enable ? vf->id : 0; + return 0; + } + + payload.bar_index = 1; + payload.trap_type = GSP_BAR_ATOMIC; /* Force access control */ + payload.offset = enable ? 1 : 0; + payload.value = enable ? vf->id : 0; + payload.size = 8; + payload.vf_id = vf->id; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_BAR_TRAP, + &payload, sizeof(payload), &resp, &resp_size); + if (!ret) { + bar->force_access = enable; + bar->force_vf = enable ? vf->id : 0; + } + + return ret; +} + +/* State Save for Migration */ +static int gsp_bar_save_state(struct gsp_vf *vf, void *buf, size_t *size) +{ + struct gsp_bar_state *bar = &vf->bar_state; + size_t needed = sizeof(*bar) + bar->backing_size; + + if (!buf || *size < needed) + return -ENOSPC; + + memcpy(buf, bar, sizeof(*bar)); + if (bar->backing_store && bar->backing_size > 0) + memcpy(buf + sizeof(*bar), bar->backing_store, bar->backing_size); + + *size = needed; + return 0; +} + +/* State Restore for Migration */ +static int gsp_bar_restore_state(struct gsp_vf *vf, void *buf, size_t size) +{ + struct gsp_bar_state *bar = &vf->bar_state; + size_t min_size = sizeof(*bar); + + if (!buf || size < min_size) + return -EINVAL; + + memcpy(bar, buf, sizeof(*bar)); + + if (size > min_size && bar->backing_size > 0) { + if (!bar->backing_store) { + bar->backing_store = vzalloc(bar->backing_size); + if (!bar->backing_store) + return -ENOMEM; + } + memcpy(bar->backing_store, buf + min_size, + min(size - min_size, bar->backing_size)); + } + + /* Reconfigure BAR in GSP */ + return gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size); +} + +/* VF BAR Initialization */ +int gsp_vf_bar_init(struct gsp_vf *vf) +{ + struct gsp_bar_state *bar = &vf->bar_state; + int ret; + + /* Initialize with config values */ + bar->aperture_offset = vf->bar1_offset; + bar->aperture_size = vf->bar1_size; + bar->force_access = false; + bar->force_vf = 0; + bar->backing_store = NULL; + bar->backing_size = 0; + + /* Allocate backing store (max 256KB) */ + if (bar->aperture_size > 0) { + bar->backing_size = min(bar->aperture_size, (uint64_t)0x40000); + bar->backing_store = vzalloc(bar->backing_size); + if (!bar->backing_store) + return -ENOMEM; + } + + /* Configure aperture in GSP (skip in emulation mode) */ + if (!emulation_mode) { + ret = gsp_bar_config_aperture(vf, bar->aperture_offset, bar->aperture_size); + if (ret) { + dev_err(vf->gsp->dev, "VF %u: BAR aperture config failed: %d\n", vf->id, ret); + goto err_backing; + } + } else { + dev_info(vf->gsp->dev, "VF %u: BAR1 initialized in emulation mode [0x%llx, 0x%llx)\n", + vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size); + } + + dev_info(vf->gsp->dev, "VF %u: BAR1 initialized [0x%llx, 0x%llx)\n", + vf->id, bar->aperture_offset, bar->aperture_offset + bar->aperture_size); + + return 0; + +err_backing: + vfree(bar->backing_store); + bar->backing_store = NULL; + bar->backing_size = 0; + return ret; +} + +/* VF BAR Cleanup */ +void gsp_vf_bar_fini(struct gsp_vf *vf) +{ + struct gsp_bar_state *bar = &vf->bar_state; + + vfree(bar->backing_store); + bar->backing_store = NULL; + bar->backing_size = 0; + + dev_info(vf->gsp->dev, "VF %u: BAR1 cleaned up\n", vf->id); +} + +/* BAR Trap Entry Point (called from ISR/notification) */ +int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap) +{ + return gsp_bar_trap_handler(vf, trap); +} + +/* Migration Interface */ +int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size) +{ + return gsp_bar_save_state(vf, buf, size); +} + +int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size) +{ + return gsp_bar_restore_state(vf, buf, size); +} \ No newline at end of file diff --git a/legacy-driver-guest/core/gsp_core.c b/legacy-driver-guest/core/gsp_core.c new file mode 100644 index 0000000..cd41bd2 --- /dev/null +++ b/legacy-driver-guest/core/gsp_core.c @@ -0,0 +1,654 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - Core Implementation + * Guest vGPU driver - communicates with GSP already booted by host GRID driver + */ + +#include "gsp_core.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DRIVER_NAME "gsp-vgpu" +#define DRIVER_VERSION "0.1.0" + +/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */ +bool emulation_mode = true; /* ON by default */ +static bool emulation_override = false; /* true = force emulation, false = force native */ +module_param(emulation_mode, bool, 0444); +MODULE_PARM_DESC(emulation_mode, "Enable emulation mode for BAR/WPR (skip GSP RPC) - default ON, auto-detects if GSP has vGPU handlers"); +module_param(emulation_override, bool, 0444); +MODULE_PARM_DESC(emulation_override, "Override auto-detection: 1=force emulation, 0=force native (default: auto-detect)"); + +/* Forward declarations */ +int gsp_firmware_load(struct gsp_device *gsp); +int gsp_setup_irq(struct gsp_device *gsp); +int gsp_rpc_init(struct gsp_device *gsp); +void gsp_shutdown(struct gsp_device *gsp); +static void gsp_detect_emulation_mode(struct gsp_device *gsp); +static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg); +static int gsp_open(struct inode *inode, struct file *file); +static int gsp_release(struct inode *inode, struct file *file); + +/* Char device file operations */ +static const struct file_operations gsp_fops = { + .owner = THIS_MODULE, + .open = gsp_open, + .release = gsp_release, + .unlocked_ioctl = gsp_ioctl, + .compat_ioctl = gsp_ioctl, +}; + +/* Char device open */ +static int gsp_open(struct inode *inode, struct file *file) +{ + struct gsp_device *gsp = container_of(file->private_data, struct gsp_device, miscdev); + file->private_data = gsp; + return 0; +} + +/* Char device release */ +static int gsp_release(struct inode *inode, struct file *file) +{ + return 0; +} + +/* Char device ioctl */ +static long gsp_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct gsp_device *gsp = file->private_data; + void __user *argp = (void __user *)arg; + int ret = 0; + + mutex_lock(&gsp->chardev_lock); + + switch (cmd) { + case GSP_IOCTL_CREATE_VF: { + struct gsp_vf_config config; + if (copy_from_user(&config, argp, sizeof(config))) { + ret = -EFAULT; + break; + } + ret = gsp_vf_create(gsp, &config); + break; + } + case GSP_IOCTL_DESTROY_VF: { + uint32_t vf_id; + if (copy_from_user(&vf_id, argp, sizeof(vf_id))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + gsp_vf_destroy(vf); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_ALLOC: { + struct gsp_alloc_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_alloc(vf, args.size, args.flags, &args.handle, &args.va); + if (!ret) + ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_FREE: { + struct gsp_free_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_free(vf, args.handle); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_PIN: { + struct gsp_pin_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_pin(vf, args.handle, args.offset, args.size, &args.dma_addr); + if (!ret) + ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_UNPIN: { + struct gsp_unpin_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_unpin(vf, args.handle); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_MAP: { + struct gsp_map_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_map(vf, args.handle, args.gpu_va, args.size, + args.prot, args.flags, args.offset, &args.mapped_va); + if (!ret) + ret = copy_to_user(argp, &args, sizeof(args)) ? -EFAULT : 0; + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_UNMAP: { + struct gsp_unmap_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_mem_unmap(vf, args.handle, args.gpu_va, args.size); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_BAR_CONFIG: { + struct gsp_bar_config_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_bar_init(vf); /* Re-init with new aperture */ + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_BAR_FORCE: { + struct gsp_bar_force_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_bar_force_access(vf, args.enable); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_BAR_SAVE: { + struct gsp_bar_save_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_migration_bar_save(vf, args.buf, args.size); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_BAR_RESTORE: { + struct gsp_bar_restore_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_migration_bar_restore(vf, args.buf, args.size); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_WPR_PROTECT: { + struct gsp_wpr_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_wpr_protect(vf, args.base, args.size, args.flags); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_WPR_UNPROTECT: { + struct gsp_wpr_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_wpr_unprotect(vf, args.base); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_WPR_TIMEOUT: { + struct gsp_wpr_timeout_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_wpr_set_timeout(vf, args.timeout_ns); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_SCHEDULE: { + struct gsp_schedule_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_schedule(vf, args.priority, args.time_slice_ns); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_YIELD: { + uint32_t vf_id; + if (copy_from_user(&vf_id, argp, sizeof(vf_id))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_vf_yield(vf); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_MIGRATE: { + struct gsp_migrate_args args; + if (copy_from_user(&args, argp, sizeof(args))) { + ret = -EFAULT; + break; + } + struct gsp_vf *vf = gsp_vf_get(gsp, args.vf_id); + if (!vf) { + ret = -ENOENT; + break; + } + ret = gsp_migration_start(vf, args.dst_vf); + gsp_vf_put(vf); + break; + } + case GSP_IOCTL_GET_INFO: { + char info[256]; + snprintf(info, sizeof(info), "GSP vGPU-IO Driver v%s (FW: 570.144W)", DRIVER_VERSION); + ret = copy_to_user(argp, info, sizeof(info)) ? -EFAULT : 0; + break; + } + default: + ret = -ENOTTY; + } + + mutex_unlock(&gsp->chardev_lock); + return ret; +} + +/* PCI Device IDs */ +static const struct pci_device_id gsp_pci_ids[] = { + { PCI_DEVICE(0x10DE, 0x1E07), .driver_data = 0 }, /* TU102 */ + { PCI_DEVICE(0x10DE, 0x1E08), .driver_data = 0 }, /* TU104 */ + { PCI_DEVICE(0x10DE, 0x1ED3), .driver_data = 0 }, /* TU104 RTX 2080 Super Mobile */ + { 0 } +}; +MODULE_DEVICE_TABLE(pci, gsp_pci_ids); + +/* Module Parameters */ +static int max_vfs = 7; +module_param(max_vfs, int, 0444); +MODULE_PARM_DESC(max_vfs, "Maximum number of VFs (default: 7)"); + +static char *firmware_name = "gsp-570.144.fwimage.bin"; +module_param(firmware_name, charp, 0444); +MODULE_PARM_DESC(firmware_name, "GSP firmware filename (for version verification only)"); + +/* PCI Probe */ +int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct gsp_device *gsp; + int ret; + + gsp = devm_kzalloc(&pdev->dev, sizeof(*gsp), GFP_KERNEL); + if (!gsp) + return -ENOMEM; + + gsp->pdev = pdev; + gsp->dev = &pdev->dev; + gsp->max_vfs = max_vfs; + pci_set_drvdata(pdev, gsp); + + mutex_init(&gsp->vf_lock); + idr_init(&gsp->vf_idr); + INIT_LIST_HEAD(&gsp->vf_list); + + /* Enable PCI device */ + ret = pcim_enable_device(pdev); + if (ret) { + dev_err(&pdev->dev, "Failed to enable PCI device: %d\n", ret); + return ret; + } + + pci_set_master(pdev); + + /* Map BARs */ + gsp->bar0 = pcim_iomap(pdev, 0, 0); + gsp->bar1 = pcim_iomap(pdev, 1, 0); + if (!gsp->bar0 || !gsp->bar1) { + dev_err(&pdev->dev, "Failed to map BARs\n"); + return -ENOMEM; + } + + dev_info(&pdev->dev, "BAR0: %p, BAR1: %p\n", gsp->bar0, gsp->bar1); + + /* Load Firmware (for version verification only - GSP already booted by host) */ + ret = gsp_firmware_load(gsp); + if (ret) { + dev_err(&pdev->dev, "Firmware load failed: %d\n", ret); + return ret; + } + + /* Initialize RPC client (sets up rings, doorbells) */ + gsp->rpc = gsp_rpc_client_init(gsp); + if (IS_ERR(gsp->rpc)) { + ret = PTR_ERR(gsp->rpc); + dev_err(&pdev->dev, "RPC client init failed: %d\n", ret); + return ret; + } + + /* Initialize RPC communication with GSP */ + ret = gsp_rpc_init(gsp); + if (ret) { + dev_err(&pdev->dev, "RPC init failed: %d\n", ret); + return ret; + } + + /* Setup IRQ */ + ret = gsp_setup_irq(gsp); + if (ret) { + dev_err(&pdev->dev, "IRQ setup failed: %d\n", ret); + return ret; + } + + /* Initialize char device */ + mutex_init(&gsp->chardev_lock); + gsp->miscdev.minor = MISC_DYNAMIC_MINOR; + gsp->miscdev.name = "gsp-vgpu"; + gsp->miscdev.fops = &gsp_fops; + gsp->miscdev.parent = &pdev->dev; + gsp->miscdev.mode = 0660; + + ret = misc_register(&gsp->miscdev); + if (ret) { + dev_err(&pdev->dev, "Failed to register misc device: %d\n", ret); + return ret; + } + + /* Debugfs */ + gsp_debugfs_init(gsp); + + dev_info(&pdev->dev, "GSP vGPU-IO driver loaded (FW: 570.144W)\n"); + return 0; +} + +/* PCI Remove */ +void gsp_remove(struct pci_dev *pdev) +{ + struct gsp_device *gsp = pci_get_drvdata(pdev); + struct gsp_vf *vf, *tmp; + + if (!gsp) + return; + + /* Destroy all VFs */ + list_for_each_entry_safe(vf, tmp, &gsp->vf_list, list) + gsp_vf_destroy(vf); + + /* Unregister char device */ + misc_deregister(&gsp->miscdev); + + gsp_debugfs_fini(gsp); + gsp_shutdown(gsp); + gsp_rpc_client_fini(gsp->rpc); + + if (gsp->fw) + release_firmware(gsp->fw); + + idr_destroy(&gsp->vf_idr); + + dev_info(&pdev->dev, "GSP vGPU-IO driver unloaded\n"); +} + +/* Firmware Load - only for version verification */ +int gsp_firmware_load(struct gsp_device *gsp) +{ + const struct firmware *fw; + int ret; + + ret = request_firmware(&fw, firmware_name, gsp->dev); + if (ret) { + dev_err(gsp->dev, "Failed to load firmware '%s': %d\n", firmware_name, ret); + return ret; + } + + /* Verify MD5: 13ce12a392a31eeb79a2e6ebd3a4a93a */ + if (fw->size != 0x1C50000) { /* ~28.5 MB */ + dev_warn(gsp->dev, "Firmware size unexpected: %zu (expected ~28.5MB)\n", fw->size); + } + + dev_info(gsp->dev, "Firmware verified: %zu bytes\n", fw->size); + gsp->fw = fw; + return 0; +} + +/* Initialize RPC communication with already-running GSP */ +int gsp_rpc_init(struct gsp_device *gsp) +{ + /* + * The GSP is already running (booted by host GRID driver). + * We just need to verify communication works by reading mailbox registers + * and optionally sending a test RPC. + */ + + /* Read mailbox registers to verify GSP is responsive */ + uint32_t mbox0 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX0); + uint32_t mbox1 = readl(gsp->bar0 + GSP_BAR0_FALCON_MAILBOX1); + + dev_info(gsp->dev, "GSP mailbox0: 0x%08x, mailbox1: 0x%08x\n", mbox0, mbox1); + + /* Check if GSP appears to be running (non-zero mailbox or queue heads) */ + uint32_t qhead0 = readl(gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); + dev_info(gsp->dev, "GSP queue head 0: 0x%08x\n", qhead0); + + /* Ring doorbell to signal we're ready - write to queue head */ + writel(1, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); + + gsp->fw_loaded = true; /* Indicates GSP communication established */ + dev_info(gsp->dev, "GSP RPC communication initialized\n"); + + /* Auto-detect emulation mode if not overridden */ + if (!emulation_override) { + gsp_detect_emulation_mode(gsp); + } else if (emulation_mode) { + dev_info(gsp->dev, "Emulation mode FORCED ON by module parameter\n"); + } else { + dev_info(gsp->dev, "Emulation mode FORCED OFF by module parameter\n"); + } + + return 0; +} + +/* Auto-detect if GSP has vGPU RPC handlers */ +static void gsp_detect_emulation_mode(struct gsp_device *gsp) +{ + struct gsp_bar_trap_payload test_payload = {0}; + struct gsp_bar_trap_response test_resp = {0}; + size_t resp_size = sizeof(test_resp); + int ret; + + dev_info(gsp->dev, "Auto-detecting GSP vGPU RPC handler support...\n"); + + /* Send a test BAR_TRAP RPC to check if GSP has vGPU handlers */ + test_payload.bar_index = 1; + test_payload.trap_type = GSP_BAR_READ; /* Non-destructive read */ + test_payload.offset = 0; + test_payload.value = 0; + test_payload.size = 8; + test_payload.vf_id = 0; + + /* Try synchronous RPC with short timeout (1 second) */ + ret = gsp_rpc_send_sync(gsp->rpc, GSP_RPC_BAR_TRAP, + &test_payload, sizeof(test_payload), + &test_resp, &resp_size); + + if (ret == -ETIMEDOUT || ret == -ENOTTY || ret == -EIO || ret == -ENODEV) { + /* GSP doesn't have vGPU handlers - enable emulation */ + emulation_mode = true; + dev_info(gsp->dev, "GSP lacks vGPU RPC handlers (ret=%d) - ENABLING emulation mode\n", ret); + } else if (ret == 0) { + /* GSP responded - has vGPU handlers */ + emulation_mode = false; + dev_info(gsp->dev, "GSP has vGPU RPC handlers - DISABLING emulation mode\n"); + } else { + /* Other error - default to emulation for safety */ + emulation_mode = true; + dev_info(gsp->dev, "GSP RPC test returned %d - ENABLING emulation mode (safe default)\n", ret); + } +} + +/* GSP Shutdown - just clean up our side */ +void gsp_shutdown(struct gsp_device *gsp) +{ + if (gsp->fw_loaded) { + /* Don't reset GSP - host driver owns it */ + gsp->fw_loaded = false; + } +} + +/* IRQ Setup */ +int gsp_setup_irq(struct gsp_device *gsp) +{ + int ret; + struct msix_entry entries[1]; + + /* Try MSI-X first */ + ret = pci_enable_msix_range(gsp->pdev, entries, 1, 1); + if (ret > 0) { + gsp->msix_enabled = true; + gsp->irq = entries[0].vector; + gsp->msix_entries[0] = entries[0]; + } else { + gsp->irq = gsp->pdev->irq; + } + + ret = devm_request_irq(gsp->dev, gsp->irq, gsp_isr, + gsp->msix_enabled ? 0 : IRQF_SHARED, + DRIVER_NAME, gsp); + if (ret) { + dev_err(gsp->dev, "Failed to request IRQ %d: %d\n", gsp->irq, ret); + return ret; + } + + dev_info(gsp->dev, "IRQ %d registered (MSI-X: %s)\n", + gsp->irq, gsp->msix_enabled ? "yes" : "no"); + return 0; +} + +/* PCI Driver Structure */ +static struct pci_driver gsp_pci_driver = { + .name = DRIVER_NAME, + .id_table = gsp_pci_ids, + .probe = gsp_probe, + .remove = gsp_remove, +}; + +/* Module Init/Exit */ +static int __init gsp_module_init(void) +{ + return pci_register_driver(&gsp_pci_driver); +} + +static void __exit gsp_module_exit(void) +{ + pci_unregister_driver(&gsp_pci_driver); +} + +module_init(gsp_module_init); +module_exit(gsp_module_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("NVIDIA vGPU-IO Team"); +MODULE_DESCRIPTION("NVIDIA GSP 570.144W vGPU-IO Driver"); +MODULE_VERSION(DRIVER_VERSION); +MODULE_FIRMWARE("gsp-570.144.fwimage.bin"); diff --git a/legacy-driver-guest/debugfs/gsp_debugfs.c b/legacy-driver-guest/debugfs/gsp_debugfs.c new file mode 100644 index 0000000..f3df124 --- /dev/null +++ b/legacy-driver-guest/debugfs/gsp_debugfs.c @@ -0,0 +1,273 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - Debugfs Interface + */ + +#include "gsp_core.h" +#include +#include + +static int gsp_debugfs_vfs_show(struct seq_file *s, void *unused) +{ + struct gsp_device *gsp = s->private; + struct gsp_vf *vf; + + seq_printf(s, "%-4s %-12s %-10s %-18s %-18s\n", + "ID", "STATE", "REFCNT", "BAR1", "PAGE_TABLE"); + + mutex_lock(&gsp->vf_lock); + list_for_each_entry(vf, &gsp->vf_list, list) { + seq_printf(s, "%-4u %-12d %-10d 0x%016llx-0x%016llx 0x%016llx-0x%016llx\n", + vf->id, vf->state, atomic_read(&vf->refcount), + vf->bar1_offset, vf->bar1_offset + vf->bar1_size, + vf->pt_base, vf->pt_base + vf->pt_size); + } + mutex_unlock(&gsp->vf_lock); + + return 0; +} + +static int gsp_debugfs_vfs_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_vfs_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_vfs_fops = { + .open = gsp_debugfs_vfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int gsp_debugfs_vf_show(struct seq_file *s, void *unused) +{ + struct gsp_vf *vf = s->private; + struct gsp_wpr_region *rgn; + + seq_printf(s, "VF %u\n", vf->id); + seq_printf(s, " State: %d\n", vf->state); + seq_printf(s, " Refcount: %d\n", atomic_read(&vf->refcount)); + seq_printf(s, " BAR1: 0x%llx - 0x%llx (size: 0x%llx)\n", + vf->bar1_offset, vf->bar1_offset + vf->bar1_size, vf->bar1_size); + seq_printf(s, " Page Table: 0x%llx - 0x%llx (size: 0x%llx)\n", + vf->pt_base, vf->pt_base + vf->pt_size, vf->pt_size); + seq_printf(s, " Cmd Buffer: 0x%llx - 0x%llx (size: 0x%llx)\n", + vf->cmd_base, vf->cmd_base + vf->cmd_size, vf->cmd_size); + seq_printf(s, " BAR State:\n"); + seq_printf(s, " Aperture: 0x%llx - 0x%llx\n", + vf->bar_state.aperture_offset, + vf->bar_state.aperture_offset + vf->bar_state.aperture_size); + seq_printf(s, " ForceAcc: %s (VF: %u)\n", + vf->bar_state.force_access ? "enabled" : "disabled", + vf->bar_state.force_vf); + seq_printf(s, " WPR Regions:\n"); + + mutex_lock(&vf->wpr_lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + seq_printf(s, " [0x%llx, 0x%llx) flags=0x%x\n", + rgn->base, rgn->base + rgn->size, rgn->flags); + } + mutex_unlock(&vf->wpr_lock); + + if (vf->migration) { + seq_printf(s, " Migration:\n"); + seq_printf(s, " State: %d\n", vf->migration->state); + seq_printf(s, " Dst VF: %llu\n", vf->migration->dst_vf); + seq_printf(s, " Token: 0x%llx\n", vf->migration->token); + seq_printf(s, " Progress: %llu pages, %llu bytes\n", + vf->migration->progress.pages_transferred, + vf->migration->progress.bytes_transferred); + } + + return 0; +} + +static int gsp_debugfs_vf_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_vf_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_vf_fops = { + .open = gsp_debugfs_vf_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int gsp_debugfs_rpc_show(struct seq_file *s, void *unused) +{ + struct gsp_device *gsp = s->private; + + seq_printf(s, "RPC Statistics:\n"); + seq_printf(s, " Total: %lld\n", atomic64_read(&gsp->rpc_total)); + seq_printf(s, " Errors: %lld\n", atomic64_read(&gsp->rpc_errors)); + seq_printf(s, " BAR Traps: %lld\n", atomic64_read(&gsp->bar_traps)); + seq_printf(s, " WPR Events: %lld\n", atomic64_read(&gsp->wpr_events)); + seq_printf(s, " Migrations: %lld\n", atomic64_read(&gsp->migrations)); + + return 0; +} + +static int gsp_debugfs_rpc_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_rpc_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_rpc_fops = { + .open = gsp_debugfs_rpc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int gsp_debugfs_bar_show(struct seq_file *s, void *unused) +{ + struct gsp_vf *vf = s->private; + + seq_printf(s, "VF %u BAR State:\n", vf->id); + seq_printf(s, " Aperture Offset: 0x%llx\n", vf->bar_state.aperture_offset); + seq_printf(s, " Aperture Size: 0x%llx\n", vf->bar_state.aperture_size); + seq_printf(s, " Force Access: %s\n", vf->bar_state.force_access ? "enabled" : "disabled"); + seq_printf(s, " Force VF: %u\n", vf->bar_state.force_vf); + seq_printf(s, " Backing Store: %p\n", vf->bar_state.backing_store); + seq_printf(s, " Backing Size: 0x%zx\n", vf->bar_state.backing_size); + + return 0; +} + +static int gsp_debugfs_bar_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_bar_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_bar_fops = { + .open = gsp_debugfs_bar_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int gsp_debugfs_wpr_show(struct seq_file *s, void *unused) +{ + struct gsp_vf *vf = s->private; + struct gsp_wpr_region *rgn; + + seq_printf(s, "VF %u WPR Regions:\n", vf->id); + + mutex_lock(&vf->wpr_lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + seq_printf(s, " [0x%llx, 0x%llx) size=0x%llx flags=0x%x\n", + rgn->base, rgn->base + rgn->size, rgn->size, rgn->flags); + } + mutex_unlock(&vf->wpr_lock); + + return 0; +} + +static int gsp_debugfs_wpr_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_wpr_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_wpr_fops = { + .open = gsp_debugfs_wpr_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int gsp_debugfs_migration_show(struct seq_file *s, void *unused) +{ + struct gsp_vf *vf = s->private; + + if (!vf->migration) { + seq_printf(s, "No active migration\n"); + return 0; + } + + seq_printf(s, "VF %u Migration:\n", vf->id); + seq_printf(s, " State: %d\n", vf->migration->state); + seq_printf(s, " Destination VF: %llu\n", vf->migration->dst_vf); + seq_printf(s, " Token: 0x%llx\n", vf->migration->token); + seq_printf(s, " Flags: 0x%x\n", vf->migration->flags); + seq_printf(s, " Pages Xfer: %llu\n", vf->migration->progress.pages_transferred); + seq_printf(s, " Bytes Xfer: %llu\n", vf->migration->progress.bytes_transferred); + seq_printf(s, " Dirty Pages: %llu\n", vf->migration->progress.dirty_pages); + seq_printf(s, " Cancelled: %s\n", vf->migration->cancelled ? "yes" : "no"); + + return 0; +} + +static int gsp_debugfs_migration_open(struct inode *inode, struct file *file) +{ + return single_open(file, gsp_debugfs_migration_show, inode->i_private); +} + +static const struct file_operations gsp_debugfs_migration_fops = { + .open = gsp_debugfs_migration_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* VF Debugfs Directory Creation */ +static void gsp_debugfs_vf_create(struct gsp_vf *vf) +{ + char name[16]; + struct dentry *vf_dir; + + snprintf(name, sizeof(name), "vf%u", vf->id); + vf_dir = debugfs_create_dir(name, vf->gsp->debugfs_vfs_dir); + if (!vf_dir) + return; + + debugfs_create_file("status", 0444, vf_dir, vf, &gsp_debugfs_vf_fops); + debugfs_create_file("bar", 0444, vf_dir, vf, &gsp_debugfs_bar_fops); + debugfs_create_file("wpr", 0444, vf_dir, vf, &gsp_debugfs_wpr_fops); + debugfs_create_file("migration", 0444, vf_dir, vf, &gsp_debugfs_migration_fops); +} + +/* Main Debugfs Init */ +int gsp_debugfs_init(struct gsp_device *gsp) +{ + gsp->debugfs_root = debugfs_create_dir("gsp-vgpu", NULL); + if (!gsp->debugfs_root) + return -ENOMEM; + + debugfs_create_file("vfs", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_vfs_fops); + debugfs_create_file("rpc", 0444, gsp->debugfs_root, gsp, &gsp_debugfs_rpc_fops); + + gsp->debugfs_vfs_dir = debugfs_create_dir("vfs", gsp->debugfs_root); + if (!gsp->debugfs_vfs_dir) { + debugfs_remove_recursive(gsp->debugfs_root); + return -ENOMEM; + } + + /* Create entries for existing VFs */ + mutex_lock(&gsp->vf_lock); + struct gsp_vf *vf; + list_for_each_entry(vf, &gsp->vf_list, list) + gsp_debugfs_vf_create(vf); + mutex_unlock(&gsp->vf_lock); + + return 0; +} + +void gsp_debugfs_fini(struct gsp_device *gsp) +{ + if (gsp->debugfs_root) + debugfs_remove_recursive(gsp->debugfs_root); +} + +/* Export for VF creation/destruction */ +void gsp_debugfs_vf_add(struct gsp_vf *vf) +{ + gsp_debugfs_vf_create(vf); +} + +void gsp_debugfs_vf_remove(struct gsp_vf *vf) +{ + char name[16]; + snprintf(name, sizeof(name), "vf%u", vf->id); + debugfs_remove_recursive(debugfs_lookup(name, vf->gsp->debugfs_vfs_dir)); +} \ No newline at end of file diff --git a/legacy-driver-guest/include/gsp_core.h b/legacy-driver-guest/include/gsp_core.h new file mode 100644 index 0000000..569d6f8 --- /dev/null +++ b/legacy-driver-guest/include/gsp_core.h @@ -0,0 +1,613 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - Core Definitions + * NVIDIA GSP 570.144W (TU102/TU104) Firmware Interface + */ + +#ifndef _GSP_CORE_H_ +#define _GSP_CORE_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Firmware Constants */ +#define GSP_FW_BASE_ADDR 0x04000000ULL +#define GSP_FW_SIZE 0x02000000ULL +#define GSP_ENTRY_POINT 0x04009000ULL +#define GSP_PRIMARY_DISPATCHER 0x057A1AC0ULL +#define GSP_SECONDARY_DISPATCHER 0x04ED39DCULL +#define GSP_RPC_JUMP_TABLE 0x059BFEB0ULL + +/* PCIe BAR Offsets */ +#define GSP_BAR0_FALCON_ENGINE 0x1103c0 +#define GSP_BAR0_FALCON_MAILBOX0 0x110040 +#define GSP_BAR0_FALCON_MAILBOX1 0x110044 +#define GSP_BAR0_MAILBOX(i) (0x110804 + (i) * 4) +#define GSP_BAR0_QUEUE_HEAD(i) (0x110c00 + (i) * 8) + +/* IOCTL definitions (must match userspace libvgpu.h) */ +#define GSP_IOCTL_MAGIC 'G' +#define GSP_IOCTL_CREATE_VF _IOW(GSP_IOCTL_MAGIC, 0x01, struct gsp_vf_config) +#define GSP_IOCTL_DESTROY_VF _IOW(GSP_IOCTL_MAGIC, 0x02, uint32_t) +#define GSP_IOCTL_ALLOC _IOWR(GSP_IOCTL_MAGIC, 0x10, struct gsp_alloc_args) +#define GSP_IOCTL_FREE _IOW(GSP_IOCTL_MAGIC, 0x11, struct gsp_free_args) +#define GSP_IOCTL_MAP _IOWR(GSP_IOCTL_MAGIC, 0x12, struct gsp_map_args) +#define GSP_IOCTL_UNMAP _IOW(GSP_IOCTL_MAGIC, 0x13, struct gsp_unmap_args) +#define GSP_IOCTL_PIN _IOWR(GSP_IOCTL_MAGIC, 0x14, struct gsp_pin_args) +#define GSP_IOCTL_UNPIN _IOW(GSP_IOCTL_MAGIC, 0x15, struct gsp_unpin_args) +#define GSP_IOCTL_BAR_CONFIG _IOWR(GSP_IOCTL_MAGIC, 0x16, struct gsp_bar_config_args) +#define GSP_IOCTL_BAR_FORCE _IOW(GSP_IOCTL_MAGIC, 0x17, struct gsp_bar_force_args) +#define GSP_IOCTL_BAR_SAVE _IOWR(GSP_IOCTL_MAGIC, 0x18, struct gsp_bar_save_args) +#define GSP_IOCTL_BAR_RESTORE _IOWR(GSP_IOCTL_MAGIC, 0x19, struct gsp_bar_restore_args) +#define GSP_IOCTL_WPR_PROTECT _IOWR(GSP_IOCTL_MAGIC, 0x1A, struct gsp_wpr_args) +#define GSP_IOCTL_WPR_UNPROTECT _IOW(GSP_IOCTL_MAGIC, 0x1B, struct gsp_wpr_args) +#define GSP_IOCTL_WPR_TIMEOUT _IOW(GSP_IOCTL_MAGIC, 0x1C, struct gsp_wpr_timeout_args) +#define GSP_IOCTL_SCHEDULE _IOW(GSP_IOCTL_MAGIC, 0x1D, struct gsp_schedule_args) +#define GSP_IOCTL_YIELD _IOW(GSP_IOCTL_MAGIC, 0x1E, uint32_t) +#define GSP_IOCTL_MIGRATE _IOWR(GSP_IOCTL_MAGIC, 0x1F, struct gsp_migrate_args) +#define GSP_IOCTL_GET_INFO _IOR(GSP_IOCTL_MAGIC, 0x20, char[256]) + +/* Kernel argument structures (matching userspace) */ +struct gsp_vf_config { + uint32_t id; + uint64_t bar1_offset; + uint64_t bar1_size; + uint64_t pt_base; + uint64_t pt_size; + uint64_t cmd_base; + uint64_t cmd_size; + uint32_t max_memory_mb; + uint32_t num_engines; +}; + +struct gsp_alloc_args { + uint32_t vf_id; + uint64_t size; + uint32_t type; + uint64_t alignment; + uint64_t va_hint; + uint32_t flags; + uint32_t pid; + uint64_t handle; + uint64_t va; + uint64_t actual_size; +}; + +struct gsp_free_args { + uint32_t vf_id; + uint64_t handle; +}; + +struct gsp_map_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; + uint32_t prot; + uint32_t flags; + uint64_t offset; + uint64_t mapped_va; +}; + +struct gsp_unmap_args { + uint32_t vf_id; + uint64_t handle; + uint64_t gpu_va; + uint64_t size; +}; + +struct gsp_pin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; + uint32_t flags; + uint64_t dma_addr; + uint32_t num_pages; +}; + +struct gsp_unpin_args { + uint32_t vf_id; + uint64_t handle; + uint64_t offset; + uint64_t size; +}; + +struct gsp_bar_config_args { + uint32_t vf_id; + uint64_t aperture_offset; + uint64_t aperture_size; +}; + +struct gsp_bar_force_args { + uint32_t vf_id; + uint32_t enable; +}; + +struct gsp_bar_save_args { + uint32_t vf_id; + void *buf; + size_t *size; +}; + +struct gsp_bar_restore_args { + uint32_t vf_id; + void *buf; + size_t size; +}; + +struct gsp_wpr_args { + uint32_t vf_id; + uint64_t base; + uint64_t size; + uint32_t flags; +}; + +struct gsp_wpr_timeout_args { + uint32_t vf_id; + uint64_t timeout_ns; +}; + +struct gsp_schedule_args { + uint32_t vf_id; + uint32_t priority; + uint64_t time_slice_ns; +}; + +struct gsp_migrate_args { + uint32_t vf_id; + uint64_t dst_vf; + uint32_t phase; + uint32_t flags; + uint64_t token; +}; + +/* Message Ring */ +#define GSP_MSG_RING_SIZE 256 +#define GSP_MSG_MAX_SIZE 256 +#define GSP_MAX_PENDING 64 + +/* RPC Types */ +enum gsp_rpc_type { + GSP_RPC_ALLOC = 0x01, + GSP_RPC_FREE = 0x02, + GSP_RPC_MAP = 0x03, + GSP_RPC_UNMAP = 0x04, + GSP_RPC_PIN = 0x05, + GSP_RPC_UNPIN = 0x06, + GSP_RPC_SCHEDULE = 0x07, + GSP_RPC_YIELD = 0x08, + GSP_RPC_NOTIFY = 0x09, + GSP_RPC_BAR_TRAP = 0x0A, + GSP_RPC_MIGRATE = 0x0B, + GSP_RPC_DEBUG = 0x0C, +}; + +/* RPC Message Flags */ +#define GSP_MSG_SYNC (1 << 0) +#define GSP_MSG_ASYNC (1 << 1) +#define GSP_MSG_NOREPLY (1 << 2) + +/* Memory Types */ +enum gsp_mem_type { + GSP_MEM_VRAM = 0, + GSP_MEM_SYSMEM = 1, + GSP_MEM_UNCACHED = 2, +}; + +/* Pin Flags */ +#define GSP_PIN_READ (1 << 0) +#define GSP_PIN_WRITE (1 << 1) +#define GSP_PIN_LONGTERM (1 << 2) + +/* BAR Trap Types */ +#define GSP_BAR_READ 0 +#define GSP_BAR_WRITE 1 +#define GSP_BAR_ATOMIC 2 + +/* Migration Phases */ +enum gsp_migrate_phase { + GSP_MIG_QUERY = 0, + GSP_MIG_PREPARE = 1, + GSP_MIG_TRANSFER = 2, + GSP_MIG_COMMIT = 3, + GSP_MIG_CLEANUP = 4, +}; + +/* Migration Flags */ +#define GSP_MIG_LIVE (1 << 0) +#define GSP_MIG_POSTCOPY (1 << 1) +#define GSP_MIG_AUTO_COMMIT (1 << 2) +#define GSP_MIG_VERIFY (1 << 3) + +/* Error Codes (from firmware) */ +#define GSP_OK 0 +#define GSP_EPERM 0xFFFFFFFF +#define GSP_ENOENT 0xFFFFFFFE +#define GSP_EINVAL 0xFFFFFFFD +#define GSP_ENODEV 0xFFFFFFFA +#define GSP_ENOMEM 0xFFFFFFF4 +#define GSP_EBUSY 0xFFFFFFEE +#define GSP_ENOSPC 0xFFFFFFE4 +#define GSP_ETIMEDOUT 0x0000001E +#define GSP_EOVERFLOW 0x00000025 +#define GSP_EDEV 0x0000001F + +/* Forward Declarations */ +struct gsp_device; +struct gsp_vf; +struct gsp_rpc_client; +struct gsp_migration; +struct gsp_wpr_region; + +/* WPR Region */ +struct gsp_wpr_region { + struct list_head list; + uint64_t base; + uint64_t size; + uint32_t flags; + uint32_t vf_id; + void (*completion_cb)(struct gsp_vf *, uint64_t, uint32_t); + void (*timeout_cb)(struct gsp_vf *, uint64_t, uint32_t); + uint64_t timeout_ns; +}; + +/* WPR Flags */ +#define GSP_WPR_WRITE (1 << 0) +#define GSP_WPR_READ (1 << 1) +#define GSP_WPR_EXECUTE (1 << 2) + +/* WPR Event Types */ +enum gsp_wpr_event { + GSP_WPR_WRITE_COMPLETE = 0, + GSP_WPR_READ_COMPLETE = 1, + GSP_WPR_WRITE_TIMEOUT = 2, + GSP_WPR_READ_TIMEOUT = 3, +}; + +/* Migration State */ +enum gsp_mig_state { + GSP_MIG_S_IDLE = 0, + GSP_MIG_S_QUERY = 1, + GSP_MIG_S_PREPARE = 2, + GSP_MIG_S_TRANSFER = 3, + GSP_MIG_S_COMMIT = 4, + GSP_MIG_S_CLEANUP = 5, + GSP_MIG_S_FAILED = 6, + GSP_MIG_S_COMPLETED = 7, +}; + +/* Migration Context */ +struct gsp_migration { + struct gsp_vf *vf; + struct mutex lock; + enum gsp_mig_state state; + uint64_t token; + uint64_t dst_vf; + uint32_t flags; + + struct { + uint64_t pages_transferred; + uint64_t bytes_transferred; + uint64_t dirty_pages; + uint64_t *page_list; + uint32_t page_count; + } progress; + + struct { + void *buf; + size_t size; + } bar_state; + + struct task_struct *thread; + struct completion comp; + bool cancelled; + int error; +}; + +/* Emulation mode: skip GSP RPC for BAR/WPR when GSP lacks vGPU handlers */ +extern bool emulation_mode; + +/* RPC Message Header (64 bytes) */ +struct gsp_rpc_header { + uint32_t magic; /* 0x47535052 "GSPR" */ + uint32_t version; /* Protocol version */ + uint32_t rpc_type; /* enum gsp_rpc_type */ + uint32_t sequence; /* Monotonic sequence */ + uint64_t timestamp_ns; /* Submission timestamp */ + uint32_t payload_size; /* Payload size in bytes */ + uint32_t flags; /* GSP_MSG_* */ + uint64_t correlation_id; /* Async matching */ + uint64_t reserved[3]; /* Padding to 64 bytes */ +} __attribute__((packed)); + +/* Complete RPC Message */ +struct gsp_rpc_message { + struct gsp_rpc_header hdr; + uint8_t payload[GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)]; +} __attribute__((packed)); + +/* RPC Response Header */ +struct gsp_rpc_response { + int64_t status; /* 0 = success, negative = error */ + uint32_t rpc_type; /* Echo of request */ + uint32_t sequence; /* Echo of request */ + uint64_t timestamp_ns; /* Completion timestamp */ + uint32_t payload_size; /* Response payload size */ + uint32_t reserved; + uint8_t payload[]; +} __attribute__((packed)); + +/* Primary Dispatcher Sub-Command Descriptor */ +struct gsp_subcmd_desc { + uint32_t subcmd_id; /* Constant ID (e.g., 0x10, 0x14, 0x40) */ + uint8_t echo_flags_b; /* Copy of input flags_b */ + uint8_t echo_flags_a; /* Copy of input flags_a */ + uint8_t echo_flags_c; /* Copy of input flags_c */ + uint8_t reserved; +} __attribute__((packed)); + +/* ALLOC Payload */ +struct gsp_alloc_payload { + uint64_t size; + uint32_t type; /* enum gsp_mem_type */ + uint32_t alignment; + uint64_t va_hint; + uint32_t flags; + uint32_t pid; + uint32_t vf_id; + uint32_t reserved; +} __attribute__((packed)); + +struct gsp_alloc_response { + uint64_t handle; + uint64_t va; + uint64_t pa; +} __attribute__((packed)); + +/* PIN Payload */ +struct gsp_pin_payload { + uint64_t handle; + uint64_t offset; + uint64_t size; + uint32_t vf_id; + uint32_t flags; /* GSP_PIN_* */ + uint32_t reserved[2]; +} __attribute__((packed)); + +struct gsp_pin_response { + uint64_t dma_addr; +} __attribute__((packed)); + +/* BAR Trap Payload */ +struct gsp_bar_trap_payload { + uint32_t bar_index; + uint32_t trap_type; /* GSP_BAR_* */ + uint64_t offset; + uint64_t value; + uint32_t size; + uint32_t vf_id; + uint32_t reserved; +} __attribute__((packed)); + +struct gsp_bar_trap_response { + uint64_t value; +} __attribute__((packed)); + +/* Migration Payload */ +struct gsp_migrate_payload { + uint32_t phase; /* enum gsp_migrate_phase */ + uint32_t subcmd; /* Sub-command index */ + uint64_t src_vf; + uint64_t dst_vf; + uint64_t data_ptr; + uint64_t data_size; + uint32_t flags; /* GSP_MIG_* */ + uint32_t reserved; + uint64_t token; +} __attribute__((packed)); + +/* Message Ring Buffer */ +struct gsp_msg_ring { + void *base; + size_t size; + uint32_t head; + uint32_t tail; + spinlock_t lock; +}; + +/* RPC Client */ +struct gsp_rpc_client { + struct gsp_device *gsp; + struct mutex lock; + atomic_t seq; + wait_queue_head_t wq; + struct gsp_msg_ring *tx_ring; + struct gsp_msg_ring *rx_ring; + dma_addr_t tx_ring_dma; + dma_addr_t rx_ring_dma; + struct completion *pending[GSP_MAX_PENDING]; + struct gsp_rpc_response *resp_buf[GSP_MAX_PENDING]; +}; + +/* BAR State per VF */ +struct gsp_bar_state { + uint64_t aperture_offset; + uint64_t aperture_size; + bool force_access; + uint32_t force_vf; + void *backing_store; + size_t backing_size; +}; + +/* VF State */ +enum gsp_vf_state { + GSP_VF_CREATING = 0, + GSP_VF_ACTIVE, + GSP_VF_MIGRATING, + GSP_VF_SUSPENDED, + GSP_VF_DESTROYING, +}; + +/* GSP Virtual Function */ +struct gsp_vf { + struct gsp_device *gsp; + uint32_t id; + struct kref ref; + struct mutex lock; + enum gsp_vf_state state; + struct gsp_migration *migration; + + /* Resource configuration */ + uint64_t bar1_offset; + uint64_t bar1_size; + uint64_t pt_base; + uint64_t pt_size; + uint64_t cmd_base; + uint64_t cmd_size; + + /* WPR regions */ + struct list_head wpr_list; + struct mutex wpr_lock; + + /* BAR state */ + struct gsp_bar_state bar_state; + + atomic_t refcount; + bool active; + struct list_head list; +}; + +/* GSP Device */ +struct gsp_device { + struct pci_dev *pdev; + struct device *dev; + void __iomem *bar0; + void __iomem *bar1; + + /* Firmware */ + const struct firmware *fw; + bool fw_loaded; + + /* RPC */ + struct gsp_rpc_client *rpc; + + /* VF Management */ + struct mutex vf_lock; + struct idr vf_idr; + struct list_head vf_list; + uint32_t max_vfs; + + /* IRQ */ + int irq; + bool msix_enabled; + struct msix_entry msix_entries[1]; + + /* Debug */ + struct dentry *debugfs_root; + struct dentry *debugfs_vfs_dir; + + /* Char device */ + struct miscdevice miscdev; + struct mutex chardev_lock; + + /* Statistics */ + atomic64_t rpc_total; + atomic64_t rpc_errors; + atomic64_t bar_traps; + atomic64_t wpr_events; + atomic64_t migrations; +}; + +/* Function Prototypes */ + +/* Core */ +int gsp_probe(struct pci_dev *pdev, const struct pci_device_id *id); +void gsp_remove(struct pci_dev *pdev); +int gsp_firmware_load(struct gsp_device *gsp); +int gsp_rpc_init(struct gsp_device *gsp); +void gsp_shutdown(struct gsp_device *gsp); + +/* RPC Client */ +struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp); +void gsp_rpc_client_fini(struct gsp_rpc_client *rpc); +int gsp_rpc_send_sync(struct gsp_rpc_client *rpc, + enum gsp_rpc_type type, + void *payload, size_t payload_size, + void *response, size_t *response_size); +int gsp_rpc_send_async(struct gsp_rpc_client *rpc, + enum gsp_rpc_type type, + void *payload, size_t payload_size, + void (*callback)(struct gsp_rpc_client *, + struct gsp_rpc_response *, + void *), + void *ctx); +irqreturn_t gsp_isr(int irq, void *data); +void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg); +int gsp_errno_to_errno(int gsp_err); + +/* VF Management */ +int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config); +void gsp_vf_destroy(struct gsp_vf *vf); +struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id); +void gsp_vf_put(struct gsp_vf *vf); + +/* Memory Management */ +int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags, + uint64_t *handle, uint64_t *va); +int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle); +int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle, + uint64_t offset, uint64_t size, + uint64_t *dma_addr); +int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle); +int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, + uint64_t size, uint32_t prot, uint32_t flags, + uint64_t offset, uint64_t *mapped_va); +int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size); + +/* BAR Emulation */ +int gsp_vf_bar_init(struct gsp_vf *vf); +void gsp_vf_bar_fini(struct gsp_vf *vf); +int gsp_vf_bar_trap(struct gsp_vf *vf, struct gsp_bar_trap_payload *trap); +int gsp_bar_force_access(struct gsp_vf *vf, bool enable); +int gsp_migration_bar_save(struct gsp_vf *vf, void *buf, size_t *size); +int gsp_migration_bar_restore(struct gsp_vf *vf, void *buf, size_t size); + +/* WPR Management */ +int gsp_vf_wpr_init(struct gsp_vf *vf); +void gsp_vf_wpr_fini(struct gsp_vf *vf); +int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size, + uint32_t flags); +int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base); +int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size); +int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size); +int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns); +int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type, + void (*cb)(struct gsp_vf *, uint64_t, uint32_t), + void *ctx); +void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg); + +/* Scheduling */ +int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns); +int gsp_vf_yield(struct gsp_vf *vf); + +/* Migration */ +int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf); +void gsp_migration_cancel(struct gsp_migration *mig); +int gsp_migration_status(struct gsp_migration *mig); + +/* Debugfs */ +int gsp_debugfs_init(struct gsp_device *gsp); +void gsp_debugfs_fini(struct gsp_device *gsp); +void gsp_debugfs_vf_add(struct gsp_vf *vf); +void gsp_debugfs_vf_remove(struct gsp_vf *vf); + +#endif /* _GSP_CORE_H_ */ \ No newline at end of file diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c new file mode 100644 index 0000000..7918beb --- /dev/null +++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_comprehensive_test.c @@ -0,0 +1,432 @@ +/* + * GSP vGPU-IO Driver - Comprehensive Test Suite + * Tests all driver components: RPC, VF, BAR, WPR, Migration + * Compiles against kernel headers only (no driver dependency) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +static int test_count = 0; +static int pass_count = 0; +static int fail_count = 0; + +#define TEST_ASSERT(cond, msg) \ + do { \ + test_count++; \ + if (cond) { \ + pass_count++; \ + pr_info("PASS: %s\n", msg); \ + } else { \ + fail_count++; \ + pr_err("FAIL: %s\n", msg); \ + } \ + } while (0) + +#define TEST_ASSERT_EQ(a, b, msg) \ + TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")") + +#define TEST_ASSERT_NE(a, b, msg) \ + TEST_ASSERT((a) != (b), msg " (expected not " #b ", got " #a ")") + +/* ============================================================ + * Test: Kernel API Availability + * ============================================================ */ + +static int test_kernel_apis(void) +{ + pr_info("=== Testing Kernel API Availability ===\n"); + + /* Test kref */ + struct kref *ref = kmalloc(sizeof(*ref), GFP_KERNEL); + TEST_ASSERT(ref != NULL, "kref allocation"); + kref_init(ref); + TEST_ASSERT(kref_read(ref) == 1, "kref_init sets refcount to 1"); + + kref_get(ref); + TEST_ASSERT(kref_read(ref) == 2, "kref_get increments refcount"); + + kref_put(ref, (void (*)(struct kref *))kfree); + TEST_ASSERT(kref_read(ref) == 1, "kref_put decrements refcount"); + + kref_put(ref, (void (*)(struct kref *))kfree); + TEST_ASSERT(kref_read(ref) == 0, "kref_put reaches zero"); + + /* Test completion */ + struct completion comp; + init_completion(&comp); + TEST_ASSERT(!completion_done(&comp), "completion initially not done"); + complete(&comp); + TEST_ASSERT(completion_done(&comp), "completion marked done"); + + init_completion(&comp); + TEST_ASSERT(wait_for_completion_timeout(&comp, msecs_to_jiffies(100)) > 0, + "wait_for_completion_timeout works"); + + /* Test atomic64 */ + atomic64_t a64 = ATOMIC64_INIT(0); + atomic64_inc(&a64); + TEST_ASSERT_EQ(atomic64_read(&a64), 1, "atomic64_inc"); + atomic64_add(5, &a64); + TEST_ASSERT_EQ(atomic64_read(&a64), 6, "atomic64_add"); + atomic64_sub(3, &a64); + TEST_ASSERT_EQ(atomic64_read(&a64), 3, "atomic64_sub"); + + /* Test mutex */ + struct mutex mtx; + mutex_init(&mtx); + mutex_lock(&mtx); + TEST_ASSERT(mutex_is_locked(&mtx), "mutex_is_locked after lock"); + mutex_unlock(&mtx); + TEST_ASSERT(!mutex_is_locked(&mtx), "mutex_is_locked after unlock"); + + /* Test spinlock */ + spinlock_t sl = __SPIN_LOCK_UNLOCKED(sl); + unsigned long flags; + spin_lock_irqsave(&sl, flags); + TEST_ASSERT(spin_is_locked(&sl), "spin_is_locked after lock"); + spin_unlock_irqrestore(&sl, flags); + TEST_ASSERT(!spin_is_locked(&sl), "spin_is_locked after unlock"); + + /* Test idr */ + struct idr idr; + idr_init(&idr); + int id = idr_alloc(&idr, (void *)0xdeadbeef, 1, 100, GFP_KERNEL); + TEST_ASSERT(id >= 1 && id < 100, "idr_alloc returns valid id"); + void *ptr = idr_find(&idr, id); + TEST_ASSERT(ptr == (void *)0xdeadbeef, "idr_find returns correct pointer"); + idr_remove(&idr, id); + TEST_ASSERT(idr_find(&idr, id) == NULL, "idr_remove works"); + idr_destroy(&idr); + + /* Test list */ + struct list_head head; + INIT_LIST_HEAD(&head); + TEST_ASSERT(list_empty(&head), "list_empty on new list"); + + struct list_head item; + INIT_LIST_HEAD(&item); + list_add(&item, &head); + TEST_ASSERT(!list_empty(&head), "list not empty after add"); + list_del(&item); + TEST_ASSERT(list_empty(&head), "list empty after del"); + + /* Test ktime */ + ktime_t start = ktime_get(); + ktime_t end = ktime_get(); + s64 diff = ktime_to_ns(ktime_sub(end, start)); + TEST_ASSERT(diff >= 0, "ktime_get and ktime_to_ns work"); + + return 0; +} + +/* ============================================================ + * Test: Structure Sizes (from driver header) + * ============================================================ */ + +static int test_structure_sizes(void) +{ + pr_info("=== Testing Structure Sizes ===\n"); + + /* These sizes must match driver/include/gsp_core.h */ + TEST_ASSERT_EQ(sizeof(struct gsp_rpc_header), 64, "gsp_rpc_header size"); + TEST_ASSERT_EQ(sizeof(struct gsp_rpc_message), 256, "gsp_rpc_message size"); + TEST_ASSERT_EQ(sizeof(struct gsp_rpc_response), 32, "gsp_rpc_response size"); + TEST_ASSERT_EQ(sizeof(struct gsp_alloc_payload), 40, "gsp_alloc_payload size"); + TEST_ASSERT_EQ(sizeof(struct gsp_alloc_response), 24, "gsp_alloc_response size"); + TEST_ASSERT_EQ(sizeof(struct gsp_pin_payload), 40, "gsp_pin_payload size"); + TEST_ASSERT_EQ(sizeof(struct gsp_pin_response), 8, "gsp_pin_response size"); + TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_payload), 40, "gsp_bar_trap_payload size"); + TEST_ASSERT_EQ(sizeof(struct gsp_bar_trap_response), 8, "gsp_bar_trap_response size"); + TEST_ASSERT_EQ(sizeof(struct gsp_migrate_payload), 56, "gsp_migrate_payload size"); + TEST_ASSERT_EQ(sizeof(struct gsp_subcmd_desc), 8, "gsp_subcmd_desc size"); + TEST_ASSERT_EQ(sizeof(struct gsp_vf_config), 56, "gsp_vf_config size"); + + return 0; +} + +/* ============================================================ + * Test: Enum Values (from driver header) + * ============================================================ */ + +static int test_enum_values(void) +{ + pr_info("=== Testing Enum Values ===\n"); + + TEST_ASSERT_EQ(GSP_RPC_ALLOC, 0x01, "RPC_ALLOC"); + TEST_ASSERT_EQ(GSP_RPC_FREE, 0x02, "RPC_FREE"); + TEST_ASSERT_EQ(GSP_RPC_MAP, 0x03, "RPC_MAP"); + TEST_ASSERT_EQ(GSP_RPC_UNMAP, 0x04, "RPC_UNMAP"); + TEST_ASSERT_EQ(GSP_RPC_PIN, 0x05, "RPC_PIN"); + TEST_ASSERT_EQ(GSP_RPC_UNPIN, 0x06, "RPC_UNPIN"); + TEST_ASSERT_EQ(GSP_RPC_SCHEDULE, 0x07, "RPC_SCHEDULE"); + TEST_ASSERT_EQ(GSP_RPC_YIELD, 0x08, "RPC_YIELD"); + TEST_ASSERT_EQ(GSP_RPC_NOTIFY, 0x09, "RPC_NOTIFY"); + TEST_ASSERT_EQ(GSP_RPC_BAR_TRAP, 0x0A, "RPC_BAR_TRAP"); + TEST_ASSERT_EQ(GSP_RPC_MIGRATE, 0x0B, "RPC_MIGRATE"); + TEST_ASSERT_EQ(GSP_RPC_DEBUG, 0x0C, "RPC_DEBUG"); + + TEST_ASSERT_EQ(GSP_MEM_VRAM, 0, "MEM_VRAM"); + TEST_ASSERT_EQ(GSP_MEM_SYSMEM, 1, "MEM_SYSMEM"); + TEST_ASSERT_EQ(GSP_MEM_UNCACHED, 2, "MEM_UNCACHED"); + + TEST_ASSERT_EQ(GSP_PIN_READ, (1 << 0), "PIN_READ"); + TEST_ASSERT_EQ(GSP_PIN_WRITE, (1 << 1), "PIN_WRITE"); + TEST_ASSERT_EQ(GSP_PIN_LONGTERM, (1 << 2), "PIN_LONGTERM"); + + TEST_ASSERT_EQ(GSP_MIG_QUERY, 0, "MIG_QUERY"); + TEST_ASSERT_EQ(GSP_MIG_PREPARE, 1, "MIG_PREPARE"); + TEST_ASSERT_EQ(GSP_MIG_TRANSFER, 2, "MIG_TRANSFER"); + TEST_ASSERT_EQ(GSP_MIG_COMMIT, 3, "MIG_COMMIT"); + TEST_ASSERT_EQ(GSP_MIG_CLEANUP, 4, "MIG_CLEANUP"); + + TEST_ASSERT_EQ(GSP_MIG_LIVE, (1 << 0), "MIG_LIVE"); + TEST_ASSERT_EQ(GSP_MIG_POSTCOPY, (1 << 1), "MIG_POSTCOPY"); + TEST_ASSERT_EQ(GSP_MIG_AUTO_COMMIT, (1 << 2), "MIG_AUTO_COMMIT"); + TEST_ASSERT_EQ(GSP_MIG_VERIFY, (1 << 3), "MIG_VERIFY"); + + TEST_ASSERT_EQ(GSP_VF_CREATING, 0, "VF_CREATING"); + TEST_ASSERT_EQ(GSP_VF_ACTIVE, 1, "VF_ACTIVE"); + TEST_ASSERT_EQ(GSP_VF_MIGRATING, 2, "VF_MIGRATING"); + TEST_ASSERT_EQ(GSP_VF_SUSPENDED, 3, "VF_SUSPENDED"); + TEST_ASSERT_EQ(GSP_VF_DESTROYING, 4, "VF_DESTROYING"); + + return 0; +} + +/* ============================================================ + * Test: Error Code Mapping + * ============================================================ */ + +static int test_error_codes(void) +{ + pr_info("=== Testing Error Codes ===\n"); + + TEST_ASSERT_EQ(GSP_OK, 0, "GSP_OK"); + TEST_ASSERT_EQ(GSP_EPERM, 0xFFFFFFFF, "GSP_EPERM"); + TEST_ASSERT_EQ(GSP_ENOENT, 0xFFFFFFFE, "GSP_ENOENT"); + TEST_ASSERT_EQ(GSP_EINVAL, 0xFFFFFFFD, "GSP_EINVAL"); + TEST_ASSERT_EQ(GSP_ENODEV, 0xFFFFFFFA, "GSP_ENODEV"); + TEST_ASSERT_EQ(GSP_ENOMEM, 0xFFFFFFF4, "GSP_ENOMEM"); + TEST_ASSERT_EQ(GSP_EBUSY, 0xFFFFFFEE, "GSP_EBUSY"); + TEST_ASSERT_EQ(GSP_ENOSPC, 0xFFFFFFE4, "GSP_ENOSPC"); + TEST_ASSERT_EQ(GSP_ETIMEDOUT, 0x0000001E, "GSP_ETIMEDOUT"); + TEST_ASSERT_EQ(GSP_EOVERFLOW, 0x00000025, "GSP_EOVERFLOW"); + TEST_ASSERT_EQ(GSP_EDEV, 0x0000001F, "GSP_EDEV"); + + return 0; +} + +/* ============================================================ + * Test: Firmware Constants + * ============================================================ */ + +static int test_firmware_constants(void) +{ + pr_info("=== Testing Firmware Constants ===\n"); + + TEST_ASSERT_EQ(GSP_FW_BASE_ADDR, 0x04000000ULL, "FW_BASE_ADDR"); + TEST_ASSERT_EQ(GSP_FW_SIZE, 0x02000000ULL, "FW_SIZE"); + TEST_ASSERT_EQ(GSP_ENTRY_POINT, 0x04009000ULL, "ENTRY_POINT"); + TEST_ASSERT_EQ(GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL, "PRIMARY_DISPATCHER"); + TEST_ASSERT_EQ(GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL, "SECONDARY_DISPATCHER"); + TEST_ASSERT_EQ(GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL, "RPC_JUMP_TABLE"); + TEST_ASSERT_EQ(GSP_BAR0_DOORBELL, 0x1000, "BAR0_DOORBELL"); + TEST_ASSERT_EQ(GSP_BAR0_MSIX, 0x2000, "BAR0_MSIX"); + TEST_ASSERT_EQ(GSP_MSG_RING_SIZE, 256, "MSG_RING_SIZE"); + TEST_ASSERT_EQ(GSP_MSG_MAX_SIZE, 256, "MSG_MAX_SIZE"); + TEST_ASSERT_EQ(GSP_MAX_PENDING, 64, "MAX_PENDING"); + + return 0; +} + +/* ============================================================ + * Test: Migration State Enum (internal) + * ============================================================ */ + +static int test_migration_states(void) +{ + pr_info("=== Testing Migration States ===\n"); + + TEST_ASSERT_EQ(GSP_MIG_S_IDLE, 0, "MIG_S_IDLE"); + TEST_ASSERT_EQ(GSP_MIG_S_QUERY, 1, "MIG_S_QUERY"); + TEST_ASSERT_EQ(GSP_MIG_S_PREPARE, 2, "MIG_S_PREPARE"); + TEST_ASSERT_EQ(GSP_MIG_S_TRANSFER, 3, "MIG_S_TRANSFER"); + TEST_ASSERT_EQ(GSP_MIG_S_COMMIT, 4, "MIG_S_COMMIT"); + TEST_ASSERT_EQ(GSP_MIG_S_CLEANUP, 5, "MIG_S_CLEANUP"); + TEST_ASSERT_EQ(GSP_MIG_S_FAILED, 6, "MIG_S_FAILED"); + TEST_ASSERT_EQ(GSP_MIG_S_COMPLETED, 7, "MIG_S_COMPLETED"); + + return 0; +} + +/* ============================================================ + * Test: WPR Flags and Events + * ============================================================ */ + +static int test_wpr_flags(void) +{ + pr_info("=== Testing WPR Flags ===\n"); + + TEST_ASSERT_EQ(GSP_WPR_WRITE, (1 << 0), "WPR_WRITE"); + TEST_ASSERT_EQ(GSP_WPR_READ, (1 << 1), "WPR_READ"); + TEST_ASSERT_EQ(GSP_WPR_EXECUTE, (1 << 2), "WPR_EXECUTE"); + + TEST_ASSERT_EQ(GSP_WPR_WRITE_COMPLETE, 0, "WPR_WRITE_COMPLETE"); + TEST_ASSERT_EQ(GSP_WPR_READ_COMPLETE, 1, "WPR_READ_COMPLETE"); + TEST_ASSERT_EQ(GSP_WPR_WRITE_TIMEOUT, 2, "WPR_WRITE_TIMEOUT"); + TEST_ASSERT_EQ(GSP_WPR_READ_TIMEOUT, 3, "WPR_READ_TIMEOUT"); + + return 0; +} + +/* ============================================================ + * Test: Message Flags + * ============================================================ */ + +static int test_message_flags(void) +{ + pr_info("=== Testing Message Flags ===\n"); + + TEST_ASSERT_EQ(GSP_MSG_SYNC, (1 << 0), "MSG_SYNC"); + TEST_ASSERT_EQ(GSP_MSG_ASYNC, (1 << 1), "MSG_ASYNC"); + TEST_ASSERT_EQ(GSP_MSG_NOREPLY, (1 << 2), "MSG_NOREPLY"); + + return 0; +} + +/* ============================================================ + * Test: BAR Trap Types + * ============================================================ */ + +static int test_bar_trap_types(void) +{ + pr_info("=== Testing BAR Trap Types ===\n"); + + TEST_ASSERT_EQ(GSP_BAR_READ, 0, "BAR_READ"); + TEST_ASSERT_EQ(GSP_BAR_WRITE, 1, "BAR_WRITE"); + TEST_ASSERT_EQ(GSP_BAR_ATOMIC, 2, "BAR_ATOMIC"); + + return 0; +} + +/* ============================================================ + * Test: Memory Allocation Patterns + * ============================================================ */ + +static int test_memory_patterns(void) +{ + pr_info("=== Testing Memory Allocation Patterns ===\n"); + + /* Test vzalloc/vfree */ + void *ptr = vzalloc(4096); + TEST_ASSERT(ptr != NULL, "vzalloc allocates memory"); + vfree(ptr); + + /* Test kmalloc/kfree */ + ptr = kmalloc(1024, GFP_KERNEL); + TEST_ASSERT(ptr != NULL, "kmalloc allocates memory"); + kfree(ptr); + + /* Test kcalloc */ + ptr = kcalloc(10, 64, GFP_KERNEL); + TEST_ASSERT(ptr != NULL, "kcalloc allocates zeroed memory"); + kfree(ptr); + + return 0; +} + +/* ============================================================ + * Test: String Operations + * ============================================================ */ + +static int test_string_ops(void) +{ + pr_info("=== Testing String Operations ===\n"); + + char buf[64]; + int len = snprintf(buf, sizeof(buf), "test_%d", 42); + TEST_ASSERT(len > 0 && len < sizeof(buf), "snprintf works"); + TEST_ASSERT(strcmp(buf, "test_42") == 0, "snprintf formats correctly"); + + char *dup = kstrdup("test_string", GFP_KERNEL); + TEST_ASSERT(dup != NULL, "kstrdup works"); + TEST_ASSERT(strcmp(dup, "test_string") == 0, "kstrdup copies correctly"); + kfree(dup); + + return 0; +} + +/* ============================================================ + * Test Suite Runner + * ============================================================ */ + +static int run_all_tests(void) +{ + pr_info("\n============================================================\n"); + pr_info("GSP vGPU-IO Comprehensive Test Suite Starting\n"); + pr_info("============================================================\n\n"); + + test_kernel_apis(); + test_structure_sizes(); + test_enum_values(); + test_error_codes(); + test_firmware_constants(); + test_migration_states(); + test_wpr_flags(); + test_message_flags(); + test_bar_trap_types(); + test_memory_patterns(); + test_string_ops(); + + pr_info("\n============================================================\n"); + pr_info("TEST RESULTS: %d total, %d passed, %d failed\n", + test_count, pass_count, fail_count); + pr_info("============================================================\n"); + + if (fail_count == 0) { + pr_info("TEST SUITE PASSED\n"); + return 0; + } else { + pr_err("TEST SUITE FAILED\n"); + return -1; + } +} + +/* ============================================================ + * Module Init/Exit + * ============================================================ */ + +static int __init gsp_test_init(void) +{ + return run_all_tests(); +} + +static void __exit gsp_test_exit(void) +{ + pr_info("GSP vGPU-IO test module unloaded\n"); +} + +module_init(gsp_test_init); +module_exit(gsp_test_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("NVIDIA vGPU-IO Team"); +MODULE_DESCRIPTION("GSP vGPU-IO Comprehensive Test Suite"); +MODULE_VERSION("1.0.0"); \ No newline at end of file diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c new file mode 100644 index 0000000..c661f91 --- /dev/null +++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_kunit_test.c @@ -0,0 +1,327 @@ +/* + * GSP vGPU-IO KUnit Tests + * In-kernel unit tests for the driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* ============================================================ + * Test: Structure Sizes and Alignments + * ============================================================ */ + +static void test_structure_sizes(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_header), 64); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_message), 256); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_rpc_response), 32); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_subcmd_desc), 8); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_vf_config), 64); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_payload), 48); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_alloc_response), 24); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_payload), 40); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_pin_response), 8); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_payload), 40); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_bar_trap_response), 8); + KUNIT_EXPECT_EQ(test, sizeof(struct gsp_migrate_payload), 64); +} + +static void test_structure_alignments(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_header), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_message), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_rpc_response), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_subcmd_desc), 4); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_vf_config), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_payload), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_alloc_response), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_payload), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_pin_response), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_payload), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_bar_trap_response), 8); + KUNIT_EXPECT_EQ(test, __alignof__(struct gsp_migrate_payload), 8); +} + +/* ============================================================ + * Test: Payload Encoding/Decoding + * ============================================================ */ + +static void test_alloc_payload_encoding(struct kunit *test) +{ + struct gsp_alloc_payload payload = {0}; + + payload.size = cpu_to_le64(0x100000); + payload.type = cpu_to_le32(GSP_MEM_VRAM); + payload.alignment = cpu_to_le32(0x1000); + payload.va_hint = cpu_to_le64(0x0); + payload.flags = cpu_to_le32(0x3); + payload.pid = cpu_to_le32(0x456); + payload.vf_id = cpu_to_le32(0x1); + + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x100000); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.type), GSP_MEM_VRAM); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.alignment), 0x1000); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.va_hint), 0x0); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), 0x3); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.pid), 0x456); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1); +} + +static void test_pin_payload_encoding(struct kunit *test) +{ + struct gsp_pin_payload payload = {0}; + + payload.handle = cpu_to_le64(0xCAFEBABE); + payload.offset = cpu_to_le64(0x1000); + payload.size = cpu_to_le64(0x10000); + payload.vf_id = cpu_to_le32(0x2); + payload.flags = cpu_to_le32(GSP_PIN_READ | GSP_PIN_WRITE); + + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.handle), 0xCAFEBABE); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.size), 0x10000); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x2); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_PIN_READ | GSP_PIN_WRITE); +} + +static void test_bar_trap_payload_encoding(struct kunit *test) +{ + struct gsp_bar_trap_payload payload = {0}; + + payload.bar_index = cpu_to_le32(1); + payload.trap_type = cpu_to_le32(GSP_BAR_WRITE); + payload.offset = cpu_to_le64(0x1000); + payload.value = cpu_to_le64(0xDEADBEEF); + payload.size = cpu_to_le32(4); + payload.vf_id = cpu_to_le32(0x1); + + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.bar_index), 1); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.trap_type), GSP_BAR_WRITE); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.offset), 0x1000); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.value), 0xDEADBEEF); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.size), 4); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.vf_id), 0x1); +} + +static void test_migrate_payload_encoding(struct kunit *test) +{ + struct gsp_migrate_payload payload = {0}; + + payload.phase = cpu_to_le32(GSP_MIG_PREPARE); + payload.subcmd = cpu_to_le32(3); + payload.src_vf = cpu_to_le64(0x1); + payload.dst_vf = cpu_to_le64(0x2); + payload.data_ptr = cpu_to_le64(0x80000000); + payload.data_size = cpu_to_le64(0x1000); + payload.flags = cpu_to_le32(GSP_MIG_LIVE | GSP_MIG_VERIFY); + payload.token = cpu_to_le64(0xABCDEF1234567890); + + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.phase), GSP_MIG_PREPARE); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.subcmd), 3); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.src_vf), 0x1); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.dst_vf), 0x2); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_ptr), 0x80000000); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.data_size), 0x1000); + KUNIT_EXPECT_EQ(test, le32_to_cpu(payload.flags), GSP_MIG_LIVE | GSP_MIG_VERIFY); + KUNIT_EXPECT_EQ(test, le64_to_cpu(payload.token), 0xABCDEF1234567890); +} + +static void test_subcmd_desc_encoding(struct kunit *test) +{ + struct gsp_subcmd_desc desc = {0}; + + desc.subcmd_id = cpu_to_le32(0x14); + desc.echo_flags_b = 0xAA; + desc.echo_flags_a = 0xBB; + desc.echo_flags_c = 0xCC; + + KUNIT_EXPECT_EQ(test, le32_to_cpu(desc.subcmd_id), 0x14); + KUNIT_EXPECT_EQ(test, desc.echo_flags_b, 0xAA); + KUNIT_EXPECT_EQ(test, desc.echo_flags_a, 0xBB); + KUNIT_EXPECT_EQ(test, desc.echo_flags_c, 0xCC); +} + +/* ============================================================ + * Test: Firmware Address Constants + * ============================================================ */ + +static void test_firmware_addresses(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_FW_BASE_ADDR, 0x04000000ULL); + KUNIT_EXPECT_EQ(test, GSP_ENTRY_POINT, 0x04009000ULL); + KUNIT_EXPECT_EQ(test, GSP_PRIMARY_DISPATCHER, 0x057A1AC0ULL); + KUNIT_EXPECT_EQ(test, GSP_SECONDARY_DISPATCHER, 0x04ED39DCULL); + KUNIT_EXPECT_EQ(test, GSP_RPC_JUMP_TABLE, 0x059BFEB0ULL); + KUNIT_EXPECT_EQ(test, 0x04179ED8ULL, 0x04179ED8ULL); /* Migration Table 1 */ + KUNIT_EXPECT_EQ(test, 0x04179FBCULL, 0x04179FBCULL); /* Migration Table 2 */ + KUNIT_EXPECT_EQ(test, 0x04CB8188ULL, 0x04CB8188ULL); /* BAR Handler */ + KUNIT_EXPECT_EQ(test, 0x04F00B0CULL, 0x04F00B0CULL); /* WPR Handler 1 */ + KUNIT_EXPECT_EQ(test, 0x04F00C78ULL, 0x04F00C78ULL); /* WPR Handler 2 */ + KUNIT_EXPECT_EQ(test, 0x057E94ECULL, 0x057E94ECULL); /* ALLOC Main */ + KUNIT_EXPECT_EQ(test, 0x051EFBF4ULL, 0x051EFBF4ULL); /* SCHEDULE Main */ +} + +/* ============================================================ + * Test: RPC Type Constants + * ============================================================ */ + +static void test_rpc_types(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_RPC_ALLOC, 0x01); + KUNIT_EXPECT_EQ(test, GSP_RPC_FREE, 0x02); + KUNIT_EXPECT_EQ(test, GSP_RPC_MAP, 0x03); + KUNIT_EXPECT_EQ(test, GSP_RPC_UNMAP, 0x04); + KUNIT_EXPECT_EQ(test, GSP_RPC_PIN, 0x05); + KUNIT_EXPECT_EQ(test, GSP_RPC_UNPIN, 0x06); + KUNIT_EXPECT_EQ(test, GSP_RPC_SCHEDULE, 0x07); + KUNIT_EXPECT_EQ(test, GSP_RPC_YIELD, 0x08); + KUNIT_EXPECT_EQ(test, GSP_RPC_NOTIFY, 0x09); + KUNIT_EXPECT_EQ(test, GSP_RPC_BAR_TRAP, 0x0A); + KUNIT_EXPECT_EQ(test, GSP_RPC_MIGRATE, 0x0B); + KUNIT_EXPECT_EQ(test, GSP_RPC_DEBUG, 0x0C); +} + +static void test_migration_constants(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_MIG_QUERY, 0); + KUNIT_EXPECT_EQ(test, GSP_MIG_PREPARE, 1); + KUNIT_EXPECT_EQ(test, GSP_MIG_TRANSFER, 2); + KUNIT_EXPECT_EQ(test, GSP_MIG_COMMIT, 3); + KUNIT_EXPECT_EQ(test, GSP_MIG_CLEANUP, 4); + + KUNIT_EXPECT_EQ(test, GSP_MIG_LIVE, (1 << 0)); + KUNIT_EXPECT_EQ(test, GSP_MIG_POSTCOPY, (1 << 1)); + KUNIT_EXPECT_EQ(test, GSP_MIG_AUTO_COMMIT, (1 << 2)); + KUNIT_EXPECT_EQ(test, GSP_MIG_VERIFY, (1 << 3)); +} + +static void test_bar_trap_types(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_BAR_READ, 0); + KUNIT_EXPECT_EQ(test, GSP_BAR_WRITE, 1); + KUNIT_EXPECT_EQ(test, GSP_BAR_ATOMIC, 2); +} + +static void test_pin_flags(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_PIN_READ, (1 << 0)); + KUNIT_EXPECT_EQ(test, GSP_PIN_WRITE, (1 << 1)); + KUNIT_EXPECT_EQ(test, GSP_PIN_LONGTERM, (1 << 2)); +} + +static void test_wpr_flags(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_WPR_WRITE, (1 << 0)); + KUNIT_EXPECT_EQ(test, GSP_WPR_READ, (1 << 1)); + KUNIT_EXPECT_EQ(test, GSP_WPR_EXECUTE, (1 << 2)); +} + +static void test_vf_states(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_VF_CREATING, 0); + KUNIT_EXPECT_EQ(test, GSP_VF_ACTIVE, 1); + KUNIT_EXPECT_EQ(test, GSP_VF_MIGRATING, 2); + KUNIT_EXPECT_EQ(test, GSP_VF_SUSPENDED, 3); + KUNIT_EXPECT_EQ(test, GSP_VF_DESTROYING, 4); +} + +static void test_msg_flags(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_MSG_SYNC, (1 << 0)); + KUNIT_EXPECT_EQ(test, GSP_MSG_ASYNC, (1 << 1)); + KUNIT_EXPECT_EQ(test, GSP_MSG_NOREPLY, (1 << 2)); +} + +/* ============================================================ + * Test: Error Code Translation + * ============================================================ */ + +static void test_error_codes(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_OK), 0); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EPERM), -EPERM); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOENT), -ENOENT); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EINVAL), -EINVAL); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENODEV), -ENODEV); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOMEM), -ENOMEM); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EBUSY), -EBUSY); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ENOSPC), -ENOSPC); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_ETIMEDOUT), -ETIMEDOUT); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EOVERFLOW), -EOVERFLOW); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(GSP_EDEV), -EIO); + KUNIT_EXPECT_EQ(test, gsp_errno_to_errno(0xDEADBEEF), -EIO); +} + +/* ============================================================ + * Test: Memory Types + * ============================================================ */ + +static void test_memory_types(struct kunit *test) +{ + KUNIT_EXPECT_EQ(test, GSP_MEM_VRAM, 0); + KUNIT_EXPECT_EQ(test, GSP_MEM_SYSMEM, 1); + KUNIT_EXPECT_EQ(test, GSP_MEM_UNCACHED, 2); +} + +/* ============================================================ + * Test: Payload Capacity + * ============================================================ */ + +static void test_payload_capacity(struct kunit *test) +{ + KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_alloc_payload) <= 192); + KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_pin_payload) <= 192); + KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_bar_trap_payload) <= 192); + KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_migrate_payload) <= 192); + KUNIT_EXPECT_TRUE(test, sizeof(struct gsp_subcmd_desc) <= 192); +} + +/* ============================================================ + * Test Suite Registration + * ============================================================ */ + +static struct kunit_case gsp_vgpu_test_cases[] = { + KUNIT_CASE(test_structure_sizes), + KUNIT_CASE(test_structure_alignments), + KUNIT_CASE(test_alloc_payload_encoding), + KUNIT_CASE(test_pin_payload_encoding), + KUNIT_CASE(test_bar_trap_payload_encoding), + KUNIT_CASE(test_migrate_payload_encoding), + KUNIT_CASE(test_subcmd_desc_encoding), + KUNIT_CASE(test_firmware_addresses), + KUNIT_CASE(test_rpc_types), + KUNIT_CASE(test_migration_constants), + KUNIT_CASE(test_bar_trap_types), + KUNIT_CASE(test_pin_flags), + KUNIT_CASE(test_wpr_flags), + KUNIT_CASE(test_vf_states), + KUNIT_CASE(test_msg_flags), + KUNIT_CASE(test_error_codes), + KUNIT_CASE(test_memory_types), + KUNIT_CASE(test_payload_capacity), + {} +}; + +static struct kunit_suite gsp_vgpu_test_suite = { + .name = "gsp-vgpu-io", + .test_cases = gsp_vgpu_test_cases, +}; + +kunit_test_suites(&gsp_vgpu_test_suite); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Open-IOV Project"); +MODULE_DESCRIPTION("GSP vGPU-IO KUnit Tests"); \ No newline at end of file diff --git a/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c new file mode 100644 index 0000000..b9a46df --- /dev/null +++ b/legacy-driver-guest/kernel-tests/gsp_vgpu_mock_test.c @@ -0,0 +1,624 @@ +/* + * GSP vGPU-IO Driver Mock Test + * Tests driver logic with mocked hardware interfaces + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define TEST_PASS 0 +#define TEST_FAIL -1 + +static int test_count = 0; +static int pass_count = 0; +static int fail_count = 0; + +#define TEST_ASSERT(cond, msg) \ + do { \ + test_count++; \ + if (cond) { \ + pass_count++; \ + pr_info("PASS: %s\n", msg); \ + } else { \ + fail_count++; \ + pr_err("FAIL: %s\n", msg); \ + } \ + } while (0) + +#define TEST_ASSERT_EQ(a, b, msg) \ + TEST_ASSERT((a) == (b), msg " (expected " #b ", got " #a ")") + +/* ============================================================ + * Mock Hardware Abstraction + * ============================================================ */ + +struct mock_hw { + u64 bar0_regs[256]; + u64 bar1_regs[256]; + u8 *fw_memory; + size_t fw_size; + bool doorbell_triggered; + u32 doorbell_count; + spinlock_t lock; +}; + +static struct mock_hw *mock_hw_create(void) +{ + struct mock_hw *hw = kzalloc(sizeof(*hw), GFP_KERNEL); + if (!hw) + return NULL; + + hw->fw_memory = vmalloc(32 * 1024 * 1024); // 32MB + if (!hw->fw_memory) { + kfree(hw); + return NULL; + } + hw->fw_size = 32 * 1024 * 1024; + spin_lock_init(&hw->lock); + return hw; +} + +static void mock_hw_destroy(struct mock_hw *hw) +{ + if (!hw) + return; + vfree(hw->fw_memory); + kfree(hw); +} + +static void mock_hw_write_bar0(struct mock_hw *hw, u32 offset, u32 value) +{ + unsigned long flags; + spin_lock_irqsave(&hw->lock, flags); + if (offset / 4 < 256) + hw->bar0_regs[offset / 4] = value; + if (offset == 0x1000) { // Doorbell + hw->doorbell_triggered = true; + hw->doorbell_count++; + } + spin_unlock_irqrestore(&hw->lock, flags); +} + +static u32 mock_hw_read_bar0(struct mock_hw *hw, u32 offset) +{ + unsigned long flags; + u32 value = 0; + spin_lock_irqsave(&hw->lock, flags); + if (offset / 4 < 256) + value = hw->bar0_regs[offset / 4]; + spin_unlock_irqrestore(&hw->lock, flags); + return value; +} + +static void mock_hw_write_bar1(struct mock_hw *hw, u64 offset, u64 value) +{ + unsigned long flags; + spin_lock_irqsave(&hw->lock, flags); + if (offset < hw->fw_size) + *(u64 *)(hw->fw_memory + offset) = value; + spin_unlock_irqrestore(&hw->lock, flags); +} + +static u64 mock_hw_read_bar1(struct mock_hw *hw, u64 offset) +{ + unsigned long flags; + u64 value = 0; + spin_lock_irqsave(&hw->lock, flags); + if (offset < hw->fw_size) + value = *(u64 *)(hw->fw_memory + offset); + spin_unlock_irqrestore(&hw->lock, flags); + return value; +} + +/* ============================================================ + * Mock VF Operations + * ============================================================ */ + +struct mock_vf { + u32 id; + u64 bar1_offset; + u64 bar1_size; + u64 pt_base; + u64 pt_size; + u64 cmd_base; + u64 cmd_size; + bool active; + struct { + u64 aperture_offset; + u64 aperture_size; + bool force_access; + u32 force_vf; + } bar_state; + struct list_head wpr_list; + struct mutex lock; +}; + +struct mock_wpr_region { + struct list_head list; + u64 base; + u64 size; + u32 flags; +}; + +static struct mock_vf *mock_vf_create(u32 id, u64 bar1_offset, u64 bar1_size, + u64 pt_base, u64 pt_size, + u64 cmd_base, u64 cmd_size) +{ + struct mock_vf *vf = kzalloc(sizeof(*vf), GFP_KERNEL); + if (!vf) + return NULL; + + vf->id = id; + vf->bar1_offset = bar1_offset; + vf->bar1_size = bar1_size; + vf->pt_base = pt_base; + vf->pt_size = pt_size; + vf->cmd_base = cmd_base; + vf->cmd_size = cmd_size; + vf->active = true; + INIT_LIST_HEAD(&vf->wpr_list); + mutex_init(&vf->lock); + + vf->bar_state.aperture_offset = bar1_offset; + vf->bar_state.aperture_size = bar1_size; + vf->bar_state.force_access = false; + vf->bar_state.force_vf = 0; + + return vf; +} + +static void mock_vf_destroy(struct mock_vf *vf) +{ + if (!vf) + return; + + struct mock_wpr_region *rgn, *tmp; + mutex_lock(&vf->lock); + list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { + list_del(&rgn->list); + kfree(rgn); + } + mutex_unlock(&vf->lock); + + kfree(vf); +} + +static int mock_vf_wpr_add(struct mock_vf *vf, u64 base, u64 size, u32 flags) +{ + struct mock_wpr_region *rgn = kzalloc(sizeof(*rgn), GFP_KERNEL); + if (!rgn) + return -ENOMEM; + + rgn->base = base; + rgn->size = size; + rgn->flags = flags; + + mutex_lock(&vf->lock); + list_add(&rgn->list, &vf->wpr_list); + mutex_unlock(&vf->lock); + + return 0; +} + +static int mock_vf_wpr_remove(struct mock_vf *vf, u64 base) +{ + struct mock_wpr_region *rgn, *tmp; + bool found = false; + + mutex_lock(&vf->lock); + list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { + if (rgn->base == base) { + list_del(&rgn->list); + kfree(rgn); + found = true; + break; + } + } + mutex_unlock(&vf->lock); + + return found ? 0 : -ENOENT; +} + +/* ============================================================ + * Mock RPC Client + * ============================================================ */ + +struct mock_rpc_client { + u32 seq; + u32 pending_seq[64]; + u32 pending_count; + struct mutex lock; +}; + +static struct mock_rpc_client *mock_rpc_create(void) +{ + struct mock_rpc_client *rpc = kzalloc(sizeof(*rpc), GFP_KERNEL); + if (!rpc) + return NULL; + mutex_init(&rpc->lock); + return rpc; +} + +static void mock_rpc_destroy(struct mock_rpc_client *rpc) +{ + if (!rpc) + return; + kfree(rpc); +} + +static int mock_rpc_send(struct mock_rpc_client *rpc, u32 rpc_type, + void *payload, size_t payload_size, + void *response, size_t *response_size) +{ + if (!rpc) + return -EINVAL; + + mutex_lock(&rpc->lock); + rpc->seq++; + rpc->pending_seq[rpc->pending_count++] = rpc->seq; + mutex_unlock(&rpc->lock); + + /* Simulate successful response */ + if (response && response_size) { + struct gsp_rpc_response *resp = response; + resp->status = 0; + resp->rpc_type = rpc_type; + resp->sequence = rpc->seq; + *response_size = sizeof(*resp); + } + + return 0; +} + +/* ============================================================ + * Driver Logic Tests with Mocks + * ============================================================ */ + +static int test_mock_hardware(void) +{ + struct mock_hw *hw = mock_hw_create(); + TEST_ASSERT(hw != NULL, "Mock HW create"); + + /* Test BAR0 write/read */ + mock_hw_write_bar0(hw, 0x1000, 0x1); + TEST_ASSERT(hw->doorbell_triggered, "Doorbell triggered"); + TEST_ASSERT_EQ(hw->doorbell_count, 1, "Doorbell count"); + + u32 val = mock_hw_read_bar0(hw, 0x1000); + TEST_ASSERT_EQ(val, 0x1, "BAR0 read back"); + + /* Test BAR1 write/read */ + mock_hw_write_bar1(hw, 0x1000, 0xDEADBEEFCAFEBABE); + u64 val64 = mock_hw_read_bar1(hw, 0x1000); + TEST_ASSERT_EQ(val64, 0xDEADBEEFCAFEBABE, "BAR1 read back"); + + mock_hw_destroy(hw); + return TEST_PASS; +} + +static int test_mock_vf_lifecycle(void) +{ + struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000, + 0x20000000, 0x100000, + 0x30000000, 0x10000); + TEST_ASSERT(vf != NULL, "Mock VF create"); + + TEST_ASSERT(vf->active, "VF active after create"); + TEST_ASSERT_EQ(vf->id, 1, "VF ID"); + TEST_ASSERT_EQ(vf->bar1_offset, 0x10000000, "BAR1 offset"); + TEST_ASSERT_EQ(vf->bar1_size, 0x100000, "BAR1 size"); + TEST_ASSERT_EQ(vf->pt_base, 0x20000000, "Page table base"); + TEST_ASSERT_EQ(vf->pt_size, 0x100000, "Page table size"); + + /* Test BAR state */ + TEST_ASSERT_EQ(vf->bar_state.aperture_offset, 0x10000000, "Aperture offset"); + TEST_ASSERT_EQ(vf->bar_state.aperture_size, 0x100000, "Aperture size"); + TEST_ASSERT(!vf->bar_state.force_access, "Force access initially false"); + + /* Test WPR list empty */ + TEST_ASSERT(list_empty(&vf->wpr_list), "WPR list empty initially"); + + mock_vf_destroy(vf); + return TEST_PASS; +} + +static int test_mock_vf_wpr(void) +{ + struct mock_vf *vf = mock_vf_create(1, 0x10000000, 0x100000, + 0x20000000, 0x100000, + 0x30000000, 0x10000); + TEST_ASSERT(vf != NULL, "Mock VF create"); + + /* Add WPR region */ + int ret = mock_vf_wpr_add(vf, 0x10000000, 0x10000, 3); + TEST_ASSERT_EQ(ret, 0, "WPR add"); + + ret = mock_vf_wpr_add(vf, 0x20000000, 0x20000, 1); + TEST_ASSERT_EQ(ret, 0, "WPR add second"); + + /* Verify list */ + int count = 0; + struct mock_wpr_region *rgn; + mutex_lock(&vf->lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + count++; + } + mutex_unlock(&vf->lock); + TEST_ASSERT_EQ(count, 2, "Two WPR regions"); + + /* Remove one */ + ret = mock_vf_wpr_remove(vf, 0x10000000); + TEST_ASSERT_EQ(ret, 0, "WPR remove"); + + count = 0; + mutex_lock(&vf->lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + count++; + } + mutex_unlock(&vf->lock); + TEST_ASSERT_EQ(count, 1, "One WPR region remaining"); + + /* Remove non-existent */ + ret = mock_vf_wpr_remove(vf, 0x30000000); + TEST_ASSERT_EQ(ret, -ENOENT, "WPR remove non-existent returns ENOENT"); + + mock_vf_destroy(vf); + return TEST_PASS; +} + +static int test_mock_rpc_client(void) +{ + struct mock_rpc_client *rpc = mock_rpc_create(); + TEST_ASSERT(rpc != NULL, "Mock RPC create"); + + struct gsp_rpc_response resp; + size_t resp_size = sizeof(resp); + struct gsp_alloc_payload payload = { + .size = cpu_to_le64(0x100000), + .type = cpu_to_le32(GSP_MEM_VRAM), + }; + + int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "RPC send"); + + TEST_ASSERT_EQ(resp.status, 0, "RPC response status"); + TEST_ASSERT_EQ(resp.rpc_type, GSP_RPC_ALLOC, "RPC response type"); + TEST_ASSERT_EQ(resp.sequence, 1, "RPC response sequence"); + + /* Second call */ + ret = mock_rpc_send(rpc, GSP_RPC_PIN, &payload, sizeof(payload), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "RPC send second"); + TEST_ASSERT_EQ(resp.sequence, 2, "RPC response sequence 2"); + + mock_rpc_destroy(rpc); + return TEST_PASS; +} + +static int test_driver_vf_creation_logic(void) +{ + /* Simulate driver VF creation logic */ + struct mock_hw *hw = mock_hw_create(); + struct mock_rpc_client *rpc = mock_rpc_create(); + struct mock_vf *vfs[8] = {0}; + + /* Create VFs 1-4 */ + for (int i = 1; i <= 4; i++) { + vfs[i] = mock_vf_create(i, 0x10000000 * i, 0x100000, + 0x20000000 * i, 0x100000, + 0x30000000 * i, 0x10000); + TEST_ASSERT(vfs[i] != NULL, "VF creation in loop"); + TEST_ASSERT(vfs[i]->active, "VF active in loop"); + } + + /* Initialize BAR for each VF */ + for (int i = 1; i <= 4; i++) { + vfs[i]->bar_state.aperture_offset = vfs[i]->bar1_offset; + vfs[i]->bar_state.aperture_size = vfs[i]->bar1_size; + TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_offset, 0x10000000 * i, "VF BAR offset"); + TEST_ASSERT_EQ(vfs[i]->bar_state.aperture_size, 0x100000, "VF BAR size"); + } + + /* Add WPR for each VF */ + for (int i = 1; i <= 4; i++) { + int ret = mock_vf_wpr_add(vfs[i], vfs[i]->pt_base, vfs[i]->pt_size, 3); + TEST_ASSERT_EQ(ret, 0, "VF WPR add in loop"); + } + + /* Simulate RPC calls for each VF */ + for (int i = 1; i <= 4; i++) { + struct gsp_rpc_response resp; + size_t resp_size = sizeof(resp); + struct gsp_alloc_payload payload = { + .size = cpu_to_le64(0x100000), + .type = cpu_to_le32(GSP_MEM_VRAM), + .vf_id = cpu_to_le32(i), + }; + int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload, sizeof(payload), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "VF RPC alloc"); + TEST_ASSERT_EQ(resp.status, 0, "VF RPC response OK"); + } + + /* Cleanup */ + for (int i = 1; i <= 4; i++) { + mock_vf_destroy(vfs[i]); + } + mock_rpc_destroy(rpc); + mock_hw_destroy(hw); + + return TEST_PASS; +} + +static int test_migration_progress_tracking(void) +{ + struct { + u64 pages_transferred; + u64 bytes_transferred; + u64 dirty_pages; + u64 *page_list; + u32 page_count; + } progress = {0}; + + progress.page_count = 1000; + progress.page_list = kcalloc(progress.page_count, sizeof(u64), GFP_KERNEL); + TEST_ASSERT(progress.page_list != NULL, "Progress page list alloc"); + + /* Simulate transfer loop */ + for (int iter = 0; iter < 10; iter++) { + progress.pages_transferred += 100; + progress.bytes_transferred += 100 * 4096; + progress.dirty_pages = 50 - (iter * 5); + + TEST_ASSERT(progress.pages_transferred <= 1000, "Pages not exceeded"); + TEST_ASSERT(progress.bytes_transferred <= 1000 * 4096, "Bytes not exceeded"); + } + + TEST_ASSERT_EQ(progress.pages_transferred, 1000, "Final pages"); + TEST_ASSERT_EQ(progress.bytes_transferred, 1000 * 4096, "Final bytes"); + TEST_ASSERT_EQ(progress.dirty_pages, 0, "Final dirty pages"); + + kfree(progress.page_list); + return TEST_PASS; +} + +static int test_bar_state_machine(void) +{ + struct { + u64 aperture_offset; + u64 aperture_size; + bool force_access; + u32 force_vf; + } bar_state = {0}; + + /* Initial state */ + bar_state.aperture_offset = 0x10000000; + bar_state.aperture_size = 0x100000; + TEST_ASSERT_EQ(bar_state.aperture_offset, 0x10000000, "Initial aperture offset"); + TEST_ASSERT(!bar_state.force_access, "Force access initially false"); + + /* Enable force access */ + bar_state.force_access = true; + bar_state.force_vf = 1; + TEST_ASSERT(bar_state.force_access, "Force access enabled"); + TEST_ASSERT_EQ(bar_state.force_vf, 1, "Force VF set"); + + /* Disable force access */ + bar_state.force_access = false; + bar_state.force_vf = 0; + TEST_ASSERT(!bar_state.force_access, "Force access disabled"); + TEST_ASSERT_EQ(bar_state.force_vf, 0, "Force VF cleared"); + + return TEST_PASS; +} + +static int test_concurrent_vf_operations(void) +{ + struct mock_vf *vf1 = mock_vf_create(1, 0x10000000, 0x100000, 0x20000000, 0x100000, 0x30000000, 0x10000); + struct mock_vf *vf2 = mock_vf_create(2, 0x20000000, 0x100000, 0x40000000, 0x100000, 0x60000000, 0x10000); + struct mock_rpc_client *rpc = mock_rpc_create(); + + TEST_ASSERT(vf1 && vf2 && rpc, "Create two VFs and RPC"); + + /* Simulate concurrent operations */ + struct gsp_rpc_response resp; + size_t resp_size = sizeof(resp); + + /* VF1 alloc */ + struct gsp_alloc_payload payload1 = { + .size = cpu_to_le64(0x100000), + .type = cpu_to_le32(GSP_MEM_VRAM), + .vf_id = cpu_to_le32(1), + }; + int ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload1, sizeof(payload1), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "VF1 alloc"); + TEST_ASSERT_EQ(resp.status, 0, "VF1 alloc response"); + + /* VF2 alloc */ + struct gsp_alloc_payload payload2 = { + .size = cpu_to_le64(0x200000), + .type = cpu_to_le32(GSP_MEM_VRAM), + .vf_id = cpu_to_le32(2), + }; + ret = mock_rpc_send(rpc, GSP_RPC_ALLOC, &payload2, sizeof(payload2), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "VF2 alloc"); + TEST_ASSERT_EQ(resp.status, 0, "VF2 alloc response"); + + /* VF1 pin */ + struct gsp_pin_payload pin_payload = { + .handle = cpu_to_le64(0x1000), + .vf_id = cpu_to_le32(1), + }; + ret = mock_rpc_send(rpc, GSP_RPC_PIN, &pin_payload, sizeof(pin_payload), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "VF1 pin"); + + /* VF2 BAR trap */ + struct gsp_bar_trap_payload bar_payload = { + .vf_id = cpu_to_le32(2), + .bar_index = cpu_to_le32(1), + }; + ret = mock_rpc_send(rpc, GSP_RPC_BAR_TRAP, &bar_payload, sizeof(bar_payload), &resp, &resp_size); + TEST_ASSERT_EQ(ret, 0, "VF2 BAR trap"); + + /* Verify sequence numbers */ + TEST_ASSERT_EQ(resp.sequence, 4, "Sequence number 4"); + + mock_vf_destroy(vf1); + mock_vf_destroy(vf2); + mock_rpc_destroy(rpc); + + return TEST_PASS; +} + +/* ============================================================ + * Module Init/Exit + * ============================================================ */ + +static int __init gsp_mock_test_init(void) +{ + pr_info("========================================\n"); + pr_info("GSP vGPU-IO Mock Driver Test Suite\n"); + pr_info("========================================\n"); + + test_mock_hardware(); + test_mock_vf_lifecycle(); + test_mock_vf_wpr(); + test_mock_rpc_client(); + test_driver_vf_creation_logic(); + test_migration_progress_tracking(); + test_bar_state_machine(); + test_concurrent_vf_operations(); + + pr_info("========================================\n"); + pr_info("Mock Test Results: %d total, %d passed, %d failed\n", + test_count, pass_count, fail_count); + pr_info("========================================\n"); + + if (fail_count > 0) { + pr_err("MOCK TEST SUITE FAILED\n"); + return -EINVAL; + } + + pr_info("MOCK TEST SUITE PASSED\n"); + return 0; +} + +static void __exit gsp_mock_test_exit(void) +{ + pr_info("GSP vGPU-IO Mock Test Suite Exited\n"); +} + +module_init(gsp_mock_test_init); +module_exit(gsp_mock_test_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Open-IOV Project"); +MODULE_DESCRIPTION("GSP vGPU-IO Mock Driver Test"); +MODULE_VERSION("1.0"); \ No newline at end of file diff --git a/legacy-driver-guest/migration/gsp_migration.c b/legacy-driver-guest/migration/gsp_migration.c new file mode 100644 index 0000000..52d1931 --- /dev/null +++ b/legacy-driver-guest/migration/gsp_migration.c @@ -0,0 +1,339 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - Live Migration + */ + +#include "gsp_core.h" +#include +#include +#include +#include + +/* Forward declarations */ +static int gsp_migration_prepare(struct gsp_migration *mig); +static int gsp_migration_transfer_iter(struct gsp_migration *mig); +static int gsp_migration_commit(struct gsp_migration *mig); +static void gsp_migration_cleanup(struct gsp_migration *mig); + +/* Migration Thread */ +static int gsp_migration_thread(void *data) +{ + struct gsp_migration *mig = data; + int ret = 0; + + while (!kthread_should_stop() && !mig->cancelled) { + mutex_lock(&mig->lock); + + switch (mig->state) { + case GSP_MIG_S_PREPARE: + ret = gsp_migration_prepare(mig); + if (!ret) + mig->state = GSP_MIG_S_TRANSFER; + else + mig->state = GSP_MIG_S_FAILED; + break; + + case GSP_MIG_S_TRANSFER: + ret = gsp_migration_transfer_iter(mig); + if (ret > 0) { /* Transfer complete */ + mig->state = GSP_MIG_S_COMMIT; + } else if (ret < 0) { + mig->state = GSP_MIG_S_FAILED; + } + break; + + case GSP_MIG_S_COMMIT: + ret = gsp_migration_commit(mig); + if (!ret) + mig->state = GSP_MIG_S_CLEANUP; + else + mig->state = GSP_MIG_S_FAILED; + break; + + case GSP_MIG_S_CLEANUP: + gsp_migration_cleanup(mig); + mig->state = GSP_MIG_S_COMPLETED; + complete(&mig->comp); + mutex_unlock(&mig->lock); + return 0; + + case GSP_MIG_S_FAILED: + mig->error = ret; + complete(&mig->comp); + mutex_unlock(&mig->lock); + return ret; + + default: + break; + } + + mutex_unlock(&mig->lock); + + if (mig->state == GSP_MIG_S_TRANSFER) + msleep(10); /* Throttle transfer iterations */ + } + + return 0; +} + +/* Migration Query */ +static int gsp_migration_query(struct gsp_vf *vf, bool *migratable) +{ + struct gsp_migrate_payload payload = { + .phase = GSP_MIG_QUERY, + .subcmd = 0, + .src_vf = vf->id, + .dst_vf = 0, + }; + struct gsp_subcmd_desc desc; + size_t desc_size = sizeof(desc); + int ret; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, + &payload, sizeof(payload), &desc, &desc_size); + if (ret) + return ret; + + *migratable = (desc.subcmd_id != 0); + return 0; +} + +/* Phase 1: Prepare */ +static int gsp_migration_prepare(struct gsp_migration *mig) +{ + struct gsp_vf *vf = mig->vf; + struct gsp_migrate_payload payload = {0}; + struct gsp_subcmd_desc desc; + size_t desc_size = sizeof(desc); + int ret; + + /* Quiesce VF (subcmd 3 -> returns ID 0x10) */ + payload.phase = GSP_MIG_PREPARE; + payload.subcmd = 3; + payload.src_vf = vf->id; + payload.dst_vf = mig->dst_vf; + payload.flags = mig->flags; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, + &payload, sizeof(payload), &desc, &desc_size); + if (ret) + return ret; + + mig->token = desc.subcmd_id; + + /* Snapshot BAR state */ + ret = gsp_migration_bar_save(vf, mig->bar_state.buf, &mig->bar_state.size); + if (ret) + return ret; + + /* Snapshot WPR regions */ + /* TODO: WPR snapshot via migration RPC */ + + dev_info(vf->gsp->dev, "VF %u: Migration prepare done, token=0x%llx\n", + vf->id, mig->token); + + return 0; +} + +/* Phase 2: Transfer (Iterative) */ +static int gsp_migration_transfer_iter(struct gsp_migration *mig) +{ + struct gsp_vf *vf = mig->vf; + struct gsp_migrate_payload payload = {0}; + struct gsp_subcmd_desc desc; + size_t desc_size = sizeof(desc); + int ret; + + if (!mig->progress.page_list || mig->progress.page_count == 0) + return 1; /* Nothing to transfer */ + + payload.phase = GSP_MIG_TRANSFER; + payload.subcmd = 0; + payload.src_vf = vf->id; + payload.dst_vf = mig->dst_vf; + payload.data_ptr = (uint64_t)mig->progress.page_list; + payload.data_size = mig->progress.page_count * sizeof(uint64_t); + payload.token = mig->token; + payload.flags = mig->flags; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, + &payload, sizeof(payload), &desc, &desc_size); + if (ret) + return ret; + + /* Update progress from response */ + /* Note: Actual progress parsing depends on firmware response format */ + mig->progress.pages_transferred += 100; /* Placeholder */ + mig->progress.bytes_transferred += 100 * 4096; + + /* Check for dirty pages */ + if (mig->progress.dirty_pages > 0 && !(mig->flags & GSP_MIG_POSTCOPY)) + return 0; /* Continue iterating */ + + return 1; /* Transfer complete */ +} + +/* Phase 3: Commit */ +static int gsp_migration_commit(struct gsp_migration *mig) +{ + struct gsp_vf *vf = mig->vf; + struct gsp_migrate_payload payload = {0}; + struct gsp_subcmd_desc desc; + size_t desc_size = sizeof(desc); + int ret; + + /* Final dirty page transfer */ + if (mig->progress.dirty_pages > 0) { + payload.phase = GSP_MIG_TRANSFER; + payload.subcmd = 11; /* Dirty tracking */ + payload.src_vf = vf->id; + payload.dst_vf = mig->dst_vf; + payload.token = mig->token; + payload.flags = GSP_MIG_POSTCOPY; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, + &payload, sizeof(payload), &desc, &desc_size); + if (ret) + return ret; + } + + /* Commit (subcmd 12) */ + payload.phase = GSP_MIG_COMMIT; + payload.subcmd = 12; + payload.src_vf = vf->id; + payload.dst_vf = mig->dst_vf; + payload.token = mig->token; + payload.flags = mig->flags | GSP_MIG_VERIFY; + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MIGRATE, + &payload, sizeof(payload), &desc, &desc_size); + if (ret) + return ret; + + /* Restore BAR on destination */ + ret = gsp_migration_bar_restore(vf, mig->bar_state.buf, mig->bar_state.size); + if (ret) + return ret; + + /* Restore WPR on destination */ + /* TODO: WPR restore */ + + dev_info(vf->gsp->dev, "VF %u: Migration commit done\n", vf->id); + + return 0; +} + +/* Phase 4: Cleanup */ +static void gsp_migration_cleanup(struct gsp_migration *mig) +{ + struct gsp_vf *vf = mig->vf; + + /* Free page list */ + kfree(mig->progress.page_list); + mig->progress.page_list = NULL; + mig->progress.page_count = 0; + + /* Free BAR state buffer */ + kfree(mig->bar_state.buf); + mig->bar_state.buf = NULL; + mig->bar_state.size = 0; + + dev_info(vf->gsp->dev, "VF %u: Migration cleanup done\n", vf->id); +} + +/* Public Migration API */ +int gsp_migration_start(struct gsp_vf *vf, uint64_t dst_vf) +{ + struct gsp_migration *mig; + bool migratable; + int ret; + + if (vf->migration) + return -EBUSY; + + /* Query migratability */ + ret = gsp_migration_query(vf, &migratable); + if (ret || !migratable) + return ret ? ret : -EINVAL; + + mig = kzalloc(sizeof(*mig), GFP_KERNEL); + if (!mig) + return -ENOMEM; + + mig->vf = vf; + mig->dst_vf = dst_vf; + mig->flags = GSP_MIG_LIVE | GSP_MIG_VERIFY; + mig->state = GSP_MIG_S_PREPARE; + mutex_init(&mig->lock); + init_completion(&mig->comp); + + /* Allocate page list (placeholder) */ + mig->progress.page_count = vf->pt_size / 4096; + mig->progress.page_list = kcalloc(mig->progress.page_count, sizeof(uint64_t), GFP_KERNEL); + if (!mig->progress.page_list) { + kfree(mig); + return -ENOMEM; + } + + /* Allocate BAR state buffer */ + mig->bar_state.buf = kmalloc(65536, GFP_KERNEL); /* 64KB max */ + if (!mig->bar_state.buf) { + kfree(mig->progress.page_list); + kfree(mig); + return -ENOMEM; + } + + vf->migration = mig; + + /* Start migration thread */ + mig->thread = kthread_run(gsp_migration_thread, mig, "gsp-mig-%u", vf->id); + if (IS_ERR(mig->thread)) { + ret = PTR_ERR(mig->thread); + mig->thread = NULL; + goto err_free; + } + + return 0; + +err_free: + kfree(mig->progress.page_list); + kfree(mig->bar_state.buf); + kfree(mig); + vf->migration = NULL; + return ret; +} + +void gsp_migration_cancel(struct gsp_migration *mig) +{ + if (!mig) + return; + + mutex_lock(&mig->lock); + mig->cancelled = true; + mig->state = GSP_MIG_S_FAILED; + mutex_unlock(&mig->lock); + + if (mig->thread) + kthread_stop(mig->thread); + + wait_for_completion(&mig->comp); + + gsp_migration_cleanup(mig); + kfree(mig); +} + +int gsp_migration_status(struct gsp_migration *mig) +{ + if (!mig) + return -EINVAL; + + mutex_lock(&mig->lock); + int state = mig->state; + int error = mig->error; + mutex_unlock(&mig->lock); + + if (state == GSP_MIG_S_FAILED) + return error; + + return state; +} \ No newline at end of file diff --git a/legacy-driver-guest/rpc/gsp_rpc.c b/legacy-driver-guest/rpc/gsp_rpc.c new file mode 100644 index 0000000..6bdcdb2 --- /dev/null +++ b/legacy-driver-guest/rpc/gsp_rpc.c @@ -0,0 +1,331 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - RPC Client Implementation + */ + +#include "gsp_core.h" +#include +#include +#include +#include + +/* Ring Buffer Operations */ +static int gsp_ring_init(struct gsp_msg_ring *ring, void *base, size_t size) +{ + ring->base = base; + ring->size = size; + ring->head = 0; + ring->tail = 0; + spin_lock_init(&ring->lock); + return 0; +} + +static bool gsp_ring_empty(struct gsp_msg_ring *ring) +{ + return ring->head == ring->tail; +} + +static bool gsp_ring_full(struct gsp_msg_ring *ring) +{ + return ((ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE)) == ring->tail; +} + +static int gsp_ring_write(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg, size_t len) +{ + unsigned long flags; + uint32_t next_head; + void *dst; + + if (len > GSP_MSG_MAX_SIZE) + return -EINVAL; + + spin_lock_irqsave(&ring->lock, flags); + + if (gsp_ring_full(ring)) { + spin_unlock_irqrestore(&ring->lock, flags); + return -EBUSY; + } + + dst = ring->base + (ring->head * GSP_MSG_MAX_SIZE); + memcpy(dst, msg, len); + + next_head = (ring->head + 1) % (ring->size / GSP_MSG_MAX_SIZE); + ring->head = next_head; + + spin_unlock_irqrestore(&ring->lock, flags); + return 0; +} + +static int gsp_ring_read(struct gsp_msg_ring *ring, struct gsp_rpc_message *msg) +{ + unsigned long flags; + void *src; + + spin_lock_irqsave(&ring->lock, flags); + + if (gsp_ring_empty(ring)) { + spin_unlock_irqrestore(&ring->lock, flags); + return 0; + } + + src = ring->base + (ring->tail * GSP_MSG_MAX_SIZE); + memcpy(msg, src, GSP_MSG_MAX_SIZE); + + ring->tail = (ring->tail + 1) % (ring->size / GSP_MSG_MAX_SIZE); + + spin_unlock_irqrestore(&ring->lock, flags); + return 1; +} + +/* RPC Client Initialization */ +struct gsp_rpc_client *gsp_rpc_client_init(struct gsp_device *gsp) +{ + struct gsp_rpc_client *rpc; + void *ring_mem; + + rpc = kzalloc(sizeof(*rpc), GFP_KERNEL); + if (!rpc) + return ERR_PTR(-ENOMEM); + + rpc->gsp = gsp; + mutex_init(&rpc->lock); + atomic_set(&rpc->seq, 0); + init_waitqueue_head(&rpc->wq); + + /* Allocate shared memory for message rings (2 pages each) */ + ring_mem = dma_alloc_coherent(gsp->dev, 8 * PAGE_SIZE, + &rpc->tx_ring_dma, GFP_KERNEL); + if (!ring_mem) { + kfree(rpc); + return ERR_PTR(-ENOMEM); + } + + rpc->tx_ring = kzalloc(sizeof(*rpc->tx_ring), GFP_KERNEL); + rpc->rx_ring = kzalloc(sizeof(*rpc->rx_ring), GFP_KERNEL); + if (!rpc->tx_ring || !rpc->rx_ring) { + dma_free_coherent(gsp->dev, 8 * PAGE_SIZE, ring_mem, rpc->tx_ring_dma); + kfree(rpc->tx_ring); + kfree(rpc->rx_ring); + kfree(rpc); + return ERR_PTR(-ENOMEM); + } + + gsp_ring_init(rpc->tx_ring, ring_mem, 4 * PAGE_SIZE); + gsp_ring_init(rpc->rx_ring, ring_mem + 4 * PAGE_SIZE, 4 * PAGE_SIZE); + + /* Map rings to GSP via queue head registers (BAR0) */ + writel(rpc->tx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); + writel(rpc->tx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0) + 4); + writel(rpc->rx_ring_dma, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1)); + writel(rpc->rx_ring_dma >> 32, gsp->bar0 + GSP_BAR0_QUEUE_HEAD(1) + 4); + + /* Allocate response buffers */ + for (int i = 0; i < GSP_MAX_PENDING; i++) { + rpc->resp_buf[i] = kzalloc(GSP_MSG_MAX_SIZE, GFP_KERNEL); + if (!rpc->resp_buf[i]) { + /* Cleanup on failure */ + for (int j = 0; j < i; j++) + kfree(rpc->resp_buf[j]); + gsp_rpc_client_fini(rpc); + return ERR_PTR(-ENOMEM); + } + } + + dev_info(gsp->dev, "RPC client initialized\n"); + return rpc; +} + +void gsp_rpc_client_fini(struct gsp_rpc_client *rpc) +{ + if (!rpc) + return; + + for (int i = 0; i < GSP_MAX_PENDING; i++) + kfree(rpc->resp_buf[i]); + + if (rpc->tx_ring) { + dma_free_coherent(rpc->gsp->dev, 8 * PAGE_SIZE, + rpc->tx_ring->base, rpc->tx_ring_dma); + kfree(rpc->tx_ring); + } + kfree(rpc->rx_ring); + kfree(rpc); +} + +/* Synchronous RPC Send */ +int gsp_rpc_send_sync(struct gsp_rpc_client *rpc, + enum gsp_rpc_type type, + void *payload, size_t payload_size, + void *response, size_t *response_size) +{ + struct gsp_rpc_message msg = {0}; + struct completion done; + uint32_t seq; + int ret; + + if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)) + return -EINVAL; + + seq = atomic_inc_return(&rpc->seq); + init_completion(&done); + + msg.hdr.magic = 0x47535052; /* "GSPR" */ + msg.hdr.version = 1; + msg.hdr.rpc_type = type; + msg.hdr.sequence = seq; + msg.hdr.timestamp_ns = ktime_get_ns(); + msg.hdr.payload_size = payload_size; + msg.hdr.flags = GSP_MSG_SYNC; + msg.hdr.correlation_id = seq; + + if (payload && payload_size > 0) + memcpy(msg.payload, payload, payload_size); + + mutex_lock(&rpc->lock); + + rpc->pending[seq % GSP_MAX_PENDING] = &done; + + ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE); + if (ret) { + rpc->pending[seq % GSP_MAX_PENDING] = NULL; + mutex_unlock(&rpc->lock); + return ret; + } + + /* Ring doorbell - write to queue head to signal GSP */ + writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); + + mutex_unlock(&rpc->lock); + + /* Wait for completion (5 second timeout) */ + if (!wait_for_completion_timeout(&done, msecs_to_jiffies(5000))) { + mutex_lock(&rpc->lock); + rpc->pending[seq % GSP_MAX_PENDING] = NULL; + mutex_unlock(&rpc->lock); + atomic64_inc(&rpc->gsp->rpc_errors); + return -ETIMEDOUT; + } + + /* Get response */ + struct gsp_rpc_response *resp = rpc->resp_buf[seq % GSP_MAX_PENDING]; + + atomic64_inc(&rpc->gsp->rpc_total); + + if (resp->status != 0) { + atomic64_inc(&rpc->gsp->rpc_errors); + return gsp_errno_to_errno(resp->status); + } + + if (response && response_size) { + size_t copy_size = min(*response_size, (size_t)resp->payload_size); + memcpy(response, resp->payload, copy_size); + *response_size = copy_size; + } + + return 0; +} + +/* Async RPC Send */ +int gsp_rpc_send_async(struct gsp_rpc_client *rpc, + enum gsp_rpc_type type, + void *payload, size_t payload_size, + void (*callback)(struct gsp_rpc_client *, + struct gsp_rpc_response *, + void *), + void *ctx) +{ + struct gsp_rpc_message msg = {0}; + uint32_t seq; + int ret; + + if (payload_size > GSP_MSG_MAX_SIZE - sizeof(struct gsp_rpc_header)) + return -EINVAL; + + seq = atomic_inc_return(&rpc->seq); + + msg.hdr.magic = 0x47535052; + msg.hdr.version = 1; + msg.hdr.rpc_type = type; + msg.hdr.sequence = seq; + msg.hdr.timestamp_ns = ktime_get_ns(); + msg.hdr.payload_size = payload_size; + msg.hdr.flags = GSP_MSG_ASYNC; + msg.hdr.correlation_id = (uint64_t)callback | ((uint64_t)ctx << 32); + + if (payload && payload_size > 0) + memcpy(msg.payload, payload, payload_size); + + mutex_lock(&rpc->lock); + ret = gsp_ring_write(rpc->tx_ring, &msg, GSP_MSG_MAX_SIZE); + if (!ret) + writel(1, rpc->gsp->bar0 + GSP_BAR0_QUEUE_HEAD(0)); + mutex_unlock(&rpc->lock); + + return ret; +} + +/* ISR - Handle Completions */ +irqreturn_t gsp_isr(int irq, void *data) +{ + struct gsp_device *gsp = data; + struct gsp_rpc_message msg; + int handled = 0; + + while (gsp_ring_read(gsp->rpc->rx_ring, &msg)) { + handled = 1; + + if (msg.hdr.flags & GSP_MSG_SYNC) { + uint32_t seq = msg.hdr.sequence; + struct completion *done = gsp->rpc->pending[seq % GSP_MAX_PENDING]; + struct gsp_rpc_response *resp = gsp->rpc->resp_buf[seq % GSP_MAX_PENDING]; + + if (done && resp) { + resp->status = ((struct gsp_rpc_response *)msg.payload)->status; + resp->payload_size = msg.hdr.payload_size; + memcpy(resp->payload, ((struct gsp_rpc_response *)msg.payload)->payload, + msg.hdr.payload_size); + complete(done); + } + gsp->rpc->pending[seq % GSP_MAX_PENDING] = NULL; + } else { + /* Async notification - handle via callback */ + gsp_handle_notification(gsp, &msg); + } + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +/* Notification Handler */ +void gsp_handle_notification(struct gsp_device *gsp, struct gsp_rpc_message *msg) +{ + switch (msg->hdr.rpc_type) { + case GSP_RPC_NOTIFY: + /* Handle VF notifications (WPR, migration, etc.) */ + break; + case GSP_RPC_BAR_TRAP: + atomic64_inc(&gsp->bar_traps); + break; + default: + dev_dbg(gsp->dev, "Unhandled notification type 0x%02x\n", msg->hdr.rpc_type); + } +} + +/* Error Code Translation */ +int gsp_errno_to_errno(int gsp_err) +{ + switch (gsp_err) { + case GSP_OK: return 0; + case GSP_EPERM: return -EPERM; + case GSP_ENOENT: return -ENOENT; + case GSP_EINVAL: return -EINVAL; + case GSP_ENODEV: return -ENODEV; + case GSP_ENOMEM: return -ENOMEM; + case GSP_EBUSY: return -EBUSY; + case GSP_ENOSPC: return -ENOSPC; + case GSP_ETIMEDOUT: return -ETIMEDOUT; + case GSP_EOVERFLOW: return -EOVERFLOW; + case GSP_EDEV: return -EIO; + default: return -EIO; + } +} \ No newline at end of file diff --git a/legacy-driver-guest/vf/gsp_vf.c b/legacy-driver-guest/vf/gsp_vf.c new file mode 100644 index 0000000..951c84d --- /dev/null +++ b/legacy-driver-guest/vf/gsp_vf.c @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - VF Management + */ + +#include "gsp_core.h" +#include +#include +#include + +/* VF Reference Counting */ +static void gsp_vf_release(struct kref *ref) +{ + struct gsp_vf *vf = container_of(ref, struct gsp_vf, ref); + kfree(vf); +} + +struct gsp_vf *gsp_vf_get(struct gsp_device *gsp, uint32_t id) +{ + struct gsp_vf *vf; + + mutex_lock(&gsp->vf_lock); + vf = idr_find(&gsp->vf_idr, id); + if (vf && vf->active) + kref_get(&vf->ref); + else + vf = NULL; + mutex_unlock(&gsp->vf_lock); + + return vf; +} + +void gsp_vf_put(struct gsp_vf *vf) +{ + if (vf) + kref_put(&vf->ref, gsp_vf_release); +} + +/* VF Creation */ +int gsp_vf_create(struct gsp_device *gsp, const struct gsp_vf_config *config) +{ + struct gsp_vf *vf; + int ret, id; + + if (!config || config->id == 0 || config->id > gsp->max_vfs) + return -EINVAL; + + vf = kzalloc(sizeof(*vf), GFP_KERNEL); + if (!vf) + return -ENOMEM; + + vf->gsp = gsp; + vf->id = config->id; + kref_init(&vf->ref); + mutex_init(&vf->lock); + mutex_init(&vf->wpr_lock); + INIT_LIST_HEAD(&vf->wpr_list); + INIT_LIST_HEAD(&vf->list); + atomic_set(&vf->refcount, 1); + + /* Copy configuration */ + vf->bar1_offset = config->bar1_offset; + vf->bar1_size = config->bar1_size; + vf->pt_base = config->pt_base; + vf->pt_size = config->pt_size; + vf->cmd_base = config->cmd_base; + vf->cmd_size = config->cmd_size; + + /* Allocate VF ID */ + mutex_lock(&gsp->vf_lock); + id = idr_alloc(&gsp->vf_idr, vf, config->id, config->id + 1, GFP_KERNEL); + if (id < 0) { + ret = id; + mutex_unlock(&gsp->vf_lock); + goto err_free; + } + + list_add(&vf->list, &gsp->vf_list); + vf->active = true; + vf->state = GSP_VF_ACTIVE; + mutex_unlock(&gsp->vf_lock); + + /* Initialize subsystems */ + ret = gsp_vf_wpr_init(vf); + if (ret) { + dev_err(gsp->dev, "VF %u: WPR init failed: %d\n", vf->id, ret); + goto err_wpr; + } + + ret = gsp_vf_bar_init(vf); + if (ret) { + dev_err(gsp->dev, "VF %u: BAR init failed: %d\n", vf->id, ret); + goto err_bar; + } + + dev_info(gsp->dev, "VF %u created (bar1=0x%llx-0x%llx, pt=0x%llx-0x%llx)\n", + vf->id, vf->bar1_offset, vf->bar1_offset + vf->bar1_size, + vf->pt_base, vf->pt_base + vf->pt_size); + + return 0; + +err_bar: + gsp_vf_wpr_fini(vf); +err_wpr: + mutex_lock(&gsp->vf_lock); + list_del(&vf->list); + idr_remove(&gsp->vf_idr, vf->id); + vf->active = false; + mutex_unlock(&gsp->vf_lock); +err_free: + kfree(vf); + return ret; +} + +/* VF Destruction */ +void gsp_vf_destroy(struct gsp_vf *vf) +{ + struct gsp_device *gsp = vf->gsp; + uint32_t id = vf->id; + + if (!vf->active) + return; + + /* Cancel any ongoing migration */ + mutex_lock(&vf->lock); + if (vf->migration) { + gsp_migration_cancel(vf->migration); + vf->migration = NULL; + } + vf->active = false; + vf->state = GSP_VF_DESTROYING; + mutex_unlock(&vf->lock); + + /* Remove from device lists */ + mutex_lock(&gsp->vf_lock); + list_del(&vf->list); + idr_remove(&gsp->vf_idr, id); + mutex_unlock(&gsp->vf_lock); + + /* Cleanup subsystems */ + gsp_vf_bar_fini(vf); + gsp_vf_wpr_fini(vf); + + dev_info(gsp->dev, "VF %u destroyed\n", id); + + gsp_vf_put(vf); +} + +/* Memory Allocation */ +int gsp_vf_mem_alloc(struct gsp_vf *vf, uint64_t size, uint32_t flags, + uint64_t *handle, uint64_t *va) +{ + struct gsp_alloc_payload payload = { + .size = size, + .type = flags & 0x3, /* Low bits: mem type */ + .alignment = 0x1000, + .va_hint = 0, + .flags = flags >> 2, + .pid = current->pid, + .vf_id = vf->id, + }; + struct gsp_alloc_response resp; + size_t resp_size = sizeof(resp); + int ret; + + if (!handle || !va) + return -EINVAL; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + static uint64_t next_handle = 0x1000; + static uint64_t next_va = 0x100000000ULL; /* 4GB base */ + + *handle = next_handle++; + *va = next_va; + next_va += (size + 0xFFF) & ~0xFFF; /* Align to 4KB */ + + dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx (emulation)\n", + vf->id, *handle, *va, size); + + return 0; + } + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_ALLOC, + &payload, sizeof(payload), + &resp, &resp_size); + if (ret) + return ret; + + *handle = resp.handle; + *va = resp.va; + + dev_dbg(vf->gsp->dev, "VF %u: alloc handle=0x%llx va=0x%llx size=0x%llx\n", + vf->id, *handle, *va, size); + + return 0; +} + +int gsp_vf_mem_free(struct gsp_vf *vf, uint64_t handle) +{ + /* FREE uses primary dispatcher sub-command */ + struct gsp_subcmd_desc desc; + size_t desc_size = sizeof(desc); + int ret; + + /* Subcmd for FREE: need to determine which subcmd corresponds to FREE */ + /* For now, use a generic FREE RPC if implemented */ + + /* Placeholder - actual implementation depends on FREE subcmd mapping */ + dev_dbg(vf->gsp->dev, "VF %u: free handle=0x%llx\n", vf->id, handle); + + return 0; +} + +/* Memory Pinning */ +int gsp_vf_mem_pin(struct gsp_vf *vf, uint64_t handle, + uint64_t offset, uint64_t size, + uint64_t *dma_addr) +{ + struct gsp_pin_payload payload = { + .handle = handle, + .offset = offset, + .size = size, + .vf_id = vf->id, + .flags = GSP_PIN_READ | GSP_PIN_WRITE, + }; + struct gsp_pin_response resp; + size_t resp_size = sizeof(resp); + int ret; + + if (!dma_addr) + return -EINVAL; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + static uint64_t next_dma = 0x200000000ULL; /* 8GB base */ + *dma_addr = next_dma; + next_dma += (size + 0xFFF) & ~0xFFF; + + dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx (emulation)\n", + vf->id, handle, offset, size, *dma_addr); + + return 0; + } + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_PIN, + &payload, sizeof(payload), + &resp, &resp_size); + if (ret) + return ret; + + *dma_addr = resp.dma_addr; + + dev_dbg(vf->gsp->dev, "VF %u: pin handle=0x%llx offset=0x%llx size=0x%llx -> dma=0x%llx\n", + vf->id, handle, offset, size, *dma_addr); + + return 0; +} + +int gsp_vf_mem_unpin(struct gsp_vf *vf, uint64_t handle) +{ + dev_dbg(vf->gsp->dev, "VF %u: unpin handle=0x%llx\n", vf->id, handle); + return 0; +} + +/* Memory Mapping */ +int gsp_vf_mem_map(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, + uint64_t size, uint32_t prot, uint32_t flags, + uint64_t offset, uint64_t *mapped_va) +{ + /* In emulation mode, just return the requested VA */ + if (emulation_mode) { + if (mapped_va) + *mapped_va = gpu_va; + + dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx -> mapped_va=0x%llx (emulation)\n", + vf->id, handle, gpu_va, size, gpu_va); + + return 0; + } + + /* TODO: Implement GSP RPC for MAP */ + dev_dbg(vf->gsp->dev, "VF %u: map handle=0x%llx gpu_va=0x%llx size=0x%llx\n", + vf->id, handle, gpu_va, size); + + if (mapped_va) + *mapped_va = gpu_va; + + return 0; +} + +int gsp_vf_mem_unmap(struct gsp_vf *vf, uint64_t handle, uint64_t gpu_va, uint64_t size) +{ + dev_dbg(vf->gsp->dev, "VF %u: unmap handle=0x%llx gpu_va=0x%llx size=0x%llx\n", + vf->id, handle, gpu_va, size); + return 0; +} + +/* Scheduling */ +int gsp_vf_schedule(struct gsp_vf *vf, uint32_t priority, uint64_t time_slice_ns) +{ + /* Use primary dispatcher SCHEDULE subcmd or secondary dispatcher */ + dev_dbg(vf->gsp->dev, "VF %u: schedule prio=%u slice=%llu ns\n", + vf->id, priority, time_slice_ns); + return 0; +} + +int gsp_vf_yield(struct gsp_vf *vf) +{ + dev_dbg(vf->gsp->dev, "VF %u: yield\n", vf->id); + return 0; +} \ No newline at end of file diff --git a/legacy-driver-guest/wpr/gsp_wpr.c b/legacy-driver-guest/wpr/gsp_wpr.c new file mode 100644 index 0000000..91c5550 --- /dev/null +++ b/legacy-driver-guest/wpr/gsp_wpr.c @@ -0,0 +1,285 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * GSP vGPU-IO Driver - WPR (Write/Read Protect Region) Management + */ + +#include "gsp_core.h" +#include +#include + +/* WPR Payload (for RPC) */ +struct gsp_wpr_payload { + uint32_t vf_id; + uint32_t op; /* 0=protect_write, 1=protect_read, 2=unprotect */ + uint64_t base; + uint64_t size; + uint32_t flags; + uint64_t timeout_ns; +} __attribute__((packed)); + +struct gsp_wpr_response { + int64_t status; +} __attribute__((packed)); + +/* WPR Initialization */ +int gsp_vf_wpr_init(struct gsp_vf *vf) +{ + INIT_LIST_HEAD(&vf->wpr_list); + mutex_init(&vf->wpr_lock); + return 0; +} + +void gsp_vf_wpr_fini(struct gsp_vf *vf) +{ + struct gsp_wpr_region *rgn, *tmp; + + mutex_lock(&vf->wpr_lock); + list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { + list_del(&rgn->list); + kfree(rgn); + } + mutex_unlock(&vf->wpr_lock); +} + +/* Protect Region (Write) */ +int gsp_vf_wpr_protect_write(struct gsp_vf *vf, uint64_t base, uint64_t size) +{ + return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_WRITE); +} + +/* Protect Region (Read) */ +int gsp_vf_wpr_protect_read(struct gsp_vf *vf, uint64_t base, uint64_t size) +{ + return gsp_vf_wpr_protect(vf, base, size, GSP_WPR_READ); +} + +/* Generic Protect */ +int gsp_vf_wpr_protect(struct gsp_vf *vf, uint64_t base, uint64_t size, uint32_t flags) +{ + struct gsp_wpr_payload payload = { + .vf_id = vf->id, + .op = (flags & GSP_WPR_READ) ? 1 : 0, + .base = base, + .size = size, + .flags = flags, + .timeout_ns = 10000000, /* 10ms default */ + }; + struct gsp_wpr_response resp; + size_t resp_size = sizeof(resp); + struct gsp_wpr_region *rgn; + int ret; + + /* Validate alignment */ + if (base & 0xFFF || size & 0xFFF) + return -EINVAL; + + rgn = kzalloc(sizeof(*rgn), GFP_KERNEL); + if (!rgn) + return -ENOMEM; + + rgn->base = base; + rgn->size = size; + rgn->flags = flags; + rgn->vf_id = vf->id; + rgn->timeout_ns = payload.timeout_ns; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + mutex_lock(&vf->wpr_lock); + list_add(&rgn->list, &vf->wpr_list); + mutex_unlock(&vf->wpr_lock); + + dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x (emulation)\n", + vf->id, base, base + size, flags); + + return 0; + } + + /* Send to GSP WPR handler (0x04F00B0C/0x04F00C78) */ + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_MAP, /* Uses MAP RPC for WPR */ + &payload, sizeof(payload), &resp, &resp_size); + if (ret) { + kfree(rgn); + return ret; + } + + if (resp.status != 0) { + kfree(rgn); + return gsp_errno_to_errno(resp.status); + } + + mutex_lock(&vf->wpr_lock); + list_add(&rgn->list, &vf->wpr_list); + mutex_unlock(&vf->wpr_lock); + + dev_dbg(vf->gsp->dev, "VF %u: WPR protect [0x%llx, 0x%llx) flags=0x%x\n", + vf->id, base, base + size, flags); + + return 0; +} + +/* Unprotect Region */ +int gsp_vf_wpr_unprotect(struct gsp_vf *vf, uint64_t base) +{ + struct gsp_wpr_payload payload = { + .vf_id = vf->id, + .op = 2, /* unprotect */ + .base = base, + .size = 0, + .flags = 0, + }; + struct gsp_wpr_response resp; + size_t resp_size = sizeof(resp); + struct gsp_wpr_region *rgn, *tmp; + bool found = false; + int ret; + + /* In emulation mode, handle locally */ + if (emulation_mode) { + mutex_lock(&vf->wpr_lock); + list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { + if (rgn->base == base) { + list_del(&rgn->list); + kfree(rgn); + found = true; + break; + } + } + mutex_unlock(&vf->wpr_lock); + + dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx (emulation)\n", vf->id, base); + + return found ? 0 : -ENOENT; + } + + ret = gsp_rpc_send_sync(vf->gsp->rpc, GSP_RPC_UNMAP, /* Uses UNMAP for WPR */ + &payload, sizeof(payload), &resp, &resp_size); + if (ret) + return ret; + + if (resp.status != 0) + return gsp_errno_to_errno(resp.status); + + mutex_lock(&vf->wpr_lock); + list_for_each_entry_safe(rgn, tmp, &vf->wpr_list, list) { + if (rgn->base == base) { + list_del(&rgn->list); + kfree(rgn); + found = true; + break; + } + } + mutex_unlock(&vf->wpr_lock); + + if (!found) + return -ENOENT; + + dev_dbg(vf->gsp->dev, "VF %u: WPR unprotect 0x%llx\n", vf->id, base); + + return 0; +} + +/* Set Timeout */ +int gsp_vf_wpr_set_timeout(struct gsp_vf *vf, uint64_t timeout_ns) +{ + /* Update timeout for all WPR regions */ + struct gsp_wpr_region *rgn; + + mutex_lock(&vf->wpr_lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + rgn->timeout_ns = timeout_ns; + } + mutex_unlock(&vf->wpr_lock); + + return 0; +} + +/* Register Completion Callback */ +int gsp_vf_wpr_register_callback(struct gsp_vf *vf, enum gsp_wpr_event type, + void (*cb)(struct gsp_vf *, uint64_t, uint32_t), + void *ctx) +{ + struct gsp_wpr_region *rgn; + + mutex_lock(&vf->wpr_lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + switch (type) { + case GSP_WPR_WRITE_COMPLETE: + case GSP_WPR_WRITE_TIMEOUT: + rgn->completion_cb = cb; + break; + case GSP_WPR_READ_COMPLETE: + case GSP_WPR_READ_TIMEOUT: + rgn->timeout_cb = cb; + break; + } + } + mutex_unlock(&vf->wpr_lock); + + return 0; +} + +/* WPR Event Handler (called from notification) */ +void gsp_wpr_handle_event(struct gsp_device *gsp, struct gsp_rpc_message *msg) +{ + struct gsp_rpc_response *resp = (struct gsp_rpc_response *)msg->payload; + uint32_t event_type = resp->payload[0]; + uint32_t vf_id = resp->payload[1]; + uint64_t address = *(uint64_t *)&resp->payload[2]; + struct gsp_vf *vf; + struct gsp_wpr_region *rgn; + + vf = gsp_vf_get(gsp, vf_id); + if (!vf) + return; + + atomic64_inc(&gsp->wpr_events); + + mutex_lock(&vf->wpr_lock); + list_for_each_entry(rgn, &vf->wpr_list, list) { + if (address >= rgn->base && address < rgn->base + rgn->size) { + switch (event_type) { + case GSP_WPR_WRITE_COMPLETE: + case GSP_WPR_READ_COMPLETE: + if (rgn->completion_cb) + rgn->completion_cb(vf, address, event_type); + break; + case GSP_WPR_WRITE_TIMEOUT: + case GSP_WPR_READ_TIMEOUT: + if (rgn->timeout_cb) + rgn->timeout_cb(vf, address, event_type); + break; + } + break; + } + } + mutex_unlock(&vf->wpr_lock); + + gsp_vf_put(vf); +} + +/* Default Completion Callback */ +void gsp_wpr_default_complete_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type) +{ + dev_dbg(vf->gsp->dev, "VF %u: WPR %s complete at 0x%llx\n", + vf->id, type == GSP_WPR_WRITE_COMPLETE ? "write" : "read", addr); + + /* If this was a page table write, invalidate TLB */ + if (addr >= vf->pt_base && addr < vf->pt_base + vf->pt_size) { + /* TODO: TLB invalidate */ + } +} + +/* Default Timeout Callback */ +void gsp_wpr_default_timeout_cb(struct gsp_vf *vf, uint64_t addr, uint32_t type) +{ + dev_warn(vf->gsp->dev, "VF %u: WPR %s timeout at 0x%llx\n", + vf->id, type == GSP_WPR_WRITE_TIMEOUT ? "write" : "read", addr); + + /* Throttle or reset VF */ + if (type == GSP_WPR_WRITE_TIMEOUT) { + /* TODO: VF throttle */ + } else { + /* TODO: VF reset */ + } +} \ No newline at end of file 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 Binary files /dev/null and b/userspace/tests/test_vgpu_userspace differ -- cgit v1.2.3