summaryrefslogtreecommitdiffstats
path: root/debug/risc-v-gsp-rm-vm/sim/devices/falcon.py
diff options
context:
space:
mode:
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.py228
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))