summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md')
-rw-r--r--debug/risc-v-gsp-rm-vm/docs/36-GSP-DEVICE-MODEL.md127
1 files changed, 98 insertions, 29 deletions
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