summaryrefslogtreecommitdiffstats
path: root/docs/03-architecture/06-host-module-and-hybrid-model.md
blob: 7f670d7894bb443afd2c8d7a6d68bd504189cf7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
# The Host Module and the Hybrid Model

> `vgsp.ko` in detail: the virtual device, the emulation engine, the
> memory model, and how work will be forwarded. This is the "where the code
> lives" document.

## 1. Role and position

```
      guest VM
        │  PCIe MMIO / DMA / MSI-X
   ┌────┴──────────────────────────────────────────────┐
   │ hypervisor (KVM/QEMU or Xen)                      │
   │   VFIO mdev transport, IOMMU (per-tenant memory)  │
   └────┬──────────────────────────────────────────────┘
        │
   ┌────┴───────────────────────────────────────────────────────────────┐
   │ vgsp.ko                                                   │
   │   virtual device ── emulation core ── memory model ── work path    │
   └────┬─────────────────────────────────────┬─────────────────────────┘
        │ module-owned device + real GSP      │ (future) vendor-managed
   ┌────┴───────────────────────────────┐      │  sharing backends
   │ physical GPU and its GSP firmware, │      vGPU host driver / SR-IOV
   │ driven by this module (the host    │      (licensed / hardware-gated)
   │ driver stays unmodified, unbound)  │
   └────────────────────────────────────┘
```

The module registers itself as an **mdev parent** on the physical GPU and
exposes one device type per supported profile. Each `mdev` instance is an
independent virtual GPU with its own state, its own BAR windows, its own
interrupts and its own emulation context.

```mermaid
sequenceDiagram
  autonumber
  participant ORCH as Orchestrator
  participant SYS as Module and mdev sysfs
  participant QEMU as QEMU with vfio-pci
  participant KVM as KVM
  participant GUEST as Guest

  ORCH->>SYS: create mdev for a VM
  QEMU->>SYS: open VFIO device, mmap regions, set IRQ eventfds
  QEMU->>KVM: map guest RAM, register irqfd
  GUEST->>SYS: MMIO and DMA through the emulated device
  SYS-->>GUEST: MSI-X via eventfd to irqfd
  ORCH->>SYS: destroy mdev after the VM stops
```

The full interaction and scheduling detail is in
[`10-interaction-and-scheduling.md`](10-interaction-and-scheduling.md); the
end-to-end maps are in
[`09-full-system-picture.md`](09-full-system-picture.md).

## 2. Virtual device

### 2.1 PCI configuration space

The guest sees a plausible Turing-class GPU: vendor/device IDs, class code,
BARs, capability list (PCIe, MSI-X, power management), and the subsystem
identifiers the driver keys on. Several fields exist only because the driver
reads them for validation:

