summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/csb.py
blob: 818a263fa70a01c00f10f9a60df865a1e677ac9c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
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()}