From 1f2073d4fbb1d412d9bbf373312e3c51b83db92a Mon Sep 17 00:00:00 2001 From: Arthur Hanson Rasmusson Date: Sun, 4 Oct 2026 18:42:36 -0400 Subject: greeneye: GMMU/FBIF observability, PTE decoder and oracle, MPU/libos simulator wave Simulator development wave (docs 42-44): GMMU page-table entry decode with its oracle/workflow harness, FBIF observability, and MPU/LIBOS device surfaces in the device model, plus refreshed boot/machine/profile data (tu104-570.144), the qemu-line results, and the new unit tests. Docs 00/06/35-41 updated to match; harness helpers and .gitignore included. --- debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md | 127 ++++++++++++++++----- 1 file changed, 98 insertions(+), 29 deletions(-) (limited to 'debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md') diff --git a/debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md b/debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md index 9516fd5..2741748 100644 --- a/debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md +++ b/debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md @@ -7,7 +7,7 @@ > `06-ACCURACY.md`. Development-only: nothing here authorises GPU hardware, > kernel modules, mdev, guest access or reboots. > -> **See also:** `37-SIM-QEMU-LINE.md` (the B0–B2 device model on the frozen +> **See also:** `37-SIM-QEMU-LINE.md` (the B0–B3 device model on the frozen > simulator-vs-QEMU checks) and `40-NEW-VERSION-TRIAGE.md` (one-command triage > of a new image through this ladder). @@ -47,13 +47,15 @@ sim/devices/__init__.py registry_from_profile(profile, machine) │ FBIF control/transcfg ├─ priscv.py RISC-V-local block: IRQ mask, trace, error regs ├─ timer.py PTIMER (modeled virtual time; opt-in) - ├─ csb.py CSB/TRANSCOM message window seam (B3 landing) + ├─ csb.py CSB/TRANSCOM message window seam (B4 landing) ├─ fwsec.py WPR2 address pair (FWSEC state unmodeled) ├─ pmu.py PMU stub: observes, never fakes ├─ dma.py aperture → RAM translation seam (FBGPA/SYSGPA) └─ irq.py falcon + RISC-V-local IRQ aggregation ▼ sim/machine.py Machine(devices=..., translator=..., reset_regs=...) + │ + sim/mpu.py: XMPU entry model (fetch/load/store) + │ + sim/libos.py: libos syscall service model ▼ sim/gsp_boot.py bounded real-firmware run + session + rung ``` @@ -80,7 +82,7 @@ noted; every unlisted offset is unhandled and logged. | RISC-V local | `+0x111000`, alias `0x300001000` | `IRQMSET` 0x2ac, `IRQMCLR` 0x2b0, `IRQMASK` 0x2b4 (read), `IRQDEST` 0x2b8, `IRQTYPE` 0x2bc, `TRACECTL` 0x344, `TRACE_RDIDX` 0x348, `TRACE_WTIDX` 0x34c, `PRIV_ERR_STAT` 0x360, `PRIV_ERR_INFO` 0x364 (read), `PRIV_ERR_ADDR` 0x368 (read), `HUB_ERR_STAT` 0x36c | header + observed | | PTIMER | `+0x9000` | `TIME_0` aperture 0x9400 / block `+0x400` (NSEC 31:5), `TIME_1` aperture 0x9410 / block `+0x410` (NSEC 28:0); value = instruction count × `ns_per_insn` (**modeled**, not calibrated) | header + **modeled** time | | WPR2 | `+0x1FA000` | `WPR2_ADDR_LO` 0x1FA824, `WPR2_ADDR_HI` 0x1FA828 (4 KiB alignment; HI points at the last 128 KiB block) | header + firmware use | -| CSB/TRANSCOM | profile-declared | none by default; a profile may declare individual registers with width/value and `rw`/`ro`/`wo`/`w1c`/`set`/`clear` semantics | none yet (B3 landing point) | +| CSB/TRANSCOM | profile-declared | none by default; a profile may declare individual registers with width/value and `rw`/`ro`/`wo`/`w1c`/`set`/`clear` semantics | none yet (B4 landing point) | | PMU | profile-declared | none; the stub records accesses and returns unhandled | none yet | | DMA seam | FBGPA/SYSGPA (profile) | GPU-physical → RAM windows for falcon DMA and aperture loads/stores; unmapped transfers are refused and recorded | address-map header + firmware use | @@ -99,10 +101,15 @@ and records each as an assumption in `session.json.boot_shim.assumptions`: * `NV_RISCV_BOOT_PARAMS` at a RAM address (bootType `NVRM`, size 120, version 1, core-dump buffer, one `WPRM` libos init arg); * a `GspFwWprMeta` (magic/revision/verified and a self-consistent FB layout); -* raw-image mirror at the DMEM aperture base so the app's own relocation copy - is self-consistent (it is not: see §6); -* `a0`–`a5` register arguments (bootargs PA/VA, ELF PA/size, load base, WPR - id). +* a `libos_bootstrap_args_t` — six `u64` fields (IMEM and DMEM + `base`/`size`/`bs`) the real bootloader builds from its TCM configuration + and passes in `a0`; the M-mode TCM configuration is declared in the + profile (offsets 0, 64 KiB windows). This replaces the earlier + raw-image mirror at the DMEM aperture: with the XMPU model (§3.2) the + resident-section copy reads its real backing and no image mirror is needed; +* `a0`–`a5` register arguments (a0 = bootstrap-args pointer; the rest are + declared assumptions the boot path only uses where its own computation is + not self-sufficient). The DEBUGINFO **host-consume** behaviour is also a shim: the firmware writes a boot-stage marker and polls bits 22:20 until they clear, and no self-clearing @@ -110,28 +117,86 @@ path exists in the firmware, so the host side is modelled as consuming the stage marker on read. The device never changes a read value except for this documented shim, and the shim is profile-enabled and session-recorded. +### 3.2 CPU protection unit (XMPU) — core model, `sim/mpu.py` + +The GSP core boots in bare mode and then enables a small protection unit +through M-mode CSRs; the firmware programs entries for its code/data ranges +and, during init, for a scratch window it copies resident sections through. +Evidence kind: **hardware CSR/field header + firmware use + observed access +sequence** (the 570.144 bootstrap writes are visible in any run's device/CSR +trace). Modelled CSRs: + +| CSR | value | semantics | +|---|---|---| +| `MMPUIDX` (0x7C8) | entry index | INDEX field 5:0 (64 entries) | +| `MMPUVA` (0x7C9) | virtual base | BASE 63:12, `VLD` bit 0 | +| `MMPUPA` (0x7CA) | physical base | BASE 63:12 | +| `MMPURNG` (0x7CB) | range (a size) | RANGE 63:12 | +| `MMPUATTR` (0x7CC) | attributes | recorded, not enforced | + +Translation is active only when `mstatus.VM == NVMPU` (`0x1E`; field bits +28:24); lookup is lowest-index-first (the firmware's own bootstrap comment +documents the precedence). An entry maps `addr` to `pa + (addr - va_base)` +when `va_base <= addr < va_base + range`. The model applies to instruction +fetch, loads, stores and atomics; permissions/cache attributes are recorded +but not enforced, and **no match is treated as identity** (more lenient than +silicon) because the simulator keeps local memory flat and the boot path's +mappings are self-consistent. `read_mem`/`write_mem` (debugger surface) +remain raw and untranslated. + +Evidence in the session: `session.json.mpu` carries the enabled flag, entry +table and `mstatus`. The model removes the B2 ceiling: with the correct +bootstrap args and the scratch mapping, the resident-section copy reads the +in-place image and the run continues (no DMEM image mirror, no faked copy). + +### 3.3 libos syscall surface — service model, `sim/libos.py` + +The application is a libos task that requests kernel services with `ecall` +(the syscall number in `t0`). The native machine stops on `ecall` unless a +handler is installed, so the boot runner attaches a deterministic service +model that: + +* records every request (number, name, arguments, pc, instruction count) in + `session.json.libos`; +* acknowledges only no-ops and modelled services — task yield, cache + invalidation, external-interrupt arming, profiler enable/query, the + modeled timer read, DMA bounce-buffer registration (`TCM` region), + inverted-pagetable registration, and DMA copy/flush (an in-RAM copy in the + flat model: the real kernel drives the falcon DMA through TCM, the copy + effect is identical and is not faked — both sides must resolve or the run + stops); +* **never fakes waits**: `processor_suspend`, `task_port_send_recv_wait`, + `task_panic` and every unimplemented syscall stop the run with `R_ECALL` + and are reported in `session.json.libos.stopped`. + +Task switching, interrupt delivery, pagination, partition switch and memory +queries are not modelled (force-stopped, not invented). The B3 result is +exactly this stop: the message-loop task blocks in `task_port_send_recv_wait` +with `armed_mask=0xFF`. + ## 4. Boot ladder results (observed, 2026-10-03) | Rung | Claim | Evidence | |---|---|---| | **B0** | flat image loads; first MMIO is the unmodelled PMC `BOOT_42` read at `0x2000_0000_0000_0A00` | `tests/test_sim_devices.py` (B0 classification) | | **B1** | device model serves the whole boot path: **0 unknown MMIO, 0 unhandled in windows**, deterministic 2M-instruction run stopping at the boot-halt/report loop (`0x4010928`) | `run_boot(use_shim=False)`; sessions `dumps/gsp-boot-b1-run1`, `dumps/gsp-boot-b1-run2`; test `test_no_shim_b1_device_model_serves_boot` | -| **B2** | with the shim, the firmware passes the chip gate, MPU/vector setup, the bootargs consumer (`0x4007A30`) and reaches its **relocation copy** (`0x4007B7C`), then stops at a known unknown (DMEM runspace) | sessions `dumps/gsp-boot-b2-run1`, `dumps/gsp-boot-b2-run2`; test `test_shim_reaches_relocation_b2` | +| **B2** | with the shim, the firmware passes the chip gate, XMPU setup, the bootstrap-args consumer and its **relocation copy** (`0x4007B7C`); the XMPU model resolves the copy's real backing and the run continues into libos init | sessions `dumps/gsp-boot-b2-run1`, `dumps/gsp-boot-b2-run2`; test `test_shim_reaches_relocation_plus_mmu` | +| **B3** | libos init completes (TCM registration `0x5B22000`/`0x1000`, pagetable registration, DMA flush, external-interrupt arming `0xFF`) and the message-loop task blocks in the port wait | sessions `dumps/gsp-boot-b3-run1`, `dumps/gsp-boot-b3-run2`; test `test_shim_reaches_message_loop_b3` | Reproduction (read-only; no hardware): ```bash cd open-iov/debug/risc-v-gsp-rm-vm -python3 -m sim.gsp_boot --instructions 2000000 --record dumps/gsp-boot-b2-run1 -python3 -m unittest tests.test_sim_devices -v +python3 -m sim.gsp_boot --instructions 2000000 --record dumps/gsp-boot-b3-run1 +python3 -m unittest tests.test_sim_devices tests.test_mpu -v ``` Two-run determinism is asserted by `test_two_runs_identical` and was checked -at the recorded B2 sessions: identical instruction count (14 682), stop -reason/PC (`fault @ 0x400EA0C`), device-state SHA-256 -(`3fa264b9…`) and register file. The B1 no-shim run is likewise identical -across runs (2 000 000 instructions, budget stop at `0x4010928`, device-state -SHA-256 `88eb07b7…`). +at the recorded B3 sessions: identical instruction count (474 043), stop +reason/PC (`ecall @ 0x4018038`, syscall +`task_port_send_recv_wait`), device-state SHA-256 (`5b3cc7e4…`) and register +file. The B1 no-shim run is likewise identical across runs (2 000 000 +instructions, budget stop at `0x4010928`, device-state SHA-256 `88eb07b7…`). ## 5. Session evidence @@ -139,21 +204,24 @@ Each `run_boot` invocation writes a `greeneye-session/1` directory with: * `session.json` — firmware SHA-256, profile name, load layout, rung, milestones, stop reason/PC, unknown-MMIO counts, device-state SHA-256, - device-access counts and the full boot-shim assumption list; + device-access counts, the full boot-shim assumption list, the XMPU state + (`mpu`: enabled, entries, mstatus) and the libos service log (`libos`: + calls, counts, TCM/pagetable registrations, the stopping request); * `blobs/device-state.json` — the `greeneye-sim-devices/1` snapshot of every device (registers, IRQ state, DMA windows/counters, shim flags); * `events.jsonl` — the normal session event stream (`gsp_boot` note). ## 6. Unknowns (explicitly not modeled / not verified) -1. **DMEM runspace content.** The app's relocation copy reads the DMEM - aperture (`0x1_0000_0000+`) and writes it to the `0x400E000` runtime - region; the bootloader-provided content is not reconstructed, so the copy - writes zeros and the run stops at `0x400EA0C` (illegal instruction). This - is the B2 ceiling today. -2. **CSB/TRANSCOM register semantics.** No message-window poll has been - observed yet, so no register is modeled. B3 waits for the first observed - poll; the `csb.py` seam and profile register declarations are in place. +1. **CSB/TRANSCOM register semantics.** No message-window poll has been + observed yet, so no register is modeled. The 570.144 firmware blocks in + the message-loop port wait before any CSB access; B4 waits for the first + host-traffic-triggered poll; the `csb.py` seam and profile register + declarations are in place. +2. **Libos services.** Task switching, interrupt delivery, pagination, + partition switch and memory queries are not modelled: the service model + records and force-stops on them (never invents progress). DMA copies are + modelled as in-RAM copies in the flat memory model. 3. **PMU.** No PMU register access observed; stub only. 4. **FWSEC state machine.** Only the WPR2 address pair is modeled; FWSEC execution is not. @@ -161,14 +229,15 @@ Each `run_boot` invocation writes a `greeneye-session/1` directory with: not proven; only the profile's DMA windows decide what an address means. 6. **Interrupt delivery.** Falcon/RISC-V IRQ state is aggregated and serialisable, but mstatus/mie/mip/mtvec trap entry and interrupt injection - are not modelled. + are not modelled (the firmware arms external interrupts with mask `0xFF` + and then waits forever in the flat model). 7. **PTIMER calibration.** Values are modeled from the instruction count with a placeholder `ns_per_insn`; no firmware PTIMER read has been observed and no timing claim is made. -8. **MPU translation.** The firmware programs its MPU CSRs (observed), but - the machine does not enforce translations; the libos private mapping is - served by explicit device aliases and the FBGPA/SYSGPA seam, not by an MPU - model. +8. **XMPU enforcement.** Translation, VLD gating, mstatus enable and + lowest-index precedence are modelled; permissions, cache/coherence + attributes and WPR IDs are recorded but not enforced, and an unmatched + access passes through as identity (lenient by design, listed here). Nothing in this page is a silicon-accuracy claim; `06-ACCURACY.md` fidelity levels apply (L0/L1 modelled, L2 modelled-not-calibrated, L3 partial and -- cgit v1.2.3