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
|
# 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.
|