summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md253
1 files changed, 253 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md b/debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md
new file mode 100644
index 0000000..e56094c
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/docs/51-HOST-TRAFFIC-LOOP.md
@@ -0,0 +1,253 @@
+# 51 — Host-traffic message loop: doorbell + msgq transport + replay (in development)
+
+> **Status: in development (2026-10-06, B4/B5 transport wave).** This page
+> documents `sim/host_traffic.py`, the transport half of boot-ladder rungs
+> B4/B5 (docs/35-GREENEYE-SIMULATOR-DESIGN.md §3.3): a modelled host
+> doorbell, a command/status message-queue pair laid out with the real queue
+> arithmetic, queue-element framing with the real checksum, a stub dispatcher
+> and deterministic replay of an `rm_capture` stimulus (docs/45). The **real
+> 570.144 firmware is not run by this module**: there is no real message loop,
+> no RM semantics and no firmware-behaviour claim.
+>
+> Development-only, offline: no GPU, kernel module, mdev, guest, network,
+> QEMU, `/dev` access or reboot. Nothing here is a product dependency and
+> nothing is injected into a guest.
+>
+> **See also:** `35-GREENEYE-SIMULATOR-DESIGN.md` §3.3 (ladder), `36` (device
+> model; the falcon window this doorbell overlays), `45` (capture decode and
+> stimulus), `49` (surface map; B4/B5 is queued build #1), `06-ACCURACY.md`
+> (L0/L1 only), `open-iov/docs/eng-ops.md` §2 (admission rule).
+
+## 1. What this is and why
+
+The campaign keeps hitting one wall class: *what does the server do with the
+traffic we send it, and what does it read/write/wait on for this control*.
+The strategic tool for that class is B4/B5: replay a captured real RPC
+sequence into the emulated GSP through an emulated transport and record the
+replies and memory writes as evidence (handoff-simulator §27, §30, §31 item
+B1; docs/49 §6 build order #1).
+
+This wave lands the **transport and framing** so the replay plumbing exists
+and is tested before the firmware loop is connected:
+
+* a doorbell register model at the real hardware offset;
+* command/status msgq rings in simulated memory, laid out with the arithmetic
+ the real host queue code uses (including the `MSGQ_FLAGS_SWAP_RX`
+ cross-link of the two rx headers);
+* queue-element and RPC framing with the real header layouts and checksum;
+* a 2-handler stub dispatcher plus `NV_ERR_NOT_SUPPORTED` for real RM
+ functions, so a message can travel host → command ring → dispatch → status
+ ring → host and be recorded;
+* replay of a `greeneye-rm-rpc-stimulus/1` document (or a capture document
+ converted by `harness/rm_capture.py`), message by message, in order.
+
+The dispatcher is deliberately a **stub**: answers are labelled
+`stub:echo`, `stub:status_query`, `stub:unsupported` in the transcript. A
+stub verdict is never presented as firmware behaviour.
+
+## 2. Admission record (draft — pending maintainer filing)
+
+Copied from the queued execution item (private live contract §31 item B1),
+expanded to the `open-iov/docs/eng-ops.md` §2 five-field format; a maintainer
+files the final record. One scenario, one axis (axis 4, bug-analysis time).
+
+```text
+TOOL ADMISSION RECORD id: TOOL-2026-XX (draft)
+tool: sim/host_traffic.py (new; B4/B5 transport + replay wave)
+loop: dynamic-RE (campaign traffic-interaction walls)
+axis: 4 — bug-analysis time (supervised cycles avoided / TTFA)
+owner/decider: <maintainer>; executor: agent
+scenario: a campaign traffic-interaction wall (e.g. the V6 launch
+ silence) asks what the server does with a given control or
+ reply traffic; replay the captured stimulus into the modelled
+ transport and read the server-side evidence offline.
+measurable: wall resolved from emulator evidence alone or with < 1
+ supervised cycle; per wall: a session/transcript pair with
+ every submitted message, dispatch handler and reply recorded.
+baseline: today = static decompiler dig + one or more supervised
+ hardware cycles per hypothesis (unrecorded as a single
+ number; docs/49 §4).
+threshold: keep if 2 of the next 3 wall-class instances are answered
+ offline; N >= 3 wall-class instances; environment: this tool
+ checkout, simulator only.
+failure signal: replies diverge from captured server behaviour, or the tool
+ cannot represent the control/queue shape -> record the
+ fidelity gap and stop (no emulation edit from simulator-only
+ evidence).
+evidence: tests/test_host_traffic.py (36 tests) + `demo`/`replay`
+ session directories under the `--out` directory.
+decision: admit-pilot
+review-by: one quarter / five sessions trials-dir: dumps/
+```
+
+Queued-item exit criterion (handoff §31 B1): *one real 512-byte RPC control
+replayed end-to-end with the reply captured in a session*. This wave's demo
+replays a **real-shaped** 512-byte `GSP_RM_CONTROL` (built to the documented
+control shape, from no capture) end-to-end through the transport; its reply
+is the stub dispatcher's framed `NV_ERR_NOT_SUPPORTED`, captured in a session.
+`replay` consumes a real stimulus document the moment a supervised capture
+provides one — the capture itself is still pending, so no captured-sequence
+claim is made yet.
+
+## 3. What the transport is based on (real offsets and structures)
+
+All facts below come from public OpenRM register/queue header material (the
+same class of evidence the tracked device model already uses). The capture
+tool exposes the overlapping facts as constants (`RPC_HEADER_BYTES`, the
+`GSP_RM_*` ids, the payload offsets), not as struct types, so this module
+mirrors them and `test_rpc_constants_match_capture_tool` fails if they drift.
+The models are L0/L1 framing and ordering only; no timing claim.
+
+| Fact | Value | Use in the model |
+|---|---|---|
+| GSP queue-head register | `NV_PGSP_QUEUE_HEAD(i) = 0x110c00 + i*8`, 32-bit RW, i < 8 | doorbell window `PRIV+0x110c00`, 0x40 bytes; reached by the firmware through the PRIV aperture |
+| Host kick | the 570.144 submit path writes the queue head after queueing (observed value 0) | `HostTraffic.submit` rings queue 0 with 0; the recorded value is the firmware-visible read-back |
+| Falcon mailbox | `NV_PGSP_FALCON_MAILBOX0/1` 0x110040/0x110044 | already modelled by `sim/devices/falcon.py`; not duplicated here |
+| `msgqTxHeader` | 8 x u32: version, size, msgSize, msgCount, writePtr, flags, rxHdrOff, entryOff | `MsgqTxHeader` / `MsgqQueue` |
+| `msgqRxHeader` | 1 x u32: readPtr | `MsgqQueue.set_read` / `.read_ptr` |
+| msgq arithmetic | `rxHdrOff = align32(16) = 32`, `entryOff = align36(16) = 48`, `msgCount = (size - entryOff) / msgSize` | `MsgqQueue.format` reproduces it exactly |
+| Queue flag | `MSGQ_FLAGS_SWAP_RX = 1`; the real command queue sets it and `msgqRxLink` cross-links the rx headers between the command and status buffers | `SharedQueues` places the command rx header in the status buffer and vice versa |
+| `GSP_MSG_QUEUE_ELEMENT` | 16 B authTag + 16 B aad + checksum + seqNum + elemCount + 4 B reserved + 32 B `rpc_message_header_v` (payload at 0x50) | `build_queue_element` / `parse_queue_element` |
+| Element/queue sizes | element min 0x1000, max 0x10000; default command and status queue 0x40000 each; header size 0x1000; header align 4, element align 12 | defaults in the module; a message stages contiguously and splits across ring slots exactly as the real producer copies it |
+| Multi-element | elemCount in the first element; the contiguous staged stream is split into 0x1000-byte slots (tail zero-padded); checksum covers the staged stream | `split_element_stream` + `SharedQueues._place`/`_take` (wrap-safe); max 16 elements |
+| Checksum | XOR of 64-bit words over `0x30 + rpc.length` bytes, then hi32 ^ lo32; the stored field makes the receiver's recomputation zero | `compute_message_checksum` / `build_queue_element` (verified) |
+| RPC header | 32 bytes: header_version (0x03000000), signature ("VRPC" 0x43505256), length (= 32 + payload), function, rpc_result, rpc_result_private, sequence, spare | `build_rpc_message` / `parse_rpc_message` |
+| RM ids | `GSP_RM_CONTROL = 76`, `GSP_RM_ALLOC = 103`, `GSP_GET_GSP_STATIC_INFO = 65` (same as the capture tool) | replay mapping for `control`/`alloc` records |
+| NV status | `NV_OK = 0`, `NV_ERR_NOT_SUPPORTED = 0x56` | stub replies |
+
+Modelled rather than proven, and therefore labelled:
+
+* the **shared memory address and queue sizes** are fixture defaults; the real
+ driver learns them from the `GSP_ARGUMENTS_CACHED` / message-queue init
+ arguments. Nothing here reads a real `MESSAGE_QUEUE_INIT_ARGUMENTS`.
+* the **doorbell read-back** is storage of the last written value (the header
+ says RW); the firmware's read use of this register has **not** been observed.
+* the **command/status direction** is the documented one (host writes the
+ command queue, GSP writes the status queue), but no real GSP has yet been
+ observed consuming or producing through this model.
+* the **MSI-X/event path** by which the real GSP signals a status-queue write
+ is not modelled; replies are read by polling (`read_reply`).
+
+## 4. Files and API
+
+| File | Contents |
+|---|---|
+| `sim/host_traffic.py` | framing (`build_rpc_message`, `parse_rpc_message`, `build_queue_element`, `parse_queue_element`, `compute_message_checksum`), `MsgqQueue`, `SharedQueues`, `DoorbellModel`, `DoorbellBlock` (a `sim.devices` overlay), `HostTraffic`, `load_stimulus`, `replay_stimulus`, `run_demo`, CLI |
+| `tests/test_host_traffic.py` | 36 offline tests: framing/checksum, queue arithmetic + cross-link, doorbell model + guest instruction access, submit/dispatch/reply, determinism, replay (stimulus + capture doc), recorder events, demo + CLI |
+| `docs/51-HOST-TRAFFIC-LOOP.md` | this page |
+
+`HostTraffic` surface:
+
+* `submit(message)` / `submit_rpc(function, params, sequence=None)` — frame,
+ write one command element, advance `writePtr`, ring the doorbell (recorded).
+* `poll()` — GSP-side drain of the command ring through the handler table,
+ writing framed replies to the status ring; returns the dispatched count.
+ Bounded by construction (each iteration consumes one element).
+* `read_reply()` — host-side consumption of one status element.
+* `run_cycles(budget)` — run the attached `sim.machine.Machine` for a bounded
+ budget (cap 2M) and then `poll`; without a machine it only polls.
+* `transcript()` / `state()` / `close()` — deterministic transcript, queue and
+ doorbell state; `close()` writes `blobs/host-traffic.json` when a recorder
+ is attached.
+* `doorbell_block()` — a `DoorbellBlock` sharing the same `DoorbellModel`, so
+ the guest-visible register and the host model are one state. Attach it with
+ `machine.devices.add(host.doorbell_block())`; the narrow window overrides
+ the wider falcon window only for `0x110c00..0x110c3f`.
+
+Records: `greeneye-host-traffic-{demo,replay,log,reply}/1`. The transcript has
+no host timestamps and is byte-identical across runs; the optional
+`greeneye-session/1` recorder events (`host_traffic_config`, `doorbell`,
+`host_submit`, `host_dispatch`, `host_reply_read`, `host_replay_skip`,
+`host_transcript`) follow the standard event shape, where `t` is host-timed
+and non-semantic.
+
+## 5. How to run it
+
+```bash
+cd open-iov/debug/risc-v-gsp-rm-vm
+
+# Deterministic end-to-end demo (echo + status query + real-shaped 512-byte
+# GSP_RM_CONTROL; stub replies), with a recorded session:
+python3 -m sim.host_traffic demo --out dumps/host-traffic-demo
+
+# Replay a stimulus document (or a capture document / raw capture log):
+python3 -m sim.host_traffic replay --stimulus dumps/rm-stimulus.json --out dumps/host-traffic-replay
+python3 -m sim.host_traffic replay --stimulus capture.json --allow-truncated
+
+# Tests:
+python3 -m unittest tests.test_host_traffic -v
+```
+
+`replay` maps stimulus records as follows: `send` carries the wire function
+and payload directly; `control` and `alloc` records carry a named id and are
+mapped with the capture tool's constants (76 / 103) using the captured
+parameter block as the payload. That identity is an **assumption** until a
+capture with full send-side payloads exists (the current runbook helper also
+truncates payloads above 192 bytes; truncated messages are refused by default
+and skipped with `--allow-truncated`).
+
+## 6. Real vs stubbed — the honest table
+
+| Piece | State |
+|---|---|
+| RPC header + queue element framing, checksum | **real** (mirrored; drift-tested against `harness/rm_capture.py` constants) |
+| msgq header arithmetic + `SWAP_RX` cross-link | **real** (mirrored from the public queue header and `msgqTxCreate`/`msgqRxLink`) |
+| Doorbell register offset/width/access | **real** (public register header); read-back is storage of the last write, firmware read use unobserved |
+| Guest access to the doorbell window through `Machine` | **real** (device window + recorded store PC); exercised by instruction-level tests |
+| Command/status rings in simulated memory | **real layout**, fixture addresses and sizes |
+| Multi-element messages | **real split** (contiguous staged stream across ring slots, wrap-safe, checksum over the stream) |
+| Dispatcher (`echo`, `status_query`) | **stub** (ids 0x40/0x41 are not RM functions) |
+| Reply to real RM functions | **stub** `NV_ERR_NOT_SUPPORTED` — framed and transported exactly; not firmware behaviour |
+| Real firmware message loop | **not run** (B5 remains open) |
+| RM semantics (controls, allocs, objects, events) | **absent** |
+| MSI-X / GSP→host interrupt | **not modelled** (polling) |
+| Timing/cycles | **not claimed** (L2 absent; `run_cycles` is an instruction budget) |
+
+## 7. What this does NOT do yet (and the next step to B5)
+
+Not done: booting the real image and serving its message-loop poll; reading
+the real `GSP_ARGUMENTS_CACHED`/queue-init parameters; event/MSI-X delivery;
+any RM handler.
+
+Next step for B5, concretely (each point is bounded and session-recorded):
+
+1. Boot the real 570.144 image to B3 (`sim.gsp_boot`, 474k instructions,
+ stop at the message-loop port wait, `ecall` at `0x4018038`).
+2. Attach `host.doorbell_block()` to the boot registry before the run, so the
+ firmware's first queue-head access is served by the model and recorded,
+ and place the command/status rings at addresses the firmware is told
+ (stub: the fixture block; real: the addresses from a captured
+ `GSP_ARGUMENTS_CACHED`).
+3. Ring the doorbell from the host model and continue the machine with
+ `run_cycles` until it polls the rings; classify the first poll: which
+ address does it read (queue header? Tx/Rx header? entry?) and what
+ read-back semantics does it need.
+4. Compare the firmware's observed ring arithmetic against this module's
+ (field-by-field); where the real init used `MSGQ_FLAGS_SWAP_RX`, the
+ cross-link must already match; fix only with a recorded session pair
+ (docs/06 accuracy loop), never from a decompiler reading alone.
+5. Then replay one captured stimulus message with the real image and record
+ the reply/writes as the B5 session. Until a supervised capture exists,
+ this stops at step 3–4 with the stub dispatcher.
+
+The seam for step 3 already exists: `HostTraffic(machine=..., shared_base=...)`
+uses the machine's RAM for the rings and `run_cycles` steps the core; the
+doorbell block overlays the falcon window without touching
+`sim/devices/falcon.py` or the profile.
+
+## 8. Limits, deferrals and verification
+
+Limits: L0/L1 only; no capture has been consumed yet (no supervised runbook
+execution); control/alloc payload identity is an assumption; truncated
+payloads are refused or skipped, never padded; a stub reply is never a
+firmware claim; no real queue-init parameters.
+
+Deferred: real-firmware B4/B5 wiring (above), event/MSI-X delivery, RM-object
+state seeding (docs/49 build #2), golden matrices (#3).
+
+Verification of this wave: `python3 -m unittest tests.test_host_traffic -v`
+→ 36 tests green; full suite `make test` green; `demo` `ok: true` with all
+checks (`echo` payload round-trip, status magic, 512-byte control replay,
+framed stub verdict, one doorbell ring per submit, all replies read);
+`ruff check` clean on the new files. Two-run determinism is asserted by
+tests (transcript and replies byte-equal).