summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/csb.py
diff options
context:
space:
mode:
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/devices/csb.py')
-rw-r--r--debug/risc-v-gsp-rm-vm/sim/devices/csb.py89
1 files changed, 89 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/devices/csb.py b/debug/risc-v-gsp-rm-vm/sim/devices/csb.py
new file mode 100644
index 0000000..818a263
--- /dev/null
+++ b/debug/risc-v-gsp-rm-vm/sim/devices/csb.py
@@ -0,0 +1,89 @@
+"""CSB / TRANSCOM message-window seam (B3 landing point).
+
+Evidence kind: **hardware register/field header + observed access pattern**
+(once the firmware reaches the message loop). At the time of writing the
+570.144 boot has not been observed polling the GSP message-queue CSB window,
+so this module deliberately models **no register semantics by default**: a
+window is only served when a profile declares it *and* lists the individual
+registers with their widths, reset values and semantics (``rw``, ``w1c``,
+``set``, ``clear``). Undeclared offsets stay unhandled and are logged.
+
+The CSB/TRANSCOM window carries the host<->GSP message queues and doorbells
+(head/tail pointers, interrupt set/clear); B3 requires the first such poll to
+return the documented read-back, which will be added here together with the
+observed access evidence and a test.
+"""
+from __future__ import annotations
+
+from .base import Device
+
+#: Per-register semantics accepted by a profile.
+SEMANTICS = ("rw", "ro", "wo", "w1c", "set", "clear")
+
+
+class CsbWindow(Device):
+ """Profile-declared CSB/mailbox register window (empty by default)."""
+
+ name = "csb"
+
+ def __init__(self, *, base: int = 0, size: int = 0x1000, registers=None):
+ super().__init__(base=base, size=size)
+ # registers: {offset: {"width": 4, "value": int, "semantics": "rw"}}
+ self.registers: dict[int, dict] = {}
+ for off, spec in (registers or {}).items():
+ self.declare(int(off), spec)
+ self.reset()
+
+ def declare(self, offset: int, spec: dict) -> None:
+ semantics = spec.get("semantics", "rw")
+ if semantics not in SEMANTICS:
+ raise ValueError(f"unknown CSB semantics {semantics!r}")
+ width = int(spec.get("width", 4))
+ if width not in (1, 2, 4, 8):
+ raise ValueError(f"unsupported CSB register width {width}")
+ self.registers[offset] = {
+ "width": width,
+ "reset": int(spec.get("value", 0)),
+ "semantics": semantics,
+ }
+
+ def reset(self) -> None:
+ self.values = {off: spec["reset"] for off, spec in self.registers.items()}
+
+ def _spec(self, offset: int, size: int) -> dict | None:
+ spec = self.registers.get(offset)
+ if spec is None or spec["width"] != size:
+ return None
+ return spec
+
+ def read(self, offset: int, size: int) -> int | None:
+ spec = self._spec(offset, size)
+ if spec is None or spec["semantics"] == "wo":
+ return None
+ return self.values.get(offset, 0)
+
+ def write(self, offset: int, size: int, value: int) -> bool:
+ spec = self._spec(offset, size)
+ if spec is None or spec["semantics"] == "ro":
+ return False
+ mask = (1 << (8 * size)) - 1
+ value &= mask
+ sem = spec["semantics"]
+ if sem == "w1c":
+ self.values[offset] = self.values.get(offset, 0) & ~value & mask
+ elif sem == "set":
+ self.values[offset] = (self.values.get(offset, 0) | value) & mask
+ elif sem == "clear":
+ self.values[offset] = (self.values.get(offset, 0) & ~value) & mask
+ else:
+ self.values[offset] = value
+ return True
+
+ def state(self) -> dict:
+ return {"values": {str(k): v for k, v in sorted(self.values.items())},
+ "declared": sorted(self.registers)}
+
+ def load_state(self, data: dict) -> None:
+ values = {int(k): int(v) for k, v in data.get("values", {}).items()}
+ self.values = {off: values.get(off, spec["reset"])
+ for off, spec in self.registers.items()}