summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md172
1 files changed, 172 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md b/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md
new file mode 100644
index 0000000..8d17859
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/47-VALIDATION-PREDICATE-PROBE.md
@@ -0,0 +1,172 @@
+# 47 — Validation-predicate probe (`valprobe`) — in development
+
+> **Status: in development (2026-10-06, wall-directed wave).** Documents
+> `harness/valprobe.py`: run one firmware **validation branch** of the real
+> image with crafted register/memory state (plus, where needed, one recorded
+> inline stub for a dependency outside the predicate) and record the branch
+> the firmware took. Born from the live GR wall's question class: *which
+> control/entry shapes does 570.144 GSP-RM accept or refuse, and why?*
+>
+> Development-only: executes firmware on the simulator. Never touches a GPU,
+> kernel module, mdev, guest or network. No timing/cycle claim (L0/L1).
+>
+> **See also:** `46-SURGICAL-REHOST.md` (whole-function rehost — the parent
+> tool), `44-GMMU-ORACLE-AND-WORKFLOW.md` (evidence discipline),
+> `39-RE-WORKFLOW-REVIEW.md` (time-to-value ranking), `06-ACCURACY.md`
+> (fidelity levels).
+
+## 1. Why it exists
+
+The live GR campaign submits `GPU_PROMOTE_CTX` shapes to the real GSP-RM and
+reads accept/refuse per supervised cycle. The decisive questions are
+firmware-side predicates, but a whole-function rehost (`docs/46`) early-outs
+on missing RM object state (`0x40`) before the predicate executes. This
+harness executes the predicate branch itself:
+
+* **entry**: the validation branch point (not the full handler);
+* **state**: exactly the registers/memory the branch reads, crafted in RAM;
+* **stub**: at most one `lui/addi/ret` stub for a dependency that is *not*
+ the predicate (e.g. a lock/table walk), recorded in the report;
+* **outcome**: execution stops on labelled outcome addresses; the verdict is
+ the branch taken, with the firmware's own arithmetic/indexing.
+
+## 2. Invocation
+
+```bash
+# Bundled matrix: the 570.144 PROMOTE_CTX per-buffer validation (FECS etc.)
+python3 harness/valprobe.py sweep # crafted-state, ~2 s
+python3 harness/valprobe.py sweep --boot-to B3 # boot the image first
+python3 harness/valprobe.py demo # matrix twice (determinism)
+python3 harness/valprobe.py run --spec my-spec.json # generic spec
+```
+
+A spec is `greeneye-valprobe-spec/1` with a `target`, an `outcomes` map
+(address → label), optional `boot_to`, and `variants` carrying `registers`,
+`memory` (`hex|u64|u32|u16|u8`), `stubs` (`addr` + `return`), `reads`
+(post-run) and `expect`. Report schemas: `greeneye-valprobe/1` per variant,
+`greeneye-valprobe-matrix/1` for the run (`valprobe-matrix.json`), with one
+`greeneye-session/1` per variant when recording is on.
+
+Outcome statuses: `outcome` (a labelled address was reached — the verdict),
+`return` (`ra` sentinel), `fault` (first missing state), `ecall`, `timeout`,
+`budget`. `pass` compares `outcome` to `expect`; `sweep` exits 3 on any fail.
+
+## 3. The current wall, answered offline (570.144, GR PROMOTE_CTX)
+
+Live facts this reproduces (V6 KMD-managed channel, supervised cycles):
+`PM`/`PATCH` init accepted, **FECS_EVENT init refused `0x1f`**, FECS VA-only
+accepted; engine dispatch still absent. The handoff's suggested next hardware
+experiment was "find a FECS init form V6 accepts (e.g. a sysmem attr 0x6 /
+different size / different bufferId)".
+
+The probe enters the per-buffer validation branch `0x5290dd4`
+(`FUN_ram_05290a48`) and executes the firmware's own logic. Firmware facts
+(disassembly, numeric addresses only):
+
+| # | Fact |
+|---|---|
+| 1 | `bInitialize=1`, `bufferId==9` (FECS) takes the branch at `0x5290e3c` |
+| 2 | that branch reads the **global GR-context memDesc slot**: `instance = pGpu[(idx+0x37c)]`, `table = *(u64*)(instance+0x190)`, slot index from the firmware's own `0x531b944` jump table (**FECS → 6**), and returns `NV_ERR_INVALID_ARGUMENT` (`0x1f`) **iff `slot != 0`** |
+| 3 | `physAttr`, `size`, `gpuPhysAddr`, `gpuVirtAddr` are **not read** in that branch |
+| 4 | `bInitialize=0`: `bNonmapped && !bInitialize` → `0x1f`; otherwise the entry passes |
+| 5 | init is allowed for ids 0..2, 8..11; refused for 3..7 and 12; ids > 12 refused a block earlier |
+
+So **if the server has already populated the FECS slot, no init form exists**
+— the aperture/size variants cannot change the verdict, and the only accepted
+FECS shape is `bInitialize=0` (a VA booking, matching the live VA-only
+accept). The campaign can redirect from "find a FECS init form" to
+"prevent/clear the server's FECS slot" or "accept VA-only and look at
+dispatch elsewhere" without spending a cycle on the aperture test.
+
+Matrix (15/15, deterministic; run twice with identical verdicts; 1.9 s
+crafted-state, 13 s with `--boot-to B3`):
+
+| variant | verdict |
+|---|---|
+| FECS init vidmem, slot non-NULL | `refuse-fecs-slot-exists-0x1f` |
+| FECS init vidmem, slot NULL | accept (commit path) |
+| FECS init **sysmem attr 0x6**, slot non-NULL | `refuse-fecs-slot-exists-0x1f` |
+| FECS init **sysmem attr 0x6**, slot NULL | accept |
+| FECS init size 0x1000, slot non-NULL | `refuse-fecs-slot-exists-0x1f` |
+| FECS no-init mapped, slot non-NULL | accept (skip path) |
+| FECS no-init nonmapped | `refuse-invalid-argument-0x1f` |
+| PM init / PATCH init | accept |
+| GFXP_CTRL_BLK (8) init | accept (skip path) |
+| PRIV (10) / UNR (11) init | accept (skip path) |
+| BUNDLE_CB (3) / GLOBAL_PRIV (12) init | `refuse-invalid-argument-0x1f` |
+| unknown id 13 | `refuse-invalid-argument-0x1f` |
+
+Each variant's report also carries the firmware-mapped slot index read back
+from the run (`6` for FECS) and the applied stub, so the data flow is in the
+evidence, not in prose.
+
+### 3.1 The FECS buffer is global and one-shot (live-confirmed 2026-10-06)
+
+The probe's refusal condition was then joined to a live backend run and the
+semantics are now settled:
+
+* The FECS memDesc lives in a **global GR context-buffer table shared across
+ channels** (per GPU/client, per gfid); the table's only writer is the
+ init-promote path, and a successful init leaves the slot set so that
+ **every later `bInitialize=1` FECS promote is refused `0x1f`** — independent
+ of aperture, size, address or channel.
+* A live run showed the decisive ordering: the backend's own **golden-channel
+ 9-entry atomic promote** (including `FECS_EVENT bInitialize=1`) was accepted,
+ and the per-entry FECS promote on a *different channel of the same client*
+ was refused `0x1f` **~0.1–0.3 s later**. The refusal was the shared
+ one-shot rule, not a channel deficiency.
+* The per-channel requirement for FECS is therefore only the **VA booking**
+ (`bInitialize=0`), which the same live channel already accepted. No client
+ "FECS init form" exists once any earlier promote initialized the shared
+ buffer; an atomic multi-entry set containing FECS init fails as a whole
+ after that point.
+
+This is what the probe class is for: it turns a supervised-cycle guessing loop
+into a seconds-long offline verdict plus one log join.
+
+## 4. Honesty rules
+
+* A probe is **not a boot** and **not a model**: only the seeded state exists;
+ the report lists entry, registers, memory, stubs, outcomes and limits.
+* A stub replaces an explicitly named dependency outside the predicate; the
+ predicate, its id→slot mapping call and its memory reads are the firmware's
+ own code. Prefer entering after the dependency when that is safe.
+* A fault is the **first missing state observed**, not a product defect; a
+ timeout/budget is not a verdict.
+* `--boot-to <rung>` runs the real image first so predicates that need
+ initialized globals can be probed with real state; the report records the
+ observed rung and whether the request was reached.
+* No timing/cycle claims; sessions and matrix JSON are the evidence.
+
+## 5. Tests
+
+`tests/test_valprobe.py` — stub encoding decodes as `lui/addi/jalr`, memory
+spec widths, spec shape, scratch layout bounds, a live reduced matrix with
+determinism, the firmware slot-index read-back (asserts `6`), boot-to-B3, and
+CLI exit codes. Live tests skip without the shipped image.
+
+```bash
+python3 -m unittest tests.test_valprobe -v
+python3 harness/valprobe.py demo
+```
+
+## 6. Problem classes and time-to-value
+
+Use this tool when the question can be stated as *"what will this firmware
+branch do with input/state X?"* — control-entry validation, accept/refuse
+rules, error-path predicates, field-dependency questions. It answers offline,
+in seconds, with labelled outcomes and a session; the alternative is static
+reading (rule known, cause not), or a supervised hardware cycle per variant
+(minutes to an hour plus teardown risk).
+
+The reference case (2026-10-06): a wall that had consumed five-plus supervised
+cycles and was still misdiagnosed as a channel limitation was closed by a
+15-variant probe (≈2 s, run twice, identical) plus one log join. The rule to
+copy: phrase the question as a predicate → probe it offline → join the verdict
+to live logs → spend the supervised cycle only on what the probe cannot see
+(the engine/silicon side).
+
+Companion tools: `harness/rehost.py` (docs/46) for whole-function calls with
+crafted state and snapshots; `sim.gsp_boot` for boot-state evidence (rung and
+state); `harness/triage.py` / `version_diff.py` for release-to-release
+questions.