diff options
| author | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
|---|---|---|
| committer | Arthur Hanson Rasmusson <arthur@vgpu.io> | 2026-10-04 18:42:36 -0400 |
| commit | 1f2073d4fbb1d412d9bbf373312e3c51b83db92a (patch) | |
| tree | a661537d7ec9510324e0a40a5a65e4b8c853f01b /debug/risc-v-gsp-rm-vm/sim/mpu.py | |
| parent | c1adf2727983f5cace8ca0f5d1119ac2256c348e (diff) | |
| download | open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.tar.gz open-iov-1f2073d4fbb1d412d9bbf373312e3c51b83db92a.zip | |
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.
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/mpu.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/sim/mpu.py | 154 |
1 files changed, 154 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/mpu.py b/debug/risc-v-gsp-rm-vm/sim/mpu.py new file mode 100644 index 0000000..3f24358 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/sim/mpu.py @@ -0,0 +1,154 @@ +"""RISC-V MMU (NVMPU) CSR model for the GSP core. + +Evidence kind: **hardware CSR/field header + firmware use + observed access +sequence.** + +The GSP RISC-V core boots in *bare* mode and then enables a small protection +unit through CSRs; the firmware maps resident code/data into TCM and uses a +scratch window to copy it there. Without this model the flat-RAM simulator +serves those accesses at their virtual addresses and the relocated copy reads +the wrong backing (the B2 ceiling in ``docs/36-GSP-DEVICE-MODEL.md``). + +Modelled CSRs (M-mode values from the hardware CSR header; the firmware writes +exactly these - observed at the 570.144 bootstrap ``0x4007000``..``0x4007C00``): + +============================================ ====== ========================= +CSR value semantics +============================================ ====== ========================= +``MMPUIDX`` (0x7C8) entry INDEX field 5:0 +``MMPUVA`` (0x7C9) VA BASE 63:12, VLD bit 0 +``MMPUPA`` (0x7CA) PA BASE 63:12 +``MMPURNG`` (0x7CB) range RANGE 63:12 (a size) +``MMPUATTR`` (0x7CC) attr recorded, not enforced +``mstatus.VM`` (0x300) mode 0x1E = NVMPU (enabled) +============================================ ====== ========================= + +Semantics modelled: + +* an entry is *valid* when ``MMPUVA.VLD`` is set; writes to ``MMPUVA`` with + VLD clear disable the selected entry (the firmware's clear sequence); +* translation requires ``mstatus.VM == NVMPU`` (``0x1E``, field bits 28:24); + before the enable write the CSR entries exist but no access is translated; +* lookup is **lowest-index-first** (the firmware's own bootstrap comment: + "the MPU gives precedence to lower mappings"); a matching entry maps + ``addr`` to ``pa + (addr - va_base)`` when + ``va_base <= addr < va_base + range``; +* **no match is not a fault here**: the machine treats "no matching entry" as + identity. This is deliberately more lenient than silicon (which would + fault under the kernel's management) because the simulator keeps local + memory flat and the boot path's identity mappings are self-consistent; the + choice is recorded in the session and in ``docs/36`` ยง6. + +Permissions and cache/coherence attributes are recorded for evidence but not +enforced; no timing or side-channel claim is made. This module has no device +or host dependency and is deterministic. +""" +from __future__ import annotations + +#: CSR numbers (M-mode protection unit; hardware CSR header values). +MMPUIDX = 0x7C8 +MMPUVA = 0x7C9 +MMPUPA = 0x7CA +MMPURNG = 0x7CB +MMPUATTR = 0x7CC + +#: CSRs this model consumes when written by the guest. +WATCHED_CSRS = (MMPUIDX, MMPUVA, MMPUPA, MMPURNG, MMPUATTR) + +MSTATUS = 0x300 +MSTATUS_VM_SHIFT = 24 +MSTATUS_VM_MASK = 0x1F << MSTATUS_VM_SHIFT +#: mstatus.VM value that enables the protection unit (header addendum). +MSTATUS_VM_NVMPU = 0x1E + +#: Field masks (header: BASE/RANGE fields start at bit 12, 4 KiB granularity). +_PAGE_MASK = ~0xFFF & ((1 << 64) - 1) +_INDEX_MASK = 0x3F +_MAX_ENTRIES = 64 + + +class RiscvMpu: + """MMPU entry table + mstatus enable state (deterministic, serialisable).""" + + def __init__(self) -> None: + self.index = 0 + self.mstatus = 0 + self.entries: dict[int, dict] = {} + #: Ascending [(va_base, pa, range)] of valid entries; kept in sync on + #: every CSR write so translation is a short loop. + self._active: list[tuple[int, int, int]] = [] + self.enabled = False + + # -- guest CSR writes -------------------------------------------------- + + def on_csr_write(self, csr: int, value: int) -> None: + """Apply one guest CSR write (``value`` already masked to 64 bits).""" + if csr == MMPUIDX: + self.index = value & _INDEX_MASK + elif csr == MMPUVA: + entry = self.entries.setdefault(self.index, self._blank()) + entry["va"] = value & _PAGE_MASK + entry["valid"] = bool(value & 1) + self._refresh() + elif csr == MMPUPA: + entry = self.entries.setdefault(self.index, self._blank()) + entry["pa"] = value & _PAGE_MASK + elif csr == MMPURNG: + entry = self.entries.setdefault(self.index, self._blank()) + entry["range"] = value & _PAGE_MASK + elif csr == MMPUATTR: + entry = self.entries.setdefault(self.index, self._blank()) + entry["attr"] = value + elif csr == MSTATUS: + self.mstatus = value & ((1 << 64) - 1) + self.enabled = ((self.mstatus & MSTATUS_VM_MASK) >> MSTATUS_VM_SHIFT + == MSTATUS_VM_NVMPU) + + @staticmethod + def _blank() -> dict: + return {"va": 0, "pa": 0, "range": 0, "attr": 0, "valid": False} + + def _refresh(self) -> None: + self._active = [ + (e["va"], e["pa"], e["range"]) + for _, e in sorted(self.entries.items()) + if e.get("valid") and e.get("range") + ] + + # -- translation ------------------------------------------------------- + + @property + def active(self) -> bool: + """True when a matching entry could translate (fast-path switch).""" + return self.enabled and bool(self._active) + + def translate(self, addr: int) -> int | None: + """Virtual -> physical for ``addr``, or ``None`` when unmatched.""" + if not self.enabled: + return None + for va, pa, rng in self._active: + off = addr - va + if 0 <= off < rng: + return pa + off + return None + + # -- evidence / snapshot ---------------------------------------------- + + def state(self) -> dict: + return { + "enabled": self.enabled, + "mstatus": self.mstatus, + "index": self.index, + "entries": [ + {"index": i, **{k: e[k] for k in ("va", "pa", "range", "attr", "valid")}} + for i, e in sorted(self.entries.items()) + if e.get("valid") or e.get("pa") or e.get("range") or e.get("attr") + ], + } + + def reset(self) -> None: + self.index = 0 + self.mstatus = 0 + self.entries.clear() + self._active = [] + self.enabled = False |
