From 9d1e0cf87df63a2f81c7bb471481336384874861 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 8 Oct 2026 13:41:31 -0400 Subject: docs(vexecution-plane): add the vGSP Execution Plane architecture doc, wiki deep dive and indexes --- docs/03-architecture/13-vexecution-plane.md | 1029 +++++++++++++++++++++++++++ docs/README.md | 34 +- wiki/Execution-Plane.md | 179 +++++ wiki/README.md | 1 + 4 files changed, 1228 insertions(+), 15 deletions(-) create mode 100644 docs/03-architecture/13-vexecution-plane.md create mode 100644 wiki/Execution-Plane.md diff --git a/docs/03-architecture/13-vexecution-plane.md b/docs/03-architecture/13-vexecution-plane.md new file mode 100644 index 0000000..3cc6adb --- /dev/null +++ b/docs/03-architecture/13-vexecution-plane.md @@ -0,0 +1,1029 @@ +# The vGSP Execution Plane (vEP) + +> **What this document is.** The canonical architecture and design document for +> the layer of `vgsp.ko` that turns guest-authored GPU work into physical +> execution and guest-visible completion: from a guest CUDA kernel launch's +> GPFIFO push, through ring discovery, VA/PA truth, semaphores, counters and +> notifiers, the QMD and shadow windows, submission to the physical engines, +> and the completion published back to the guest. It names that layer +> **vGSP Execution Plane (vEP)**, defines its boundaries and its four work +> domains, and maps every major mechanism to the code that implements it. +> +> **Audience.** Developers and architects working on guest execution, the +> forwarding ladder, or the GR launch frontier. Read +> [`01-system-overview.md`](01-system-overview.md) and +> [`09-full-system-picture.md`](09-full-system-picture.md) first for the whole +> machine; read [`11-physical-backend-guest-wiring.md`](11-physical-backend-guest-wiring.md) +> for the physical-session lifecycle this document builds on. +> +> **Status: 2026-10-08, tree `master @ 66388560`.** Every empirical claim below +> cites a commit, a code location, a tracked document, or a local campaign +> evidence file (paths under `backups/evidence-…/` are local evidence +> artifacts, not shipped in the repository). Where a mechanism is a leading +> hypothesis rather than a proven fact, the text says so. Nothing in this +> document is a stability or support claim. + +Status tags used throughout (same convention as docs 09/10/11): +`[live]` proven with the stock driver; `[gated]` implemented but default-off or +parameter-gated; `[wip]` partially implemented; `[wall]` the current blocker; +`[target]` the named success latch; `[planned]` designed, not implemented. + +--- + +## 1. What the vEP is: name, boundaries, domains + +### 1.1 The name and why + +**Canonical name: vGSP Execution Plane (vEP).** + +> The **vGSP Execution Plane** is the host-side subsystem of `vgsp.ko` that +> turns guest-authored GPU work into physical execution and guest-visible +> completion. It spans channel-ring and USERD discovery, GPU VA-to-PA truth, +> semaphore-release application, completion-counter and notifier publication, +> QMD handling for forwarded launches, and submission to the physical GPU's +> engines under the module-owned physical-backend session. + +Two framing terms, used consistently from here on: + +* **vGSP (virtual GSP)** — our host-side reimplementation of the GSP/GSP-RM + interface: the virtual device plus the emulated firmware service that the + stock guest driver interacts with. `vgsp.ko` is vGSP's implementation. The + guest believes it is talking to its GPU's GSP; vGSP is the thing it is + actually talking to. +* **vEP** — the execution half of vGSP. The *control plane* (boot handshake, + message queues, RPC, RM objects, BAR shadows) makes the guest driver run; + the *execution plane* makes the guest's work run. This document covers the + second half and only the seams of the first that shape execution. + +The name is **ours**, the name of a subsystem we implement. It is deliberately +not a vendor term: the three-letter form reads as the sibling of the existing +project vocabulary (`vGSP`, `vGPU`, `vgsp.ko`), and "execution plane" states +the job (execute guest work) rather than the mechanism (forwarding), which +leaves room for the fast paths that stay in software. + +**Alternates considered and rejected:** + +| Rejected name | Why not | +|---|---| +| "Work plane" / "work path" | Already used in [`06-host-module-and-hybrid-model.md`](06-host-module-and-hybrid-model.md) and [`09-full-system-picture.md`](09-full-system-picture.md) for the software-executed bridge inside the pump; reusing it would silently change the documented model. | +| "Submission plane" | Covers only the submit half; hides discovery, translation, counters and completion — over half the subsystem. | +| "Forwarding plane" | Mechanism-centric: forwarding is one execution mode (some work is applied in software by design), and "forwarding" already means a specific ladder in docs 11. | +| "Data plane" | Networking connotation; the plane's most latency-critical work is state operations (releases, fences), not bulk data. | +| "vGPU Work Engine" | "Engine" is the hardware term for GR/CE; naming software an engine invites confusion in every review. | +| "GPU Execution Plane (GEP)" | Reads as a hardware/PCIe concept, not as our software subsystem. | +| "RAS plane" (Ring/Alloc/Sem) | Cryptic, and it omits the completion/publication half. | +| "vGSP Runlist" | Collides with the hardware runlist/TSG concept it actually schedules onto. | + +### 1.2 Scope: in and out + +**In scope (implemented by the vEP):** + +* the service thread and its per-channel pump + (`svc_thread`, `svc_pump_channels`, both `kernel/gsp_sema_service.c`); +* ring/USERD discovery, autopin and the VA-to-PA truth model + (`svc_ring_autopin`, `svc_gr_ring_autopin`, `svc_gr_content_discover`, + `svc_chan_userd`, `svc_lookup`, `svc_walk_vaspaces`, `svc_gmmu_walk`, + `svc_pd_discover`, `svc_targeted_resolve`, `svc_pool_pa_resolve`); +* push decode and QMD capture (`svc_decode_stream`, `svc_qmd_accum`, + `svc_gr_qmd_decode`, `svc_gr_cand_capture`); +* release application and completion publication (`svc_sem_write`, + `svc_counter_release_apply`, `svc_counter_derive`, `svc_counter_autoreg`, + `svc_counter_flush_pending`, `svc_counter_publish`); +* notifier and channel-event delivery (`svc_notify_complete`, + `gsp_rm_post_channel_event`); +* physical submission and its mode matrix (`svc_gr_req_fill`, + `svc_gr_request`, `svc_fwd_gr_work`, `gsp_phys_backend_guest_gr_submit`, + `gsp_phys_backend_guest_ce_copy`, the ladder channel and its VAs); +* the pacing of all of the above (doorbell steering, discovery cadence, the + single-inflight ordered workqueue) and its sharing across mdevs. + +**Out of scope (context, not this layer):** the GSP boot handshake and +message-queue transport ([`03-gsp-and-gsp-rm.md`](03-gsp-and-gsp-rm.md)), +the RM control/object surface as a whole +([`03-gsp-and-gsp-rm.md`](03-gsp-and-gsp-rm.md) §4, `kernel/gsp_rm_emulation.c`), +BAR shadows and MSI-X injection +([`06-host-module-and-hybrid-model.md`](06-host-module-and-hybrid-model.md)), +the physical claim/boot lifecycle +([`08-physical-gpu-integration.md`](08-physical-gpu-integration.md)), and the +guest stack ([`02-guest-stack-and-cudasw.md`](02-guest-stack-and-cudasw.md)). +The vEP *uses* those interfaces; it does not own them. + +### 1.3 The four work domains + +The vEP can be described as one pipeline, but it answers four distinct +questions. This mapping is the document's organizing model. + +| Domain | Question | vEP components | State | +|---|---|---|---| +| **Scheduler** | Which guest channel's work runs next, and at what pace? | Doorbell token steering (`gsp_sema_service_doorbell`, called from `main.c:865`); per-pass channel pump (`svc_pump_channels`); discovery cadence (`svc_gr_content_discover`); single-inflight ordered workqueue `gsp-phys-work` (`svc_gr_request`, `svc_fwd_request`); deferred-release hold (`svc_fwd_hold`); the service thread loop (`svc_thread`) | `[live]` software pacing and ordering; `[planned]` tenant fairness | +| **Allocator** | Where do rings, USERDs, counters, notifiers, scratch pages and PT windows live, and who owns them? | CE/GR ring autopin and pins (`svc_ring_autopin`, `svc_gr_ring_autopin`, `svc_pin_store`/`svc_ipin_store`); GR content-keyed ring/push pairing (`svc_gr_content_discover`, `svc_gr_content_pin`); counter slot derive/autoreg (`svc_counter_derive`); ladder scratch windows and PT pools (window-0 shadow pool, data-prewin pool, the 128-byte PTE fetch-line pool in `gsp_phys_booter.c`); the BAR1 vidmem shadow (`vgpu->bars[1].vaddr`) | `[live]` discovery, pins, scratch pools; `[wip]` protocol-derived allocation; `[planned]` real VRAM backing | +| **Control** | Which RM objects and controls shape execution, and how are they emulated or forwarded? | Channel registration from `GSP_RM_ALLOC` (`gsp_rm_register_channel` → `gsp_sema_service_channel_add`); event registration; `SET_PAGE_DIRECTORY` recording; `PROMOTE_CTX`; engine/class topology; the mode matrix (§2.3) | `[live]` registration + emulate; `[gated]` read-only control forwarding; `[planned]` alloc/free forwarding | +| **Share** | How do clients and mdevs share one physical GPU with isolation and fairness? | mdev backend refcount and ownership (`gsp_phys_backend_start/stop`); one real GSP session serialized by `gsp_phys_rm_lock`; one ordered workqueue and one in-flight request slot; per-mdev emulation context; planned per-mdev real clients and fairness policy | `[live]` single-tenant; `[wip]` per-client state reset (ISSUE-0002); `[planned]` multi-tenant | + +The four domains cut across one code path. The rest of this document follows +that path: the architecture and its diagrams (§2), how each stage actually +works today (§3), the end-to-end GR chain (§4), then the program-level goals, +challenges, portability and future work. + +--- + +## 2. Architecture + +### 2.1 Component map + +```mermaid +flowchart TB + subgraph GUEST["Guest VM - stock software"] + APP["CUDA application"] + LCU["libcuda - builds push + QMD"] + GKO["nvidia.ko - RM + UVM"] + APP --> LCU --> GKO + end + + subgraph VDEV["vgsp.ko - guest-facing"] + BAR["BAR0 shadow + doorbell 0x88704"] + RM["GSP-RM emulation - controls, allocs, events"] + end + + subgraph VEP["vGSP Execution Plane"] + DB["Doorbell steering"] + PUMP["Channel pump svc_pump_channels"] + DISC["Ring/USERD discovery + autopin"] + TRUTH["VA-to-PA truth - walk, pins, rescue"] + DEC["Push decode + QMD capture"] + REL["Release apply + counters + notifiers"] + WQ["Ordered workqueue gsp-phys-work"] + SUB["Ladder submission gr/ce"] + MODES["Mode matrix"] + PUMP --> DISC --> TRUTH --> DEC --> REL + DB --> PUMP + DEC --> WQ --> SUB + MODES --> SUB + end + + subgraph BE["Physical backend - real GSP session"] + LAD["Module-owned ladder channel"] + GSP["Real GSP-RM server"] + ENG["GR and CE engines"] + end + + subgraph HW["GPU hardware"] + GMMU["GMMU page tables"] + VRAM["VRAM / FB"] + end + + GKO -->|"MMIO + DMA + MSI-X"| BAR + BAR --> DB + RM -->|"channel/event/vas registration"| PUMP + REL -->|"release, counter, notifier, event"| RM + RM -->|"MSI-X"| GKO + TRUTH -->|"reads guest page tables"| GKO + SUB --> LAD --> GSP + LAD -->|"GPFIFO + doorbell"| ENG + GSP --> ENG + ENG -->|"release semaphore"| SUB + ENG --> GMMU + SUB -.->|"planted PTEs"| GMMU + VRAM -.-> GMMU + + classDef done fill:#d5f5d5,stroke:#1e7a1e + classDef wip fill:#fff3cd,stroke:#9a7b1a + classDef plan fill:#f8d7da,stroke:#9a1a1a + classDef hw fill:#e2e8f0,stroke:#4a5568 + class DB,PUMP,DISC,TRUTH,DEC,REL done + class WQ,SUB,MODES wip + class BAR,RM done + class LAD,GSP,ENG hw + class GMMU,VRAM hw +``` + +ASCII fallback (reading top to bottom: guest → vgsp.ko → physical GPU): + +```text +Guest VM (stock) + CUDA app -> libcuda (push + QMD) -> nvidia.ko (RM, UVM) + | MMIO write to BAR0 doorbell 0x88704 ^ MSI-X (events) + v | +vgsp.ko ============================================ | ===================== + BAR0 shadow / doorbell -> Doorbell steering | + GSP-RM emulation <------- release/counter/event --+ + | channel, event, vaspace registration + v + +----------------- vGSP Execution Plane ----------------------+ + | pump -> ring/USERD discovery -> VA-to-PA truth -> decode | + | -> release apply + counter publish + notifier | + | -> ordered workqueue -> ladder submission (mode matrix) | + +-------------------------- | ---------------------------------+ + v +Physical backend (module-owned GPU, real GSP session) + ladder channel -> real GSP-RM -> GR/CE engines -> GMMU/VRAM + ^ engine-authored release semaphore (polled by the submit) +``` + +### 2.2 Data flow of one CUDA kernel launch + +The full chain, from CUDA context creation to the guest waking on the +completion. Stages 4–8 are shared by every channel type; stage 9 onward is the +GR-specific chain of §4. Status per stage is in the table after the diagram. + +```mermaid +sequenceDiagram + autonumber + participant CU as Guest CUDA app + participant LC as libcuda / nvidia.ko + participant DEV as BAR0 shadow + doorbell + participant PU as vEP pump (service kthread) + participant TR as vEP truth (walk/pins/rescue) + participant DC as vEP decode + capture + participant WQ as Ordered workqueue + participant LB as Ladder submission + participant GP as Physical GSP + GR engine + participant CM as Completion publication + + CU->>LC: cuCtxCreate, cuMemAlloc, cuLaunchKernel + Note over LC: builds GR push: ctx-init methods,
QMD install, SASS pointers, params + LC->>DEV: GPFIFO entry appended; doorbell 0x88704 + DEV->>PU: doorbell token steering (main.c:865) + PU->>TR: resolve ring VA and USERD (discovery/autopin) + PU->>TR: read GP_PUT, fetch GPFIFO entry, decode method stream + TR-->>PU: translated pages (or bounded rescue) + PU->>DC: accumulate QMD window + launch candidate + DC->>WQ: one GR request (single in-flight) + WQ->>LB: guest GR submit (QMD relocate, PTEs, method block) + LB->>GP: ladder GPFIFO publish + doorbell + GP->>GP: front-end fetch, inline-QMD install [wall],
ctx switch, SASS dispatch + GP-->>LB: engine authors release semaphore in module/guest page + LB-->>PU: completion observed (poll) + PU->>CM: apply deferred release once; publish counter; notify + CM->>DEV: channel event -> MSI-X (gsp_rm_post_channel_event) + DEV->>LC: guest wakes; CUDA continues +``` + +ASCII fallback of the same chain: + +```text +cuLaunchKernel + -> push built (ctx init, QMD, SASS ptrs, params) + GPFIFO entry + doorbell + -> pump reads GP_PUT / entry; auth: ring + USERD discovered, page translated + -> decode stream; capture QMD window and launch candidate + -> ordered workqueue: one in-flight GR request + -> ladder submission: relocate QMD, plant PTEs (pre-built fetch lines), + build launch methods, GPFIFO publish + doorbell on the real channel + -> physical front-end fetch -> inline-QMD install [wall] -> GR engine + -> engine authors release -> poll observes + -> apply deferred release exactly once + counter publish + notifier + -> channel event -> MSI-X -> guest tracker wakes +``` + +| Stage | What happens | State (2026-10-08) | +|---|---|---| +| 1 | Guest CUDA builds the launch (push methods, QMD, params, SASS refs) | `[live]` observed with the stock driver | +| 2 | GPFIFO entry + doorbell written into the BAR0 shadow | `[live]` | +| 3 | Pump steering, ring/USERD discovery, entry fetch, translation | `[live]` for CE; `[wip]` for GR (discovery now succeeds; see §3.2) | +| 4 | Push decode; QMD window accumulation; launch candidate capture | `[live]` observed (ring entry consumed; methods execute) | +| 5 | Physical submit on the ladder channel (mode-matrix shaped) | `[gated]` default-off; latched on `rmcall_gr_exec_ok` | +| 6 | Front-end fetch + method execution on the real channel | `[live]` observed | +| 7 | **Inline-QMD install / engine reads the descriptor** | **`[wall]` not observed** | +| 8 | Context switch + SASS dispatch to SMs | `[wall]` not observed for forwarded launches | +| 9 | Engine-authored release + channel release | `[wall]` absent for forwarded launches | +| 10 | Completion application, counter publish, notifier, event, MSI-X | `[wip]` CE path live default-off; GR wake-up is the open question (ISSUE-0001) | + +The stage table's authority is +[`04-implementation/03-execution-frontier.md`](../04-implementation/03-execution-frontier.md) +(tracked) and the 2026-10-08 campaigns below it. Stage 1–4 status is +re-verified by the `grv3` campaign: ring entries *were* consumed for ranks +0–2, releases *were* applied, and the guest still did not proceed (local +evidence `backups/evidence-20261002-victory/analysis-2026-10-08-stall-v2/FORENSICS-grv3.md` +§0 and §2). + +### 2.3 The mode matrix + +The physical submit path is parameterized by a mode matrix: a set of +module parameters, almost all default-off, that select rebuild-free A/B +variants of the GR context commit, the launch method stream, the memory +windows and the completion relay. The matrix exists because the rules the front-end and engine +enforce are still being recovered; each knob isolates one variable so a +supervised cycle can falsify one hypothesis at a time. Every non-neutral knob +is documented in `kernel/gsp_phys_gpu.c` (and, for the work-forwarding waves, +`main.c`) with its rationale and rollback; the parameter is the experiment's +record. + +```mermaid +flowchart LR + subgraph CTX["GR context commit"] + A1["gr_promote 0..35
promote shape bisection"] + A2["srv_ctxbuf / srv_ctxcommit
server-managed ctx"] + A3["channel variants V5/V6
golden replay"] + A4["vas_shared / ctxshare_force
VAS ownership"] + end + subgraph LAUNCH["GR launch shape"] + B1["prelude / qmdvirt / pcas
method-stream form"] + B2["lmem_en - SKEDCHECK05 fix"] + B3["lane_skip / lane_tail
schedule-state controls"] + B4["r1_first / recommit
ordering"] + end + subgraph MEM["GR memory windows"] + C1["qmdwin / shadow_win0
win0 PT line"] + C2["data_prewin / ptr_guest
PTE kind"] + C3["mail_patch / inst_sys
instance aperture"] + C4["gpc_mmu_init
GPC walker"] + end + subgraph COMP["Completion + diagnostics"] + D1["rel_mod - engine-completion discriminant"] + D2["skedrec / img_probe
notifier_sys / faultbuf_sys"] + end + subgraph WAVES["CE forwarding waves"] + E1["work_forward -> work_defer_release"] + E2["work_hw_primary -> relay"] + E3["fault_relay / fault_inject"] + end + LAUNCH --> MEM --> CTX + CTX --> COMP + WAVES --> COMP +``` + +ASCII fallback: + +```text +CTX COMMIT gr_promote(0..35) srv_ctxbuf srv_ctxcommit srv_main_ignore + channel(V5/V6) golden golden_exec_shape ctxshare_force vas_shared +LAUNCH SHAPE prelude qmdvirt pcas pcas_two_step lmem_en lmem_base lmem_tpc_size + lane_skip lane_tail r1_first recommit override_hex +MEM WINDOWS qmdwin shadow_win0 data_prewin ptr_guest pte_kind mail_patch + inst_sys gpc_mmu_init +COMPLETION rel_mod skedrec img_probe notifier_sys faultbuf_sys +CE WAVES work_forward -> work_defer_release -> work_hw_primary -> relay + fault_relay fault_inject +GATES gsp_phys_gpu mdev_owned gr_forward guest_mem static_live ctrl_forward +``` + +Not all of these are part of the vEP's steady state: `gsp_phys_gpu`, +`gsp_phys_hazard_ack` and `mdev_owned` gate the physical session itself +(docs 08/11); `static_live` and `ctrl_forward` are control-plane overlays. +Everything else in the matrix shapes execution. The single most important +gate is `rmcall_gr_exec_ok`: the latched proof, set only once a real GR +submission has executed on the ladder (`gsp_phys_booter.c:14614`), that +`gsp_phys_backend_gr_ready()` (`:14865`) requires before any guest GR launch +may forward. A parameter alone can never make the module pretend GR works. + +--- + +## 3. Design principles and implementation reality + +### 3.1 Principles the implementation enforces + +1. **Emulate the interface, execute the work.** The vEP never modifies the + guest driver; it satisfies the interfaces the guest observes and arranges + real execution underneath (project principle, doc 01 §1). +2. **Guest truth over guesses.** Translations come from driver-published + structures (VAS page directories, channel descriptors) or validated + content, with a read-back proof before a pin is latched; rank-derived and + convention-derived addresses are candidates, never facts. The pinned + truth model is explicit in `gsp_sema_service.h` (`struct sema_channel_desc`, + `struct sema_vaspace_desc`). +3. **One writer per structure.** Channel/vaspace state is copied out under + `svc.chan_lock`/`svc.vas_lock`; injected pins never overwrite authoritative + discovered data; guest-status-queue writes are single-writer in + `gsp_rm_send` (doc 10 §5, doc 11 §1.3). +4. **Bounded budgets everywhere.** Bounded scans, retry counts, grace windows + (`sema_ce_grace_ms=25000`, `sema_gr_grace_ms=8000`, `sema_push_hold_ms=2500`), + a 5→60-pass discovery epoch with cooldown, bounded poll quanta (5 ms in the + ladder, `gsp_phys_booter.c:1404`). A miss must degrade, never freeze; the + starvation telemetry from ISSUE-0001 fix #2 exists to make a bound miss + visible. +5. **Default-off, fail-closed, one-line rollback.** Every execution-changing + behavior is behind a parameter, and every parameter has a fallback story + (docs 11 §5). Forwarding refuses when the backend is not READY; a held + release is applied exactly once by software when the hardware path misses + (P6b wave 1.5 / P6c). +6. **Evidence gates the latch.** A capability is enabled only when a named + acceptance signal has been observed (`REAL GR SEM OK` → `rmcall_gr_exec_ok`); + "silence is not health" (doc 03-execution-frontier §8). + +### 3.2 Ring discovery: kernel-native, both apertures, content-keyed + +The pump cannot execute anything until it can name two addresses per channel: +the GPFIFO ring VA's translation, and the USERD's translation (PUT/GET live at +USERD+0x8c/0x88, `SVC_USERD_GP_PUT`/`SVC_USERD_GP_GET`, +`kernel/gsp_sema_service.c:143-144`). + +Today discovery has three cooperating sources, in order of preference: + +1. **Injected pins** (debug tooling and, historically, the userspace watcher + pipeline) — authoritative but external; `svc_pin_lookup_any`. +2. **The kernel-native trusted walk** — driver-published page directories + recorded from `SET_PAGE_DIRECTORY` (`svc_walk_vaspaces`), a self-discovered + directory (`svc_pd_discover`, `svc_walk_discovered`), and per-VA targeted + resolution (`svc_targeted_resolve`), all through the shared four-level + `svc_gmmu_walk` with read-back validation (`svc_lookup_trusted`). +3. **Content-keyed discovery** — when no translation source can name a GR + ring, `svc_gr_content_discover` scans for the *content* of the ring and the + staged ctx-init push, pairs them per rank, and validates the pair in + `svc_gr_content_pin` before pinning. The scan covers **both apertures**: + the emulated vidmem aperture (`vgpu->bars[1].vaddr`) and guest system RAM + through `svc_read()` = `vfio_dma_rw` (`gsp_sema_service.c:754`). + +The history matters because it explains the current code shape: + +* CE channels were discovered first and stably: the CE ring convention + `FB 0x80000 + 0x20000*rank` pinned 6/6 CE rings (`ring_auto=6`) in the + stalled v2/grv1 boots (FORENSICS-v2 §a; FORENSICS-grv1 §0.1). +* GR rings follow `VA 0x200200000 + 0x3000*rank` with the USERD at + `+0x2000` (`SVC_GR_RING_VA_BASE/STRIDE`, `SVC_GR_USERD_OFF`, + `gsp_sema_service.c:7334-7431`), but *where the ring physically lives* is + boot-dependent: green m104 found all 20 GR rings at + `FB 0x140000 + 0x3000*rank`; grv1's guest never wrote them to FB; + grv3's guest wrote only ranks 0–2 (FORENSICS-grv3 §1). +* FORENSICS-grv1 proved the ring was **not in guest RAM** and recommended an + FB-aperture scan; FORENSICS-grv3 identified the 60-pass epoch death and + recommended periodic re-arm plus an FB-first fast path. Those three changes + are in the tree as commits `ebcfc1c2` (both-aperture kernel-native + walk + content fallback), `7abd8f87` (FB-aperture discovery + per-channel + re-arm, FORENSICS-grv1 R1) and `66388560` (periodic re-arm + FB-first fast + path, FORENSICS-grv3 R1) — the current HEAD. + +In the current code (`svc_gr_content_discover`, `gsp_sema_service.c:7650`): +the FB scan runs first at a page stride (milliseconds), push candidates +persist across passes so a late ring can pair without re-walking 6 GiB, a +registration re-arms the epoch immediately, and hitting the pass cap starts a +30-second cooldown and then a fresh epoch while any registered rank still +lacks a ring — never the old permanent retirement. + +**Known limits.** The content path is gated to ranks 0–7 +(`SVC_GR_CONTENT_RANKS=8`) and to the documented VA layout +(`gpfifo_va == BASE + STRIDE*rank`); a layout change deliberately falls back +to the safe paths. The walk depends on the guest publishing the owning VAS: +grv1/grv3 saw only two `SET_PAGE_DIRECTORY` publications, and the ring VA was +not covered by either (FORENSICS-grv1 §2). Content discovery is the fallback +for exactly that hole; a protocol-derived source for the ring VA remains the +durable direction (ISSUE-0001 "durable direction", `issue-number-1.md:107`). + +### 3.3 The semaphore service: release application + +`gsp_sema_service.c` is the vEP's engine room. Its service thread +(`svc_thread`, `:13287`) loops over the open mdev, runs the pump, retries +pending releases, and paces discovery. The release path: + +1. **Decode** (`svc_decode_stream`, `:5170`) turns a consumed GPFIFO entry's + method stream into operations; releases are recognized as + increment-method writes to a semaphore VA with a payload. +2. **Translate** the target VA: `svc_lookup_trusted` first, then the pool + resolver `svc_pool_pa_resolve` (`:2284`) — the resolver ISSUE-0001 fix #1 + completed by adding the driver-published VAS walk + (`svc_walk_vaspaces`, `:1274`); a miss is queued in `svc_pending_add` + (`:2472`) and retried every pass by `svc_apply_pending` (`:6201`), with the + per-entry `tries` telemetry added by the same fix (`7f21daa7`). +3. **Apply** (`svc_sem_write`, `:2474`) reads the current word, writes only + forward (never backwards), and is the sole software author of a release; + a forwarded operation's releases can instead be deferred and then applied + exactly once by the hold's finish path (P6b wave 1.5), or authored by the + engine itself under P6c's relay (default off, docs 11 §P6c). + +The complementary **counter** path handles GR completion *counters* (distinct +from CE tracking semaphores): `svc_counter_derive` (`:4658`) derives the +counter slot from structural translations; `svc_counter_autoreg` registers it +per GR channel; `svc_counter_release_apply` (`:4433`) applies decoded releases +to the slot; `svc_counter_flush_pending` (`:4575`) lands payloads recorded +before the slot was known; `svc_counter_publish` (`:9077`) writes the GPFIFO +PUT into the slot when it is ahead of the current value. Two facts learned the +hard way are now encoded: the counter pool total is fixed at 20 +(`SVC_GR_COUNTER_TOTAL`, commit `fcb96912`) because a partial live channel +count poisons every later rank's slot; and the early publish is guarded so a +guessed PUT is never written (the H3 finding, `gsp_sema_service.c:11448-11477`, +FORENSICS-v2 §b/H3). + +### 3.4 Notifiers, GPFIFO consume, event delivery + +* **GPFIFO consume.** The pump reads `GP_PUT` from the USERD, walks entries + from its own GET, decodes, and advances; a replay-suppression path keeps + pin batches from re-consuming old slots (`gpfifo_skipped`), and a per-entry + grace window holds an untranslatable GR entry before it is consumed as + stale (`sema_gr_grace_ms`, default 8 s). `svc_userd_probe` (`:7099`) + distinguishes "unreadable" from "readable but idle (0/0)" so a zero page is + never mistaken for a live channel. +* **Notifiers.** `svc_notify_complete` (`:7207`) services the channel's error + notifier token and posts the channel event. The notifier page address and + aperture come from the channel allocation descriptor + (`sema_channel_desc.notifier_pa/as`, `gsp_sema_service.h:31-32`), recorded + at channel registration. +* **Events.** `gsp_rm_post_channel_event` (`gsp_rm_emulation.c:602`) finds the + guest's registered event objects (`rm->events[]`, registered from the event + alloc in `gsp_rm_handle_rm_alloc`, `:2443-2489`) and sends the RPC event; + the reply path injects MSI-X through the live eventfd + (`gsp_emu_inject_completion_interrupt`). A green boot shows the sequence + `notifier cleared … status=0xffff` (per GR channel) followed by the event + post; grv3 shows neither, and its three completed channels re-queued the + identical push and gave up — the leading reading is "counter moved, wake-up + never arrived" (FORENSICS-grv3 §3, hypothesis (d); not yet proven live + because the relevant traces are `sema_dbg`-gated). + +### 3.5 Physical submit + +Forwarding is a two-stage ladder. The sema service captures a candidate and +queues one request on the single ordered workqueue `gsp-phys-work` +(`svc_gr_request`, `:10991`; `svc_fwd_hold` is the single in-flight slot). +The work item calls into the backend: `gsp_phys_backend_guest_ce_copy` for CE +(`gsp_phys_booter.c:15621`) or `gsp_phys_backend_guest_gr_submit` (`:16457`) +for GR, both gated by `gsp_phys_backend_work_ready()/ +gsp_phys_backend_gr_ready()` (`:14865`), which require READY, a live claim, +an unload not committed, a client-memory ladder channel, a work token, every +ladder VA, `gsp_phys_guest_mem` and an active guest RAM backing file. + +GR submission (`gsp_phys_backend_guest_gr_submit`) is the vEP's most +parameterized function: it relocates and pre-writes the QMD (inline-QMD +window, `gsp_phys_rmcall_gr_qmdwin`), copies program/param pages into shadow +windows whose PTEs are planted at stage 3 before the engine's first fetch +(`gsp_phys_rmcall_gr_shadow_win0`, `data_prewin`), builds the launch method +block (`gsp_phys_rmcall_build_gr_launch`, `:15862`) from the recovered method +vocabulary (compute init, inline-QMD window, PCAS pairs, report semaphore), +publishes it to the ladder channel's GPFIFO and rings the doorbell +(`gsp_phys_rmcall_publish_locked`, `:14345`), then polls for the release with +`rel_mod`/`skedrec` diagnostics. The PTE fetch-line rule is enforced: the +engine caches PTE fetches at 128-byte granularity with no invalidate path, so +each scratch pair consumes a never-reused fetch line from pools built before +first engine access (docs 11 §P6; `gsp_phys_booter.c:1044-1070`). + +Measured cost: the m141-r3 baseline (`895ab8b8`) is `elapsed_ms=189` for the +first forwarded launch and `166` for the second, of which only ~7.9 ms is the +post-doorbell engine path; ~180 ms is serialized per-window `PDE_UPDATE` +forwards (local evidence and the prepared harness: +`tools/cuda-ab/perf/PLAN-AND-HARNESS.md` §1.3). The baseline is a +forwarding-service cost, not engine time — the number to drive down is the +7.9 ms and the per-window round trips. + +### 3.6 Citation map for this section + +| Claim | Authority | +|---|---| +| Both-aperture kernel-native GR discovery + content fallback | commit `ebcfc1c2`; `gsp_sema_service.c:7334-7529` | +| FB-aperture discovery + re-arm; periodic re-arm + FB-first | commits `7abd8f87`, `66388560`; `gsp_sema_service.c:7650-7800` | +| Ring not in guest RAM; RAM-only scan limitation | `FORENSICS-grv1.md` §0/§1/§A (local evidence) | +| 60-pass cap dead-end; wake-up hypothesis | `FORENSICS-grv3.md` §0/§3/§4/§5 (local evidence) | +| CE clean consumption at 89 releases; no starvation | `FORENSICS-v2.md` §b/§c (local evidence) | +| Pool resolver + starvation telemetry | commit `7f21daa7`; ISSUE-0001 fix #1/#2 | +| Counter slot fixed-total 20 | commit `fcb96912` | +| PROMOTE_CTX `bInitialize` must stay set | commit `88d47c1f`; ISSUE-0001 fix #3 | +| GR exec latch + readiness gate | `gsp_phys_booter.c:14614`, `:14865`; `main.c:2609` | +| PTE fetch-line rule, scratch pools | docs 11 §P6; `gsp_phys_booter.c:1044-1070` | +| m141-r3 baseline 189/166 ms decomposition | `tools/cuda-ab/perf/PLAN-AND-HARNESS.md` §1 | + +--- + +## 4. End-to-end GR execution, in our words + +This section defines the vocabulary as the code uses it, then walks the GR +chain. The concepts are standard NVIDIA terminology; the definitions below +are **ours**, written against the interfaces we implement. + +| Term | Our definition | +|---|---| +| **GR engine** | The physical engine that executes shader work: both compute grids and graphics pipelines. CUDA kernels are GR work. It is the one engine that can run arbitrary guest-authored code, which is why it is the frontier. | +| **Channel** | A hardware execution context: a GPFIFO ring, a push-buffer pointer, a USERD control block, an instance/RAMFC block, and the engine object bound to it. The guest creates channels through RM allocs; the vEP registers them (`gsp_rm_register_channel` → `gsp_sema_service_channel_add`). | +| **GPFIFO** | The ring of entries a channel consumes. Each entry points at a push buffer (or carries inline data); the front-end fetches it, then executes the method stream it references. | +| **USERD** | The channel's control block in GPU memory. `GP_PUT` (offset 0x8c) is where the guest declares submissions; `GP_GET` (0x88) is the channel's consumption cursor. The pump reads these through `svc_read_ap` with the channel's aperture. | +| **Push buffer / method stream** | The command stream: incrementing-method headers plus payload, addressing engine registers and subchannels. Decoding it is how the vEP learns what the guest asked for (`svc_decode_stream`). | +| **QMD** | The launch descriptor: the fixed-layout structure GR reads to dispatch a grid (program address, grid/block dimensions, parameter pointers, constant-bank pointers, release targets). CUDA builds it; the forwarded path must install the inline copy into the channel's descriptor buffer in the form the engine reads (§6, the wall). | +| **Context / ctx-init** | The per-channel engine state block (main + per-VEID subcontext images) that must be initialized and committed before GR can switch to the channel. The guest drives this with `GPU_PROMOTE_CTX`; the server-side rules are what the `gr_promote`/`srv_ctxcommit` matrix is bisecting. | +| **Golden context** | The reference channel/3D object set the stock driver creates right after load so the server-side GR instance state exists (the `gsp_phys_rmcall_golden` replay). A context committed without that state does not dispatch. | +| **SASS** | The compiled shader machine code the GR executes. Its pointers reach GR through QMD constant banks; for forwarded launches the vEP relocates referenced pages into engine-walkable windows rather than trusting guest pointers directly. | +| **Semaphore release** | The completion primitive: a method that writes a payload word into a semaphore VA. CE work releases tracking semaphores; GR work releases the channel semaphore and the QMD release word. The vEP applies releases in software or, gated, lets the engine author them. | +| **Counter** | The GR completion counter slot: the u32 the guest's userspace tracker polls for channel progress; the vEP derives, registers and publishes it (`svc_counter_*`). | +| **Notifier** | The channel error/completion notification block. `svc_notify_complete` services it and the RM event path delivers the channel event. | +| **Shadow window / scratch PT** | Module-owned pages mapped into the engine's page-table tree at VAs the ladder controls, used to place QMD/program/parameter/data content where GR can walk it. Window-0 PTEs are pre-planted before the channel commit because late writes into already-fetched PTE lines are invisible. | + +**The chain the vEP implements for GR:** + +1. **Context creation.** The guest RM allocates VRAM objects, a VAS, a TSG, + GR channels; the vEP registers each channel at alloc time and records its + descriptor. The guest's `SET_PAGE_DIRECTORY` publications become the + trusted translation sources. The guest then promotes its GR context + (`PROMOTE_CTX`); the emulation acknowledges and keeps `bInitialize` set + (fix `88d47c1f`) so the client marks its buffers initialized. +2. **Golden context / server state.** On the physical ladder, the + `golden` replay allocates the stock client's post-load channel set so the + server-side GR instance state exists; the context commit variants + (`gr_promote`, `srv_ctxcommit`, `vas_shared`) are the ladder's bisection of + what the real server requires. The vEP's guest side does not depend on the + ladder shape; the gate is the latch. +3. **QMD capture.** The pump decodes the guest's launch stream, accumulates + the inline QMD window (`svc_qmd_accum`), and captures the launch candidate + (`svc_gr_cand_capture`), including the release target and the referenced + program/param pages. +4. **Shadow windows.** The submit relocates the QMD and copies referenced + pages into window-0 shadow pages whose PTEs were planted pre-commit; + data pages go through the pre-installed data pool; `mail_patch` injects a + module message page when the guest's constant bank is empty. `ptr_guest` + is the A/B for keeping the guest's original pointers (the SM walk appears + to resolve through the guest subcontext PDB; m91). +5. **Physical submit.** The submit builds the launch method block, publishes + it to the ladder channel's GPFIFO, rings the doorbell, and polls for the + engine-authored release. +6. **SASS on GR.** Between publish and release, the front-end executes the + methods; the inline-QMD install and the engine's read of the descriptor + are the stages the current campaigns have not yet observed. Once they + fire, GR switches to the context and dispatches the grid to the SMs. +7. **Completion.** The engine authors the QMD release word and the channel + release; the poll observes them; the deferred release is applied once + (P6b wave 1.5 semantics); the counter is published; the notifier is + cleared; the channel event is posted; MSI-X wakes the guest. For GR the + last step is the open wake-up question (§6.1). + +--- + +## 5. Enabling arbitrary CUDA code execution + +"Arbitrary CUDA" is the 1.0 acceptance bar: stock guest CUDA works for +real workloads, not one demo, and every time. The program is staged: + +* **P1 — single-workload A/B.** `tools/cuda-ab/ab.sh` runs one deterministic + workload on the host (bare-metal oracle) and in the guest, then compares + the stable result lines. Workloads: `victory_mail` (the campaign mail + kernel), `vector_add`, `smiley` (an ASCII-art deliverable). This is the + rung that produced the m141-r3 first green forwarded launches in 189/166 ms + (`895ab8b8`). +* **P2 — the "arbitrary CUDA kernels" acceptance suite.** + `tools/cuda-ab/p2/` is eleven self-checking programs covering launch shape + (1D/2D/3D grids, odd sizes), parameter counts 0–12 with mixed types, + register/launch-bounds variants, shared memory (reduction + tiled GEMM), + streams including eight independent kernels in flight, allocator churn, + device printf/assert, error reporting and recovery, a correctly-rounded + compute benchmark, and a combined end-to-end program. References are + deterministic (EMIT mode) until the host GPU is free for capture; the + suite currently runs `[planned]` pending the reproducibility gate + (`tools/cuda-ab/READINESS-P2-2026-10-08.md`). +* **P3 — near-native performance.** `tools/cuda-ab/perf/` is the prepared + measurement program: host-oracle vs guest ratios for init, malloc, launch + (first/warm/throughput), HtoD/DtoH bandwidth, compute GFLOP/s, plus host + metrics (the backend `elapsed_ms`, injected-latency proxies). Status: + prepared, not yet run; every threshold provisional until a green + calibration (`tools/cuda-ab/perf/PLAN-AND-HARNESS.md` §1/§4). + +**Proven vs pending.** Proven today: single forwarded launches of two small +kernels (mail, vadd) with correct release and clean teardown, on a lucky +green boot; CE-class copies forwarded end-to-end with engine-authored release +and fault relay (default off); compiled kernels of the P2 complexity are not +yet observed executing through the stack. Pending: reproducibility +(ISSUE-0001), the GR descriptor-install wall (§6.1), completion wake-up +(§6.1), multiple kernels/clients per VM (ISSUE-0002), and P2/P3 in a green +boot. The gating order is deliberate: reproducibility first, then the wall, +then breadth, then performance. + +--- + +## 6. Challenges + +### 6.1 Current + +* **The GR completion wall — two faces.** The older face is the + launch-descriptor install: the forwarded launch reaches and runs + front-end methods, the descriptor destination window is accepted, the + context is valid at dispatch, and yet the inline-QMD install is never + observed and no grid dispatches + ([`04-implementation/03-execution-frontier.md`](../04-implementation/03-execution-frontier.md) + §1–§5). The exonerated list is long (context validity, destination mapping, + entry fetch/alignment, TSG parenting); the live issues are the stream's + verbatim prelude, the absent descriptor-virtualization control method, and + the relocation transform, each with a one-cycle falsifier. The current code + carries many of those falsifiers as mode-matrix knobs (m26 falsified the + nonzero `qmdvirt` hypothesis; m72 found the SKED `LOCAL_MEMORY_TOTAL_SIZE` + cancel and added the `lmem_en` block; m91 showed the SM I-fetch MMU_NACK at + shadow VAs and added `ptr_guest`). +* **The second face — completion wake-up.** FORENSICS-grv3 shows a boot where + discovery succeeded (three GR rings pinned), the ctx-init releases landed + (`gr_rel=3`, counter words = 1), and the guest *still* stopped: it + re-queued the identical push and gave up. The best-supported reading is + that the client waits for the completion *wake-up* (notifier clear + event), + not the counter value; green boots show `notifier cleared` ×N and the event + post, grv3 shows neither (FORENSICS-grv3 §3). The one-variable experiment is + a `sema_dbg=Y` cycle to un-gate the notifier/publish/post_event traces. +* **Reproducibility (ISSUE-0001).** The green/stall split is per-boot. The + first fix wave landed (kernel-native pool translation `7f21daa7`, + PROMOTE_CTX reply `88d47c1f`, issue register `a1407659`), and the + earlier stall mode — the client freezing at DEVICE because its last CE + completion could not be written — is fixed: the v2 campaign consumed all 89 + CE pushes with zero holds and zero starvation. The stall then moved one + phase later, to GR ring discovery, where the tree now carries the + kernel-native discovery lineage (`1ba8ba65`, `ebcfc1c2`) plus the fixed + re-arm of `7abd8f87`/`66388560`. Acceptance is 10/10 consecutive cold boots + green (`issue-number-1.md` validation plan); that count is pending. +* **Multiple kernels/clients per VM (ISSUE-0002).** A second CUDA process in + one boot wedges in guest UVM because the host's per-client sema/GR state is + not reset when the guest re-runs the GSP boot handshake per client; the + guest's UVM holds a global lock across the registration RM exchange, so a + host-side quiet path freezes every later client (`issue-number-2.md`). The + durable direction is per-client/per-channel state reset plus per-mdev sema + contexts and P0 isolation work, with the ordered hunks already recorded + (validation plan in the issue). + +### 6.2 Historical (fixed or retired — do not relitigate) + +* **CE starvation at the boot-critical release.** The old stall was one + deferred CE release whose semaphore page had no translation, waiting on a + racy userspace watcher. Fixed kernel-natively by the pool resolver's VAS + walk (`7f21daa7`) and the GR ring/USERD discovery lineage; `pushes_nomap` + now reaches 0 on consumed boots (FORENSICS-v2 §0/§c). +* **The ring-location class.** Early discovery tried only FB conventions + (`0x140000 + 0x3000*rank`) and RAM scans; grv1 proved the ring was not in + guest RAM and the RAM-only content scan burned 60 passes. The fix is the + both-aperture, FB-first, re-armable discovery now in the tree + (`7abd8f87`, `66388560`). +* **The notifier gate as a discovery symptom.** The `notifier cleared` line + historically correlated with ring resolution; it is a publication-side + signal and is now understood as the wake-up face of §6.1, not a discovery + fix in itself. + +### 6.3 What is working well, and what is not + +**Working well:** the CE path end-to-end (copies, deferred exactly-once +release, hardware-primary data, engine-authored relay, fault relay) — all +live-verified default-off; kernel-native discovery and the trusted-walk +truth model; deterministic counter semantics with fixed pool total; the +mode-matrix discipline (one variable per supervised cycle, one-line +rollback); the bounded-budget culture that turns potential freezes into +logged, counted degradations. + +**Not working:** GR dispatch (no grid observed); completion wake-up for GR; +reproducibility at the required 10/10 bar; second-client state isolation; +and the observability gaps that made both bugs slow to find — counter +`pub_calls` counting calls rather than writes (H3; the increment still sits +before the no-op guards at `gsp_sema_service.c:9085` and correction is +planned, FORENSICS-grv1 §5 R3), +`sema_dbg`-gated traces hiding the wake-up path, and the absence of a GR +error-reporting object on the forwarded path ("silence is not health", +doc 03-execution-frontier §8). + +--- + +## 7. Goals, gaps, and the iteration plan + +### 7.1 Goals + +1. **Correctness:** a stock guest runs arbitrary CUDA in one boot, repeatedly. +2. **Reproducibility:** 10/10 cold boots green (ISSUE-0001 exit criterion). +3. **Breadth:** P2 suite green in a single boot; multi-client and multi-stream + (ISSUE-0002); then multi-mdev. +4. **Performance:** near-native P3 numbers with no-regression discipline. +5. **Portability:** the vEP survives driver-version and GPU-generation churn by + re-deriving facts, not by patching snapshots. + +### 7.2 The iteration loop + +Every change follows the same evidence loop. This is the operating system of +the vEP program, not an appendix: + +1. **Question as a discriminating sentence + named acceptance signal.** E.g. + "does the notifier clear and the event post fire for a GR ctx-init + channel?" with the signal `notifier cleared` + `post_event` + guest + advancement past the re-queue. +2. **Offline first.** Use the risc-v-gsp-rm-debugger (`debug/risc-v-gsp-rm-vm/`) + and its validation tools — `harness/valprobe.py` for one firmware + validation branch, `harness/rehost.py` for one whole function, RPC + capture/decode/compare (`debug/risc-v-gsp-rm-vm/docs/45-47`) — to test a + firmware-side rule without a supervised physical cycle. The entry points + are [`../05-operations/03-risc-v-gsp-rm-vm.md`](../05-operations/03-risc-v-gsp-rm-vm.md) + and [`../05-operations/04-risc-v-gsp-rm-debugger-user-guide.md`](../05-operations/04-risc-v-gsp-rm-debugger-user-guide.md). +3. **One supervised cycle, one variable.** A mode-matrix knob, a trace flag, + or a single code change; never two. +4. **Evidence pair.** Session/counter pair (green vs this run) with the + discriminators named before the run; campaign evidence goes to + `backups/evidence-…/` (local), the public claim cites the behavior only. +5. **Land or revert.** A landed change updates the doc and (if needed) the + issue; a falsified hypothesis is recorded in the knob comment so no future + session re-runs it. + +### 7.3 Tooling gaps and the plan for them + +| Gap | Tool / practice | Plan | +|---|---|---| +| GR rule recovery without burning cycles | risc-v-gsp-rm-debugger valprobe/rehost, RPC capture/replay (docs 45–47) | probe candidate front-end/engine rules offline; queue only falsifiable physical experiments | +| Arbitrary-kernel acceptance | `tools/cuda-ab/p2/` (11 programs) | run in the first green boot after ISSUE-0001 acceptance; add a multi-process/multi-stream variant for ISSUE-0002 | +| Performance measurement | `tools/cuda-ab/perf/` + `collect.py` | freeze thresholds on first green run; make the serialized `PDE_UPDATE` forwards the first optimization candidate | +| Wake-up observability | unconditional or counted `notified/posted` telemetry in `svc_notify_complete`/`gsp_rm_post_channel_event` (FORENSICS-grv3 §5B) | land as telemetry, not a behavior change; treat `posted==0` after a real clear as a first-class failure | +| Counter telemetry honesty | move the `pub_calls` increment after the write (FORENSICS-grv1 §5 R3) | land with the telemetry wave | +| Multi-client state | ISSUE-0002 fix plan (per-client reset, per-mdev sema context, P0 hunks) | stage A1–A6 per the recorded plan; graduation criteria sequential/concurrent/simultaneous | +| Evidence discipline | one evidence bundle per claim; counters as first-class discriminators; "no claim without a pair" | already the campaign norm (FORENSICS reports); keep counters stable enough to be quotable (see counter telemetry fix) | +| Sub-agent workflows | read-only analysis sessions (`SA-ANALYSIS`, `SA-CHK`, `SA-DOC`) producing FORENSICS/readiness docs while the lead runs cycles | continue; keep analysis strictly read-only and cite line numbers; never touch the rig in analysis sessions | + +--- + +## 8. Portability + +### 8.1 What is version-portable + +* The **architecture** of the vEP: observe pushes → resolve truth → decode → + execute/forward → publish completion. This shape does not depend on a + driver version or a GPU generation; only the facts it consumes do. +* The **RPC/RM object model** the vEP registers against (channel alloc + descriptors, page-directory publications, event objects, `PROMOTE_CTX`) + is stable in structure across the 5xx/6xx families; new fields appear, but + the wire structures are versioned and compiled into the driver. +* The **mode-matrix method**: parameterized, default-off, rollbackable + experiments and an evidence latch (`rmcall_gr_exec_ok`) port as a practice. +* The **counter/notifier/event completion model** (a polled u32, a notifier + block, an RM event, MSI-X) is the same completion vocabulary across + versions. +* The **QMD vocabulary at a coarse level** (a launch descriptor with program, + grid, parameters, release targets) is stable; exact field offsets are not. + +### 8.2 What must be re-derived per version or architecture + +| Item | Why | Re-derivation source | +|---|---|---| +| Firmware/driver identity, RPC inventory, control IDs | versions add/retire controls | measurement: `firmware_analyzer` signatures, live RPC captures (`docs/45`), host-oracle A/B (docs 07 P1) | +| Ring/USERD VA and FB layout conventions | the guest's memory allocator changes with the driver | first-touch probes + content discovery; never hardcode beyond the documented candidate list | +| QMD layout and launch method stream | the launch vocabulary changes (Turing → Ampere → Hopper → Blackwell) | reference capture of a working launch; differential against the forwarded stream | +| Context image layout, promote entry sets, golden-context shape | server-side RM rules and image formats change | ladder bisection + offline valprobe/rehost | +| PTE encodings, page-table geometry, fetch-line rule | GMMU changes per architecture | hardware observation; document the encoding proven in each profile | +| Engine inventory and classes | missing/zeroed records roll whole contexts back (doc 05 §6) | bare-metal oracle capture; profile tables | +| Scratch-window VA layout and pools | ours, but coupled to the ladder channel's VAS geometry | re-lay per profile; keep the pre-commit planting rule | + +Portability discipline: **protocol-level facts port; address-level facts do +not.** Any new version or architecture enters as a profile plus a measured +delta, never as a guess (docs 06 §5, `RUNBOOK.md` S-scenarios). + +### 8.3 Concrete paths + +* **Driver 610.** Hard gate first: RM 610 requires explicit named-human + approval — no acquisition, analysis, emulation edit, physical work or + support claim before it (`docs/23-RM610-READINESS.md`). Once approved: + acquire the image, run one-command triage + (`risc-v-gsp-rm-debugger.sh triage`, `docs/40`), diff the RPC inventory + with `harness/version_diff.py`/`ci_release.py` (`docs/21`), then port the + vEP facts in the order of §8.2. This is speculative until the gate opens; + nothing in the current tree pre-adapts to 610. +* **Grace Blackwell (GB10/GB2xx, aarch64 hosts).** A different problem class: + different host CPU architecture (the module's guest-RAM translation, + `read_cache_page`-based HPA mapping, must remain architecture-neutral), an + engine inventory and GMMU generation with no current profile, and a + firmware family the debugger would need to re-host/port (docs 07 P3). The + vEP plan is: profile capture (PCI IDs, BAR geometry, engine list), GMMU + encoding work, then the same discovery/decode/submit ladder on a supervised + device, with the support ladder (docs 06 §1) moving only on evidence. +* **Compute profiles.** The near-term "profile" is a small, per-version file + of measured facts (ring conventions, VA windows, QMD offsets, method + encodings, PTE kind) consumed by the vEP at runtime; today those facts are + in code comments and constants with commit provenance, which is the + pre-profile state. + +--- + +## 9. Mapping vEP concepts to CUDA and to architecture + +### 9.1 CUDA high-level concepts + +| CUDA concept | What the guest does | vEP counterpart | +|---|---|---| +| Context | `cuCtxCreate`; RM client/device/subdevice + a GR engine context | guest context is emulated (objects, VAS, promote); the physical ladder commits its own context (`gr_promote`/`srv_ctxcommit`) | +| Module / function | loads SASS/cubin into GPU memory; `cuModuleGetFunction` | SASS reaches GR through QMD program pointers; the vEP relocates referenced pages into engine-walkable windows (`shadow_win0`, `data_prewin`, `ptr_guest`) | +| Stream | ordered work queue on a channel (or a graph) | channel + TSG; the guest's push order on the channel is the stream order the pump preserves | +| Kernel launch | builds push (ctx init, QMD install, params), appends GPFIFO entry, rings doorbell | capture → one in-flight request → ladder submission; the QMD install is the wall (§6.1) | +| Memory | `cuMemAlloc`/copies; VA objects mapped into the VAS | emulated VRAM + guest RAM with the trusted truth model; forwarded operands resolve VA→PA→HPA via the guest RAM backing | +| Synchronisation | events, stream sync, semaphores, `cudaDeviceSynchronize` | releases, counters, notifiers, channel events; the vEP is the sole publication authority (or relays engine-authored releases) | + +### 9.2 Architecture details + +| Architecture concept | vEP relationship | +|---|---| +| Channel / GPFIFO / USERD | the unit of scheduling and consumption (§4); registration from the alloc descriptor | +| TSG / runlist | the guest's scheduling vocabulary; the ladder's channel sits in a TSG on the real server; guest-side pacing is the pump today, tenant fairness is planned on top (doc 09 §5) | +| Engine types / classes | GR is engine type 1, COPY0 is 0x9 (doc 05 §6); the ladder selects GR0 for GR work and refuses CE paths on a GR ladder (`gsp_phys_backend_ladder_ready`) | +| GMMU | the translation ground truth; the trusted walk reads guest page tables, and the engine's view is fed by planted PTEs in pre-built fetch lines | +| VRAM / FB apertures | emulated as a host buffer today; apertures appear in channel descriptors and pins (`SVC_APERTURE_SYS/FB`); real VRAM backing is future work | +| MSI-X / interrupts | completion delivery: `gsp_rm_post_channel_event` → eventfd → irqfd; physical-engine faults are observed and relayed (C3, default off) | + +--- + +## 10. Future challenges the current tools do not address + +These are the known gaps for which no tool or design exists yet. Each gets a +proactive design direction so the architecture is not surprised later. + +1. **Multi-tenant fairness and performance isolation.** Today one mdev, one + real session, one in-flight request. The planned per-mdev real clients + need a policy layer (time slices, quotas, accounting) and a measurement + method that can fail (doc 09 §5; docs 06 §2.8). Proactive design: keep + every per-client datum inside a container keyed by the mdev/session, and + build the reset path (ISSUE-0002) as the container's lifecycle, not as + scattered clears. +2. **Fault recovery semantics.** Fault relay exists (C3); fault *replay*, + *cancel*, and channel recovery after a fault do not. Proactive design: + model faults as first-class completion outcomes in the request record, so + a fault can trigger the same exactly-once release discipline as success. +3. **Context switching and preemption between tenants.** The ladder leaves a + context loaded; the client preempts its TSG for a clean first dispatch + (`lane_tail`). A multi-tenant scheduler must own load/unload lifecycles. + Proactive design: the mode matrix's lane controls are the seed of a + documented context-switch protocol, not a launcher-only trick. +4. **Hostile-guest safety.** A malformed push, a forged VA, or a wedged ring + must not corrupt the host or another tenant. Guards exist (bounds checks, + read-back proofs, `gsp_phys_seq_addr_valid`), but there is no fuzzing + harness for guest-authored streams. Proactive design: a synthetic-push + generator against the decoder with the same evidence discipline as the + physical ladders. +5. **Observability as a product property.** The vEP's bugs were found by + counter archaeology; counters that count the wrong thing (H3) cost days. + Proactive design: every counter must answer one named question, and + telemetry for the wake-up path lands before it is needed again. +6. **Version drift under an adversarial schedule.** New images will arrive + without notice; the triage/porting tooling exists but is unexercised on a + real new version. Proactive design: golden sessions per supported version + and a one-command triage that can fail loudly. +7. **Windows guests and non-KVM transports.** They change the RAM/IRQ + plumbing under the vEP, not its pipeline. Proactive design: keep the + guest-memory accessor and the event injection behind interfaces (they + already are: guest-RAM backing file, eventfd), so the pipeline is + transport-agnostic. + +--- + +## 11. 3D graphics interaction + +Graphics workloads (OpenGL/Vulkan) are GR work too, but they differ from +compute in ways the vEP will feel directly: + +* **Engines:** more than GR — a 3D context still uses GR (graphics + pipelines), but video (NVDEC/NVENC) and image (NVJPG/OFA) engines join + later; CE remains for buffer moves; the engine inventory is a correctness + surface (doc 05 §6). +* **Channels and sync:** drivers use multiple channels per context, TSGs for + scheduling groups, and inter-engine semaphores/fences (a copy waits on a + render; a present waits on a copy). The vEP's release/counter vocabulary + covers the primitives; the *volume and cross-engine ordering* of + synchronisation is higher. +* **Pushes:** the graphics method vocabulary is much larger (pipeline state, + shader binding, render targets, viewports), and state lives in big + per-context image blocks. The current capture/decode path is compute-first; + a graphics capture would need the same treatment the CUDA launch got + (reference method stream, differential against the forwarded stream). +* **Memory:** surfaces, tiled/swizzled layouts, render targets in VRAM; the + GMMU and aperture handling are the same, but the emulated host-RAM VRAM + becomes a real fidelity problem for tiling/swizzle expectations. +* **Interaction with the current wall:** the descriptor-install wall is + shared — a graphics draw also dispatches through GR descriptors. Ceiling + note: the current docs mark OpenGL/Vulkan `not yet` (docs README status + table); the 3D plan is to reuse the vEP pipeline once compute dispatch is + proven, then add a graphics-capture workstream (mode-matrix discipline + applies: one class/method family at a time). + +--- + +## 12. Overarching framing: why the vEP is a core problem + +**What vGSP is.** vGSP is the virtual GPU System Processor: the host-side +reimplementation of the GSP/GSP-RM interface that lets a stock guest driver +believe it owns a real NVIDIA GPU. `vgsp.ko` is vGSP: the virtual device, the +emulated boot and message transport, the RM object/control service, and the +execution plane. The guest never learns it is virtualized for these paths, +and no component in the stack — host driver, guest driver, firmware, or +hypervisor — is modified (docs 01, 09 §13). + +**Why the execution plane is one of vGSP's core problems.** Everything else +in vGSP is necessary but not sufficient: the control plane makes the driver +run, but only the execution plane makes applications *work*. It is where the +project's hardest constraints collide: + +* **Correctness without cooperation.** The guest does not help; the vEP must + recover VA/PA truth, ring locations and completion semantics from + observation, and satisfy contracts (completion, ordering) that hardware + would otherwise enforce. +* **Performance without emulation.** Compute cannot be emulated at useful + speed; the plan of record is real execution on real engines, which means + every semantic the emulation would have provided for free (memory + visibility, ordering, faults) must be provided by the forwarding path. +* **Isolation across tenants.** The same plane is where the multi-tenant + promise is kept or broken: one physical GPU's engines shared by mdevs + requires per-client state, scheduling and fault containment — the + ISSUE-0002 class today, the fairness policy tomorrow. + +**Fit with HAAEEP and the ecosystem.** HAAEEP's premise is compute at the +edge — local inference, full isolation, no cloud dependency, no license +(`docs/08-ecosystem/haaeep-and-collaboration.md` §1/§3). That premise is only +real if a guest can run its +own CUDA workloads on hardware it owns, under vGSP, with isolation that a +security stack can trust. The vEP is the subsystem that turns that premise +from an interface emulation into execution; its progress metrics +(reproducibility, arbitrary-kernel acceptance, near-native performance, +per-tenant fairness) are the ecosystem's prerequisites, not internal +milestones. When this document says "the wall", it is naming the last +technical barrier between the project's architecture and its promise. + +--- + +## See also + +* [`01-system-overview.md`](01-system-overview.md) — principles and layer map +* [`05-gmmu-amap-hal-gr.md`](05-gmmu-amap-hal-gr.md) — GMMU, channels, GPFIFO, GR, QMD concepts +* [`09-full-system-picture.md`](09-full-system-picture.md) — the end-to-end map +* [`10-interaction-and-scheduling.md`](10-interaction-and-scheduling.md) — interaction, locks and scheduling detail +* [`11-physical-backend-guest-wiring.md`](11-physical-backend-guest-wiring.md) — the physical session and the P0–P6 ladder +* [`../04-implementation/03-execution-frontier.md`](../04-implementation/03-execution-frontier.md) — the GR launch chain and its leading edge +* [`../04-implementation/01-known-knowns-and-unknowns.md`](../04-implementation/01-known-knowns-and-unknowns.md) — the execution gate +* [`../../debug/issue/issue-number-1.md`](../../debug/issue/issue-number-1.md) and [`issue-number-2.md`](../../debug/issue/issue-number-2.md) — reproducibility and multi-kernel tracking +* [`../05-operations/03-risc-v-gsp-rm-vm.md`](../05-operations/03-risc-v-gsp-rm-vm.md) — the offline firmware debugger used for offline-first work +* [`../../wiki/Execution-Plane.md`](../../wiki/Execution-Plane.md) — the condensed public wiki version of this document + +## Change log + +| Date | Change | +|---|---| +| 2026-10-08 | Created: canonical name (vGSP Execution Plane, vEP), four domains, component/data-flow/mode-matrix diagrams, discovery/truth/release/completion/submit reality, GR chain vocabulary, arbitrary-CUDA program, challenges, iteration plan, portability paths, CUDA/architecture mapping, future gaps, 3D interaction, framing. Tree `66388560`. | \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index 3955b6b..fe39077 100644 --- a/docs/README.md +++ b/docs/README.md @@ -172,20 +172,21 @@ Read in order if you are new. Jump directly if you know what you need. | 12 | [Driver lifecycle field guide](03-architecture/07-driver-lifecycle-field-guide.md) | developers, debuggers | 30 min | | 13 | [Physical GPU integration](03-architecture/08-physical-gpu-integration.md) | developers, architects | 20 min | | 14 | [Physical backend guest wiring](03-architecture/11-physical-backend-guest-wiring.md) | developers, architects | 25 min | -| 15 | [The execution frontier: the GR compute-launch chain](04-implementation/03-execution-frontier.md) | developers, debuggers | 15 min | -| 16 | [Host VF acceleration — future goal (deferred)](03-architecture/12-host-vf-acceleration-future-goal.md) | architects, planners | 10 min | -| 17 | [Known-knowns and known-unknowns](04-implementation/01-known-knowns-and-unknowns.md) | everyone technical | 25 min | -| 18 | [Practical challenges and adversarial interoperability](04-implementation/02-practical-challenges-and-adversarial-interop.md) | developers, stakeholders | 20 min | -| 19 | [Performance, correctness, security](05-operations/01-performance-correctness-security.md) | developers, auditors | 25 min | -| 20 | [Debugging and maintenance workflows](05-operations/02-debugging-and-maintenance-workflows.md) | developers, maintainers | 25 min | -| 21 | [GSP in a RISC-V VM (risc-v-gsp-rm-debugger, experimental)](05-operations/03-risc-v-gsp-rm-vm.md) — implemented, documented and tested ([audit](../debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md); [value demos](../debug/risc-v-gsp-rm-vm/docs/25-VALUE-VALIDATION.md)) | developers, analysts | 15 min | -| 22 | [risc-v-gsp-rm-debugger user guide (experimental)](05-operations/04-risc-v-gsp-rm-debugger-user-guide.md) — implemented, documented and tested ([audit](../debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md); [value demos](../debug/risc-v-gsp-rm-vm/docs/25-VALUE-VALIDATION.md)) | developers, analysts | 15 min | -| 23 | [Defensive Operations Runbook](RUNBOOK.md) | maintainers, operators | 20 min | -| 24 | [Engineering operations](eng-ops.md) — tool admission and workflows (Part I) plus live physical-backend operations (Part II) | maintainers, developers | 20 min | -| 25 | [Hardware and software matrix](06-portability/hardware-software-matrix.md) | developers, integrators | 20 min | -| 26 | [Developer onboarding and standards](07-developers/onboarding-and-standards.md) | contributors | 20 min | -| 27 | [HAAEEP and collaboration](08-ecosystem/haaeep-and-collaboration.md) | stakeholders, partners | 20 min | -| 28 | [Glossary and FAQ](09-reference/glossary-and-faq.md) | everyone | reference | +| 15 | [The vGSP execution plane (vEP)](03-architecture/13-vexecution-plane.md) | developers, architects | 25 min | +| 16 | [The execution frontier: the GR compute-launch chain](04-implementation/03-execution-frontier.md) | developers, debuggers | 15 min | +| 17 | [Host VF acceleration — future goal (deferred)](03-architecture/12-host-vf-acceleration-future-goal.md) | architects, planners | 10 min | +| 18 | [Known-knowns and known-unknowns](04-implementation/01-known-knowns-and-unknowns.md) | everyone technical | 25 min | +| 19 | [Practical challenges and adversarial interoperability](04-implementation/02-practical-challenges-and-adversarial-interop.md) | developers, stakeholders | 20 min | +| 20 | [Performance, correctness, security](05-operations/01-performance-correctness-security.md) | developers, auditors | 25 min | +| 21 | [Debugging and maintenance workflows](05-operations/02-debugging-and-maintenance-workflows.md) | developers, maintainers | 25 min | +| 22 | [GSP in a RISC-V VM (risc-v-gsp-rm-debugger, experimental)](05-operations/03-risc-v-gsp-rm-vm.md) — implemented, documented and tested ([audit](../debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md); [value demos](../debug/risc-v-gsp-rm-vm/docs/25-VALUE-VALIDATION.md)) | developers, analysts | 15 min | +| 23 | [risc-v-gsp-rm-debugger user guide (experimental)](05-operations/04-risc-v-gsp-rm-debugger-user-guide.md) — implemented, documented and tested ([audit](../debug/risc-v-gsp-rm-vm/docs/27-TOOL-AUDIT.md); [value demos](../debug/risc-v-gsp-rm-vm/docs/25-VALUE-VALIDATION.md)) | developers, analysts | 15 min | +| 24 | [Defensive Operations Runbook](RUNBOOK.md) | maintainers, operators | 20 min | +| 25 | [Engineering operations](eng-ops.md) — tool admission and workflows (Part I) plus live physical-backend operations (Part II) | maintainers, developers | 20 min | +| 26 | [Hardware and software matrix](06-portability/hardware-software-matrix.md) | developers, integrators | 20 min | +| 27 | [Developer onboarding and standards](07-developers/onboarding-and-standards.md) | contributors | 20 min | +| 28 | [HAAEEP and collaboration](08-ecosystem/haaeep-and-collaboration.md) | stakeholders, partners | 20 min | +| 29 | [Glossary and FAQ](09-reference/glossary-and-faq.md) | everyone | reference | chain" as row 15 (implementation reality); renumber rows 15-27 to 16-28; additive only. --> -Row 16 is a **deferred future goal**: host NVIDIA-driver acceleration over a + + +Row 17 is a **deferred future goal**: host NVIDIA-driver acceleration over a virtual function. The feasibility verdict and a first design sketch are recorded there for planning; it is not on the active critical path (guest CUDA execution, multi-VM, 3D, performance come first). diff --git a/wiki/Execution-Plane.md b/wiki/Execution-Plane.md new file mode 100644 index 0000000..0f6eafe --- /dev/null +++ b/wiki/Execution-Plane.md @@ -0,0 +1,179 @@ +# The vGSP Execution Plane (vEP) + +> **Canonical, maintained version of this page:** +> [`../docs/03-architecture/13-vexecution-plane.md`](../docs/03-architecture/13-vexecution-plane.md). +> This wiki page is the condensed public deep-dive: what the layer is, how a +> guest CUDA launch flows through it, where it stands today, and where it is +> going. It is condensed from the canonical document and its cited evidence; +> when the two disagree, the docs tree wins. Front door: +> [`../docs/README.md`](../docs/README.md). + +Status: 2026-10-08, tree `master @ 66388560`. Tags: `[live]` proven with the +stock guest driver; `[gated]` implemented, default-off or parameter-gated; +`[wip]` partial; `[wall]` current blocker; `[target]` named success latch; +`[planned]` designed, not built. + +## 1. What it is + +**vGSP (virtual GSP)** is our host-side reimplementation of the GPU System +Processor interface: the virtual device plus the emulated GSP-RM service the +stock guest driver talks to, implemented by `vgsp.ko`. The **vGSP Execution +Plane (vEP)** is the half of vGSP that turns guest-authored GPU work into +physical execution and guest-visible completion: channel ring and USERD +discovery, GPU VA-to-PA truth, semaphore-release application, completion +counters and notifiers, QMD handling for forwarded launches, and submission to +the physical GPU's engines under the module-owned backend session. + +The other half — the control plane (boot handshake, message queues, RPC, RM +objects, BAR shadows) — makes the guest driver *run*. The vEP makes the +guest's work *run*. The canonical document defines the name and rejects the +alternates ("work plane", "submission plane", "forwarding plane", ...) that +would collide with existing project vocabulary. + +**Four work domains** organize the subsystem: + +| Domain | Question | Implemented by | +|---|---|---| +| Scheduler | which channel's work runs, at what pace | doorbell steering, the per-channel pump, discovery cadence, one ordered workqueue, one in-flight request | +| Allocator | where rings, USERDs, counters, notifiers and scratch windows live | ring/USERD autopin and pins, content-keyed pairing, counter derive, ladder scratch/PT pools, the BAR1 vidmem shadow | +| Control | which RM objects/controls shape execution | channel and event registration, `SET_PAGE_DIRECTORY`, `PROMOTE_CTX`, engine topology, the mode matrix | +| Share | how clients/mdevs share the one GPU | mdev backend refcount, one real session under a single lock, one workqueue; per-mdev clients and fairness are planned | + +## 2. The shape of the plane + +```mermaid +flowchart TB + subgraph GUEST["Guest VM - stock software"] + APP["CUDA application -> libcuda -> nvidia.ko
push + QMD + GPFIFO + doorbell"] + end + subgraph VEP["vGSP Execution Plane inside vgsp.ko"] + DB["doorbell steering"] --> PUMP["channel pump"] + PUMP --> DISC["ring/USERD discovery + pins"] + DISC --> TRUTH["VA-to-PA truth
walk, rescue, pins"] + TRUTH --> DEC["push decode + QMD capture"] + DEC --> REL["release apply + counters + notifiers"] + DEC --> WQ["ordered workqueue
one in-flight request"] + WQ --> SUB["ladder submission
mode matrix"] + end + subgraph PHY["Physical backend"] + LAD["module-owned ladder channel"] --> GSP["real GSP-RM server"] + GSP --> ENG["GR / CE engines"] + end + APP -->|"MMIO + DMA"| DB + REL -->|"release, counter, event"| APP + SUB --> LAD + ENG -->|"engine-authored release"| SUB +``` + +ASCII fallback: + +```text +guest: push + QMD + GPFIFO entry + doorbell (BAR0 0x88704) + -> pump (per-channel) -> ring/USERD discovery -> VA-to-PA truth -> decode + -> release apply + counter publish + notifier/event -> MSI-X -> guest wakes + -> (for real work) ordered workqueue -> ladder submission -> real GSP/engines + -> engine-authored release -> poll -> completion publication +``` + +## 3. One CUDA kernel launch, end to end + +1. **Launch build (guest).** `libcuda`/`nvidia.ko` build the GR push: context + methods, the inline launch descriptor (QMD), SASS and parameter pointers, + and a release target. They append a GPFIFO entry and ring the BAR0 + doorbell. `[live]` observed with the stock driver. +2. **Steering and truth (host).** The doorbell wakes the service thread; the + pump resolves the channel's ring VA and USERD, reads `GP_PUT`, fetches the + GPFIFO entry and decodes the method stream. Translations are read-back + proven from driver-published page directories, pins, or content-rescue. + `[live]` for CE; `[wip]` for GR (discovery now succeeds, see §4). +3. **Capture.** The decoder accumulates the QMD window and captures the launch + candidate; the sema service queues one request on the single ordered + workqueue. `[live]` observed (ring consumed, methods execute). +4. **Physical submit.** The backend relocates the QMD, copies program/param + pages into module shadow windows whose PTEs were planted before the channel + commit (the engine never sees a late PTE write — it caches 128-byte fetch + lines with no invalidate), builds the launch method block, publishes it to + the ladder channel's GPFIFO and rings the real doorbell. `[gated]` + default-off; latched on a proven real GR submission. +5. **Engine.** The front-end fetches the entry and executes methods; then the + **inline-QMD install** must materialise the descriptor for the engine to + read, currently **`[wall]`** — not observed. Context validity, destination + mapping and fetch/alignment are exonerated; the recovered-method-stream + form is the investigation. +6. **Completion.** The engine authors the QMD release word and the channel + release; the host applies the deferred release exactly once, publishes the + completion counter, clears the notifier and posts the channel event; MSI-X + wakes the guest. CE work is live in this shape `[gated]`; for GR the + wake-up step is the current second face of the wall. + +## 4. How work is found: discovery, truth, completion + +* **Ring/USERD discovery** has three cooperating sources: injected pins, a + kernel-native trusted walk of driver-published page directories, and + content-keyed discovery when neither names the ring. Content discovery + scans **both apertures** (emulated vidmem and guest RAM), runs the FB scan + first on a 2-second cadence, pairs a ring with the rank's staged ctx-init + push, and validates the pair before pinning. Since the 2026-10-08 re-arm + work (`7abd8f87`, `66388560`) the discovery no longer retires permanently + at a pass cap. +* **The semaphore service** decodes releases, translates their target pages + through the trusted resolver plus a driver-published-VAS pool resolver, + defers untranslatable releases with retry telemetry, and applies each + release exactly once — in software, or by letting the engine author it under + the relay (default off). +* **Completion counters** use a fixed 20-slot pool (a partial live channel + count poisoned slots before the fix); each decoded release lands in the + channel's exact slot. **Notifiers** are serviced by the pump, and family + events are delivered as RM events + MSI-X through the guest's registered + event objects. + +## 5. Challenges, honestly + +* **GR dispatch wall.** The forward path runs front-end methods, the context + is valid and the destination window is accepted — and the descriptor + install is still not observed. The mode matrix carries dozens of + single-variable falsifiers (promote shapes, prelude/PCAS form, local-memory + block, lane controls, shadow windows, pointer relocation). +* **Completion wake-up.** A boot where GR rings were discovered and releases + applied still saw the guest re-queue the same push and give up; the + best-supported reading is that the notifier/event wake-up never arrived. + The named one-variable experiment is a `sema_dbg` cycle to un-gate the + notifier/publish/event traces. +* **Reproducibility (ISSUE-0001).** The green/stall split is per-boot. The + first fix wave landed and the earlier CE freeze is gone; the stall moved to + GR ring discovery, which the latest discovery work addresses. Acceptance + is 10 cold boots green, pending. +* **Multiple clients (ISSUE-0002).** A second CUDA process wedges in guest + UVM because host per-client sema/GR state is not reset when the guest + re-runs its per-client boot handshake. Per-client state containers and + reset are the designed path. + +## 6. Portability + +The **architecture** ports: observe pushes, resolve truth, decode, +execute/forward, publish completion. So does the **RPC/RM object model** and +the mode-matrix practice. What does **not** port is address-level fact: ring +conventions, VA windows, QMD offsets, context-image layout, PTE encodings, +engine inventories and scratch layouts must be re-derived per driver version +and GPU architecture by measurement, never guessed. A new version enters as a +profile plus a measured delta; driver 610 additionally sits behind an explicit +human approval gate. + +## 7. Future + +The planned next steps are reproducibility acceptance, the descriptor-install +and wake-up investigations (offline-first with the RISC-V GSP-RM debugger +where possible), the arbitrary-kernel acceptance suite, then the near-native +performance program. The unsolved future classes — multi-tenant fairness, +fault replay/cancel, context switching between tenants, hostile-guest +hardening, and version drift — each have a proactive design direction in the +canonical document. 3D graphics reuses this pipeline once compute dispatch is +proven; it adds engine breadth (video/image), more channels and synchronisation +volume, and a much larger method vocabulary, but not a new plane. + +--- + +*Canonical document: [`docs/03-architecture/13-vexecution-plane.md`](../docs/03-architecture/13-vexecution-plane.md). +See also: [full system picture](../docs/03-architecture/09-full-system-picture.md), +[physical backend guest wiring](../docs/03-architecture/11-physical-backend-guest-wiring.md), +[the execution frontier](../docs/04-implementation/03-execution-frontier.md).* \ No newline at end of file diff --git a/wiki/README.md b/wiki/README.md index 4c90b66..378ea31 100644 --- a/wiki/README.md +++ b/wiki/README.md @@ -9,6 +9,7 @@ ## Documentation Index ### Core Analysis +- [The vGSP Execution Plane (vEP)](Execution-Plane.md) - The work/execution layer of `vgsp.ko`: guest pushes to physical execution and guest-visible completion (condensed; canonical doc [`../docs/03-architecture/13-vexecution-plane.md`](../docs/03-architecture/13-vexecution-plane.md)) - [GSP 570.144W Firmware Analysis](GSP-570.144W-Firmware-Analysis.md) - Firmware decompilation and address record - [Address Reference](Address-Reference.md) - Firmware-side address tables - [CUDA Userspace Driver](CUDA-Userspace-Driver.md) - `libcuda.so` deep dive: layer model, guest interface, forwarding status, crash-path lesson and lookup tooling -- cgit v1.2.3 From d4d24b038b13c401b20ae9461f117819bb2f1784 Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Thu, 8 Oct 2026 13:51:22 -0400 Subject: docs(multi-tenant-gmmu): multi-mdev isolation design, GMMU tenant/agent paged-attention protection, VRAM oversubscription, portability and M1-M8 roadmap (wiki summary; local provenance in adjacent LOCAL notes) --- .../14-multi-tenant-gmmu-protection.md | 1095 ++++++++++++++++++++ wiki/GMMU-Multi-Tenant-Protection.md | 110 ++ 2 files changed, 1205 insertions(+) create mode 100644 docs/03-architecture/14-multi-tenant-gmmu-protection.md create mode 100644 wiki/GMMU-Multi-Tenant-Protection.md diff --git a/docs/03-architecture/14-multi-tenant-gmmu-protection.md b/docs/03-architecture/14-multi-tenant-gmmu-protection.md new file mode 100644 index 0000000..c3f58c7 --- /dev/null +++ b/docs/03-architecture/14-multi-tenant-gmmu-protection.md @@ -0,0 +1,1095 @@ +# Multi-Tenant GMMU Protection: Isolation Design and Paged-Attention Controls + +> **Status: design (2026-10-08, master `66388560`).** This document specifies +> how Open-IOV isolates multiple tenants — multiple mdevs *and* multiple +> tenants inside one guest — at the GPU memory-management unit (GMMU), and how +> a paged-attention inference stack (vLLM-style) can use those protections. +> Sections 1–3 are **baseline 1.0 design**; sections 4.5+ and roadmap stages +> M6–M8 are **post-1.0 research**, explicitly gated (see §7.4 and +> `AGENTS.md` index entry 11). No part of the research design may become a +> design input for baseline 1.0 code: no forward-looking parameter, hook or +> abstraction may be added to a product path for it. +> +> **Method.** Every empirical claim about our system cites a code line or +> tracked document; hardware-format claims are marked **proven** (observed +> live, or in a tracked field catalogue) or **hypothesis** (needs a +> supervised experiment). No timing/cycle/silicon-accuracy claim is made. +> +> +> **Companion document.** [`13-vexecution-plane.md`](13-vexecution-plane.md) +> (written in parallel) defines the vEP/instance/agent vocabulary used here; +> where the two disagree, 13 is the vocabulary authority and this document +> is the GMMU-policy authority. + +Tags: `[live]` exercised; `[wip]` default-off; `[design]` not built; `[hypothesis]` unexercised; `[research]` post-1.0, dual-gated. + +--- + +## 1. Problem statement: 1.0 needs N mdevs, N guests, and flawless isolation + +### 1.1 The 1.0 requirement + +The project's stated 1.0 gate is multiple mediated devices, two or more +guests, multiple VRAM sizes, and isolation that survives adversarial review. +The tracked status is explicit that this area is **designed, partially +implemented** ([`../README.md` §3](../README.md), row "Multi-tenant isolation +(VFs, mdev-per-VM)"), and the multi-tenancy model is stated as a first-class +value: one mdev per VM, per-instance interrupts, no shared host state, fail +closed ([`../01-orientation/01-purpose-and-scope.md` §5](../01-orientation/01-purpose-and-scope.md)). +The evaluation ladder puts "N VMs, isolation + fairness + no cross-talk" on +rung 5 ([`../05-operations/01-performance-correctness-security.md` §1](../05-operations/01-performance-correctness-security.md)). + +Three 1.0 requirements drive this document: + +1. **Two guests on one GPU.** Two VMs, two mdevs, both running the stock + driver, concurrently and repeatedly. Today the second *client in one + guest* already wedges (`../../debug/issue/issue-number-2.md`), and the + two-guest harness does not exist. +2. **Independent VRAM sizes.** Each mdev exposes an emulated framebuffer + size that need not relate to the physical framebuffer or to another + mdev's size: `nv_vgpu_type_info.vram_mb` + (`kernel/include/ioctl.h:39-46`), BAR1 sized from it + (`kernel/bar_init.c:60-77`), and GSCI/PFB reports derived from the same + value (`kernel/gsp_rm_emulation.c:1034,1106-1110`, + `kernel/bar_emulation.c:271-310`, `kernel/gsp_emulation.c:743-753`). + The mechanism exists per mdev; the *capacity* story does not (§3). +3. **Multi-tenant inference inside one guest.** A vLLM-style server + multiplexes many *logical tenants* (API users, agents, sequences) inside + one VM that owns one mdev. VM-level isolation (IOMMU, mdev) is blind to + them. This is the workload §4 targets. + +### 1.2 Where isolation can exist + +Isolation is not one mechanism; it is a stack of layers, each with a +different owner and a different failure mode. The design must say, for each +layer, what holds and what does not. + +``` +L0 PLATFORM IOMMU/SMMU: per-VM DMA address spaces [live] + │ KVM memory + vCPU boundary [live] +L1 TRANSPORT VFIO mdev: one mediated device per tenant [live] + │ config/BAR/IRQ mediation, per-device eventfd +L2 vEP STATE per-mdev emulation context (BAR shadows, [live/wip] + │ queues, msgq, interrupt state) §2.2 + │ per-mdev work-service state [design] +L3 GMMU per-context page directory (VA space) [live//design] + │ PTE-level permissions (RO, privilege) [proven fmt] +L4 ARBITRATION channel ownership, runlist/TSG scheduling [design] +L5 COMPLETION semaphore resolution per owner; no cross- [wip] + │ tenant release apply §2.5 +L6 FAULT fault attribution to the owning tenant; [wip] + notifier only to that tenant §2.6 +L7 APPLICATION per-tenant/per-agent VA partitioning inside [research] + a guest (vLLM + agent utility) §4 +``` + +The honest summary from the tracked docs applies: **memory isolation between +VMs is hardware-plus-hypervisor enforced today; engine-level context +separation and performance isolation are what the project must still build** +([`09-full-system-picture.md` §6](09-full-system-picture.md)). + +### 1.3 Trust boundaries and the principals + +| Boundary | Attacker | Capability | Must hold | +|---|---|---|---| +| Guest ↔ guest | whole VM | arbitrary device programming, arbitrary guest kernel | no memory, state, interrupt or completion observation across mdevs | +| Guest ↔ host | whole VM | arbitrary DMA/MMIO against its own device | no host memory access, no module state escape, fail closed | +| Host user ↔ device | unprivileged host user | access to `/dev/*` as granted | no privilege escalation via our interfaces | +| Tenant ↔ tenant (in one guest) | guest userspace tenant | arbitrary CUDA kernels, arbitrary block-table edits | a tenant cannot read/write another tenant's KV blocks | +| Agent ↔ agent (in one tenant) | guest userspace agent | arbitrary CUDA kernels | an agent cannot read/write a sibling agent's KV blocks | + +The first three are baseline 1.0 (they follow the threat model in +[`../05-operations/01-performance-correctness-security.md` §4.1](../05-operations/01-performance-correctness-security.md)). +The last two are the *multi-tenant inference* boundaries. Note that they +assume the **guest kernel is trusted**: everything below the application +runs the stock driver, and the tenant/agent separation is enforced at the +application's use of the device, not against a malicious guest KMD. A +malicious guest KMD is the guest↔host boundary, already covered by L0/L1. + +### 1.4 Where the code actually stands (the gap this document fills) + +The guest-facing plane is per mdev: `nv_vgpu_device.gsp_comm_ctx` +(`kernel/include/nvidia-vgpu-vfio.h:133`) wraps a per-instance +`struct gsp_emu_context` (`kernel/gsp_emulation.h:130`) created by +`gsp_emu_init()` (`kernel/gsp_emulation.c:724`) from +`nv_vgpu_device_init_work()` (`kernel/main.c:1657`). BAR shadows, message +queues and MSI-X state are per instance. + +The **work/completion service is not**. `gsp_sema_service.c` holds one +global `static struct sema_svc svc;` (`kernel/gsp_sema_service.c:676`), and: + +* the service thread pumps **the first open mdev it finds** + (`kernel/gsp_sema_service.c:13296-13308`); +* the work-submission doorbell carries no owner — `gsp_sema_service_doorbell(u32 token)` + (`kernel/gsp_sema_service.c:13456`), called from the VFIO write path with + no mdev argument (`kernel/main.c:865`); +* channel registrations are keyed by the guest-chosen `h_object` alone + (`kernel/gsp_sema_service.c:847`, table bounded by `SVC_MAX_CHANNELS 128` + at `:122`), and VA spaces by the guest-chosen `h_vaspace` alone + (`:988`, `:1019`, bounded by `SVC_MAX_VASPACES 8` at `:309`); +* the VA→PA learned map and pins are keyed by GPU VA only + (`kernel/gsp_sema_service.c:113-130`, `:1045`), with no mdev key; +* completion counter pools and rank latches are global boot state, as the + second-client wedge demonstrates: the old client's channel/rank/ring state + makes the host's completion path go quiet for the new client + (`../../debug/issue/issue-number-2.md` §Root cause). + +Two guests can trivially choose the *same* handles and GPU VAs (live: +channel `0xcaf000d4`, `../../debug/issue/issue-number-2.md` §Evidence); keyed by +value alone, those registrations and translations collide — the exact class +that already breaks the second client in one guest. **Multi-mdev isolation +is therefore first a service-state-ownership problem, and only second a GMMU +problem.** §2 fixes the ownership; §4 builds the GMMU policy on it. + +### 1.5 Vocabulary + +**Tenant** — isolation principal (`tenantId`): a VM/mdev at L0–L2, a logical +serving principal inside a guest in §4. **Agent** — finest principal inside +a tenant (`agentId`): a vLLM engine/worker, a CUDA context, a process. +**vEP** — virtual execution plane: the per-instance mediator (guest +emulation + work execution) for one mdev; see +[`13-vexecution-plane.md`](13-vexecution-plane.md). **KV block** — +paged-attention cache page (vLLM `paged_attention_v2`: fixed-size blocks, +one `block_tables` entry per logical block per sequence); a GPU allocation +range, not a hardware object. + +--- + +## 2. Security and isolation design for multi-mdev (vEP terms) + +### 2.1 Address-space separation + +**Rule S1.** Every host-side table that describes guest state is keyed by +`(mdev, guest-name)` — handle, GPU VA, doorbell token, counter slot — never +by the guest-chosen value alone. + +What exists and what changes: + +| State | Today | Required | +|---|---|---| +| emulation context | per mdev `[live]` (`nvidia-vgpu-vfio.h:133`, `gsp_emulation.h:130`) | unchanged | +| `svc.chan[]` | keyed `h_object` (`gsp_sema_service.c:847`) | key `(vgpu, h_object)`; per-mdev lifetime reset | +| `svc.vas[]` | keyed `h_vaspace` (`:988`) | key `(vgpu, h_vaspace)` | +| learned VA→PA map / pins | keyed GPU VA (`:113-130`) | key `(vgpu, VA)`; per-mdev epoch | +| doorbell token | ownerless (`:13456`, `main.c:865`) | `(vgpu, token)`; route to the owner's pump | +| completion counters | global boot state | per mdev, reset on session/rebind | +| pump | first open mdev (`:13296-13308`) | per-mdev pump (or round-robin over all open mdevs) | + +The GMMU half of address-space separation is the page directory: each VA +space has one, published by the guest through `SET_PAGE_DIRECTORY` with its +`hVASpace` and `chId` (`kernel/gsp_rm_emulation.c:3310-3314,3366-3372` for +the recording path; the format is documented at `:3307-3319`). The vEP +records the directory per owner and must translate only within the owning +mdev's tables. `[design]` + +### 2.2 Per-mdev channel ownership + +**Rule S2.** A channel belongs to exactly one mdev for its whole life, and +its engine work, doorbell token, error notifier and completion counters are +resolved through the owner only. + +The channel descriptor today carries GPFIFO VA, USERD, instance and +notifier pages (`kernel/gsp_sema_service.h:22-33`); the registration arrives +from the guest's `GSP_RM_ALLOC` handling while the emulation context is +known (`ctx->vgpu`), so the owner is available at registration time — it is +simply not stored. The fix is structural (add the owner to `svc_channel` +and to every lookup), not algorithmic. + +Engine arbitration is where ownership becomes policy: channels of the same +engine from different mdevs must be scheduled by our policy on top of the +real GSP scheduler ([`10-interaction-and-scheduling.md` §7](10-interaction-and-scheduling.md)). +Today the single real GSP session and its global `gsp_phys_rm_lock` serialize +all real firmware traffic (`11-physical-backend-guest-wiring.md` §1.3), so +one tenant's slow RPC blocks every other tenant's work — head-of-line +blocking, addressed in §2.7. + +### 2.3 DMA / IOMMU interplay + +Ground rules, unchanged by multi-tenancy: + +* Guest memory is reached only through `vfio_dma_rw(&vgpu->vfio_dev, ...)` + (`kernel/gsp_sema_service.c:754-762`, `kernel/gsp_rm_emulation.c:27-55`), + i.e. through the hypervisor's mapping for **that** mdev's VFIO container. + A GPA from guest A cannot be resolved through guest B's device — the + boundary is enforced below us, and we never weaken it + ([`../05-operations/01-performance-correctness-security.md` §4.2](../05-operations/01-performance-correctness-security.md)). +* The emulated vidmem (BAR1) is a private host-RAM buffer per mdev + (`kernel/bar_init.c:60-77`); FB-aperture accesses stay inside the owning + mdev's buffer (`svc_read_ap`/`svc_write_ap` bounds-check + `vgpu->bars[1]`, `kernel/gsp_sema_service.c:786-814`). +* Physical-backend caveat (real VRAM path): on the reference host the + physical GPU's IOMMU group is an identity domain and the mediated device + registers as an emulated-IOMMU VFIO device, so guest GPAs are translated + through the QEMU shared-RAM file rather than a kernel `iommu_domain` + (`11-physical-backend-guest-wiring.md` §P6a). This translation is + per-mdev-gated but is **not** an IOMMU guarantee; with multiple mdevs on + one backend it must be audited per mdev, and the active-mdev gate must + never select another mdev's file. `[design]` +* When the backend owns the GPU, any host-translated address that reaches a + real engine must come from the owning mdev's resolved set, never from a + global table (`11-physical-backend-guest-wiring.md` §6.4). + +### 2.4 Data-path separation + +* BAR0 shadow, BAR1 vidmem shadow, BAR3/instance window are per mdev + (`kernel/bar_init.c:59-77`; create at `kernel/main.c:554`; cited in + `11-physical-backend-guest-wiring.md` §0.1 `[live]`). Two mdevs' + shadows are separate allocations; the invariant to test is that no + handler ever mixes the two base pointers (handlers take + `struct gsp_emu_context *ctx` and dereference `ctx->vgpu`, e.g. + `kernel/gsp_rm_emulation.c:2829-2834`; the tests in §2.8 prove it). +* Real-VRAM forwarding already isolates *engines* from guest memory by + submitting through module-built scratch page tables that map + already-resolved host pages and never reuse a fetch line within a boot + (`11-physical-backend-guest-wiring.md` §P6b). With multiple mdevs, scratch + pools must be per mdev: capacity is a per-boot resource (512 pairs at + 16 PTEs/line in the current design) and shares a hardware resource with + every tenant. +* Guest page tables are **guest-owned** (the driver builds them; we walk + them — `kernel/gsp_sema_service.c:1052-1072`), so a malicious guest can + retarget its own PTEs at any time: fine for guest↔host (the IOMMU caps the + damage to the guest's own memory), but the fundamental limit of intra-guest + enforcement in §4 — *policy can be only as strong as page-table ownership*. + +### 2.5 Completion isolation + +**Rule S3.** A decoded semaphore release is applied only in the owning +mdev's address space, and a completion event is posted only to the owning +mdev's event object. + +Today the release path resolves the target VA through *global* tables +(`svc_resolve_va`, `svc_sem_write`; `kernel/gsp_sema_service.c:2098,1514`) +and posts through `gsp_rm_post_channel_event()` on the emulation context +(`kernel/gsp_rm_emulation.c:602`). The first is the cross-tenant hazard: a +release record decoded from guest A's push buffer could resolve through a +pin learned from guest B if the VA collides. The exactly-once rules must be +preserved per owner (a copy runs at most once per GPFIFO entry; a virtual +operand is translated per 4 KB page; a consumed slot never runs later — +[`06-host-module-and-hybrid-model.md` §4.4](06-host-module-and-hybrid-model.md)). + +### 2.6 Fault containment + +**Rule S4.** A fault (engine fault, malformed RPC, translation failure, +timeout) is attributed to its owner, delivered only to the owner's notifier +and event object, and must not stall or corrupt another tenant. + +The machinery exists per channel (error notifier slots in the channel +descriptor, `kernel/gsp_sema_service.h:31`) and for forwarded engines +(fault observation/relay default-off, +`11-physical-backend-guest-wiring.md` §P6c C3), and the malformed-RPC path +is fail-closed. What is missing is the owner key at the relay decision, and +a per-tenant fault-injection test (§2.8). A wedged server is a +*module-global* fail-safe today (`08-physical-gpu-integration.md` §7), which +is correct for one backend session but must never be reachable from a +guest-triggered path: guest input may abort the guest, never the service. + +### 2.7 Time-slicing risks + +With one physical GPU, tenants share engines by time slicing. Known risks: + +* **Head-of-line blocking:** the single real GSP session + `gsp_phys_rm_lock` + serializes backend RPCs; a tenant that floods controls starves the others. + Mitigation design: per-mdev backend clients (already planned, + [`09-full-system-picture.md` §5](09-full-system-picture.md)), bounded + per-tenant RPC rates in the vEP, and per-tenant accounting. +* **Slice inversion:** guest A's long-running engine work delays B's + completion relay; the relay path must stay bounded and never poll a + hardware queue while holding a lock a second tenant needs + (`11-physical-backend-guest-wiring.md` §1.3 lock order). +* **Starvation:** without a policy, first-come submission can starve a + tenant. The policy is planned + ([`10-interaction-and-scheduling.md` §7](10-interaction-and-scheduling.md)); + until it exists, the honest statement is "fairness is unmeasured". + +### 2.8 Side channels (public knowledge only) + +We make no original side-channel claims and have no measurements. The +classes that apply to any shared-GPU time-slicing design, from public +literature, are: memory-bandwidth contention observable through timing; +L2/LLC state effects; scheduler/slice timing; and interrupt-timing +observation. The project's stated bar is that an adjacent tenant must not +observe timing/state/interrupts *beyond policy* +([`../05-operations/01-performance-correctness-security.md` §4.1](../05-operations/01-performance-correctness-security.md)): +interrupts are already per-instance, timing policy must be explicit and +documented, and measurement is a 1.0-era task (rung 5). Until measured, +treat side channels as **unmitigated but bounded by per-instance state +separation**, and never claim isolation stronger than the ladder proves. + +### 2.9 Validation and test plan for the 1.0 "two guests" gate + +The gate: two QEMU guests, two mdevs, concurrent, stock driver; every test +captures evidence (dmesg, counters) and passes at least 3 consecutive runs. + +| # | Test | Pass criterion | +|---|---|---| +| 2G-01 | Boot both guests concurrently on two mdevs; `nvidia-smi`; CUDA driver-API smoke | both green, payload round-trip exact; no cross errors in dmesg | +| 2G-02 | Distinct VRAM types (e.g. 512M and 1G) on the two mdevs | each guest's `nvidia-smi`/GSCI reports its own size; BAR1 sizes differ and match | +| 2G-03 | Handle/VA collision: force both guests to allocate the same RM handles and GPU VAs (the `0xcaf000d4` class) | both clients progress; no release lands in the wrong guest (verify by canary words around each guest's semaphores) | +| 2G-04 | Doorbell cross-talk: ring guest A's tokens while B is idle | B's channels/state unchanged; B's poll loop untouched; A completes | +| 2G-05 | Completion ownership: craft a release VA that exists in both guests' address spaces | resolves only in the owner's memory; the other guest's canary unchanged | +| 2G-06 | Fault injection: malformed RPC, bad queue link, engine-fault relay from A | A fails closed (documented error); B unaffected; no module-global stall | +| 2G-07 | Teardown: remove mdev B while A runs; rmmod after both stop | A continues; teardown order preserved; module unload clean | +| 2G-08 | Stress: N≥20 concurrent start/stop cycles; guest reboots; module reloads | no leaked per-mdev state; no cross-tenant counters; clean dmesg | +| 2G-09 | Sequencing: the ISSUE-0002 acceptance (3 sequential clients in one boot) plus 2 concurrent processes | green; per-client reset proven (issue-number-2 §Validation plan) | + +Known blockers before the gate can run: the per-client/per-channel reset +(`../../debug/issue/issue-number-2.md` §Fix plan items 1–2) and the per-mdev +service-state split (§2.1 Rule S1). The test harness (two VMs; the current +tools assume one, `../../scripts/vgpu_vm.py`) is itself a deliverable. + +### 2.10 Fail-closed rules + +Isolation properties that cannot be established (missing IOMMU container, +DMA mapping failure, inconsistent owner state, unkeyed table entry) stop the +affected mdev and say why; nothing silently degrades +([`../05-operations/01-performance-correctness-security.md` §4.2](../05-operations/01-performance-correctness-security.md)). +Malformed or stale queue elements, PTE chains and release records are +rejected without disturbing the queue, with strict sequence matching +([`10-interaction-and-scheduling.md` §3](10-interaction-and-scheduling.md)). +No forwarding seam programs guest-derived page tables into a real MMU until +the population path is proven end to end +(`11-physical-backend-guest-wiring.md` §6.4). A cross-owner table lookup is +a programming error: log once and refuse, never "resolve anyway". + +--- + +## 3. VRAM larger than physical, by scheduling and virtualization + +### 3.1 What "VRAM size" means per mdev today + +Each mdev derives a consistent set of numbers from +`nv_vgpu_fb_emul_size(vgpu)` (`kernel/include/nvidia-vgpu-vfio.h:232`): +BAR1 size (`kernel/bar_init.c:68`), the `LOCAL_MEMORY_RANGE` register +encoding (`kernel/bar_emulation.c:271-310`), the GSCI `fb_length` and +`frtsOffset` (`kernel/gsp_rm_emulation.c:1106-1110,1177`) and the WPR2 +address (`kernel/gsp_emulation.c:743-753`). The mdev type advertises the +same size to management tools (`framebuffer=%uM`, +`kernel/main.c:116-132`). The sizes available are dictated by the types the +host driver registers (`kernel/include/ioctl.h:39-46`, +`kernel/nv_vgpu_register_type_info` callers). + +Two consequences: **presenting a size is solved** (two mdevs can present +512 MiB and 2 GiB simultaneously; nothing in the guest path forces them to +match the physical framebuffer), but **backing that size is not** — in +emulation mode each mdev's "VRAM" is a private host-RAM `vzalloc` +(`kernel/bar_init.c:70-77`), so the aggregate is bounded by host RAM; with +real VRAM backing (`08-physical-gpu-integration.md` §4.6) it is bounded by +physical VRAM. Either way `sum(vram_mb)` may exceed capacity — the +oversubscription problem. + +### 3.2 Design: present size independent of physical size + +**Principle.** Guest-visible VRAM size is a *contract*; the resident set is +an *implementation detail* — decouple them with tiered, revocable backing: + +``` + guest-visible VRAM (mdev A, e.g. 2 GiB) guest-visible (mdev B, 1 GiB) + │ │ + ┌──────┴──────────────────────────────┐ ┌──────┴────────────┐ + │ vEP per-mdev residency manager │ │ ... │ + │ pinned set | evictable set │ │ │ + └──────┬──────────────────────────────┘ └───────────────────┘ + │ page-in/out (4 KiB-2 MiB) + ┌────────────┴───────────┐ ┌───────────────────────────────┐ + │ real VRAM (hot blocks) │ │ sysmem staging (cold blocks) │ + └────────────────────────┘ └───────────────────────────────┘ +``` + +Techniques, each with its current status: + +1. **Sparse allocation (allocate-on-touch).** The guest-visible range exists + before it is backed; backing is populated on first write (emulation: RAM + page; backend: real VRAM object). Today the whole BAR1 is + `vzalloc`'d up front (`kernel/bar_init.c:70-77`) `[wip]`; sparse backing + is `[design]`. +2. **VRAM ↔ sysmem paging.** Cold blocks move to guest sysmem (or host + staging) and their PTEs are rewritten to the sysmem aperture; hot blocks + move back. The GMMU already distinguishes apertures + (`svc_pte_aperture`, `kernel/gsp_sema_service.c:1101-1108`) and the guest + uses `ADDR_SYSMEM=1`/`ADDR_FBMEM=2` descriptors + ([`05-gmmu-amap-hal-gr.md` §1.1](05-gmmu-amap-hal-gr.md)) `[design]`. +3. **Replayable pushes.** A push whose instruction stream is re-executed + must be idempotent or suppressed; the host already has the machinery: + per-entry identity checks, per-operation done bitmaps, consumed-slot + marks, and the deferred-release hold that waits for hardware completion + before applying releases exactly once + ([`06-host-module-and-hybrid-model.md` §4.4](06-host-module-and-hybrid-model.md), + `11-physical-backend-guest-wiring.md` §P6b wave 1.5) `[wip]`. + Paging reuses this primitive: an operation that references an evicted + block is held until the block is resident. +4. **GMMU invalidations.** Rewriting a PTE is not enough: the engine caches + PTE fetches (observed 128-byte granularity) and there is no invalidate + path in the forwarding code today; a runtime PTE write into an + already-walked line was accepted but never completed + (`11-physical-backend-guest-wiring.md` §P6b "PTE fetch-line rule"). + The guest's own `MEMSYS_L2_INVALIDATE_EVICT` control is acknowledged + without effect in emulation (`kernel/gsp_rm_emulation.c:2519-2532`). + A real invalidate path is a prerequisite for paging `[design]`. +5. **UVM fault replay.** Guest UVM already handles memory faults and + re-registration; the host can signal "not resident" and let the guest + replay the access. The guest-visible mechanism is the channel + event/notifier path (`gsp_rm_post_channel_event`, + `kernel/gsp_rm_emulation.c:602`); whether every access class can be + replayed this way is `[hypothesis]` — it needs a live experiment. +6. **Migration hooks.** The module already has save/restore state sections + (`kernel/migration.c:85+`); residency metadata (which blocks are + resident, tags, ownership) must be part of the migrated state for + correctness across live migration `[design]`. + +### 3.3 Tradeoffs + +| Effect | Cause | Control | +|---|---|---| +| Latency spike on page-in | block fetch from sysmem to VRAM | bound resident-set misses per slice; prefetch on sequential access (paged attention accesses KV blocks sequentially per sequence) | +| Throughput collapse under thrash | working set > capacity | admission control: refuse the allocation that would guarantee thrash; per-tenant quotas | +| Completion jitter | held pushes waiting for residency | bound the hold; on expiry fail closed per operation (existing bounded-hold pattern, `gsp_sema_service.h:220-236`) | +| Fairness loss | one tenant's evictions victimize another | per-tenant residency targets, self-eviction first (see §3.5) | + +These are unmeasured; the numbers must come from the benchmark harness +([`../05-operations/01-performance-correctness-security.md` §3](../05-operations/01-performance-correctness-security.md)). +Do not quote an overhead figure from this section. + +### 3.4 Front-running risks and correctness invariants + +**Front-running.** A tenant that can force reclaim of *another tenant's* +pages gains two advantages: it can degrade a competitor, and it can race to +observe reused memory if reclaim does not scrub. Rule: reclaim only within +the requesting tenant's own residency budget unless a global-capacity policy +explicitly and observably selects a victim (accounted, rate-limited, and +scrubbed before reuse). + +**Invariants (must hold for any paging implementation).** + +* **I1 — No live PTE to reclaimed memory.** A block may be reallocated only + after every PTE that references it has been revoked and the revocation is + visible to all engines. +* **I2 — Revoke implies quiesce.** No engine work may be in flight on a + block at revoke; the deferred-release hold + channel drain is the + primitive (`11-physical-backend-guest-wiring.md` §P6b). +* **I3 — Exactly-once completion.** Paging must not duplicate or lose a + release; the existing replay-suppression rules apply per owner + ([`06-host-module-and-hybrid-model.md` §4.4](06-host-module-and-hybrid-model.md)). +* **I4 — Owner-stable residency.** A block's backing is owned by one mdev + (or one intra-guest tenant, under §4); no shared physical page between + owners without an explicit, tagged share record. +* **I5 — Scrub before reuse.** Reclaimed memory is zeroed (or poison-filled) + before it can be mapped into any other owner. +* **I6 — Migration carries residency.** Save/restore includes the residency + map and tags, or the restored device must cold-start the cache. + +### 3.5 What vEP must add + +* **Per-mdev accounting:** resident/pinned bytes, fault-in counts, hold + timeouts — the counters in `struct sema_svc` + (`kernel/gsp_sema_service.c:439-676`) made per owner. +* **Reclaim signal:** a guest→vEP hint that a range is evictable (vLLM knows + when a KV block is freed): an allocation/free observed at RM-object level + in the stock-compatible design, or an explicit control in the tagged + design (§4.5). +* **QoS policy:** per-mdev weights/slices on top of the GSP scheduler + ([`10-interaction-and-scheduling.md` §7](10-interaction-and-scheduling.md)), + with measured properties. `[design]` +* **Capacity admission:** enforce the oversubscription ratio at create and + large-allocation time; refuse (fail closed) rather than promise capacity. + +--- + +## 4. Custom GMMU protections for paged-attention multi-tenancy + +> This is the core ask: given a tenant ID and an agent ID, protect a +> paged-attention KV cache shared by many tenants inside one guest. The +> design below is complete on paper across the full stack. Stages M6–M8 +> are **post-1.0 research**: dual-gated, duplicate-not-replace, never +> required by the baseline. Stages M1–M5 belong to the 1.0 programme. + +### 4.1 The workload, precisely + +Public knowledge of `paged_attention_v2`: the KV cache is a pool of +fixed-size **blocks**; each sequence has a **block table** (a GPU tensor of +block indices) mapping its logical positions to physical blocks. Decode +kernels gather K/V from the blocks named by the table. A vLLM server +multiplexes many sequences (many logical tenants) in one continuous batch +inside one process and, typically, one CUDA context. Nothing in this +pipeline is hardware-protected: any kernel in the context can dereference +any block table entry, and a buggy or malicious sequence implementation can +read every other tenant's KV cache. With prefix caching, blocks are even +*shared* between sequences (copy-on-write), which is a feature to preserve, +not eliminate. + +The assumed identities: `tenantId` identifies the serving principal; +`agentId` identifies the finest unit inside a tenant (a worker/context; in +a single-process vLLM, an engine or an attention backend instance). Both are +provided by the orchestration layer; provisioning them is out of scope. + +### 4.2 Hardware GMMU primitives: proven vs hypothesis + +What the Turing (TU104) VER2 format and our live code establish (`docs/NN` +below resolves to `../../debug/risc-v-gsp-rm-vm/docs/NN-*.md`): + +| Primitive | Status | Evidence | +|---|---|---| +| 4-level page tree; 49-bit VA; 4 KiB/64 KiB/2 MiB pages; dual 16-byte PDE0 | **proven** | tracked decoder `../../debug/risc-v-gsp-rm-vm/docs/43-GMMU-PTE-DECODER.md` §6; `kernel/gsp_sema_service.c:1058-1072` | +| VER2 leaf PTE fields incl. `read_only` (bit 6), `privilege` (5), `encrypted` (4), `atomic_disable` (7), `address_sys/vid`, `kind` | **proven format** | `docs/43` §3 | +| Separate page directory per VA space, published per channel (`SET_PAGE_DIRECTORY` with `hVASpace`/`chId`) | **proven** | `kernel/gsp_rm_emulation.c:3310-3314,3366-3372`; live walks `kernel/gsp_sema_service.c:1174-1312` | +| Directory-level apertures and big-page leaves | **proven** | `docs/43` §3–§6; `svc_pde_aperture`, `kernel/gsp_sema_service.c:1110-1117` | +| **No tenant/ACL/PASID field in the VER2 PTE or PDE** | **proven absence in the format** | the full field list in `docs/43` §3–§5 has no such field; `peer_id` is a fabric peer address selector, not a security domain | +| Read-only PTE *enforcement* (the engine refuses a write through an RO leaf) | **hypothesis** | the format defines the bit; we have never exercised an RO-protected leaf on hardware | +| Encryption-domain isolation (CCS/confidential computing) usable as a tenant boundary | **hypothesis** | format has an `encrypted` bit; CCS object model and semantics are unstudied for us | +| MMU invalidate semantics fine-grained enough for per-block revocation | **hypothesis** | no invalidate path exercised; see `11-physical-backend-guest-wiring.md` §P6b | + +**The decisive design fact:** on this generation, **the only proven +hardware isolation primitive is the page directory** (per-context page +tables). Per-PTE ACLs are research-grade and cannot be assumed. Therefore +the design must place every security-relevant decision in the *topology* +(which directory a page appears in, which channel may name it) plus +*software validation and audit* in the vEP. The `read_only` bit is a proven +format field and the right mechanism for *sharing without write access* +(e.g., shared prefix blocks), but its enforcement needs the M7 experiment +before we depend on it. + +### 4.3 The full stack + +The requested chain, end to end: + +``` +┌─────────────────────────────────────────────────────────────────────────────┐ +│ GUEST │ +│ │ +│ extended vLLM (serving engine) │ +│ ├─ per-tenant sequences + block tables (unchanged algorithms) │ +│ ├─ agent-aware KV allocator: blocks come from per-agent VA pools │ +│ └─ calls oiv-agentd for identity + sharing decisions │ +│ │ Unix socket (guest-local) │ +│ oiv-agentd (NEW guest utility; application-level, no driver patch in A) │ +│ ├─ resolves (tenantId, agentId) -> VA pool + tag records │ +│ ├─ Design A: publishes tag records to a mailbox in device memory │ +│ └─ Design B: issues new RM controls through the guest KMD │ +│ │ CUDA driver API (stock) │ RM control (B) │ +│ guest KMD (stock nvidia.ko; unmodified in A, extended in B [research]) │ +│ │ GSP-RM RPC / MMIO │ +├───────────┼─────────────────────────────────────────────────────────────────┤ +│ vEP HOST (vgsp.ko) │ +│ GSP-RM emulation: controls, objects, page-directory records │ +│ tag registry (per mdev): tenant/agent -> VA ranges, access, shares │ +│ page-table policy: owner checks at SET_PAGE_DIRECTORY / map / submit │ +│ channel binding: channel -> (tenant, agent); arbitration + completion │ +│ fault attribution: fault -> channel -> tenant; owner-only notifier │ +│ │ real page tables / forwarded work │ +│ GPU: GMMU page directories (topology) + PTE bits (read_only, kind) │ +└─────────────────────────────────────────────────────────────────────────────┘ +``` + +### 4.4 Design A — VA-subtree partitioning (no guest KMD change) + +This is the **stock-compatible** design and the one that can be prototyped +inside the 1.0 programme. It relies on one idea: **the VA space is the +security domain, and a VA is self-describing because its prefix encodes its +owner.** + +* Extended vLLM reserves a disjoint VA range per agent: `cuMemAddressReserve` + (stock CUDA VMM API) at a base derived from `(tenantId, agentId)`, e.g. + a deterministic partition of the GPU VA space into tenant subtrees and + agent subtrees. KV blocks are `cuMemCreate`d and mapped only inside the + owner's range. +* `oiv-agentd` publishes the binding records (which `(tenantId, agentId)` + owns which VA range, with what access) to a **tag mailbox**: a device + allocation whose content the vEP can read. In emulation mode this works + because guest vidmem *is* host RAM (`kernel/bar_init.c:68-77`; FB + aperture reads are direct, `svc_read_ap` `kernel/gsp_sema_service.c:786-799`), + so the vEP can scan the mailbox page through the owner's shadow. A fixed + mailbox offset (requested from the extension at init) keeps discovery + trivial. +* vEP policy, enforced when walking/populating page tables and at + work-submission time: + 1. a page directory is owned by the mdev (L3) and, under this design, by + a `(tenantId, agentId)` (L7); + 2. a PTE whose VA is in tenant T's subtree may only point at memory + tagged T (or shared with T); + 3. a channel bound to agent A may only execute work referencing VAs in + A's subtree (validation at decode/submit, using the existing + owner-aware claim of §2.2); + 4. violations are refused and attributed to the owning channel. +* Sharing for prefix caching: a shared block lives in the *owner's* range + and is exposed to the borrower through a new PTE in the borrower's + directory with `read_only` set (proven format field) — or, until M7 + proves RO enforcement, not exposed at all and prefix sharing is + downgraded to per-agent copies. + +Limits, stated honestly: + +* It does not protect against a malicious guest KMD (the KMD builds the + page tables; it can lie about prefixes). It protects tenants from each + other *within* the tenant's trust model, which is the vLLM threat model. +* Emulation-mode mailbox discovery does not survive real VRAM backing + (the host cannot read real VRAM as RAM); the real-VRAM version needs a + protocol path (Design B) or a host-readable staging mailbox. +* It requires extended vLLM (not stock vLLM) — an application change, not + a driver change. + +### 4.5 Design B — explicit tenant/agent RM controls `[research]` + +The explicit design: new vGPU-private RM controls flow through the guest +KMD to the vEP; the vEP stores tags, enforces policy, and can build real +page tables from them. This requires a guest KMD that knows the controls; +therefore it is **post-1.0** (it modifies the guest, which the baseline +guest rule forbids, [`../06-portability/hardware-software-matrix.md` §3](../06-portability/hardware-software-matrix.md)), +dual-gated, and never a 1.0 dependency. + +Proposed controls (vGPU-private control range; the proven vGPU-specific +example is `0x90f10106`, `COPY_SERVER_RESERVED_PDES`, +`kernel/gsp_rm_emulation.c:2266`): + +| Control | ID (proposed) | Semantics | +|---|---|---| +| `NV90F1_CTRL_CMD_VGPU_TENANT_BIND` | `0x90f10120` | bind the calling client to a `(tenantId, agentId)`; idempotent join; optional exclusive | +| `NV90F1_CTRL_CMD_VGPU_TENANT_TAG_RANGE` | `0x90f10121` | tag a GPU VA range with owner + access + volatility | +| `NV90F1_CTRL_CMD_VGPU_TENANT_UNTAG_RANGE` | `0x90f10122` | remove a tag; range becomes untouchable by non-owners | +| `NV90F1_CTRL_CMD_VGPU_TENANT_SHARE` | `0x90f10123` | grant a borrower a (revocable) view of an owner range | +| `NV90F1_CTRL_CMD_VGPU_AGENT_CHANNEL_BIND` | `0x90f10124` | bind an RM channel to an agent for arbitration/completion attribution | +| `NV90F1_CTRL_CMD_VGPU_TAG_QUERY` | `0x90f10125` | read-only: effective identity, enforcement mode, range tags | + +Struct sketches (wire types follow the driver's `NvU32`/`NvU64`/`NvHandle` +conventions; all little-endian, 8-byte aligned): + +```c +/* 0x90f10120 */ +typedef struct NV_VGPU_TENANT_BIND_PARAMS { + NvU64 tenantId; /* stable, VM-scoped; 0 invalid */ + NvU64 agentId; /* 0 = tenant's default agent */ + NvU32 flags; /* CREATE 0x1 | JOIN 0x2 | EXCLUSIVE 0x4 */ + NvU32 abiVersion; /* 1 */ + NvU8 name[32]; /* diagnostic only */ +} NV_VGPU_TENANT_BIND_PARAMS; + +/* 0x90f10121 / 0x90f10122 */ +typedef struct NV_VGPU_TENANT_TAG_RANGE_PARAMS { + NvU64 tenantId; + NvU64 agentId; + NvU64 gpuVa; /* page aligned */ + NvU64 size; + NvU32 access; /* READ 0x1 | WRITE 0x2 (0 invalid) */ + NvU32 flags; /* SHARED 0x1 | EVICTABLE 0x2 | PINNED 0x4 | + KV_BLOCK 0x8 | BLOCK_TABLE 0x10 */ + NvU64 tagCookie; /* opaque allocator cookie; echoed in faults */ +} NV_VGPU_TENANT_TAG_RANGE_PARAMS; + +/* 0x90f10123 */ +typedef struct NV_VGPU_TENANT_SHARE_PARAMS { + NvU64 ownerTenantId, ownerAgentId; + NvU64 borrowerTenantId, borrowerAgentId; + NvU64 gpuVa; /* range start as tagged by the owner */ + NvU64 size; + NvU32 access; /* borrower view: READ | READ|WRITE */ + NvU32 flags; /* REVOCABLE 0x1 (default) | COW 0x2 */ +} NV_VGPU_TENANT_SHARE_PARAMS; + +/* 0x90f10124 */ +typedef struct NV_VGPU_AGENT_CHANNEL_BIND_PARAMS { + NvHandle hChannel; + NvU64 tenantId, agentId; + NvU32 flags; /* EXCLUSIVE 0x1 */ + NvU32 abiVersion; /* 1 */ +} NV_VGPU_AGENT_CHANNEL_BIND_PARAMS; +``` + +Design A's mailbox record (the stock-compatible carrier): + +```c +typedef struct NV_VGPU_TAG_MAILBOX_RECORD { + NvU32 magic; /* 'OIV1' = 0x4f495631 */ + NvU32 recordType; /* 1 bind, 2 tag, 3 untag, 4 share */ + NvU64 seq; /* producer sequence; consumed strictly in order */ + NvU64 tenantId, agentId; + NvU64 gpuVa, size; + NvU32 access, flags; + NvU64 tagCookie; + NvU32 crc32; /* over the record with crc32 field = 0 */ +} NV_VGPU_TAG_MAILBOX_RECORD; +``` + +Message-semantics rules: controls are synchronous (reply = status/params); +tag records are monotonic (later record for a range wins; untag terminal); +shares are revocable, revocation delivered to the borrower's event object; +every mutating control is forwarded to the vEP, whose reply is authoritative +— not the echo/zeroed-success fallback used for unknown IDs today +(`kernel/gsp_rm_emulation.c:2829`; `11-physical-backend-guest-wiring.md` +§2.1 #7, #13). + +### 4.6 Where enforcement lives + +| Layer | Mechanism | Strength | +|---|---|---| +| Guest application (vLLM + agentd) | VA-pool discipline, block-table construction | advisory; defeated by a buggy kernel | +| Guest KMD (Design B) | control validation, identity binding, forwarding | local; guest-trusted | +| vEP tag registry + policy | owner checks at directory record / map / submit / completion | **the enforcement point** | +| GMMU topology | separate page directory (+ optional sub-directory per agent) | hardware-enforced for context separation | +| GMMU PTE bits | `read_only` for shares; `kind` for memory type | hardware-enforced *if* M7 confirms | +| Completion/fault paths | owner-keyed resolution and attribution | software, load-bearing | +| Audit | periodic owner-vs-PTE audit; canary pages | detection; catches policy escapes | + +### 4.7 The three granularities + +#### 4.7.1 Per-KV-block (page/PTE level) + +*Tag every block; enforce at map time; share with read-only where hardware +allows.* + +| Aspect | Assessment | +|---|---| +| Benefits | finest sharing (prefix cache reuse across agents); per-block revoke for eviction; fault granularity is the block | +| Drawbacks | per-block metadata and per-map validation cost; vLLM allocates blocks in bursts — validation must be batched or the map path becomes the bottleneck; sharing needs RO enforcement (M7) or degrades to copies | +| Performance | map-time cost per block (currently an emulated RPC is tens of µs — `10-interaction-and-scheduling.md` §6 — and tagging must not add a round trip per block); no extra TLB misses if topology is unchanged, but per-block VA fragmentation reduces big-page opportunities, which *does* cost TLB reach | +| Implementation cost | high: vLLM allocator change, agentd, mailbox/controls, vEP registry, audit | +| Portability | the *concept* is portable; the enforcement is software+topology, because no PTE tenant field exists (proven absence, §4.2); `read_only` mapping must be re-derived per generation | +| Failure modes | stale tag after reallocation (block freed, VA reused); share revoked but a borrower PTE survives (no invalidate, I1 violation); tag/PTE divergence after migration; audit misses a crafted PTE | + +#### 4.7.2 Per-tenant (sub-address-space: separate channel + page directory) + +*One tenant = one page directory + at least one channel; agents inside a +tenant share the directory (and are separated only by VA-subtree convention +unless per-agent directories are added).* + +| Aspect | Assessment | +|---|---| +| Benefits | **the only proven hardware lever** (§4.2); low steady-state overhead (matches the driver's own per-context model); naturally portable; fault and completion attribution are trivial (directory owner = tenant) | +| Drawbacks | no fine-grained sharing; a tenant is a scheduling unit (context switches); TLB working set per tenant; intra-tenant agents are NOT isolated | +| Performance | launch path unchanged; extra contexts cost context-switch and TLB refill; no per-block map overhead | +| Implementation cost | medium: owner keys in the vEP (§2.1–2.6) plus per-mdev backend clients; Design A needs the allocator/agentd work only for the VA pools | +| Portability | high: object model and directory-per-context survive driver-version and (probably) generation changes; exact PTE encoding does not matter | +| Failure modes | directory reuse on handle collision (today's bug class); a page accidentally mapped into two directories; one tenant's RPC wedge stalls others (single backend session); eviction invalidating a sibling's PTE without quiesce | + +#### 4.7.3 Per-agent (finest grouping inside a tenant) + +*One agent = one channel; optionally one sub-directory per agent within the +tenant directory.* + +| Aspect | Assessment | +|---|---| +| Benefits | fault containment per agent (a crashed agent context does not kill siblings); QoS per agent; completion attributed per agent; the natural mapping of CUDA contexts/streams | +| Drawbacks | more channels/contexts = more scheduler state and context switches; per-agent directories multiply TLB pressure and page-table memory; vLLM must be structured so each agent is its own context (a serving-engine change) | +| Performance | overhead grows with agent count; launch unchanged; arbitration and completion bookkeeping per channel | +| Implementation cost | medium–high: channel binding, arbitration policy, per-agent counter pools; vLLM work | +| Portability | high (channels/streams are stock); optional per-agent directories depend on the guest building them (trusted guest) or the host owning page tables (Design B/E1) | +| Failure modes | misbinding a channel to the wrong agent; handle collisions across agents (the ISSUE-0002 class); a shared channel silently breaks agent isolation; completion counters reused across agents | + +**Degradation ladder** (what you lose when you can only do a coarser +grain): + +``` +per-KV-block + RO shares best sharing, software-enforced identity, needs M7 + │ (no RO hw -> drop sharing, keep tagging) +per-tenant directories real hardware context separation; agents inside + │ a tenant share a directory -> intra-tenant + │ isolation is software-only +per-agent channels real fault/QoS separation at channel granularity, + │ no per-block sharing +per-mdev (1.0 baseline) real VM isolation; intra-guest tenants are + application-trusted only +``` + +### 4.8 Failure modes across all granularities + +* **TOCTOU on guest-owned tables.** The guest can rewrite its PTEs between + validation and execution. Mitigation: host-owned page tables for protected + ranges (server-owned contract exists: `COPY_SERVER_RESERVED_PDES`, + `kernel/gsp_rm_emulation.c:2237-2262`; real population is P6c, + `11-physical-backend-guest-wiring.md` §2.1 #9/#10) or validate at + submission and hold the entry (deferred-release hold). +* **Identity laundering.** An agent claims another's `(tenantId, agentId)`. + The identity source is provisioning (guest-local Unix socket in A; KMD + control validation in B); the vEP binds identity to the *client/context* + at bind time, and a second bind for the same range from a different + client is refused. +* **Stale-share escape.** Revoke without invalidate leaves a borrower PTE. + Invariant I1/I2; until invalidate exists, shares must be configured as + copy-on-write or non-revocable-at-runtime (drained between batches). +* **Handle collision.** Two guests/agents pick the same RM handles — + observed. Owner-keyed tables + per-client reset (issue-number-2). +* **Migration inconsistency.** Saved state without tags/residency can + reattach pages to the wrong owner. Invariant I6. + +--- + +## 5. End-to-end walked example: two tenants, `paged_attention_v2`, one guest + +### 5.1 Setup + +One guest, one mdev. Two tenants: **T1** with agent **A1** (model X), **T2** +with agent **A2** (model Y). Each runs its own serving engine process; the +extended vLLM allocates KV blocks from the agent's VA pool. `oiv-agentd` +binds `(T1,A1)` and `(T2,A2)` and publishes tag records for their pools +into the mailbox (Design A) or issues `TENANT_BIND`/`TAG_RANGE` (Design B). +Channels: A1 and A2 each own a compute channel and a copy channel; both are +bound to their agent (`AGENT_CHANNEL_BIND`). + +``` +GUEST VA SPACE (one directory per tenant under Design B, or per agent) + T1 subtree / pool T2 subtree / pool + ┌───────────────┐ ┌───────────────┐ + │ block B11 ... │ │ block B21 ... │ + └───────────────┘ └───────────────┘ + block tables (int32) block tables (int32) + ├─ seq s1: [B11,B12,..] ├─ seq s3: [B21,B22,..] + └─ seq s2: [B13,..] └─ seq s4: [B21,...] (prefix share? B21 RO) +``` + +### 5.2 Flow (happy path) + +```mermaid +sequenceDiagram + autonumber + participant V1 as vLLM engine T1 + participant V2 as vLLM engine T2 + participant AG as oiv-agentd + participant KMD as guest KMD (stock in A) + participant EP as vEP host (vgsp.ko) + participant MMU as GMMU page tables + participant GPU as engines + + V1->>AG: register (T1, A1); request VA pool + V2->>AG: register (T2, A2); request VA pool + AG->>EP: tag records: T1/A1 -> VA range R1, T2/A2 -> R2 (mailbox or RM ctrl) + EP->>EP: registry: owner(R1)=T1/A1, owner(R2)=T2/A2 + V1->>KMD: cuMemCreate/cuMemMap KV blocks inside R1 + V2->>KMD: cuMemCreate/cuMemMap KV blocks inside R2 + KMD->>EP: SET_PAGE_DIRECTORY (recorded, owner = mdev) + EP->>MMU: (backend) PTEs for R1 only in T1 dir, R2 only in T2 dir + V1->>KMD: launch decode kernel on A1 channel (block table in R1) + V2->>KMD: launch decode kernel on A2 channel (block table in R2) + EP->>EP: submit-time check: channel A1 -> VA in R1 only; A2 -> R2 only + EP->>GPU: forward work + GPU-->>EP: completions + EP-->>V1: release -> A1's semaphores only + EP-->>V2: release -> A2's semaphores only +``` + +### 5.3 Malicious attempt, and which layer stops it + +Scenario: tenant T1 crafts a decode kernel whose block table contains a VA +inside R2, or directly dereferences an R2 VA. + +1. **Application layer (none).** vLLM's own bookkeeping does not stop it — + this is the gap being fixed. +2. **GMMU topology.** In T1's page directory there is **no PTE** for an R2 + VA. The engine's access faults (invalid leaf / unmapped). Under + per-tenant directories this is the primary stop; the fault carries the + faulting VA and the channel. +3. **vEP submit-time policy.** Before forwarding, the decoded work is + checked against the channel's allowed VA set (owner map + tags). The + submission is refused and counted *before* any engine access is + programmed; this also covers accesses that would otherwise appear legal + because of a stale PTE. +4. **vEP map-time policy.** If T1 instead tries to *map* an R2-owned block + into R1 (e.g., by getting the guest KMD to place a PTE pointing at + T2's memory through a shared directory), the tag registry rejects the + mapping: owner(R2 physical page) ≠ T1. Refused at population time. +5. **Fault attribution.** The fault (from step 2/3) is attributed to + `(mdev, channel)` → `(T1, A1)`; the notifier and event go only to A1's + event object; T2's channels and completions are untouched. A1's context + is the only thing torn down (policy: kill the offending agent, not the + server). +6. **Audit.** A periodic owner-vs-PTE audit (canary pages at pool + boundaries, checksummed) detects any escape that steps 2–4 missed and + fails closed for the owning tenant. + +What this stop costs: a fault is ms-class (engine fault → status queue → +vEP relay → guest notifier), and the refused submission costs one +validation pass at decode time (class: µs, same order as the existing +per-entry decode, `10-interaction-and-scheduling.md` §6). Neither number +is measured multi-tenant yet. + +### 5.4 Degradation: if only per-tenant separation is available + +Assume the hardware/format experiments (M6–M7) do not land and we ship +only per-tenant directories plus owner-keyed software: + +* T1 and T2 remain separated **if and only if** they are in different + tenants **and** each tenant's agents do not share a directory (the + deployable unit is one agent per tenant, or per-agent contexts). +* If two agents inside one tenant share a directory (the common vLLM + single-process case), the cross-agent guarantee downgrades to + **app-level**: block tables are validated at submit time against the + agent's VA pool, but a kernel dereferencing a sibling's VA resolves (the + PTE exists). The stop becomes detection (audit) + kill, not prevention. +* Prefix-cache sharing across agents degrades to copies (no proven RO + enforcement), costing VRAM and copy bandwidth. +* Honest recommendation for the degraded mode: **one agent per tenant, one + tenant per context** (vLLM tenant count = context count) — a + configuration contract, not a hardware guarantee. + +--- + +## 6. Portability + +### 6.1 What is portable vs not + +| Artifact | Portable? | Why / what to re-derive | +|---|---|---| +| Object model (client, device, vaspace, channel, events) | **yes** | stable across 550–580 as a family; verified live only on 570.144 | +| Page-directory contract (`SET_PAGE_DIRECTORY`/`UNSET`, `COPY_SERVER_RESERVED_PDES`) | **yes, versioned** | controls carry `hVASpace`/`chId`/geometry; re-verify field offsets per version | +| Our GMMU walker/decoder approach | **yes** | decode captured tables; field catalogue per generation (Turing VER2 proven; others re-derive) | +| PTE/PDE bit encodings | **no** | generation-specific (VER2 on Turing; newer formats expected on Blackwell); re-derive from captures + format docs per device | +| Invalidate semantics / fetch-line behavior | **no** | measured per implementation; current 128-byte fetch-line, no-invalidate result is Turing+our-path-specific | +| Tag controls / mailbox (this design) | **concept yes; wire no** | new controls are per-version additions; mailbox depends on guest vidmem being host-readable (emulation only) | +| Per-tenant/per-agent policy | **yes** | policy is ours; mechanism depends on the above | +| mdev type/VRAM sizes | **yes** | per-type metadata registered by the host driver; sizes are policy, not hardware | + +### 6.2 Driver 610 + +RM 610 is under a **hard gate**: no acquisition, analysis, emulation edit, +physical work or support claim before explicit named-human approval +(`../../debug/risc-v-gsp-rm-vm/docs/23-RM610-READINESS.md`). Nothing here +authorizes 610 work; the port plan for this design is M8, gated. Expected +to change and need re-derivation when approved: control inventories and +object model extensions, engine caps, the accelerator/CCS object family, +and possibly the GMMU entry format +(`../../debug/risc-v-gsp-rm-vm/docs/34-BLACKWELL-SOURCES.md`). + +### 6.3 Grace-Hopper / Grace-Blackwell / GB10 + +Targets per the matrix +([`../06-portability/hardware-software-matrix.md` §2](../06-portability/hardware-software-matrix.md)); +what matters here: + +* **aarch64 host.** Page-size/cache-attribute differences are a known risk + ([`hardware-software-matrix.md` §8](../06-portability/hardware-software-matrix.md)); + the VA↔PA discovery technique (guest direct-map arithmetic, + [`06-host-module-and-hybrid-model.md` §4.2](06-host-module-and-hybrid-model.md)) + must be re-verified. +* **Memory hierarchy.** Coherent NVLink-C2C blurs sysmem/vidmem; the + aperture model and the §3 oversubscription design must be re-derived + around C2C coherence. `[hypothesis]` +* **GMMU formats.** Entry formats/page sizes are expected to differ; + anything per-PTE (including a possible ACL-like field) must be re-derived + before being relied on. `[hypothesis]` +* **Confidential computing (CCS).** CoCo changes the trust boundary in our + favour (hardware memory encryption) and against us: the host cannot read + guest memory content, which breaks the content-scan discovery techniques + in the current memory model ([`06-host-module-and-hybrid-model.md` + §4.4](06-host-module-and-hybrid-model.md)). The `encrypted` PTE bit + exists (proven format); semantics are unstudied. `[hypothesis]` +* **MIG** on datacenter parts: an alternative hardware partitioning + substrate the policy layer can use where available + ([`09-reference/glossary-and-faq.md`](../09-reference/glossary-and-faq.md), + "What about MIG?"). `[design]` + +### 6.4 Re-derivation checklist (per new device/version) + +Capture page tables and decode them; confirm the encoding and write down +explicitly whether any owner/ACL-like field exists. Confirm the directory +is still per-context and the controls still carry the VA space and channel. +Confirm invalidate semantics with a controlled experiment (no-op until +proven; M5/M7). Re-run the owner-keying review against the new control/RPC +inventory, then the two-guest gate and the §5 walkthrough, attaching +sessions and M1–M6-style tables per the portability discipline +(`../../debug/risc-v-gsp-rm-vm/docs/06-ACCURACY.md`). + +--- + +## 7. Current and future challenges, tooling gaps, roadmap + +### 7.1 Current challenges + +1. **The service-state split is the immediate blocker.** `gsp_sema_service.c` + is global-boot-state code (single `svc`, global counters, value-keyed + tables); splitting it per mdev is review-heavy, with latent assumptions + like the ISSUE-0002 root cause. +2. **GR execution is upstream of every engine-isolation claim.** Until guest + compute executes on real engines (`../04-implementation/03-execution-frontier.md`), + tenant engine isolation can only be validated on the emulation plane. +3. **No per-tenant fault injection.** The backend's `gsp_phys_fault_inject` + (`06-host-module-and-hybrid-model.md` §6) is tied to one session; there is + no "inject as tenant B, prove A unaffected". +4. **No GMMU permission-probe methodology.** The `read_only` bit is a proven + format field but unexercised, and we have no owner/ACL search protocol + beyond decode-and-eyeball. +5. **No two-guest harness.** Tools assume one VM/one mdev + (`../../scripts/vgpu_vm.py`); the 1.0 gate needs a two-VM driver. +6. **No side-channel measurement** — no methodology, no data; explicitly + deferred to measurement by + [`../05-operations/01-performance-correctness-security.md` §4](../05-operations/01-performance-correctness-security.md). +7. **Real VRAM backing is not implemented** (`06-host-module-and-hybrid-model.md` + §8): today isolation-by-address is over emulated RAM; real DMA, real page + tables and shared scratch pools arrive with the backend + (`11-physical-backend-guest-wiring.md` §P6b). +8. **vLLM integration cost.** The extension touches allocator/block + internals; budget a pinned fork plus a thin plugin, not an upstream bet. + +### 7.2 What our tools do not address today + +`docs/NN` below = `../../debug/risc-v-gsp-rm-vm/docs/NN-*.md`. + +| Existing tool (public) | Useful for | Gap | +|---|---|---| +| GMMU PTE decoder (`../../debug/risc-v-gsp-rm-vm/docs/43`) | decode captured entries; field catalogue | decode-only; no permission semantics, no walker dereference | +| GMMU oracle workflow (`docs/44`) | compare simulated vs observed register/value traces | no enforcement experiment; oracle capture is supervised-human | +| Validation predicate probe (`docs/47`) / surgical rehost (`docs/46`) | run one firmware validation branch offline with crafted state | firmware-side; not a GMMU permission probe (candidate substrate, needs a design) | +| Control discovery (`docs/19`) / reach (`docs/19`) | hidden control IDs; symbol reachability | simulator surface only; not real-RM | +| Host debug parameters (`06-host-module-and-hybrid-model.md` §6) | inspect/poke memory, channels, semaphores | single-device oriented; scans are device-selected by first-open | +| Two-guest harness / tenant fault injector / side-channel harness | — | **none of the three exists** | + +### 7.3 Roadmap + +Each stage has an observable acceptance criterion; no stage advances on +intention. **[1.0]** = baseline programme; **[research]** = post-1.0, +dual-gated, duplicate-not-replace, never required by the baseline. + +| Stage | Scope | Acceptance (measurable) | Class | +|---|---|---|---| +| **M1** | Owner-key every vEP table; split `svc` into per-mdev contexts (or keyed tables); per-mdev pump/service; per-client reset | two concurrent mdevs both get service (no first-open starvation); crafted handle/VA collision test fails closed; ISSUE-0002 sequential acceptance stays green; one-guest regression suite green | [1.0] | +| **M2** | Two-guest gate harness + the 2G-01…2G-09 matrix (§2.9) | all tests pass 3× consecutive with evidence; ladder rung 5 "isolation" checklist partially met (fairness still open) | [1.0] | +| **M3** | Per-mdev real GSP clients; per-tenant RPC accounting and bounded rates; head-of-line measurements | two guests' forwarded CE work both completes; p99 cross-tenant delay measured and within the published bound; no cross-tenant completion (canaries) | [1.0, after GR/backend] | +| **M4** | Independent VRAM sizes end to end + sparse backing v1 + capacity admission | two mdevs with different types; sum(declared) > capacity with refusal-only admission; per-mdev accounting counters; no leak under 20 start/stop cycles | [1.0-adjacent] | +| **M5** | Real invalidate path + eviction/reclaim for KV blocks; residency invariants I1–I6 tested | controlled page-out/page-in cycle with hardware work verified byte-exact; no PTE outlives its block; hold timeouts bounded | [1.0-adjacent, needs backend] | +| **M6** | Design A prototype: extended vLLM + `oiv-agentd` + mailbox; VA-subtree policy in the vEP | cross-tenant VA access faults (T2 block from T1 channel) and is attributed to T1; differential test shows no foreign block readable/writable; overhead vs unextended vLLM measured and published | [research] | +| **M7** | GMMU permission/predicate probe: supervised experiment for `read_only` (and any ACL-like field, per generation) | a controlled write through an RO leaf behaves as documented (or the bit is declared unusable); result recorded with sessions and the fidelity label | [research] | +| **M8** | Design B (new RM controls + guest KMD extension) and/or CCS/610 port, only when approved | controls round-trip with enforcement at map and submit; fault attribution per agent; 610/CCS work follows its own gate (`docs/23`) | [research, gated] | + +Kill criteria (so research can end): M6 dies if tagging overhead exceeds +the project's sync-heavy factor without a security gain over M1–M5 (which +deliver VM/tenant separation with stock drivers); M7 dies on a family with +no RO-equivalent and no owner field if the capture shows no enforcement +(per-PTE protection impossible; the design then rests on topology, which +M6 must state); M8 dies if M6 meets the same guarantee within the +baseline's envelope and no CCS requirement materialises. + +### 7.4 Baseline discipline (mandatory) + +Per `AGENTS.md` index entry 11 and +`../10-post-1.0/experimental-host-acceleration.md`, **post-1.0 goals are +not design inputs**: nothing in sections 4.5–4.8, 5.3's research stops, or +stages M6–M8 may shape baseline code (no "just in case" fields, hooks or +parameters). Research stages are dual-gated (compile-time + module-load +flags), duplicate rather than replace, and are never required by the +baseline build or 1.0 acceptance. The 1.0 programme owns M1–M5 only; M6+ +starts after 1.0 goals are met, on an explicit human decision. + +--- + +## 8. Cross-links + +* [`13-vexecution-plane.md`](13-vexecution-plane.md) — vEP vocabulary and + the execution plane this document polices (written in parallel). +* [`05-gmmu-amap-hal-gr.md`](05-gmmu-amap-hal-gr.md) — GMMU, page tables, + channels, scheduling; the hardware background for §4. +* [`08-physical-gpu-integration.md`](08-physical-gpu-integration.md) — + backend ownership, isolation target (§4.8), open questions; + [`11-physical-backend-guest-wiring.md`](11-physical-backend-guest-wiring.md) — + the guest-wiring seams (P6a/P6b/P6c) and risk guards this design needs. +* [`10-interaction-and-scheduling.md`](10-interaction-and-scheduling.md) — + concurrency, ownership rules, multi-tenant scheduling model (§7); + [`06-host-module-and-hybrid-model.md`](06-host-module-and-hybrid-model.md) — + memory model, VA↔PA discovery, debug parameters, exactly-once rules. +* [`../05-operations/01-performance-correctness-security.md`](../05-operations/01-performance-correctness-security.md) — + evaluation ladder (rung 5), threat model, measurement discipline; + [`../06-portability/hardware-software-matrix.md`](../06-portability/hardware-software-matrix.md) — + support ladder, new-device/version procedures, guest rule; + [`../01-orientation/01-purpose-and-scope.md` §5](../01-orientation/01-purpose-and-scope.md) — + multi-tenancy model. +* `../../debug/issue/issue-number-1.md`, `../../debug/issue/issue-number-2.md` — the + CUDA reproducibility and per-client state issues M1 resolves; + `../../debug/risc-v-gsp-rm-vm/docs/{43-GMMU-PTE-DECODER,44-GMMU-ORACLE-AND-WORKFLOW,23-RM610-READINESS}.md` — + tooling and gates cited above. +* Local analysis: `LOCAL-MULTI-TENANT-GMMU.md` (adjacent; part of the + local analysis) — provenance behind §4.2 and the unredacted design + reasoning. diff --git a/wiki/GMMU-Multi-Tenant-Protection.md b/wiki/GMMU-Multi-Tenant-Protection.md new file mode 100644 index 0000000..83b00ba --- /dev/null +++ b/wiki/GMMU-Multi-Tenant-Protection.md @@ -0,0 +1,110 @@ +# GMMU Multi-Tenant Protection + +> **Current — companion summary.** This page condenses +> [`../docs/03-architecture/14-multi-tenant-gmmu-protection.md`](../docs/03-architecture/14-multi-tenant-gmmu-protection.md) +> (the canonical document: problem statement, full design, RM struct sketches, +> walked example, portability, M1–M8 roadmap). Read the canonical document for +> anything you intend to build against; this page is the quick map. + +## Overview + +Open-IOV must isolate **multiple mdevs (VMs)** and, for multi-tenant +inference, **multiple tenants inside one guest** at the GMMU. The design has +two halves: + +1. **Service-state ownership (baseline 1.0).** The work/completion service + (`kernel/gsp_sema_service.c`) is one global context (`static struct + sema_svc svc`, `:676`), the pump services the first open mdev + (`:13296-13308`), and channels/VA spaces/translations are keyed by + guest-chosen values alone (`:847`, `:988`, `:113-130`). Two guests can + pick the same handles and VAs (live: channel `0xcaf000d4`; + `../debug/issue/issue-number-2.md`), so owner-keying is a prerequisite + for any GMMU policy. +2. **GMMU policy for paged attention (research).** A tenant/agent-aware KV + cache protection design across vLLM, a guest utility, RM controls, the + vEP, and page tables — post-1.0, dual-gated; never a baseline dependency. + +## Isolation layers + +| Layer | Mechanism | Status | +|---|---|---| +| L0 platform | IOMMU/SMMU per-VM DMA, KVM memory/vCPU boundary | live | +| L1 transport | VFIO mdev per tenant, config/BAR/IRQ mediation | live | +| L2 vEP state | per-mdev emulation context + (needed) per-mdev work service | live / design | +| L3 GMMU | per-context page directory (VA space) | live (guest-owned) / design (host-owned) | +| L4 arbitration | channel ownership, TSG/runlist policy | design | +| L5 completion | owner-keyed release resolution; no cross-tenant apply | wip | +| L6 fault | fault attributed to the owning channel/tenant only | wip | +| L7 application | per-tenant/per-agent VA pooling inside a guest | research | + +## Hardware primitives: proven vs hypothesis + +| Primitive | Status | +|---|---| +| 4-level tree, 49-bit VA, 4K/64K/2M pages, dual PDE0 | **proven** (`docs/43-GMMU-PTE-DECODER`; `kernel/gsp_sema_service.c:1058-1072`) | +| VER2 PTE fields incl. `read_only` (6), `privilege` (5), `encrypted` (4), `kind` | **proven format** (`docs/43` §3) | +| Separate page directory per VA space (`SET_PAGE_DIRECTORY` with `hVASpace`/`chId`) | **proven** (`kernel/gsp_rm_emulation.c:3310-3314`) | +| No tenant/ACL/PASID field in the VER2 PTE/PDE | **proven absence** in the field catalogue | +| RO-leaf write enforcement | **hypothesis** (needs a supervised probe; M7) | +| Encryption-domain (CCS) as a tenant boundary | **hypothesis** | + +## The design in brief + +* **Design A (stock-compatible).** Extended vLLM reserves a VA range per + agent; a new guest utility (`oiv-agentd`) publishes tag records to a + device-memory **tag mailbox** the vEP can read (emulation mode: guest + vidmem is host RAM, `kernel/bar_init.c:68-77`). The vEP enforces + owner↔VA-subtree match at map time and at submit time. No guest KMD + change. Research (M6). +* **Design B (explicit controls).** New vGPU-private RM controls — + `TENANT_BIND` (0x90f10120), `TAG_RANGE` (…21), `UNTAG_RANGE` (…22), + `TENANT_SHARE` (…23), `AGENT_CHANNEL_BIND` (…24), `TAG_QUERY` (…25) — + plus struct sketches in the canonical doc §4.5. Requires a guest KMD + extension: post-1.0, gated (M8). +* **Granularities.** Per-KV-block tagging (finest; software+PTE policy, + sharing needs RO enforcement); per-tenant separate page directories + (the only proven hardware lever); per-agent separate channels (fault/QoS + containment; optional per-agent sub-directories). Degradation ladder and + per-granularity cost/failure analysis in canonical §4.7. + +## Walked example (two tenants, paged_attention_v2) + +Two agents A1/A2 each own a VA pool and channels. A malicious T1 kernel +naming an A2 VA hits a missing PTE (GMMU fault) and/or is refused by vEP +submit-time policy; the fault is attributed to A1 only (T2 untouched); a +periodic owner-vs-PTE audit with canaries catches escapes. If only +per-tenant separation exists, the deployable contract is **one agent per +tenant, one tenant per context** (canonical §5.4). + +## Key code anchors (master `66388560`) + +| What | Where | +|---|---| +| per-mdev emulation context | `kernel/include/nvidia-vgpu-vfio.h:133`, `kernel/gsp_emulation.h:130` | +| global service state | `kernel/gsp_sema_service.c:676`; pump `:13296`; doorbell `:13456` | +| channel/vaspace registries | `kernel/gsp_sema_service.c:847`, `:988` | +| GMMU walk (VER2) | `kernel/gsp_sema_service.c:1174`; decode `:1093` | +| page directory record | `kernel/gsp_rm_emulation.c:3320`; reserved PDEs `:2237-2262` | +| per-mdev FB sizing | `kernel/bar_init.c:60-77`; `kernel/include/ioctl.h:39-46` | +| L2 invalidate acked | `kernel/gsp_rm_emulation.c:2519-2532` | + +## Roadmap (acceptance criteria in canonical §7.3) + +M1 per-mdev service state — M2 two-guest isolation gate — M3 per-mdev real +GSP clients + fairness — M4 independent VRAM sizes/sparse backing — M5 +invalidate + eviction/reclaim (I1–I6 invariants) — M6 Design A prototype — +M7 RO/permission probe — M8 Design B / CCS / 610 (gated). M1–M5 are the +1.0 programme; M6–M8 are post-1.0 research, dual-gated, duplicate-not- +replace, never required by the baseline. RM 610 stays behind its own human +gate (`docs/23-RM610-READINESS`). + +## Related documentation + +* [`../docs/03-architecture/14-multi-tenant-gmmu-protection.md`](../docs/03-architecture/14-multi-tenant-gmmu-protection.md) — canonical +* [`../docs/03-architecture/13-vexecution-plane.md`](../docs/03-architecture/13-vexecution-plane.md) — vEP vocabulary (parallel) +* [`../docs/03-architecture/05-gmmu-amap-hal-gr.md`](../docs/03-architecture/05-gmmu-amap-hal-gr.md) — GMMU background +* [`../docs/03-architecture/09-full-system-picture.md`](../docs/03-architecture/09-full-system-picture.md) — multi-tenancy and isolation map +* [`../docs/03-architecture/11-physical-backend-guest-wiring.md`](../docs/03-architecture/11-physical-backend-guest-wiring.md) — forwarding and page-table seams +* [`../docs/05-operations/01-performance-correctness-security.md`](../docs/05-operations/01-performance-correctness-security.md) — threat model, rung 5 +* [`../debug/issue/issue-number-2.md`](../debug/issue/issue-number-2.md) — per-client state wedge +* [`../debug/risc-v-gsp-rm-vm/docs/43-GMMU-PTE-DECODER.md`](../debug/risc-v-gsp-rm-vm/docs/43-GMMU-PTE-DECODER.md) — GMMU field catalogue -- cgit v1.2.3