summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py80
1 files changed, 80 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py b/debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py
new file mode 100644
index 0000000..8dcf633
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/devices/fwsec.py
@@ -0,0 +1,80 @@
+"""WPR2 / FWSEC state registers.
+
+Evidence kind: **hardware register/field header + firmware use** (the boot
+flow locates the firmware write-protected region from these registers on
+pre-Hopper parts; the host/booter writes them, the firmware reads them).
+
+Modelled registers (offsets in the FB/PFB register aperture):
+
+* ``NV_PFB_PRI_MMU_WPR2_ADDR_LO`` ``0x1FA824``: WPR2 start, bits ``31:4``
+ shifted by ``NV_PFB_PRI_MMU_WPR2_ADDR_LO_ALIGNMENT = 12`` (4 KiB).
+* ``NV_PFB_PRI_MMU_WPR2_ADDR_HI`` ``0x1FA828``: points at the start of the
+ last 128 KiB block of the WPR, same alignment; the end is
+ ``(value << 12) + 0x20000``.
+
+A profile that wants the firmware to see a WPR declares ``wpr2_start`` /
+``wpr2_end``; without a declaration the window is unhandled and logged.
+FWSEC-specific state is not modelled (no proven register use yet).
+"""
+from __future__ import annotations
+
+from .base import Device
+
+WPR2_ADDR_LO = 0x1FA824
+WPR2_ADDR_HI = 0x1FA828
+WPR2_ALIGNMENT = 12
+WPR2_BLOCK = 0x20000
+
+
+class Wpr2Block(Device):
+ """WPR2 address pair as seen by the firmware (host/booter-written)."""
+
+ name = "fwsec"
+
+ def __init__(self, *, base: int = 0, size: int = 0x1000,
+ wpr2_start: int | None = None, wpr2_end: int | None = None):
+ super().__init__(base=base, size=size)
+ self.reset()
+ if wpr2_start is not None and wpr2_end is not None:
+ self.set_range(wpr2_start, wpr2_end)
+
+ def set_range(self, start: int, end: int) -> None:
+ if end <= start:
+ raise ValueError("WPR2 end must be greater than start")
+ self.regs[WPR2_ADDR_LO] = (start >> WPR2_ALIGNMENT) & 0xFFFFFFFF
+ self.regs[WPR2_ADDR_HI] = ((end - WPR2_BLOCK) >> WPR2_ALIGNMENT) & 0xFFFFFFFF
+
+ @property
+ def start(self) -> int | None:
+ """WPR2 start derived from ADDR_LO (``None`` when unset)."""
+ lo = self.regs.get(WPR2_ADDR_LO)
+ return None if lo is None else lo << WPR2_ALIGNMENT
+
+ @property
+ def end(self) -> int | None:
+ """WPR2 end derived from ADDR_HI (``None`` when unset)."""
+ hi = self.regs.get(WPR2_ADDR_HI)
+ return None if hi is None else (hi << WPR2_ALIGNMENT) + WPR2_BLOCK
+
+ def reset(self) -> None:
+ self.regs: dict[int, int] = {}
+
+ def read(self, offset: int, size: int) -> int | None:
+ if size == 4 and offset in self.regs:
+ return self.regs[offset]
+ return None
+
+ def write(self, offset: int, size: int, value: int) -> bool:
+ if size == 4 and offset in (WPR2_ADDR_LO, WPR2_ADDR_HI):
+ self.regs[offset] = value & 0xFFFFFFFF
+ return True
+ return False
+
+ def state(self) -> dict:
+ return {"regs": {str(k): v for k, v in sorted(self.regs.items())},
+ "start": self.start, "end": self.end}
+
+ def load_state(self, data: dict) -> None:
+ # start/end are derived from regs; serialised copies are ignored so a
+ # restored block can never report a stale range.
+ self.regs = {int(k): int(v) for k, v in data.get("regs", {}).items()}