diff options
Diffstat (limited to 'wiki/UVM-in-the-Guest.md')
| -rw-r--r-- | wiki/UVM-in-the-Guest.md | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/wiki/UVM-in-the-Guest.md b/wiki/UVM-in-the-Guest.md new file mode 100644 index 0000000..53ca755 --- /dev/null +++ b/wiki/UVM-in-the-Guest.md @@ -0,0 +1,168 @@ +# UVM in the Guest + +> **Scope.** What UVM (`nvidia_uvm.ko`) does inside the guest, why it is the +> most demanding consumer of the virtual GPU's semantics, and how it waits. +> Canonical source: [`../docs/03-architecture/04-uvm.md`](../docs/03-architecture/04-uvm.md); +> where this page and the docs tree disagree, the docs tree wins. + +Status: 2026-10-12, tree `master @ 2f066727`. Tags: `[live]` observed with the +stock guest driver; `[wip]` partially working, software bridge in charge; +`[gated]` implemented default-off; `[target]` planned end state. + +## 1. What UVM is + +UVM provides unified memory and GPU-driven page-fault handling. In practice it +is a **second GPU driver layered on the RM**, owning GPU address spaces and +running its own memory-management work on the GPU. + +| Component | Role | +|---|---| +| VA spaces (`uvm_va_space`) | per-process GPU address spaces | +| Channel managers | pools of GPU channels (CE, SEC2, GR as needed) for UVM's work | +| Push buffers / GPFIFO | UVM builds and submits its own pushes | +| Semaphores and trackers | UVM waits on GPU-written semaphores to track completion | +| Page trees | GPU page tables for its VA spaces | +| Fault handling | fault buffer plus notification; UVM resolves and replays/cancels faults | + +For CUDA, UVM is entered early: `cuInit` → driver init → `UVM_REGISTER_GPU`. +If UVM cannot register the GPU, CUDA does not start — which is why "UVM +advances" is the project's primary correctness metric on the way to CUDA. + +## 2. The registration path (traced end to end) + +```text +libcuda: UVM_REGISTER_GPU ioctl (0x25) + uvm_api_register_gpu -> uvm_va_space_register_gpu -> uvm_gpu_retain_by_uuid + nvUvmInterfaceRegisterGpu / GetGpuInfo; alloc_parent_gpu / add_gpu + nvUvmInterfaceDeviceCreate (RM object creation) + uvm_hal_init_gpu / get_gpu_caps (CE caps) + uvm_ats_add_gpu / uvm_gpu_init_isr (fault/interrupt plumbing) + init_channel_manager + create semaphore pool (sysmem) + create channels per CE (GPFIFO + pushbuffer + semaphores) + channel_init() -> first push -> semaphore release -> wait + uvm_page_tree_init (page tables for the VA space) +``` + +Each stage has a distinct failure signature — see +[`Driver-Lifecycle-Field-Guide.md`](Driver-Lifecycle-Field-Guide.md). + +## 3. What UVM requires from the "GPU" + +| Requirement | Detail | Status | +|---|---|---| +| GPU identity | must match the UUID from RM | `[live]` | +| CE capabilities | per-engine support bits; UVM requires `supported && !grce && sysmem` for CPU_TO_GPU / GPU_TO_CPU channels | `[live]` caps accepted; `[gated]` copies forwarded to the physical copy engine | +| Channel creation | RM channel objects with GPFIFO/userd/instance parameters | `[live]` | +| Pushes executed | first action on a new channel is an init push plus a semaphore-tracked wait | `[wip]` software bridge executes memsets, copies and releases; `[gated]` CE-class copies forwarded | +| Page trees | UVM initializes page trees with its own pushes; server-owned ranges must be populated by us | `[wip]` | +| Fault delivery | fault buffer plus notification; replay/cancel semantics | `[gated]` backend-side observation/relay; guest contract `[target]` | +| Completion semantics | tracker values must advance exactly | `[wip]` | + +**Current frontier.** Context creation and the full CUDA driver-API smoke pass +(`ALL OK`, copies matched) run end to end. CE-class copies are forwarded to the +physical copy engine (default off) with engine-authored releases and a fault +relay. For GR, a forwarded guest launch reaches the front-end but the inline +launch-descriptor install has not been observed; context validity at dispatch +is ruled out ([`Execution-Frontier.md`](Execution-Frontier.md)). The remaining +UVM work is completion translation and the guest-side fault replay/cancel +contract. + +## 4. The discovery that defines the work: how UVM waits + +UVM's tracker is the single most visible symptom of an unfaithful GPU. It +spins, and after 30 seconds it dumps its state: + +```text +uvm_tracker.c:285 Tracker entry for value 1 (sema VA 0x121000000) +uvm_channel.c:4184 pending push '' started at uvm_channel.c:2535 in channel_init() + releasing value 1 +``` + +Mechanically, UVM built a push ending in a **semaphore release** — a method +sequence (CE channel class, e.g. `TURING_DMA_COPY_A`) that sets an address and +a payload and launches a DMA operation to write the payload to memory — then +submitted it and waits for the payload to appear. On real hardware the copy +engine writes it; on our device nobody does unless the emulation does. The +push we found in guest RAM decodes as: + +```text +20010000 0000c5b5 set-object (TURING_DMA_COPY_A) +2001a000 0000c076 set-object (GP100_UVM_SW) +20038090 00000001 incrementing-method header: count 3, addr 0x240 +21000000 00000001 lower 32 bits of the semaphore VA; payload (1) +200180c0 0000000c LAUNCH_DMA with SEMAPHORE_TYPE=RELEASE_ONE_WORD +``` + +So the emulation's job for this push is: **write `payload` to the memory at +the semaphore's GPU VA**, which requires a GPU-VA → guest-PA translation. The +host does this through the learned VA→PA map: while the channel's GPFIFO +has pending entries it reads each push, decodes the method stream and runs the +work (page-table memsets, copies, semaphore releases). When a push page cannot +be resolved — or a channel consumes entries without any release landing — it +falls back to content: guest RAM is scanned for release groups and pending +payloads are applied forward-only. A release is applied only when +`payload > current`, so recycled push buffers and stale copies are harmless. +One pitfall observed live: a semaphore-page VA can flap between two physical +pages across map rebuilds; pinning the validated translation fixes the stalled +wait. + +## 5. The memory model UVM assumes + +* UVM's semaphore pool is **system memory** (`UVM_APERTURE_SYS`), one 4 KiB + page per channel manager, addressed as 4-byte slots (1024 per page). +* Each page has a **GPU VA** (chosen by the client's RM) and a **CPU VA** (a + kernel mapping). UVM polls the CPU VA; the GPU writes the GPU VA. +* Both addresses are recorded in UVM's structures in guest memory — the key + that makes translation possible without extra help from the guest. +* The CPU VA is a direct-map address, so `PA = cpu_va - page_offset_base` + (verified live: writing the payload there completes the push). +* Values are per channel and monotonic (1, 2, 3 …); UVM's own check + (`UVM_GPU_SEMAPHORE_MAX_JUMP`) detects and reports wrong values. +* The client's page tables for **server-owned ranges are empty chains**: the + client hands us the physical addresses of its page-table levels and expects + the server to own the lower levels ([`GSP-and-GSP-RM.md`](GSP-and-GSP-RM.md)). + +## 6. Page trees and faults + +`uvm_page_tree_init` initializes a VA space's page tables with pushes on UVM's +own channels (memops and page-table writes; the copy engine executes them on +real hardware). Fault handling is event-driven: UVM programs a fault buffer +(replayable and non-replayable; sizes from `INTERNAL_GMMU_GET_STATIC_INFO`, +answered by the emulation) and, on a fault, reads the entry, populates the +page table and replays or cancels the fault with a `MEM_OP` push. Reconciling +the guest's memory model with the real GPU's here is the deepest remaining work. + +## 7. How to observe UVM + +In the guest, UVM prints nothing by default: + +```bash +echo 1 | sudo tee /sys/module/nvidia_uvm/parameters/uvm_debug_prints +``` + +Messages then appear with the `nvidia-uvm:` prefix. Known-good markers, in +order: `uvm_gpu_retain_by_uuid` → channel manager created → +`uvm_page_tree_init` pushes → `UVM_REGISTER_GPU` returns and CUDA proceeds. +Failure signatures characterized live: + +| Message | Meaning | +|---|---| +| `Failed to initialize the channel manager: NV_ERR_NOT_SUPPORTED` | CE caps wrong or missing | +| `stuck waiting` plus `Tracker entry ... channel_init()` | the push's semaphore was never written | +| `Assert failed ... unexpected semaphore ... jump from 0x0 to 0x1010101` | a wrong payload was written; UVM caught it | + +## 8. Why UVM is the perfect correctness oracle + +UVM exercises in minutes what a driver test suite would test in hours: memory +allocation, page tables, channel setup, push generation, semaphore semantics, +fault handling and error recovery — all with continuous self-checks and loud +diagnostics. When UVM registers cleanly and CUDA workloads pass, the device is +behaving like a real GPU. + +## 9. Related documentation + +* [`../docs/03-architecture/04-uvm.md`](../docs/03-architecture/04-uvm.md) — canonical chapter (this page's source). +* [`Guest-Stack-and-CUDASW.md`](Guest-Stack-and-CUDASW.md), [`GSP-and-GSP-RM.md`](GSP-and-GSP-RM.md) — the guest stack and the RM objects UVM consumes. +* [`CUDA-Userspace-Driver.md`](CUDA-Userspace-Driver.md) — the userspace half of the contract; [`Execution-Plane.md`](Execution-Plane.md), [`Execution-Frontier.md`](Execution-Frontier.md) — how UVM's pushes execute, and the current wall. +* [`../docs/04-implementation/01-known-knowns-and-unknowns.md`](../docs/04-implementation/01-known-knowns-and-unknowns.md) — server-owned page tables and the fault contract. |
