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
|
# 25 — Value validation: measurable time-to-value evidence
> Status: implemented, documented and tested (audit: docs/27-TOOL-AUDIT.md; value demos: docs/25-VALUE-VALIDATION.md).
> Every number in this document is
> either **measured** on the bundled demo analog (recorded in
> `harness/value_demos/artifacts/`) or explicitly labelled a **projection**
> for the real case. No demo claims a real-GSP result the simulator cannot
> reach; device-model-blocked scenarios say so in their result.
## 1. Why this document exists
risc-v-gsp-rm-debugger is only worth its maintenance if it turns real firmware questions
into answers faster than static analysis and supervised hardware cycles. The
project handoffs record those questions and their costs in wall-clock and
cycles: the 0x59 RM-init chase (PART 91, 2026-10-01), the 20 GR counters
(PARTs 46–64, 2026-09-24…29), the GPFIFO GET fault (PART 91 ADDENDUM 9), the
P6 service wiring (PART 92 ADDENDA 5–11, 2026-10-02), and the version pairing
question (570.144 vs 570.211). This document maps each of them to a runnable
demonstration under [`harness/value_demos/`](../harness/value_demos/), states
the baseline cost from the handoffs, and measures the risc-v-gsp-rm-debugger path.
## 2. Methodology
### 2.1 What is measured, what is projected
| Class | Meaning | Where it appears |
|---|---|---|
| **Measured (analog)** | Wall-clock of the risc-v-gsp-rm-debugger path on the bundled `uproc_demo` firmware, native RV64 interpreter or QEMU; answers, counts, session bytes, trace slowdown | `result.json` per demo, `artifacts/results.json` |
| **Recorded (real baseline)** | Cost actually paid on the physical path, as recorded in the handoffs (dates, cycles, PARTs, wall spans) | `real_case.static_baseline` in each `result.json`, §5 below |
| **Projected** | What the same tool path would save on the real firmware once H2.2 executes it; labelled `projected` | `real_case.projection`; never presented as a result |
| **Blocked** | Real firmware access requires the H2.2 QEMU device model; `blocked_on_device_model: true` | every real `.fwimage` scenario here |
Fidelity is exactly docs/06: L0 instruction semantics and L1 memory behaviour
are exercised; **L2 time/concurrency is not modelled** and **L3 device
environment is absent**. A risc-v-gsp-rm-debugger observation is evidence about the
firmware, and anything that would change `vgsp` is escalated to a
physical capture before the product changes (docs/06 §6, docs/14 §7).
### 2.2 Run contract
* Each demo is a stdlib-only script run from the tool root; it prints the
question, commands, answer, evidence and elapsed time, then one
`RESULT_JSON: {...}` line.
* `run_all.py` executes each demo in a subprocess with a hard timeout,
aggregates `artifacts/results.json`, and enforces the suite budget
(per-demo ≤ 45 s, suite ≤ 180 s).
* Demos are deterministic: native sessions use virtual time (instruction
count), stimuli are fixed, and the version-diff patch site is verified
unique before use. Artifacts contain no host paths beyond the repo.
### 2.3 Scenario selection and “why not edge cases”
Every class below recurs across multiple handoff PARTs and sits on the
critical path to `vectorAdd`. The mapping is direct:
| Demo | Recurs in | Why it is on the vectorAdd path |
|---|---|---|
| s01 reply consumer | PART 91 cycles 2–13; status-queue decode cycles 22–25 | RM init must complete (`0x1001`, `rpc_result=0`) before any RPC; a generic failure hides the first error |
| s02 counter publication | PARTs 46–64 | `cuCtxCreate` completes on per-channel counters; 4/20 blocked the client |
| s03 unknown commands | every new image; docs/19 | control inventory (M1) is the input surface the emulation must handle |
| s04 run-until | PART 91 ADDENDUM 9 (cycles 18–25) | each ladder rung (VAS, PDB, channel, token, GPFIFO, semaphore) is a reachability question |
| s05 snapshot diff | PART 91 ADDENDUM 6/7 (identity, static config) | static-config fields feed guest wiring; a zeroed field kept the device-id read at zero |
| s06 inject service | PART 92 ADDENDA 5–11 (P6a–P6c) | guest-originated work executing on the engine is the shape of the load |
| s07 version diff | PART 91 cycles 2–3; every GSP release | the emulation must track firmware changes without regressing |
| s08 mutation triage | docs/18 R5; docs/19 §3.4; PARTs 3–9 | fail-closed rules come from error paths, not happy paths |
| s09 Ghidra address | PART 91 ADDENDUM 9 cycle 24 | the GPFIFO GET fix was accepted only after the runtime fault PC existed |
| s10 evidence pack | docs/14 §6; every milestone | a finding without sessions/hashes/M1–M6 is not actionable |
None of these is a bizarre edge case: they are the same observe → decode →
change loop the project runs on every firmware version, and each one is
cited more than once in the handoffs.
## 3. Scenario → tool → baseline → measured
Measured on the reference host (CPython 3.12); numbers regenerate with
`python3 harness/value_demos/run_all.py`. Times below are from the
`run_all` aggregate (~18.4 s total).
| # | Real scenario (source, date) | Risc-V GSP-RM debugger tool | Static/Ghidra-only baseline (recorded) | Analog (measured) | Real-case projection |
|---|---|---|---|---|---|
| s01 | 0x59 RM-init chase; fn72/fn73 (PART 91 + ADDENDA 2–7, 2026-10-01) | hook dispatcher, watch `result`, decode reply | cycles 2–13, ~11:40→16:52 ≈ 5 h 10 m; 10+ supervised boots (~5–6 min each); reply was generic `0x59` | **1.14 s**; 3/3 commands consumed and decoded; FAULT traps before the reply store | one hook + one watchpoint per command once H2.2 boots the image (blocked) |
| s02 | 20 GR per-channel counters (PARTs 46–64, 2026-09-24…29) | watch publication field, per-slot readback | ~5 days; PART 49 “only 4 of 20 counters ever moved”; ~2–3 s pin vs ~2 s client deadline | **1.34 s**; 20/20 slots published, 28 stores, store PCs captured | per-channel page watchpoints on real counter pages (blocked) |
| s03 | unknown control IDs / injected slots (docs/19, 2026-10-02) | sweep + dispatcher/handler hooks + classify | 189 real probes in ~36 s; static-only requires the indirect table per image | **5.96 s** for 24 IDs: 6 known, 8 injected-slot (aliases shown), 10 default | RM control/method sweep once H1.3/H2.2 exposes the table (blocked) |
| s04 | RM-client ladder reachability (PART 91 ADDENDUM 9, 2026-10-01) | `run_until`, trigger, direct `call` | cycles 18–25 = 19:12→20:53 ≈ 1 h 41 m, 8 supervised boots | **0.24 s**; natural hit / timeout / trigger / direct call | every ladder rung becomes a probe spec (blocked) |
| s05 | fn72 identity + 1656-B static config (PART 91 ADDENDUM 6/7, 2026-10-01) | before/after snapshot + schema diff | malformed fields suspected statically; acceptance required a live boot per iteration (10 NOCAT cycles) | **0.50 s**; 6 named fields changed, 7 byte runs fully attributed | same diff over real RM control/reply pages (blocked) |
| s06 | P6 service wiring / guest CE copies (PART 92 ADDENDA 5–11, 2026-10-02) | service injection SDK, both invocation routes | kernel rebuild + supervised cycle (~5–8 min) per rung; P6a→P6c spanned ~02:20–08:30 | **0.63 s**; blob loaded/registered; remote and mailbox results agree | RM service registry (M5) once H2.2 exists (blocked) |
| s07 | 570.144 vs 570.211 pairing (PART 91 cycles 2–3, 2026-10-01) | same stimulus on two builds, `compare_sessions` | one full boot (~5–6 min) per version plus manual log comparison | **1.14 s**; only the patched reply field moved; decision-table row = “prioritise” | real versions triaged on re-hosted firmware; symbols/sessions are the artifact (blocked for the raw image) |
| s08 | mutation / fail-closed corpus (R5; docs/18 §2; docs/19 §3.4) | seeded one-field mutations, signal classification | hand-derived asserts/NOCAT across PARTs 3–9; a 64-iteration QEMU run is the tracked harness baseline | **1.82 s**; 7 mutations → {ok:4, fault:2, no-progress:1}, all expectations matched | real controls land in the same harness (H1.3 surface, blocked) |
| s09 | GPFIFO GET fault `0x40000` vs `0x101000` (PART 91 ADDENDUM 9 cycle 24) | Ghidra TSV + bias + raw-address hook + `export_hits` | static hypothesis unproven; the decoded fault ended 8 supervised cycles (~1 h 41 m) | **0.72 s**; biased address resolves, hook fires at the exact PC, TSV annotated | names.tsv + bias loop once the image executes (blocked) |
| s10 | milestone evidence pack (docs/14 §6; cycles 11–13, 2026-10-01) | one recorded session → analyze/compare/hits/manifest/M1–M6 | hashes, sessions, logs and compare gathered by hand across boots | **4.92 s**; 12,276-byte session, A/B deterministic, all artifacts + overhead numbers | same pack against real sessions (blocked) |
## 4. Exact commands (per demo)
All from `open-iov/debug/risc-v-gsp-rm-vm`. `VALUE_DEMO_BACKEND` selects the
backend (`native` default, `qemu` where noted).
```bash
# s01 — reply consumer (native; QEMU also supported)
python3 harness/value_demos/s01_reply_consumer.py
# hook("rpc_dispatch"); watch(gsp_ctrl+0x40, 8, 'w')
# mailbox cmd=1 arg0=0x1234 -> 0xacdb; cmd=7 -> ~7; cmd=6 -> trap, no result store
# s02 — counter publication
python3 harness/value_demos/s02_counter_publication.py
# watch(gsp_ctrl+0x40, 8, 'w'); 20 x (mailbox cmd=5, run) -> 20/20 published
# s03 — unknown command discovery
python3 harness/value_demos/s03_unknown_command.py
# fresh session per ID 1..24; hook dispatcher + 6 handlers; classify
# (findings.jsonl, control-discovery.md)
# s04 — run-until / reachability
python3 harness/value_demos/s04_run_until_function.py
# run_until("rpc_sum"); run_until("rpc_fault", timeout=0.2) -> RspTimeout;
# mailbox cmd=6 + run_until; call("rpc_ping", (0x41,))
# s05 — snapshot diff
python3 harness/value_demos/s05_snapshot_diff.py
# snapshot before/after ECHO; Memory.diff; decode with gsp_ctrl.schema.json
# s06 — service injection (needs `make sdk`)
python3 harness/value_demos/s06_inject_service.py
# load_service(...slot=6); invoke_remote; invoke_via_firmware
# s07 — version diff
python3 harness/value_demos/s07_version_diff.py
# byte-patch the PING key (0xbeef -> 0xbef0) into version B; identical
# stimulus; decoded field diff + compare_sessions
# s08 — mutation triage
python3 harness/value_demos/s08_mutation_triage.py
# 7 seeded one-field mutations; classify by stop signal + rpc_count
# s09 — Ghidra address join (QEMU by default)
python3 harness/value_demos/s09_grabbing_a_ghidra_address.py
# names.tsv (program = runtime - 0x1000); load_names_tsv(bias=0x1000);
# hook(ghidra + bias); export_hits -> greeneye_hits.tsv
# s10 — full evidence pack (QEMU by default)
python3 harness/value_demos/s10_full_session_evidence.py
# two identical recorded sessions; analyze + compare + hits + manifest + M1–M6
# + startup/session/trace overhead
# all of them
python3 harness/value_demos/run_all.py
python3 -m unittest tests.test_value_demos -v
```
## 5. Per-scenario detail
### s01 — reply consumer (0x59 first-error visibility)
The physical path sent `GSP_SET_SYSTEM_INFO` (fn72) and `SET_REGISTRY`
(fn73), and the only visible reply was `GSP_INIT_DONE rpc_result=0x59`
(`NV_ERR_OPERATING_SYSTEM`), wrapped generically by `RmStartup`; the RM log
ring was not mapped until the LOGRM id fix (`0x4c4f474d` “LOGM” vs
`0x4c4f47524d` “LOGRM”), so the failing collector step was invisible
(PART 91 ADDENDA 2–6). Ten-plus supervised boots later, the identity fix
(raw config-space dwords instead of unpacked fields) made init-done succeed
in 950 ms. The analog consumes each posted command, watches the result
field, and decodes the exact reply — including the case where the fault
handler traps before any reply store. **Success:** 3/3 dispatches, replies
match the ABI, FAULT has no result store. **Failure:** a command is not
consumed or a reply disagrees.
### s02 — counter publication (20 slots)
`cuCtxCreate` waits on 20 GR per-channel counters; the host saw the client’s
releases but could not translate the counter page, and for days only 4 of 20
client-visible slots moved (PART 49, 2026-09-29). The page VA
(`0x20400f000`, slots at `+0xec0`, 16-byte stride) and the different GPA per
boot were only proven by a raw live page dump (PARTs 51, 64). The analog
publishes 20 slots and attributes each store to a PC. **Success:** 20/20
published with one `rpc_count` increment per slot. **Failure:** any slot
missing or count delta disagreeing.
### s03 — unknown command discovery
New firmware can silently add or re-point dispatch entries; static reading of
an indirect table is per-image work. The 2026-10-02 sweep (189 probes, ~36 s)
found IDs 16–23 falling through to the injected-service handler table
(16–21 alias built-ins, 22–23 NULL returning `~id`) and 7–15/24–63 on the
default path (docs/19 §3.4). The analog sweeps 24 IDs with fresh sessions
and the same hook shape. **Success:** expected classes per ID. **Failure:**
a misclassification or an unreplayable probe.
### s04 — run-until reachability
The RM-client ladder asked per rung whether symbolized code executed under
the current stimulus; cycles 18–25 cost ~1 h 41 m of supervised boots
(PART 91 ADDENDUM 9). The analog shows three answers in one session: reached
on the natural path, timed out (not reached under this stimulus — docs/14 §5
explicitly says this is not unreachability), reached via a trigger, and a
direct Frida-like call. **Success:** all four behaviors agree with the ABI.
**Failure:** a route reports reachable without a hit, or a direct call
disagrees.
### s05 — snapshot diff
The fn72 payload carried unpacked identity fields where the reference sends
raw config-space dwords; the server read the device id back as zero, and the
fix was confirmed by rejecting hypotheses field-by-field (PART 91 ADDENDUM 6,
cycle 12), then by decoding the full 1656-byte static-config reply (cycle
13). The analog diffs two snapshots and attributes every changed byte run to
a schema field. **Success:** `rpc_count`/`last_rpc`/`result`/`buf` change,
`result` equals the ECHO length, no unattributed run. **Failure:** an
unattributed changed region.
### s06 — service injection
P6 wired guest-originated copies through the real engine; every rung was a
kernel rebuild plus a supervised cycle with fail-safe release discipline
(PART 92 ADDENDA 5–11, 2026-10-02). The analog loads `injected_demo.elf`,
registers it in the dispatch table, and invokes it directly and through the
mailbox. **Success:** both routes agree with the SDK ABI. **Failure:** a
route disagrees, the slot is not registered, or the blob cannot load
(skipped with a reason when `make sdk` has not run).
### s07 — version diff
Cycles 2 (570.211) and 3 (570.144) both failed init with `0x59`, disproving
the image-pairing hypothesis at the cost of one full boot each, and the
project’s H4.1 decision table exists to separate “the code changed” from
“the behaviour moved” (docs/14 §4.1). The analog patches the PING reply
constant into a labelled version B and replays an identical stimulus; only
the patched field moves. **Success:** exactly the patched reply moves.
**Failure:** unrelated movement or an unreplayable version.
### s08 — mutation triage
The fail-closed rules come from error paths; the tracked harness is bounded
seeded mutation with outcome classes (docs/18 §2), and the discovery sweep
found the sentinel/boundary pointer faults (docs/19 §3.4). The analog runs 7
one-field mutations in fresh sessions with `max_stops=1` (a faulted target
re-faults at the same PC, so the run stops at the first signal) and
classifies ok/trap/fault/no-progress. **Success:** every mutation matches
its case table. **Failure:** a contradicting class.
### s09 — grabbing a Ghidra address
The GPFIFO GET hypothesis (`0x101000`) was only settled when the decoded
status queue showed the fetch at `0x40000`; fix `6f9cbfa` followed
(PART 91 ADDENDUM 9, cycle 24). The analog exports a Ghidra-style
`names.tsv` with a deliberate base offset, loads it with the matching bias,
hooks the raw static address, triggers the path, and writes
`greeneye_hits.tsv` back for Ghidra. **Success:** biased address equals the
real function, hook records the hit, TSV annotates it. **Failure:** a hit at
a different address or an empty TSV.
### s10 — full session evidence
The evidence pack contract (docs/14 §6) was satisfied by hand for every
milestone (cycle evidence dirs, P6 dirs). The analog produces it in one run:
two deterministic sessions, analyze, compare, hits TSV, firmware/artifact
hashes, the M1–M6 skeleton (M2/M5 marked not measured without a physical
reference), and the measured overhead. **Success:** ABI reply, A/B
agreement, all artifacts, empty unexplained-delta list. **Failure:**
nondeterminism or a missing artifact.
## 6. Overhead analysis (measured, with numbers)
| Overhead | Measured on the analog | Real-path comparison |
|---|---|---|
| Session startup | native ≈ 0.001 s; QEMU ≈ 0.13 s per launch | one supervised hardware cycle ≈ 5–6 min (PART 91/92) |
| Session size | 12,276 B per recorded session (s10), including 2 snapshots and 16 event types | per-cycle evidence dirs hold multi-MB logs, rings and decodes |
| Trace slowdown | 3.1× run-only on the demo workload; 13.2× end-to-end including writing `blobs/trace.jsonl` (~200 k exec lines) | docs/16: ~2.4× exec trace on the demo workload (1.53 → 0.54 MIPS); guard ~7% |
| CI cost | `tests/test_value_demos.py`: 5 tests in ≈ 4.5 s; `run_all.py`: 10 demos in ≈ 18.4 s (budget 180 s) | a single physical cycle is 300–400× the entire demo suite |
| Host footprint | stdlib only; artifacts under `harness/value_demos/artifacts/`; no hardware, no guest, no kernel module | physical cycles hold D0/D3cold locks, mdev and a QEMU guest |
**Why the overhead is dominated by the time saved.** The expensive baselines
are per-question hardware cycles and multi-hour manual campaigns (s01 ≈ 5 h,
s02 ≈ 5 days, s04/s09 ≈ 1 h 41 m each, s06 ≈ 6 h). The tool path for the same
question class costs 0.2–6.0 s on the analog plus ≤0.13 s of launch — under
7 s end to end for every demo, or ~0.02 % of one 5–6-minute cycle even
before the campaign time. Trace logging at 3–13× of a sub-second run is
still seconds; a 12 KB session replaces a multi-MB manual capture. The only
place the tools add real cost is the once-per-question scripting in
`harness/value_demos/` and the 4.5 s test, which run in CI and are reused by
every later version.
## 7. OODA-loop compression
| Stage | Before (real path, as recorded) | After (risc-v-gsp-rm-debugger analog, measured) |
|---|---|---|
| **Observe** | one supervised boot + capture per hypothesis: 5–6 min, fail-safe discipline, one device-write experiment per cycle | launch 0.001–0.13 s, then hooks/watchpoints and bounded runs: 0.2–5.8 s per question |
| **Orient** | manual LOGINIT/LOGRM/NOCAT ring decode, static reports (REPORT3–10), hours per campaign; the ring was invisible until cycle 11 | `analyze`/`compare`/`Memory.diff` with named symbols: seconds, e.g. s05 attributes every changed byte to a schema field |
| **Decide** | one candidate fix per supervised cycle, accepted/rejected by another boot | version diff (s07), mutation matrix (s08) and per-slot publication (s02) test the whole candidate set in one deterministic run |
| **Act** | rebuild + supervised boot; escalate every conclusion to a physical capture | re-run the session (seconds); product change still gated on physical confirmation (docs/06 §6, docs/14 §7) |
The compression is not “the simulator replaces the hardware”; it is the loop
around each physical experiment. risc-v-gsp-rm-debugger reduces the number of physical
cycles needed to reach a confident hypothesis and produces the evidence pack
that makes the eventual physical confirmation cheap (s10).
## 8. Success/failure criteria summary
| Demo | Pass | Fail |
|---|---|---|
| s01 | 3/3 dispatches; PING/unknown replies match; FAULT traps before the store | command not consumed or reply disagrees |
| s02 | 20/20 slots published, `rpc_count` +20 | any slot missing / count mismatch |
| s03 | expected class per ID (6 known, 8 injected, 10 default) | misclassification or unreplayable probe |
| s04 | natural hit, timeout, trigger hit, direct call value | false reachability or wrong call value |
| s05 | required fields change, result = ECHO length, no unattributed run | unattributed changed region |
| s06 | remote and mailbox invocation agree with the SDK ABI | route disagreement or unloadable blob (skipped if SDK absent) |
| s07 | only the patched field moves | unrelated movement / unreplayable version |
| s08 | every mutation matches its expected class | contradicting class |
| s09 | biased address resolves; hook fires; TSV annotated | wrong address / empty TSV |
| s10 | ABI reply; A/B agreement; artifacts complete; no unexplained deltas | nondeterminism / missing artifact |
## 9. Reproducing and extending
```bash
cd open-iov/debug/risc-v-gsp-rm-vm
make demo && make sdk
python3 harness/value_demos/run_all.py # writes artifacts/results.json
python3 -m unittest tests.test_value_demos -v # fast gate
```
`artifacts/results.json` is the machine-readable source for the table in §3:
per-demo `elapsed_s`, `success`, `real_case`, answers, and artifact paths.
When the H2.2 device model lands, each demo's `projected` note becomes the
runbook for replacing its analog with the real image; the result format and
evidence contract do not change.
## 10. Limits and fidelity statement
* The analog firmware is not GSP-RM: no RM objects, status queue, object
model or GPU device model. Demos reproduce the *shape* of each scenario
(hook the dispatcher, watch the field, decode the reply, diff versions,
mutate, inject) and are labelled as such.
* L2 timing/concurrency is not modelled; no demo makes a timing claim.
* L3 device environment is absent; every real-firmware result is
`blocked_on_device_model`.
* Absence of a hit is a fact about that run at that fidelity, not proof of
dead code (docs/14 §2).
* Full suite status and per-demo times are regenerated by
`run_all.py`; do not quote a stale number without re-running it.
|