* **PCIe link capabilities** — the guest's driver derives bus width/speed and
  availability. The emulation must report them; the *hypervisor* can
  accidentally rewrite them (see the debugging doc's root-port note).
* **MSI-X table/PBA** — emulated, with per-vector masking and pending bits.
* **VF (virtual function) windows** — registers the SR-IOV-aware driver paths
  read during initialization; the emulation fills them with values consistent
  with a non-SR-IOV or lightly-SR-IOV configuration as appropriate.

### 2.2 BAR layout

| BAR | Size | Backing | Notes |
|---|---|---|---|
| BAR0 | 16 MB | host `vzalloc` shadow | register interface: boot, doorbells, VF registers, PRAMIN window, VBIOS |
| BAR1 | 256 MB | host `vzalloc` vidmem buffer | guest-visible VRAM aperture; replaced by real VRAM backing in the forwarding phase |
| BAR3 | — | instance window | instance/PRAMIN behaviour |

MMIO reads and writes are served either by the module's VFIO region callbacks
or directly from the shadow when the hypervisor has mapped it into the guest.

### 2.3 VRAM backing

Today BAR1 is host RAM. This is deliberate: correctness first, performance and
real VRAM semantics later. Consequences and their mitigations:

* Guest reads/writes of "VRAM" are cheap and coherent. ✅
* The real GPU cannot read it. The forwarding phase must either migrate these
  objects to real VRAM or expose a DMA-mapped system-memory region to the GPU
  (see §5).

## 3. Emulation core

### 3.1 GSP emulation thread

A kernel thread drives the device's "firmware" behaviour:

1. Watches BAR0 doorbells (and VF registers) for guest actions.
2. Reads message-queue elements from guest memory.
3. Dispatches RPCs to handlers.
4. Writes replies into the status queue.
5. Injects MSI-X interrupts to the guest.

Guest memory is accessed through the hypervisor's DMA interface
(`vfio_dma_rw()`), which requires calls to originate from a kernel thread
context — a fact the debug tooling codifies.

### 3.2 RPC handling

Handlers are organized by function and, inside controls, by control ID. The
implementation philosophy:

* **Correct shape first.** A reply must have the exact structure the driver
  expects, or the driver walks off into uninitialized memory.
* **Plausible values second.** Values must be self-consistent (e.g. FB size,
  BAR sizes, engine counts, page sizes) or later code paths compute nonsense.
* **Never invent input.** Queries that pass uninitialized *input* must not read
  it; but in/out parameter blocks must be echoed back, because the driver
  copies the reply's class parameters over its own buffer at the size the
  kernel driver declares. Zeroing an in/out block is destructive: the
  `GSP_RM_ALLOC` reply once zeroed a caller's saved frame pointer this way and
  faulted `libcuda` (fixed by echoing the request parameters).

The handlers currently cover: the boot/static-info exchanges, the device/FB/
BUS/INTR control tables, CE capability and PCE-mask queries, GMMU static info,
vaspace reserved-PDE contracts, channel/CE object allocation, and the general
"unknown control/function returns success with zeroed output" fallback. The
same fallback mattered for engine inventory: `libcuda` filters the engine
list against the class list, and an incomplete inventory once left a record
zeroed and blocked context creation (`cuCtxCreate` returned `999`); the
emulated inventory is now complete (the async copy engine included). See
[`../../userspace/cuda/READ-ME-FIRST.md`](../../userspace/cuda/READ-ME-FIRST.md)
and
[`../../userspace/cuda/docs/05-570.144-decompilation.md`](../../userspace/cuda/docs/05-570.144-decompilation.md)
section 3.7 for the analysis.

**Current work-execution bridge.** The module also decodes push buffers as
method streams and executes the work it understands in software - memsets,
copies and semaphore releases - and completes the corresponding semaphores.
That is a correctness bridge for memory operations, not the data plane: no
CUDA kernel is forwarded to the physical GPU yet (default-off CE copy
forwarding, engine-authored release relay and fault relay are implemented; see
[`11-physical-backend-guest-wiring.md`](11-physical-backend-guest-wiring.md));
the chosen execution backend
(a module-owned GPU running its real GSP firmware as the resource-manager
server) is in
[`08-physical-gpu-integration.md`](08-physical-gpu-integration.md), with the
trade-offs in
[`../04-implementation/02-practical-challenges-and-adversarial-interop.md`](../04-implementation/02-practical-challenges-and-adversarial-interop.md)
section 3.

**Subsystem status note.** Some components are compiled but not currently
wired into the live path: the physical-GSP load/boot path is default-off and
runs only behind the supervised claim gates, and the runtime firmware
analyzer, version tables, hardware abstraction tables and dynamic RPC
dispatcher are not yet called by the live flow. Treat their descriptions here
as intended design, not active code.

### 3.3 Event channels and interrupts

Guest-visible interrupts (MSI-X) are delivered via `eventfd` to the hypervisor.
The emulation tracks vector masking and pending state so that an interrupt
raised while masked is delivered when unmasked, not lost.

## 4. Memory model (the heart of the project)

The guest's GPU work refers to *GPU virtual addresses*. To act on that work the
host module must translate them to memory it can touch.

### 4.1 What the client gives us

* **Channel memory descriptors** (`NV_MEMORY_DESC_PARAMS`) with *physical*
  addresses: instance memory (VRAM offset), method buffer, userd, ramfc,
  error notifiers. These are precise and trustworthy.
* **The page-table contract** for server-owned VA ranges
  (`COPY_SERVER_RESERVED_PDES`): page-table level physical addresses, page
  shifts, VA range — but with *empty* leaf levels, because the server owns the
  leaves.
* **No mapping RPCs for the client's own system-memory objects.** The client
  allocates sysmem locally and never tells the server its physical pages.

### 4.2 What we derive instead

Because the client does not hand us its sysmem mappings, the module derives
them from the guest's own in-memory bookkeeping:

1. **VA↔PA pairs live in guest memory.** UVM and the RM keep structures that
   record, for every mapped object, both the GPU VA and the kernel CPU VA.
   Example (verified): `uvm_rm_mem_t.vas[CPU]` at +0x30 and `vas[GPU]` at
   +0x38; channel tracking semaphores at `uvm_channel_t+0x80/0x88`.
2. **CPU VAs are direct-map addresses**, so
   `PA = cpu_va - page_offset_base`. The direct-map base can be recovered from
   guest memory itself (structures that store their own address), from
   `/proc/kallsyms` during development, or from a debug poke session.
3. Therefore a translation table can be built **without any cooperation from
   the guest driver** — a crucial property for adversarial interoperability,
   and a general technique applicable to future versions.

### 4.3 Acting on translations

The module can read and write guest physical memory (via the hypervisor's DMA
interface). That is enough to:

* complete semaphore releases (write the payload to the mapped slot),
* inspect push buffers and page tables,
* and later, create page tables for server-owned ranges.

Releases are recognised in the decoded method stream in both of the encodings
the driver uses: the copy-engine `SET_SEMAPHORE_A/B` + `LAUNCH_DMA` group and
the TURING+ `SET_REPORT_SEMAPHORE_A..D` group
(`{offset_upper, offset_lower, payload, operation}`, operation 0 = release),
which is how the host class ends a push. The learned translation cache is
page-granular, so all semaphore slots of one page resolve from one entry.

One class of pages is not derivable from the structures above: client
mappings created by `mmap()` on the control device, where the kernel driver
maps sysmem pages into the process and the *same* numeric address doubles as
the GPU VA (observed live: a 2 MB shared mapping of the control device, and
push releases whose semaphore address equals the mapped client VA). Those
pages have no leaf PTEs in the GPU page tables (the client delegates them),
and the kernel-side bookkeeping records physical pages without a GPU VA.
Until a durable join is implemented, a session can feed verified pairs with
the `sema_map_add` debug parameter.

## 5. The forwarding path (design)

```
guest push submitted
   → module detects submission (doorbell/GPPUT or push-buffer scan)
   → decode the push (method stream)
   → for each command decide:
        (a) EMULATE: pure state/notification (semaphore release, membar, fence)
        (b) FORWARD: real engine work (GR grid launch, CE copy, memop)
   → for (b): submit equivalent work on a real channel bound to the physical GPU
   → relay completions (semaphore/fault/interrupt) back into guest memory
```

Backend selection, including the interfaces that actually exist and why the
distribution host driver cannot be called, is analyzed in
[`08-physical-gpu-integration.md`](08-physical-gpu-integration.md). In short:
proxying through the host driver has no callable entry point from kernel
context, so the chosen mechanism is for the module to own the device and run
its real GSP firmware as the resource-manager server, while the emulation path
remains the control plane. Software execution of decoded memory/state
operations stays a development bridge, not the data plane.

## 6. Debug tooling in the module

The module exposes debug parameters (writes to
`/sys/module/vgsp/parameters/*`) that make the guest's memory and the
emulated device inspectable without rebuilding:

| Parameter | Purpose |
|---|---|
| `scan_guest_ram=<v>` | find a 64-bit value in guest RAM (0–8 GB), report GPAs |
| `scan_vidmem=<v>` | same for the emulated VRAM shadow |
| `scan_seq=<v1,v2,...>` | find a 32-bit *sequence* (e.g. a method stream) |
| `scan_pt=<pa>` | find page-table entries pointing at a physical address |
| `scan_masked=<v>,<mask>` | find `(x & mask) == v` (e.g. a page-base VA) |
| `dump_vidmem=<off>,<len>` / `summary` | dump/scan the VRAM shadow |
| `poke_guest=<gpa>,<v>` | write a 32-bit value to guest physical memory |
| `poke_fill=<gpa>,<v>,<n>` | fill a range |
| `fill_vidmem=<v>` | fill the VRAM shadow |
| `sema_probe=<gpu_va>[,count]` | probe a semaphore VA and nearby state in guest memory |
| `sema_map_add=<va>,<pa>` | teach the service one verified `{GPU VA -> guest PA}` pair |
| `dump_guest=<gpa>,<len>` | dump guest physical memory (length capped) |

The physical-backend path adds a second, default-off parameter set:
`gsp_phys_gpu`, `gsp_phys_hazard_ack`, `gsp_phys_fw`, `gsp_phys_mdev_owned`,
`gsp_phys_static_live`, `gsp_phys_ctrl_forward`, `gsp_phys_work_forward`,
`gsp_phys_guest_mem`, `gsp_phys_work_defer_release`,
`gsp_phys_work_hw_primary`, `gsp_phys_relay`, `gsp_phys_fault_relay`,
`gsp_phys_fault_inject`, `gsp_phys_rmcall_engine`,
`gsp_phys_rmcall_gr_promote`, `gsp_phys_repower`, `gsp_phys_layout`,
`gsp_phys_boot`, `gsp_phys_boot_dryrun`, `gsp_phys_nocat` and the
`gsp_phys_fwsec*` stage controls. Their semantics, the physical-backend
debugfs inventory (`gsp-phys-state`, `gsp-phys-rm-probe`, `gsp-phys-rm-call`,
`gsp-phys-unload-rpc`, `gsp-phys-guest-ram`/`gsp-phys-guest-gpa`,
`gsp-phys-log-dump`, `gsp-phys-status-drain`) and the supervised runbooks are
maintained in the engineering-operations document
([`../eng-ops.md`](../eng-ops.md), Part II); this document does not duplicate
those tables.

These are development-grade but production-useful: they are how the memory
model was reverse-engineered, and they remain the fastest way to diagnose a
mismatch. Their workflows are documented in
[`../05-operations/02-debugging-and-maintenance-workflows.md`](../05-operations/02-debugging-and-maintenance-workflows.md).

## 7. Build and symbol contracts

* `vgsp.ko` exports the `nvidia_vgpu_vfio_*` interface
  (`nvidia_vgpu_vfio_probe/remove`, ops get/set, mdev parent registration) for
  hosts that want to pair our device with the NVIDIA OpenRM host modules.
* When NVIDIA's OpenRM modules are built from source, they must be built with
  `KBUILD_EXTRA_SYMBOLS=$(pwd)/kernel/Module.symvers`, and **after** the
  Open-IOV module, or symbols will not resolve.
* On typical single-GPU developer systems the distribution's NVIDIA driver
  manages the physical GPU and the Open-IOV module is used standalone; the
  OpenRM submodule path is only needed for the native-vGPU-management
  workflows (RM callbacks) and is unnecessary for the QEMU/emulated path.

**Build and load order on the OpenRM path:**

```bash
# 1. Kernel module first - generates kernel/Module.symvers.
make kernel                      # or: ./scripts/stack_manager.sh build-kernel

# 2. OpenRM modules with our symbols resolved.
./scripts/build_nvidia_openrm.sh # wraps KBUILD_EXTRA_SYMBOLS=<repo>/kernel/Module.symvers
grep -i vgpu kernel/Module.symvers                       # symbols exported
modinfo .../kernel-open/nvidia.ko | grep -i vgpu         # symbols consumed

# 3. Load order: Open-IOV first (exports and registers the mdev parent),
#    then the optional OpenRM modules, then firmware.
```

If a distribution driver already owns the physical GPU, skip step 2 and the
OpenRM part of step 3: the emulated QEMU path runs with the distribution
driver. Common failures on this path: `Unknown symbol nvidia_vgpu_vfio_*`
(`KBUILD_EXTRA_SYMBOLS` missing or wrong) and symbols present in
`Module.symvers` but absent from `nvidia.ko` (stale build cache — `make clean`
in `kernel-open` first).

## 8. What is implemented vs. designed

| Area | Status |
|---|---|
| mdev/VFIO device, config space, BARs, MSI-X | implemented |
| BAR0 register state machine, boot handshake | implemented |
| Message queues + RPC + all init controls observed from stock 570.144 | implemented |
| Guest sysmem access + VA↔PA discovery | implemented (debug-grade, to be productized) |
| Push decoding + semaphore completion | demonstrated live; being productized |
| Guest-RAM physical path | implemented default-off: guest RAM is file-backed shared memory and GPA→HPA goes through its page cache, verified live on the CE |
| Page-table population for server-owned ranges | implemented default-off: forwarded CE copies map resolved host pages through module-built GMMU scratch page tables; real guest page tables remain |
| GPU work forwarding (GR/CE execution) | CE forwarding, engine-authored release relay and fault relay implemented default-off and verified live; GR execution open |
| Execution backend (module-owned GPU + direct real-GSP session) | live: boot, init-done, full static-config reply and clean releases proven; RM client allocates objects/channels and has executed real CE copies; currently in the designed locked fail-safe after a GR context-promote wedge ([`08-physical-gpu-integration.md`](08-physical-gpu-integration.md)) |
| Real VRAM backing | designed, not implemented |
| Windows guest, Xen transport, aarch64 hosts | designed, not implemented |