diff options
| -rw-r--r-- | DUAL-REPORT.md | 360 |
1 files changed, 360 insertions, 0 deletions
diff --git a/DUAL-REPORT.md b/DUAL-REPORT.md new file mode 100644 index 0000000..334a772 --- /dev/null +++ b/DUAL-REPORT.md @@ -0,0 +1,360 @@ +# DUAL-REPORT.md — Stage B+C (minimal) per-mdev separation for dual-guest CUDA + +**Date:** 2026-10-15 +**Worktree:** `/home/rmcore/Git/integdev_gpu_drv/open-iov-dual` (branch `dual`, base `ecfb1278`) +**Commits:** `81c11ca0` (stage B+C separation), `5769d5c4` (CUDA-bridge owner gating) +**Build:** `cd kernel && make -j8` — clean success, `vgsp.ko` linked. No module +was loaded, no device touched, no guest run (offline implementation only). + +Implementation of stages B+C (minimal) of +`analysis-2026-10-15/AGENT-dual-guest-inventory.md` §3.1/§3.2 (items G1–G8), +against the user scope: G1, G2, G3, G5-minimal, G6, G7, G8(a). +Explicitly **not** implemented: G4 remainder (per-guest learned tables), +G10 per-mdev debugfs views, parallel forwarding, boot_stage split. + +--- + +## 1. Per-item status table + +| # | Item | Status | Change site (file:line @ `5769d5c4`) | Verified by | +|---|------|--------|--------------------------------------|-------------| +| G1 | Sema-service thread services ALL open mdevs each pass | DONE | `kernel/gsp_sema_service.c:16034` (`svc_thread`): collects every open mdev into `vgpus[]` (:16060–:16075) and runs the pass body per guest; `svc_handle_map_add()` drains once per pass; the scans/hint loop is per guest (:16174–:16226). Per-guest rebuild freshness (`sess->last_build`, :16105–:16119). | build only (`make -j8`) | +| G2 | Channel table keyed by `(vgpu, h_object)`; per-guest rank; `vgpu` field in `svc_channel` | DONE | struct field `kernel/gsp_sema_service.c:165`; match in `channel_add` :1154 + `same` :1168; rank loops :1204–:1221; `channel_del` :1328; pump snapshot :13653; pump writeback :15201; counter paths :5198/:5271/:5360/:5551/:5600/:5626/:5686; `svc_pb_discover` :7821; `svc_gr_content_discover` :9103; `svc_gr_ring_autopin` :9445; chan dump :15516. Registration carries the owner: `kernel/gsp_rm_emulation.c:2360`; free passes it: :3248; desc field `kernel/gsp_sema_service.h:30`. | build only | +| G3 | Per-vgpu session active/flush flags; departing guest never flushes a live guest | DONE (staged semantics — see §3.1) | session records + helpers `kernel/gsp_sema_service.c:468–:520` (struct :473, `svc_guest_get` :489); `channel_add` marks active :1242; `channel_del` deactivates per guest and requests the flush only when **no** guest has a live channel :1356–:1378; `gsp_sema_service_vgpu_cleanup` :16364 (session end on mdev remove). | build only | +| G4 (remainder) | Per-guest learned tables (map/pin/vas/pending/…) | NOT IN SCOPE (documented risk §5.1) | — | — | +| G5 (minimal) | Forward request/hold carry the owning vgpu; single global in-flight kept | DONE | req already carried `vgpu` (verified, set at :12972 GR `svc_gr_req_fill`, :13304 CE); hold abort keyed `(vgpu, handle)` :11091; dead-owner guards on both resolution paths :11928 (`svc_vgpu_open`), :11956 (`gone_finish`), :12019 (`stop_flush`); operand translation through the request's vgpu :10922. Follow-on: CUDA-bridge fallback queue now carries its owner (`kernel/gsp_cuda_bridge.c:143`, :624, :985, :1353). | build only | +| G6 | Per-mdev guest-RAM backing {file, path, ranges} + per-mdev selector | DONE | records `kernel/gsp_phys_gpu.c:1264–:1373`; `gsp_phys_guest_ram_configure_vgpu` :1494; `gsp_phys_guest_ram_vgpu_clear` :1586; clear-all :1564; active :1604; state (:1646, legacy format byte-identical); debugfs writer `kernel/gsp_phys_booter.c:20487` (4-token legacy or 5-token `<uuid>` selector); selector resolve :20542 via `gsp_phys_open_mdev_by_name` (`gsp_phys_gpu.c:1356`). | build only | +| G7 | `gpa_to_hpa` takes the requesting vgpu; all call sites plumbed | DONE | `gsp_phys_gpa_to_hpa_vgpu` `kernel/gsp_phys_gpu.c:1737`; wrapper `gsp_phys_gpa_to_hpa` :1827. Call sites: `gsp_sema_service.c:10922` (forwarding operands — passes the request's vgpu); `gsp_phys_booter.c:9738`, :19125, :19212 (single-guest guest-GPA pin — explicit `NULL` = first open mdev, see §3.2); no others remain (`grep`). | build only | +| G8(a) | Guest-GPA pin explicitly single-guest; refusal + log | DONE | `kernel/gsp_phys_booter.c:20430–:20436`: pin refused `-EBUSY` with a log when `gsp_phys_open_mdev_count() > 1` (`gsp_phys_gpu.c:1333`); existing different-GPA refusal kept. Boot-ladder trigger call sites documented as first-open. | build only | +| G10 | Per-mdev debugfs views | NOT IN SCOPE | — | — | + +Diff stats (`git diff --stat ecfb1278..HEAD`): + +``` + kernel/gsp_cuda_bridge.c | 92 ++++++- + kernel/gsp_phys_booter.c | 115 +++++++-- + kernel/gsp_phys_gpu.c | 432 ++++++++++++++++++++++++++----- + kernel/gsp_phys_gpu.h | 35 ++- + kernel/gsp_rm_emulation.c | 3 +- + kernel/gsp_sema_service.c | 644 +++++++++++++++++++++++++++++++++----------- + kernel/gsp_sema_service.h | 20 +- + kernel/main.c | 10 + + 8 files changed, 1098 insertions(+), 253 deletions(-) +``` + +### 1.1 One-mdev equivalence argument (explicit fast paths) + +With exactly ONE open mdev every changed path degenerates to the old one: + +1. **Thread (G1):** `vgpus[]` has one entry; the per-guest loop body is the + historical pass in the historical order (probe → walk_probe → chan_dump → + scrub → rebuild → pump → bridge → pending → sweep, then the scan loop). + `svc_handle_map_add()` runs once per pass (`gsp_sema_service.c:16085`), + exactly as before. +2. **Channels (G2):** `vgpu` is constant, so `(vgpu, h_object)` matches the + same slots as `h_object` alone; rank loops count the same set; the pump + snapshot/writeback select the same channels. +3. **Session/flush (G3):** the single guest's last-channel close empties the + global table → `svc_sess_active = 0; svc_sess_flush = 1` at the same + transition as before. +4. **Guest RAM (G6/G7):** a 4-token `gsp-phys-guest-ram` write configures the + legacy record with byte-identical log lines; `gpa_to_hpa_vgpu(vgpu…)` + resolves the same first-open mdev and the same legacy record; the + `gsp-phys-state` `guest_ram:` field renders the byte-identical + `active=1 path=… ranges=… gpa=…` text (and `active=0` when unconfigured). +5. **Forwarding (G5):** `svc_fwd_req`/`svc_fwd_hold` set and read the same + vgpu the single pump always used; the dead-owner guards never trigger while + the guest is open; the CUDA bridge's `head_owner == vgpu` is always true. +6. **Log lines relied on by the campaign** are unchanged: `sema-svc:`, + `vgsp-gr:`, `GR LAUNCH submitted`, `GUEST GR LAUNCH OK`, `REAL GR SEM OK`, + `backend start: joined the live module-owned claim (READY)`, `guest RAM + configured:` / `guest RAM released:`. Debugfs file names are unchanged + (the selector is an extra token on the same file). Parameter defaults are + untouched. + +--- + +## 2. What changed, by item + +### G6 — per-mdev guest-RAM backing (`gsp_phys_gpu.c`, `gsp_phys_booter.c`) + +* `guest_ram_file`/`guest_ram_path`/`guest_ram_ranges` globals replaced by + `guest_ram_legacy` (slot for the historical top-level write) + up to 4 + `guest_ram_mdevs[]` records (vgpu-pointer keyed), all under the existing + `guest_ram_lock`. +* New API: `gsp_phys_guest_ram_configure_vgpu()`, + `gsp_phys_guest_ram_vgpu_clear()`, `gsp_phys_open_mdev_count()`, + `gsp_phys_open_mdev_by_name()` (exports + prototypes in `gsp_phys_gpu.h`). +* debugfs `gsp-phys-guest-ram` accepts both + `"<path> <gpa_base> <off> <size>"` (legacy: first/only mdev) and + `"<mdev-uuid> <path> <gpa_base> <off> <size>"` (per-mdev). Unknown selector + → `-ENODEV` with a log; `"-"` clears every record. Token count (4 vs 5) + discriminates; paths/UUIDs contain no spaces. +* mdev removal releases the record (`main.c:1750` + `gsp_phys_guest_ram_vgpu_clear(vgpu)`); physical release clears all records + (`gsp_phys_guest_ram_clear()`). + +### G7 — vgpu-aware GPA→HPA (`gsp_phys_gpu.c` + call sites) + +* `gsp_phys_gpa_to_hpa_vgpu(vgpu, gpa, *hpa)`: + * `vgpu == NULL` → first open mdev (historical); + * named vgpu whose session is closed → `-ENODEV` (never retargeted); + * translation uses the vgpu's own record, else the legacy record when the + vgpu is the first open mdev. +* `gsp_phys_gpa_to_hpa()` kept as a thin `NULL` wrapper. +* Forwarding operands (`svc_fwd_operand_hpa`) pass the request's vgpu; the + boot-ladder guest-GPA pin paths pass `NULL` on purpose (§3.2). + +### G1 — sema-service thread (`gsp_sema_service.c`) + +* Per pass: bounded list walk collects all open mdevs (≤ `SVC_MAX_GUESTS`=8, + the driver's per-type ceiling) and runs the full per-guest body for each. +* Per-guest `svc_rebuild` freshness so the first guest's 10 s tick cannot + starve the second guest of rebuilds. +* `pumped` is an aggregate: while any guest is progressing, the pass loops + tightly and skips the heavy scans, as before. + +### G2 — channel identity (`gsp_sema_service.c`, `gsp_rm_emulation.c`) + +* `svc_channel.vgpu`; add/del match `(vgpu, h_object)`; `same` (re-register) + requires the same owner; ranks count only same-owner channels; counter, + scrubber-adjacent, opdef-drop, push-buffer discovery, GR content discovery, + GR ring autopin max-CE scan, pump snapshot and pump write-back are all + filtered by the owning vgpu. +* Scrubber channel state (`svc_scrub` → `svc_scrubs[]`) made per guest: the + authored finish/USERD words are written through the owning guest's address + space, never through whichever guest opened first. + +### G3 — sessions (`gsp_sema_service.c`) + +* `svc_guests[]` records: `active` + `last_build`, pointer-keyed, freed on + mdev cleanup. `channel_add` marks active; `channel_del` deactivates the + owner when its last channel closes and requests the global flush only when + **no guest** has a live channel (staged semantics, §3.1). +* New `gsp_sema_service_vgpu_cleanup(vgpu)` (called from + `main.c:1749`): quiesces forwarding, drops the guest's hold (pure clear — + never a resolution through a vanishing device), removes its channels, + drops its opdef release slots, frees its session and scrubber records. + +### G5 (minimal) — forwarding identity (`gsp_sema_service.c`, `gsp_cuda_bridge.c`) + +* The single-inflight request keeps ONE global slot (serialization kept) but + carries its owner: CE requests set `svc_fwd_req.vgpu` (:13304), GR requests + set it in `svc_gr_req_fill` (:12972), and the operand resolver translates + through it. +* Hold abort keyed by `(vgpu, handle)`; both hold resolution paths + (`svc_fwd_hold_gone_finish`, `svc_fwd_hold_stop_flush`) and the pump's + cross-vgpu resolution check `svc_vgpu_open()` before ever dereferencing the + stored device and drop the hold when its owner is gone. +* CUDA bridge (the GR fallback under the single-inflight slot): request and + pending records now carry the owning mdev, the owner is part of the dedup + identity, only the owner's pass marshals/finishes, and stale-owner records + are dropped. This was not itemised in the inventory, but without it the + expected dual steady state (guest B's forward refused while A's hold is + active) would marshal guest B's fallback job through guest A. + +### G8(a) — guest-GPA pin + +* Refused while more than one mdev is open, with an explicit log; the pin + remains a single-guest test aid translating through the first open mdev. + +--- + +## 3. Deviations from the inventory plan (deliberate, minimal) + +### 3.1 G3 flush is deferred, not per-guest-selective + +The inventory asks to "flush only the departing vgpu's learned state". The +learned tables (`svc.map`, `svc.pin`, `svc.ipin`, `svc.lat`, `svc.vas*`, +`svc_pool_pa`, `svc_pending`, `svc_retry_q`, `svc_pb[]`, rank bitmaps) are +still shared — that is the explicitly out-of-scope G4 remainder. With shared +tables a selective flush is impossible; the only safe minimal semantic is: +**deactivate the departing guest's session and defer the flush while any other +guest still has live channels.** One guest: identical to today (last-channel +close is the global empty transition). Consequence for T5: guest B's +close→reopen while A is live does not wipe B's stale learned entries (they +must survive for A), so B's *second* session may hit the ISSUE-0002 +contamination class for its own VAs. Closing that hole is the G4 minimum +(per-entry owner tags + per-guest lookup filter). + +### 3.2 Boot-ladder guest-GPA call sites pass `NULL` + +`gsp_phys_rmcall_pdb_build` (booter :9738) and triggers 6/7 (:19125, :19212) +have no request context; the only user of that GPA is the single-guest +test-only pin, which is now refused while >1 mdev is open (G8a). `NULL` is +the documented first-open resolution — identical behaviour for every +single-guest run, and explicitly not per-mdev. + +### 3.3 Debugfs selector: extra token, not a subdir/per-uuid file + +The hard requirement says "same debugfs file names". The recommended subdir +per UUID / `gsp-phys-guest-ram-<uuid>` file would add files; the optional +first-token UUID keeps the tree and the historical 4-token write untouched +while giving per-mdev targeting. The per-mdev form is additive: 5 tokens. + +### 3.4 Scrubber registration binds to the first open mdev + +`sema_scrubbuf_add` (debug tooling written by the guest scanner) has no +selector; it binds to the first open mdev's scrubber record (single-guest +convention, like the scanner itself) and is refused with no open mdev. + +### 3.5 CUDA bridge owner gating (follow-on commit) + +See §2/G5. Default-on (`cuda_bridge=1`); the change is inert for one guest. + +--- + +## 4. Self-check: module-global session/pump state audit + +Every read of module-global session/pump state after this change, classified: + +| # | Global state | Sites | Status | +|---|--------------|-------|--------| +| 1 | channel table `svc.chan[]` | add :1154, del :1338, ranks :1204–:1221, counters :5198/:5271/:5360/:5551/:5600/:5626/:5686, pb says :7821, gr-content :9103, gr autopin :9445, pump :13653/:15201, chan dump :15516 | **fixed** per `(vgpu, h)` | +| 2 | session flags `svc_sess_active/flush` | :468–:520, :1356–:1378, :16364 | **fixed** (per-guest records drive the global flush) | +| 3 | scrubber `svc_scrub` | :535–:580, :1272–:1295, :15973, :15912 | **fixed** per guest | +| 4 | hold `svc_fwd_hold` | :11010–:12100 (:11956/:12019 guards) | **fixed**: vgpu-keyed abort + dead-owner guards | +| 5 | request `svc_fwd_req` | :10648, :12972/:13304, :13140–:13170 | **fixed**: owner carried; single-inflight kept | +| 6 | bridge req/pend | `gsp_cuda_bridge.c` (:143, :624, :985, :1353) | **fixed**: owner carried, owner-only marshal/finish | +| 7 | opdef slots | :11185–:11500 (drop_gone :11468) | pre-existing per `(vgpu,h)`; `drop_gone` filter **fixed** | +| 8 | learned map `svc.map` / `svc.map_n` / `svc.map_hash` | lookup :3300+, rebuild :3366 | **shared (G4)** — risk §5.1 | +| 9 | pins `svc.pin[]/ipin[]/svc_ipin_alt` | :2023–:2235, flush :7176 | **shared (G4)** — risk §5.1 | +| 10 | `svc.lat[]`, `svc.vas[]`, VA shadow, `svc_hot` | :1818+, :1878+, :2011 | **shared (G4)** — risk §5.1 | +| 11 | `svc_pending[]`, `svc_retry_q[]`, `svc_pool_pa*`, `svc_cnt_der` | :7081+, :885+, :2586+, :5335 | **shared (G4)** — risk §5.1 (pending is the top cross-guest write vector) | +| 12 | rank latches `svc_ring_done`, `svc_pb_done`, `svc_gr_ring_done`, `svc_pb[]`, gr-content state | :355, :341–:347, :501–:511 | **shared latches (G4)** — re-arm is content-validated/idempotent; extra re-scans possible | +| 13 | `svc_userd_sweep_at/n` (ce_index) | :13782+ | **shared (G4)** — equal ranks across guests share sweep slots | +| 14 | per-pass capture `svc_fwd_cand`, `svc_gr_cand`, `svc_fwd_rel` | :3896+, :2587+, :7233 | serially reused by the single service thread; safe with one in-flight, listed for completeness | +| 15 | debug surfaces reading the first open mdev | `add_map` rewind :15693, `dump_vidmem` :15783, `find_vidmem` :15819, `counter_add` :15868, scrubbuf :15912 | **debug tooling, single-guest by design** — do not use in dual runs | +| 16 | `gsp_phys_gpa_to_hpa(NULL,…)` (boot-ladder pin) | booter :9738/:19125/:19212 | **by design** first-open (§3.2, G8a refuses dual) | +| 17 | last-build freshness `svc.last_build` | :3570, :16105–:16114 | **fixed**: per-guest gate; `svc.last_build` kept as the published stamp | +| 18 | bridge debugfs FIFO views (job/in/out) | `gsp_cuda_bridge.c` | one global daemon FIFO; single-guest tooling surface | + +No call site that carries a requesting/owning vgpu still matches session state +by handle alone in the paths in scope. + +--- + +## 5. Risks (ranked) + +1. **Shared learned tables (G4 remainder) — highest.** Cross-guest resolution + is still possible: a lookup for guest B's VA can hit guest A's pin/map + entry with the same VA; `svc_pending[]`/`svc_retry_q[]` entries carry no + owner, so a queued release decoded for B can be applied through A (or + vice-versa). Expected symptoms under T2/T4: wrong results, stalls, + `rel-apply` writes to the wrong guest. This is the next planned change + (G4 minimum: owner tag on pin/map/lat/pending + per-guest lookup filter). +2. **mdev-removal lifetime race.** The pump can be mid-pass for a vgpu whose + fd just closed; the vgpu struct is freed after `vfio_put_device()` in the + remove path. Pre-existing class (single-guest too), reduced by + `gsp_sema_service_vgpu_cleanup` (channels/hold/records cleared before the + free), not fully closed (the pump pass itself is not synchronised). +3. **G3 staged semantics (T5).** Guest B reopen while A is live inherits B's + previous learned entries (see §3.1). A's next run is protected (no flush + while A is live) — that is the T5 guard — but B's own reopen may hit the + ISSUE-0002 wedge until G4-minimum. +4. **Shared rank latches/pairing (`svc_pb[]`, done bitmaps, sweep arrays).** + Content validation makes re-arms idempotent, but equal ranks across guests + can pair the wrong push-buffer base records and share USERD sweep slots; + possible slowdowns/mis-pairs under concurrent CE load. +5. **Legacy guest-RAM write semantics.** With two guests, the 4-token write + configures the legacy record served by the first open mdev; a script that + writes it expecting "the new guest" will bind guest A. Use the 5-token + UUID form for the second guest. +6. **Reducer scope:** no boot_stage split, no per-mdev debugfs views, no + parallel forwarding — per plan. The single physical GR channel stays safe + under `gsp_phys_rm_lock` (inventory §4), serialized at launch granularity. + +--- + +## 6. Live-validation plan (offline work ended here) + +All of the following is for the LEAD under the supervised main-job rules +(no unattended cycles; stock guests; evidence per run). The module was NOT +loaded by this task. + +### 6.0 Build/load (from the dual worktree) + +```bash +cd ~/Git/integdev_gpu_drv/open-iov-dual/kernel && make -j8 +# note: modpost needs +# ~/Git/integdev_gpu_drv/open-iov-dual/nvidia-open-gpu-kernel-modules/kernel-open/Module.symvers +# (gitignored; copied into this worktree from the master tree — keep it present +# or the final link fails with "No such file or directory") +``` + +Load (after a clean/unloaded host; parameters per inventory §5): + +```bash +sudo insmod ~/Git/integdev_gpu_drv/open-iov-dual/kernel/vgsp.ko \ + gsp_phys_gpu=1 gsp_phys_mdev_owned=1 gsp_phys_guest_mem=1 \ + gsp_phys_work_forward=1 gsp_phys_gr_forward=1 +``` + +### 6.1 Setup — two turing mdevs, two QEMUs + +```bash +GPU=0000:01:00.0 # substitute the campaign's GPU BDF (10de:…) +T=nvidia-nvidia-vgpu-turing +UUID_A=$(uuidgen); UUID_B=$(uuidgen) +echo "$UUID_A" | sudo tee /sys/bus/pci/devices/$GPU/mdev_supported_types/$T/create +echo "$UUID_B" | sudo tee /sys/bus/pci/devices/$GPU/mdev_supported_types/$T/create +# available_instances must read 2 - 2 = 0 afterwards +``` + +Launch QEMU A and B, each bound to its own mdev +(`-device vfio-pci,sysfsdev=/sys/bus/mdev/devices/$UUID_A` resp. `$UUID_B`), +each with `memory-backend-file,share=yes` pointing at a **different** file +(e.g. `/dev/shm/vgpu-ram-a`, `/dev/shm/vgpu-ram-b`). + +Register each guest's RAM window per mdev (5-token selector form): + +```bash +echo "$UUID_A /dev/shm/vgpu-ram-a 0x100000000 0x0 0x40000000" | sudo tee /sys/kernel/debug/vgsp/gsp-phys-guest-ram +echo "$UUID_B /dev/shm/vgpu-ram-b 0x100000000 0x0 0x40000000" | sudo tee /sys/kernel/debug/vgsp/gsp-phys-guest-ram +# guest A may equally be configured by the legacy campaign scripts (4-token form); +# it serves the first open mdev. A second write of a different path to the same +# record is refused -EBUSY until that mdev is removed. +``` + +In each guest: stock 570.144 driver + `tools/cuda-ab/run/vecadd_loop` (or the +guest-side build from `tools/cuda-ab/`). + +### 6.2 Test matrix (inventory §5, T1–T5) + +| Test | Action | Pass criteria | +|------|--------|---------------| +| T1 | `nvidia-smi -L` in both guests simultaneously | no host error/stall (`dmesg -T | grep -i vgsp`) | +| T2 | start `./vecadd_loop` in A and B within 1 s | each prints `LOOP launches=100 bad=0`, exit 0 within 600 s | +| T3 | A vecadd, B started 10 s later | both green | +| T4 | `smiley` in A + `vecadd_loop` in B (CE vs GR cross) | both green | +| T5 | stop guest B → its mdev removed → recreate B (+ its RAM record) → rerun A and B | A green across B's cycle (B itself: see risk §5.3) | + +Evidence capture: per-guest client logs + host dmesg to +`backups/evidence-*/dual-guest-<run>/`; require ≥200 lines each of +`GUEST GR LAUNCH OK` and zero `fwd_gr_fault`/timeouts in the steady state. +Per-mdev attribution in the log: `guest RAM configured for mdev <uuid>`, +`channel 0x… registered` around each guest's `GR LAUNCH submitted` stream. + +Useful live checks: + +```bash +dmesg -T | grep -E "sema-svc|vgsp-gr|GUEST GR LAUNCH OK|cuda-bridge" | tail -200 +cat /sys/kernel/debug/vgsp/gsp-phys-state | grep -E "guest_ram|work_guest|backend" +sudo cat /sys/kernel/debug/vgsp/gsp-phys-state | grep guest_ram # both mdev records visible +``` + +Baseline (pre-fix) for the regression control: T2 with the old module should +show the second guest stalling or wrong results (inventory §5). + +### 6.3 Rollback + +Worktree-local: `git checkout ecfb1278 -- kernel/ && make -j8` in +`open-iov-dual`; the master worktree is untouched. + +--- + +## 7. Artifacts + +* Branch `dual` commits `81c11ca0`, `5769d5c4` (local only, not pushed). +* `kernel/vgsp.ko` built at `5769d5c4` (offline; never loaded). +* This report. No sessions/dumps generated (no device access). |
