summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/falcon.py
blob: 88419061c5594ebb00f7361a3e1e8f7ed3278633 (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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
"""GSP falcon register block (boot polling + handshake + DMA).

Evidence kind: **hardware register/field header + firmware use + observed
access pattern.**

The block is reached through the PRIV aperture (``PRIV + 0x110000``) and, in
the libos kernel's private mapping, at virtual base ``0x3_0000_0000`` (the
firmware accesses both forms during boot).  Modelled registers, all with
header offsets relative to the block:

======================================  ======  =====  =========================
register                                offset  access semantics
======================================  ======  =====  =========================
``FALCON_IRQSSET``                      0x000   W      ``irq_status |= value``
``FALCON_IRQSCLR``                      0x004   W      ``irq_status &= ~value``
``FALCON_IRQSTAT``                      0x008   R      ``irq_status``
``FALCON_MAILBOX0``                     0x040   RW     storage
``FALCON_MAILBOX1``                     0x044   RW     storage
``FALCON_OS``                           0x080   RW     storage
``FALCON_DEBUGINFO``                    0x094   RW     storage (+ host shim)
``FALCON_CPUCTL``                       0x100   RW     storage
``FALCON_BOOTVEC``                      0x104   RW     storage
``FALCON_DMACTL``                       0x10c   RW     storage
``FALCON_DMATRFBASE``                   0x110   RW     storage
``FALCON_DMATRFMOFFS``                  0x114   RW     storage
``FALCON_DMATRFCMD``                    0x118   RW     transfer on write
``FALCON_DMATRFFBOFFS``                 0x11c   RW     storage
``FALCON_DMATRFBASE1``                  0x128   RW     storage
``FBIF_TRANSCFG(0..7)``                 0x600   RW     storage
``FBIF_CTL``                            0x624   RW     storage
======================================  ======  =====  =========================

Observed firmware use (570.144): the boot code writes MAILBOX0 (progress
code), MAILBOX1, DEBUGINFO (boot-stage marker ``0x11DEAD``) and IRQSSET
``HALT`` (bit 4), then polls DEBUGINFO bits ``22:20`` until they clear; it
reads back DMACTL/DMATRFCMD/DMATRFBASE/DMATRFBASE1/DMATRFMOFFS/
DMATRFFBOFFS and IRQSTAT.  The DMA transfer semantics (size encoding, IMEM/
WRITE direction, ``((base1<<32|base)<<8)+fboffs`` addressing) follow the
register header fields and are exercised only through the translation seam
(:mod:`sim.devices.dma`); an unmapped transfer is refused and recorded, never
zero-filled.

The DEBUGINFO "host consume" behaviour is a **boot shim**, not a device
claim: the firmware's own write-then-poll loop has no self-clearing path, and
the documented debug channel is write-then-wait-for-zero (consumer is the
host side).  It is enabled per profile and recorded as an assumption.
"""
from __future__ import annotations

from .base import Device

# Register offsets (header values, relative to the falcon block base).
IRQSSET = 0x000
IRQSCLR = 0x004
IRQSTAT = 0x008
MAILBOX0 = 0x040
MAILBOX1 = 0x044
OS = 0x080
DEBUGINFO = 0x094
CPUCTL = 0x100
BOOTVEC = 0x104
DMACTL = 0x10C
DMATRFBASE = 0x110
DMATRFMOFFS = 0x114
DMATRFCMD = 0x118
DMATRFFBOFFS = 0x11C
DMATRFBASE1 = 0x128
FBIF_TRANSCFG0 = 0x600
FBIF_TRANSCFG_STRIDE = 4
FBIF_TRANSCFG_COUNT = 8
FBIF_CTL = 0x624

# DMATRFCMD fields (header).
CMD_FULL = 1 << 0
CMD_IDLE = 1 << 1
CMD_IMEM = 1 << 4
CMD_WRITE = 1 << 5
CMD_SIZE_SHIFT = 8
CMD_SIZE_MASK = 0x7
CMD_CTXDMA_SHIFT = 12
CMD_CTXDMA_MASK = 0x7
CMD_ERROR = 1 << 20

#: IRQSSET/IRQSCLR bit 4 is HALT in the header's field list; the firmware's
#: boot handshake sets it after writing MAILBOX0/DEBUGINFO.
IRQ_HALT = 1 << 4

# IMEM/DMEM apertures of the GSP RISC-V address map (header constants).
AMAP_IMEM_START = 0x0000000000000000
AMAP_DMEM_START = 0x0000000100000000

_RW_REGISTERS = {
    MAILBOX0, MAILBOX1, OS, DEBUGINFO, CPUCTL, BOOTVEC, DMACTL, DMATRFBASE,
    DMATRFMOFFS, DMATRFCMD, DMATRFFBOFFS, DMATRFBASE1, FBIF_CTL,
}


class FalconBlock(Device):
    """Falcon/GSP register block with boot-handshake and DMA semantics."""

    name = "falcon"

    def __init__(self, *, base: int = 0, size: int = 0x1000, dma=None,
                 debuginfo_host_consume: bool = False,
                 debuginfo_stage_mask: int = 0x700000,
                 debuginfo_stage_shift: int = 20):
        super().__init__(base=base, size=size)
        self.dma = dma
        self.debuginfo_host_consume = bool(debuginfo_host_consume)
        self.debuginfo_stage_mask = debuginfo_stage_mask
        self.debuginfo_stage_shift = debuginfo_stage_shift
        self.reset()

    def reset(self) -> None:
        self.irq_status = 0
        self.regs: dict[int, int] = {off: 0 for off in _RW_REGISTERS}
        self.regs[DMATRFCMD] = CMD_IDLE
        self.transcfg = [0] * FBIF_TRANSCFG_COUNT
        self.dma_transfers = 0
        self.dma_refused = 0
        self.debuginfo_consumed = 0

    # -- access ------------------------------------------------------------

    def read(self, offset: int, size: int) -> int | None:
        if size != 4:
            return None
        if offset == IRQSTAT:
            return self.irq_status
        if offset == DEBUGINFO:
            value = self.regs.get(DEBUGINFO, 0)
            if self.debuginfo_host_consume:
                stage = (value & self.debuginfo_stage_mask) >> self.debuginfo_stage_shift
                if stage:
                    # Boot shim: the host consumes the stage marker (documented
                    # write-then-wait-for-zero debug channel); the rest of the
                    # register is preserved.
                    self.regs[DEBUGINFO] = value & ~self.debuginfo_stage_mask
                    self.debuginfo_consumed += 1
            return value
        if offset in self.regs:
            return self.regs[offset]
        if FBIF_TRANSCFG0 <= offset < FBIF_TRANSCFG0 + 4 * FBIF_TRANSCFG_COUNT:
            return self.transcfg[(offset - FBIF_TRANSCFG0) // 4]
        return None

    def write(self, offset: int, size: int, value: int) -> bool:
        if size != 4:
            return False
        value &= 0xFFFFFFFF
        if offset == IRQSSET:
            self.irq_status = (self.irq_status | value) & 0xFFFFFFFF
            return True
        if offset == IRQSCLR:
            self.irq_status = (self.irq_status & ~value) & 0xFFFFFFFF
            return True
        if offset == IRQSTAT:
            return False  # read-only
        if offset == DMATRFCMD:
            self.regs[DMATRFCMD] = value
            self._dma_transfer(value)
            return True
        if offset in self.regs:
            self.regs[offset] = value
            return True
        if FBIF_TRANSCFG0 <= offset < FBIF_TRANSCFG0 + 4 * FBIF_TRANSCFG_COUNT:
            self.transcfg[(offset - FBIF_TRANSCFG0) // 4] = value
            return True
        return False

    def peek(self, offset: int, size: int) -> int | None:
        # No shim side effects for debugger reads.
        if size != 4:
            return None
        if offset == IRQSTAT:
            return self.irq_status
        if offset in self.regs:
            return self.regs[offset]
        if FBIF_TRANSCFG0 <= offset < FBIF_TRANSCFG0 + 4 * FBIF_TRANSCFG_COUNT:
            return self.transcfg[(offset - FBIF_TRANSCFG0) // 4]
        return None

    # -- DMA ---------------------------------------------------------------

    def _dma_transfer(self, cmd: int) -> None:
        """Execute one falcon DMA transfer (header field semantics)."""
        if self.dma is None:
            self.dma_refused += 1
            return
        size = 1 << (((cmd >> CMD_SIZE_SHIFT) & CMD_SIZE_MASK) + 2)
        tcm = AMAP_IMEM_START if (cmd & CMD_IMEM) else AMAP_DMEM_START
        base = ((self.regs.get(DMATRFBASE1, 0) & 0x1FF) << 32) | self.regs.get(DMATRFBASE, 0)
        fb = (base << 8) + self.regs.get(DMATRFFBOFFS, 0)
        tcm_off = self.regs.get(DMATRFMOFFS, 0) & 0xFFFF
        if cmd & CMD_WRITE:
            ok = self.dma.copy(tcm + tcm_off, fb, size)
        else:
            ok = self.dma.copy(fb, tcm + tcm_off, size)
        if ok:
            self.dma_transfers += 1
            self.regs[DMATRFCMD] = (cmd | CMD_IDLE) & ~CMD_ERROR
        else:
            self.dma_refused += 1
            self.regs[DMATRFCMD] = (cmd & ~CMD_IDLE) | CMD_ERROR

    # -- state -------------------------------------------------------------

    def state(self) -> dict:
        return {
            "irq_status": self.irq_status,
            "regs": {str(k): v for k, v in sorted(self.regs.items())},
            "transcfg": list(self.transcfg),
            "dma_transfers": self.dma_transfers,
            "dma_refused": self.dma_refused,
            "debuginfo_consumed": self.debuginfo_consumed,
            "debuginfo_host_consume": self.debuginfo_host_consume,
        }

    def load_state(self, data: dict) -> None:
        self.irq_status = int(data.get("irq_status", 0))
        regs = {int(k): int(v) for k, v in data.get("regs", {}).items()}
        self.regs = {off: regs.get(off, 0) for off in _RW_REGISTERS}
        self.transcfg = [int(v) for v in data.get("transcfg", [0] * FBIF_TRANSCFG_COUNT)]
        self.dma_transfers = int(data.get("dma_transfers", 0))
        self.dma_refused = int(data.get("dma_refused", 0))
        self.debuginfo_consumed = int(data.get("debuginfo_consumed", 0))
        self.debuginfo_host_consume = bool(
            data.get("debuginfo_host_consume", self.debuginfo_host_consume))