summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py151
1 files changed, 151 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py b/debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py
new file mode 100644
index 0000000..b86f272
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/harness/value_demos/s01_reply_consumer.py
@@ -0,0 +1,151 @@
+#!/usr/bin/env python3
+"""s01 - reply consumer: did the server consume the command, and what came back?
+
+Real scenario (Handoff-vGPU-IO.md, PART 91 + ADDENDA 2-7, 2026-10-01):
+the physical backend booted the real GSP-RM and sent the pre-boot
+``GSP_SET_SYSTEM_INFO`` (fn72) / ``SET_REGISTRY`` (fn73) RPCs; the only reply
+the host could see was a generic ``GSP_INIT_DONE rpc_result=0x59``
+(``NV_ERR_OPERATING_SYSTEM``) wrapped by ``RmStartup``. Cycles 2-13
+(~11:40-16:52, ten supervised hardware cycles) were spent chasing which
+collector step consumed which field because the RM log ring was never mapped
+(LOGRM id bug) and the failure was un-journaled. Static/Ghidra-only work
+(REPORT3-10) produced hypotheses (display, devid, chipset, ACPI) that needed a
+live ``0x59``/success reply to accept or reject.
+
+Analog: hook the demo dispatcher, watch the control block's ``result`` field,
+post known/unknown/fault commands and decode the exact reply per command.
+"""
+from __future__ import annotations
+
+import os
+
+try: # package import (tests) or direct script execution (run_all)
+ from . import _common as C
+except ImportError:
+ import sys as _sys
+
+ _sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
+ import _common as C
+
+
+def run() -> dict:
+ import greeneye as ge
+
+ backend = C.choose_backend("native")
+ out = C.artifact_dir("s01_reply_consumer")
+ commands = [
+ 'ge.launch(kernel=DEMO_ELF, backend="%s")' % backend,
+ 's.hook("rpc_dispatch", on_enter=record cmd/args)',
+ "s.watch(gsp_ctrl+0x40, size=8, kind='w') # result",
+ "s.memory.write_u64(ctrl+0x20, cmd); s.run(duration=...) # mailbox post",
+ "s.memory.read_u64(ctrl+0x40) # decode reply",
+ ]
+ scenario = C.scenario(
+ ids=[
+ "Handoff-vGPU-IO.md PART 91 (2026-10-01) cycles 2-3",
+ "PART 91 ADDENDUM 2-7 (2026-10-01): NOCAT, fn72/fn73, LOGRM id, identity",
+ ],
+ baseline=(
+ "cycles 2-13, ~5 h wall-clock and 10+ supervised hardware boots; "
+ "each candidate fix cost one full boot cycle (~5-6 min) and the "
+ "only visible reply was a generic 0x59"
+ ),
+ projection=(
+ "real GSP-RM reply consumption is blocked on the H2 device model; "
+ "on the analog the same observe->decode step is one hook + one "
+ "watchpoint per command"
+ ),
+ blocked=True,
+ device="H2.2 QEMU device model (RM message loop / real .fwimage)",
+ )
+ C.render_banner(
+ "s01_reply_consumer",
+ "When the host posts a command, did the dispatcher consume it and what exact reply came back?",
+ "0x59 RM-init chase: generic reply hid which collector step consumed which field",
+ scenario["static_baseline"],
+ commands,
+ )
+
+ cases = [
+ {"cmd": 1, "arg0": 0x1234, "label": "PING"},
+ {"cmd": 7, "arg0": 0, "label": "unknown-id"},
+ {"cmd": 6, "arg0": 0, "label": "FAULT"},
+ ]
+ replies = []
+ session = ge.launch(kernel=C.DEMO_ELF, backend=backend, name="vd-s01")
+ try:
+ ctrl = session.symbols.lookup("gsp_ctrl")
+ dispatches = []
+ stores = []
+ session.hook(
+ "rpc_dispatch",
+ on_enter=lambda hit: dispatches.append(
+ {"cmd": hit.arg(0), "arg0": hit.arg(1), "arg1": hit.arg(2), "arg2": hit.arg(3)}
+ ),
+ )
+ session.watch(ctrl + C.CTRL_RESULT, size=8, kind="w", callback=lambda hit: stores.append(hit.pc))
+ session.run(duration=0.3)
+ for case in cases:
+ before_count = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT)
+ session.memory.write_u64(ctrl + C.CTRL_ARG0, case["arg0"])
+ session.memory.write_u64(ctrl + C.CTRL_MAILBOX, case["cmd"])
+ session.run(duration=0.25)
+ after_count = session.memory.read_u64(ctrl + C.CTRL_RPC_COUNT)
+ result = session.memory.read_u64(ctrl + C.CTRL_RESULT)
+ expected = {
+ 1: (0x1234 ^ C.PING_XOR),
+ 7: (~7) & 0xFFFFFFFFFFFFFFFF,
+ 6: None,
+ }[case["cmd"]]
+ replies.append(
+ {
+ "label": case["label"],
+ "cmd": case["cmd"],
+ "arg0": case["arg0"],
+ "consumed": after_count > before_count,
+ "result": result,
+ "expected": expected,
+ "result_ok": (expected is None) or (result == expected),
+ "rpc_count_delta": after_count - before_count,
+ }
+ )
+ if case["cmd"] == 6:
+ # ebreak trap: the dispatcher's post-handler stores never run.
+ session.step(1)
+ known = replies[0]
+ unknown = replies[1]
+ fault = replies[2]
+ payload = {
+ "success": known["result_ok"] and unknown["result_ok"] and not fault["consumed"],
+ "backend": backend,
+ "firmware": os.path.relpath(C.DEMO_ELF, C.TOOL_ROOT),
+ "firmware_sha256": C.sha256_file(C.DEMO_ELF),
+ "dispatches": dispatches,
+ "reply_store_pcs": stores,
+ "replies": replies,
+ "question": "consumed and decoded every posted command",
+ "answer": (
+ f"PING consumed, reply {known['result']:#x} == arg0 ^ 0xbeef "
+ f"(expected {known['expected']:#x}); unknown id returns ~cmd "
+ f"({unknown['result']:#x}); FAULT consumed={fault['consumed']} "
+ f"(trap before the reply store) - the exact first-error visibility "
+ "gap the 0x59 chase lacked"
+ ),
+ "success_criteria": {
+ "pass": "3/3 dispatches observed; PING/unknown replies match the ABI; FAULT traps before result",
+ "fail": "a posted command is not consumed or a reply disagrees with the ABI",
+ },
+ "real_case": scenario,
+ }
+ C.write_json(os.path.join(out, "reply.json"), payload)
+ finally:
+ session.close()
+ return payload
+
+
+def main() -> int:
+ return 0 if C.timed_demo(run, C.artifact_dir("s01_reply_consumer"))["success"] else 1
+
+
+if __name__ == "__main__":
+ raise SystemExit(main())