diff options
Diffstat (limited to 'debug/risc-v-gsp-rm-vm/sim/devices/falcon.py')
| -rw-r--r-- | debug/risc-v-gsp-rm-vm/sim/devices/falcon.py | 228 |
1 files changed, 228 insertions, 0 deletions
diff --git a/debug/risc-v-gsp-rm-vm/sim/devices/falcon.py b/debug/risc-v-gsp-rm-vm/sim/devices/falcon.py new file mode 100644 index 0000000..8841906 --- /dev/null +++ b/debug/risc-v-gsp-rm-vm/sim/devices/falcon.py @@ -0,0 +1,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)) |
