summaryrefslogtreecommitdiff
path: root/gdb-helpers
diff options
context:
space:
mode:
Diffstat (limited to 'gdb-helpers')
-rw-r--r--gdb-helpers/libcr.py493
-rw-r--r--gdb-helpers/rp2040.py295
2 files changed, 788 insertions, 0 deletions
diff --git a/gdb-helpers/libcr.py b/gdb-helpers/libcr.py
new file mode 100644
index 0000000..6f95a81
--- /dev/null
+++ b/gdb-helpers/libcr.py
@@ -0,0 +1,493 @@
+# gdb-helpers/libcr.py - GDB helpers for libcr.
+#
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+import contextlib
+import time
+import typing
+
+import gdb # pylint: disable=import-error
+import gdb.unwinder # pylint: disable=import-error
+
+# GDB helpers ##################################################################
+
+# https://sourceware.org/bugzilla/show_bug.cgi?id=32428
+gdb_bug_32428 = True
+
+
+class _gdb_Locus(typing.Protocol):
+ @property
+ def frame_unwinders(self) -> list["gdb._Unwinder"]: ...
+
+
+def gdb_unregister_unwinder(
+ locus: gdb.Objfile | gdb.Progspace | None, unwinder: "gdb._Unwinder"
+) -> None:
+ _locus: _gdb_Locus = typing.cast(_gdb_Locus, gdb) if locus is None else locus
+ _locus.frame_unwinders.remove(unwinder)
+ gdb.invalidate_cached_frames()
+
+
+def gdb_is_on_os() -> bool:
+ try:
+ gdb.execute("info proc", to_string=True)
+ return True
+ except gdb.error:
+ return False
+
+
+class gdb_JmpBuf:
+ """Our own in-Python GDB-specific implementation of `jmp_buf`"""
+
+ level: int
+ registers: dict[str, str]
+
+
+def gdb_setjmp() -> gdb_JmpBuf:
+ """Our own in-Python GDB-specific implementation of `setjmp()`"""
+ buf = gdb_JmpBuf()
+ buf.level = gdb.selected_frame().level()
+ gdb.execute("select-frame level 0")
+ buf.registers = {}
+ for line in gdb.execute("info registers", to_string=True).split("\n"):
+ words = line.split(maxsplit=2)
+ if len(words) < 2:
+ continue
+ buf.registers[words[0]] = words[1]
+ gdb.execute(f"select-frame level {buf.level}")
+ return buf
+
+
+def gdb_longjmp(buf: gdb_JmpBuf) -> None:
+ """Our own in-Python GDB-specific implementation of `longjmp()`"""
+
+ gdb.execute("select-frame level 0")
+
+ if (
+ ("sp" in buf.registers)
+ and ("msp" in buf.registers)
+ and ("psp" in buf.registers)
+ and ("control" in buf.registers)
+ ):
+ # On ARM, 'sp' is an alias for either 'msp' or 'psp'
+ # (depending on 'control'&(1<<1)). We must set all 3 before
+ # fussing with 'xPSR' or frames, or GDB will get upset at us
+ # about "Invalid state".
+ gdb.execute(f"set $sp = {buf.registers['sp']}", to_string=True)
+ gdb.execute(f"set $msp = {buf.registers['msp']}")
+ gdb.execute(f"set $psp = {buf.registers['psp']}")
+
+ for reg, val in buf.registers.items():
+ gdb.execute(f"set ${reg} = {val}")
+ gdb.invalidate_cached_frames()
+
+ gdb.execute(f"select-frame level {buf.level}")
+
+
+# Core libcr functionality #####################################################
+
+
+class CrGlobals:
+ main: "CrMain"
+ coroutines: list["CrCoroutine"]
+ _breakpoint: "CrBreakpoint"
+ _known_threads: set[gdb.InferiorThread]
+
+ def __init__(self) -> None:
+ num = int(
+ gdb.parse_and_eval("sizeof(coroutine_table)/sizeof(coroutine_table[0])")
+ )
+
+ self.main = CrMain(self)
+ self.coroutines = [CrCoroutine(self, i + 1) for i in range(num)]
+
+ self._breakpoint = CrBreakpoint()
+ self._breakpoint.enabled = False
+
+ self._known_threads = set()
+
+ gdb.events.cont.connect(self._on_cont)
+
+ def delete(self) -> None:
+ self.coroutines = []
+ self._breakpoint.delete()
+ gdb.events.cont.disconnect(self._on_cont)
+
+ def readjmp(self, env_ptr_expr: str) -> gdb_JmpBuf:
+ self._breakpoint.enabled = True
+ gdb.execute(f"call (void)cr_gdb_readjmp({env_ptr_expr})")
+ self._breakpoint.enabled = False
+ if gdb_is_on_os():
+ gdb.execute("queue-signal SIGWINCH")
+ return self._breakpoint.env
+
+ def _on_cont(self, event: gdb.Event) -> None:
+ cur_threads = set(gdb.selected_inferior().threads())
+ if cur_threads - self._known_threads:
+ # Ignore thread creation events.
+ self._known_threads = cur_threads
+ return
+ if not self.coroutine_running.is_selected():
+ if gdb_bug_32428:
+ print("Must return to running coroutine before continuing.")
+ print("Hit ^C twice then run:")
+ print(f" cr select {self.coroutine_running.cid}")
+ while True:
+ time.sleep(1)
+ assert self.coroutine_running.cont_env
+ gdb_longjmp(self.coroutine_running.cont_env)
+ self.main.cont_env = None
+ for cr in self.coroutines:
+ cr.cont_env = None
+
+ def is_valid_cid(self, cid: int) -> bool:
+ return (0 < cid <= len(self.coroutines)) and (
+ self.coroutines[cid - 1].state != self.CR_NONE
+ )
+
+ @property
+ def coroutine_running(self) -> "CrMain | CrCoroutine":
+ cid = int(gdb.parse_and_eval("coroutine_running"))
+ if not self.is_valid_cid(cid):
+ return self.main
+ return self.coroutines[cid - 1]
+
+ @property
+ def coroutine_selected(self) -> "CrMain | CrCoroutine":
+ for cr in self.coroutines:
+ if cr.is_selected():
+ return cr
+ return self.main
+
+ @property
+ def CR_NONE(self) -> gdb.Value:
+ return gdb.parse_and_eval("CR_NONE")
+
+ @property
+ def CR_RUNNING(self) -> gdb.Value:
+ return gdb.parse_and_eval("CR_RUNNING")
+
+ def select(self, cr: "CrMain | CrCoroutine", level: int = -1) -> None:
+ self.coroutine_selected.cont_env = gdb_setjmp()
+
+ if cr.cont_env:
+ gdb_longjmp(cr.cont_env)
+ else:
+ env: gdb_JmpBuf
+ if cr == self.coroutine_running:
+ assert False # cr.cont_env should have been set
+ match cr:
+ case CrMain():
+ env = self.readjmp("&coroutine_main_env")
+ case CrCoroutine():
+ if cr.state == self.CR_RUNNING:
+ env = self.readjmp("&coroutine_add_env")
+ else:
+ env = self.readjmp(f"&coroutine_table[{cr.cid-1}].env")
+ gdb_longjmp(env)
+ cr_select_top_frame()
+
+ @contextlib.contextmanager
+ def with_selected(self, cr: "CrMain | CrCoroutine") -> typing.Iterator[None]:
+ saved_env = gdb_setjmp()
+ self.select(cr)
+ try:
+ yield
+ finally:
+ gdb_longjmp(saved_env)
+
+
+class CrBreakpointUnwinder(gdb.unwinder.Unwinder):
+ """Used to temporarily disable unwinding so that
+ gdb/breakpoint.c:check_longjmp_breakpoint_for_call_dummy() doesn't
+ prematurely garbage collect the `call`-dummy-frame.
+
+ """
+
+ def __init__(self) -> None:
+ super().__init__("cr_breakpoint_unwinder")
+
+ # The .pyi is wrong; it says `Frame` instead of `PendingFrame`.
+ def __call__(self, pending_frame: gdb.PendingFrame) -> gdb.UnwindInfo | None:
+ # Stop unwinding with stop_reason=UNWIND_NO_SAVED_PC by
+ # returning an UnwindInfo that doesn't have
+ # `.add_saved_register("pc", ...)`.
+ return pending_frame.create_unwind_info(
+ gdb.unwinder.FrameId(
+ sp=pending_frame.read_register("sp"),
+ pc=pending_frame.read_register("pc"),
+ )
+ )
+
+
+class CrBreakpoint(gdb.Breakpoint):
+ env: gdb_JmpBuf
+ _unwinder_locus: gdb.Objfile
+ _unwinder: CrBreakpointUnwinder
+
+ def __init__(self) -> None:
+ self.env = gdb_JmpBuf()
+
+ self._unwinder = CrBreakpointUnwinder()
+ readjmp_sym = gdb.lookup_global_symbol("cr_gdb_readjmp")
+ assert readjmp_sym
+ self._unwinder_locus = readjmp_sym.symtab.objfile
+ gdb.unwinder.register_unwinder(self._unwinder_locus, self._unwinder, True)
+ self._unwinder.enabled = False
+
+ super().__init__(
+ function="cr_gdb_breakpoint", type=gdb.BP_BREAKPOINT, internal=True
+ )
+
+ @property
+ def enabled(self) -> bool:
+ return super().enabled
+
+ @enabled.setter
+ def enabled(self, value: bool) -> None:
+ self._unwinder.enabled = value
+ # Use a dunder-call to avoid an infinite loop.
+ # pylint: disable=unnecessary-dunder-call
+ gdb.Breakpoint.enabled.__set__(self, value) # type: ignore
+
+ def stop(self) -> bool:
+ assert self._unwinder.enabled
+ self._unwinder.enabled = False
+ self.env = gdb_setjmp()
+ self._unwinder.enabled = True
+ return False # don't stop
+
+ def delete(self) -> None:
+ gdb_unregister_unwinder(self._unwinder_locus, self._unwinder)
+ super().delete()
+
+
+def cr_select_top_frame() -> None:
+ gdb.execute("select-frame level 0")
+ base_frame = gdb.selected_frame()
+ while True:
+ fn = gdb.selected_frame().name()
+ if fn and (fn.startswith("cr_") or fn.startswith("_cr_")):
+ older = gdb.selected_frame().older()
+ if not older:
+ base_frame.select()
+ break
+ older.select()
+ else:
+ break
+
+
+class CrMain:
+ cr_globals: CrGlobals
+ cont_env: gdb_JmpBuf | None
+
+ def __init__(self, cr_globals: CrGlobals) -> None:
+ self.cr_globals = cr_globals
+ self.cont_env = None
+
+ @property
+ def cid(self) -> int:
+ return 0
+
+ def is_selected(self) -> bool:
+ return not any(cr.is_selected() for cr in self.cr_globals.coroutines)
+
+
+class CrCoroutine:
+ cr_globals: CrGlobals
+ cid: int
+ cont_env: gdb_JmpBuf | None
+
+ def __init__(self, cr_globals: CrGlobals, cid: int) -> None:
+ self.cr_globals = cr_globals
+ self.cid = cid
+ self.cont_env = None
+
+ @property
+ def state(self) -> gdb.Value:
+ return gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].state")
+
+ @property
+ def name(self) -> str:
+ bs: list[int] = [0] * int(gdb.parse_and_eval("sizeof(coroutine_table[0].name)"))
+ for i, _ in enumerate(bs):
+ bs[i] = int(gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].name[{i}]"))
+ return bytes(bs).decode("UTF-8").split("\x00", maxsplit=1)[0]
+
+ def is_selected(self) -> bool:
+ sp = int(gdb.parse_and_eval("$sp"))
+ lo = int(gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].stack"))
+ hi = lo + int(gdb.parse_and_eval(f"coroutine_table[{self.cid-1}].stack_size"))
+ return lo <= sp < hi
+
+
+# User-facing commands #########################################################
+
+
+class CrCommand(gdb.Command):
+ """Use this command for libcr coroutines."""
+
+ cr_globals: CrGlobals
+
+ def __init__(self, cr_globals: CrGlobals) -> None:
+ self.cr_globals = cr_globals
+ gdb.Command.__init__(self, "cr", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE, True)
+
+ def invoke(self, arg: str, from_tty: bool) -> None:
+ gdb.execute("help cr")
+
+
+class CrListCommand(gdb.Command):
+ """List libcr coroutines.
+ Usage: cr list
+
+ In the output:
+ - the 'R' marker indicates the currently-running coroutine
+ - the 'G' marker indicates the coroutine that GDB is viewing; this may be changed with `cr select`
+ """
+
+ cr_globals: CrGlobals
+
+ def __init__(self, cr_globals: CrGlobals) -> None:
+ self.cr_globals = cr_globals
+ gdb.Command.__init__(self, "cr list", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
+
+ def invoke(self, arg: str, from_tty: bool) -> None:
+ argv = gdb.string_to_argv(arg)
+ if len(argv) != 0:
+ raise gdb.GdbError("Usage: cr list")
+
+ rows: list[tuple[str, str, str, str, str]] = [
+ ("", "Id", "Name", "State", "Frame")
+ ]
+ for cid in range(len(self.cr_globals.coroutines) + 1):
+ cr: CrMain | CrCoroutine = (
+ self.cr_globals.coroutines[cid - 1] if cid else self.cr_globals.main
+ )
+ if isinstance(cr, CrCoroutine) and cr.state == self.cr_globals.CR_NONE:
+ continue
+ rows += [
+ (
+ "".join(
+ [
+ "R" if cr == self.cr_globals.coroutine_running else " ",
+ "G" if cr.is_selected() else " ",
+ ]
+ ),
+ str(cr.cid),
+ repr(cr.name) if isinstance(cr, CrCoroutine) else "-",
+ str(cr.state) if isinstance(cr, CrCoroutine) else "-",
+ self._pretty_frame(cr, from_tty),
+ )
+ ]
+
+ widths: list[int] = [
+ max(len(row[col]) for row in rows) for col in range(len(rows[0]))
+ ]
+
+ def line(row: tuple[str, str, str, str, str]) -> str:
+
+ def cell(col: int) -> str:
+ return row[col].ljust(widths[col])
+
+ return f"{cell(0)} {cell(1)} {cell(2)} {cell(3)} {row[4]}"
+
+ maxline = 0
+ if screenwidth := gdb.parameter("width"):
+ assert isinstance(screenwidth, int)
+ maxline = max(screenwidth, len(line(rows[0])))
+
+ for row in rows:
+ l = line(row)
+ if maxline and len(l) > maxline:
+ l = l[:maxline]
+ print(l)
+
+ def _pretty_frame(self, cr: CrMain | CrCoroutine, from_tty: bool) -> str:
+ try:
+ with self.cr_globals.with_selected(cr):
+ saved_level = gdb.selected_frame().level()
+ cr_select_top_frame()
+ full = gdb.execute("frame", from_tty=from_tty, to_string=True)
+ gdb.execute(f"select-frame level {saved_level}")
+ except Exception as e: # pylint: disable=broad-exception-caught
+ full = "#0 err: " + str(e)
+ line = full.split("\n", maxsplit=1)[0]
+ return line.split(maxsplit=1)[1]
+
+
+class CrSelectCommand(gdb.Command):
+ """Select the coroutine that GDB is viewing
+ Usage: cr select COROUTINE
+ COROUTINE is either a coroutine ID or coroutine name."""
+
+ cr_globals: CrGlobals
+
+ def __init__(self, cr_globals: CrGlobals) -> None:
+ self.cr_globals = cr_globals
+ gdb.Command.__init__(self, "cr select", gdb.COMMAND_RUNNING, gdb.COMPLETE_NONE)
+
+ def invoke(self, arg: str, from_tty: bool) -> None:
+ argv = gdb.string_to_argv(arg)
+ if len(argv) != 1:
+ raise gdb.GdbError("Usage: cr select COROUTINE")
+ cr = self._find(argv[0])
+ self.cr_globals.select(cr)
+ gdb.execute("frame")
+
+ def _find(self, name: str) -> CrMain | CrCoroutine:
+ if name.isnumeric():
+ cid = int(name)
+ if cid == 0:
+ return self.cr_globals.main
+ if self.cr_globals.is_valid_cid(cid):
+ return self.cr_globals.coroutines[cid - 1]
+ crs: list[CrCoroutine] = []
+ for cr in self.cr_globals.coroutines:
+ if cr.state != self.cr_globals.CR_NONE and cr.name == name:
+ crs += [cr]
+ match len(crs):
+ case 0:
+ raise gdb.GdbError(f"No such coroutine: {name!r}")
+ case 1:
+ return crs[0]
+ case _:
+ raise gdb.GdbError(f"Ambiguous name, must use Id: {name!r}")
+
+
+# Wire it all in ###############################################################
+
+_cr_globals: CrGlobals | None = None
+
+
+def cr_initialize() -> None:
+ global _cr_globals
+ if _cr_globals:
+ old = _cr_globals
+ new = CrGlobals()
+ new.main.cont_env = old.main.cont_env
+ for i in range(min(len(old.coroutines), len(new.coroutines))):
+ new.coroutines[i].cont_env = old.coroutines[i].cont_env
+ old.delete()
+ _cr_globals = new
+ else:
+ _cr_globals = CrGlobals()
+ CrCommand(_cr_globals)
+ CrListCommand(_cr_globals)
+ CrSelectCommand(_cr_globals)
+
+
+def cr_on_new_objfile(event: gdb.Event) -> None:
+ if any(
+ objfile.lookup_global_symbol("cr_gdb_readjmp") for objfile in gdb.objfiles()
+ ):
+ print("Initializing libcr integration...")
+ cr_initialize()
+ gdb.events.new_objfile.disconnect(cr_on_new_objfile)
+
+
+if _cr_globals:
+ cr_initialize()
+else:
+ gdb.events.new_objfile.connect(cr_on_new_objfile)
diff --git a/gdb-helpers/rp2040.py b/gdb-helpers/rp2040.py
new file mode 100644
index 0000000..f019299
--- /dev/null
+++ b/gdb-helpers/rp2040.py
@@ -0,0 +1,295 @@
+# gdb-helpers/rp2040.py - GDB helpers for the RP2040 CPU.
+#
+# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+# SPDX-License-Identifier: AGPL-3.0-or-later
+
+import typing
+
+import gdb # pylint: disable=import-error
+
+
+def read_mmreg(addr: int) -> int:
+ return int(gdb.parse_and_eval(f"*(uint32_t*)({addr})"))
+
+
+def read_reg(name: str) -> int:
+ return int(gdb.selected_frame().read_register(name))
+
+
+def fmt32(x: int) -> str:
+ return "0b" + bin(x)[2:].rjust(32, "0")
+
+
+def box(title: str, content: str) -> str:
+ width = 80
+
+ lines = content.split("\n")
+ while len(lines) and lines[0] == "":
+ lines = lines[1:]
+ while len(lines) and lines[-1] == "":
+ lines = lines[:-1]
+ lines = ["", *lines, ""]
+
+ ret = "┏━[" + title + "]" + ("━" * (width - len(title) - 5)) + "┓\n"
+ for line in content.split("\n"):
+ ret += f"┃ {line:<{width-4}} ┃\n"
+ ret += "┗" + ("━" * (width - 2)) + "┛"
+ return ret
+
+
+def read_prio(addr: int) -> str:
+ prios: list[int] = 32 * [0]
+ for regnum in range(0, 8):
+ regval = read_mmreg(addr + (regnum * 4))
+ prios[(regnum * 4) + 0] = (regval >> (0 + 6)) & 0b11
+ prios[(regnum * 4) + 1] = (regval >> (8 + 6)) & 0b11
+ prios[(regnum * 4) + 2] = (regval >> (16 + 6)) & 0b11
+ prios[(regnum * 4) + 3] = (regval >> (24 + 6)) & 0b11
+ return " " + "".join(str(p) for p in reversed(prios))
+
+
+nvic_names = [
+ "TIMER_0", # 0
+ "TIMER_1", # 1
+ "TIMER_2", # 2
+ "TIMER_3", # 3
+ "PWM", # 4
+ "USB", # 5
+ "XIP", # 6
+ "PIO0_0", # 7
+ "PIO0_1", # 8
+ "PIO1_0", # 9
+ "PIO1_1", # 10
+ "DMA_0", # 11
+ "DMA_1", # 12
+ "IO_BANK0", # 13
+ "IO_QSPI", # 14
+ "SIO_PROC0", # 15
+ "SIO_PROC1", # 16
+ "CLOCKS", # 17
+ "SPI0", # 18
+ "SPI1", # 19
+ "UART0", # 20
+ "UART1", # 21
+ "ADC_FIFO", # 22
+ "I2C0", # 23
+ "I2C1", # 24
+ "RTC", # 25
+ "unused(26)", # 26
+ "unused(27)", # 27
+ "unused(28)", # 28
+ "unused(29)", # 29
+ "unused(30)", # 30
+ "unused(31)", # 31
+]
+
+exception_names = [
+ "none", # 0
+ "reset", # 1
+ "NMI", # 2
+ "hard fault", # 3
+ "reserved(4)", # 4
+ "reserved(5)", # 5
+ "reserved(6)", # 6
+ "reserved(7)", # 7
+ "reserved(8)", # 8
+ "reserved(9)", # 9
+ "reserved(10)", # 10
+ "SVCall", # 11
+ "reserved(12)", # 12
+ "reserved(13)", # 13
+ "PendSV", # 14
+ "SysTick", # 15
+ *[f"nvic_{nvic}" for nvic in nvic_names],
+]
+while len(exception_names) < 1 << 9:
+ exception_names += [f"unused({len(exception_names)})"]
+
+
+class RP2040ShowInterrupts(gdb.Command):
+ """Show the RP2040's interrupt state."""
+
+ def __init__(self) -> None:
+ super().__init__("rp2040-show-interrupts", gdb.COMMAND_USER)
+
+ def invoke(self, arg: str, from_tty: bool) -> None:
+ self.arm_cortex_m0plus_registers()
+ self.arm_cortex_m0plus_mmregisters()
+ self.rp2040_dma_mmregisters()
+
+ def arm_cortex_m0plus_mmregisters(self) -> None:
+ base: int = 0xE0000000
+ icsr = read_mmreg(base + 0xED04)
+ print(
+ box(
+ "ARM Cortex-M0+ memory-mapped registers",
+ f"""
+ clocks╖ ┌SIO
+ SPI┐ ║ │╓QSPI
+ UART┐│ ║ │║╓GPIO ╓XIP
+ ADC╖ ││ ║ │║║┌DMA ║╓USB
+ I2C┐ ║ ││ ║ │║║│ ┌PIO║║╓PWM
+ RTC╖├┐║┌┤├┐║┌┤║║├┐├──┐║║║┌──┬timers
+NVIC_ISER: {fmt32(read_mmreg(base+0xe100)) } Set-Enable
+NVIC_ICER: {fmt32(read_mmreg(base+0xe180)) } Clear-Enable
+NVIC_ISPR: {fmt32(read_mmreg(base+0xe200)) } Set-Pending
+NVIC_ICPR: {fmt32(read_mmreg(base+0xe280)) } Clear-Pending
+ ║├┤║├┤├┤║├┤║║├┤├──┤║║║├──┤
+NVIC_IPR-: {read_prio(base+0xe400) } Priority (0=highest, 3=lowest)
+
+ isr_pending╖
+ isr_preempt╖║
+ pend_st_clr╖ ║║
+ pend_st_set╖║ ║║
+ pend_sv_clr╖║║ ║║ ┌vect_pending
+ pend_sv_set╖║║║ ║║ | ┌vect_active
+ nmi_pend_set╖ ║║║║ ║║ ┌────┴──┐ ┌────┴──┐
+ICSR : {fmt32(icsr) } Control and State
+ └────┬──┘ └────┬──┘
+ | └{icsr&0x1FF} ({exception_names[icsr&0x1FF]})
+ └{(icsr>>11)&0x1FF} ({exception_names[(icsr>>11)&0x1FF]})
+
+ ╓endiannes (0=little, 1=big)
+ vect_key┐ ║ ╓sys_reset_req
+ ┌───────┴──────┐║ ║╓vect_clr_active
+AIRCR : {fmt32(read_mmreg(base+0xed0c)) } Application {{Interrupt+Reset}} Control
+
+ ╓sleep_deep
+ s_ev_on_pend╖ ║╓sleep_on_exit
+SCR : {fmt32(read_mmreg(base+0xed10)) } System Control
+""",
+ )
+ )
+
+ def arm_cortex_m0plus_registers(self) -> None:
+ psr = read_reg("xPSR")
+ print(
+ box(
+ "ARM Cortex-M0+ processor-core registers",
+ f"""
+ ╓pm (0=normal, 1=top priority)
+PRIMASK : {fmt32(read_reg('primask')) } Priority Mask
+
+ app exec intr
+ ┌┴─┐ ┌───────┴──────┐┌───┴───┐
+xPSR : {fmt32(psr) } {{App,Exec,Intr}} Program Status
+ ║║║║ ║ ║└───┬───┘
+ [N]egative╜║║║ ║ ║ └{psr&0x1FF} ({exception_names[psr&0x1FF]})
+ [Z]ero╜║║ ╙[T]humb ╙require [a]lignment
+ [C]arry╜║
+ o[V]erflow╜
+""",
+ )
+ )
+
+ def rp2040_dma_mmregisters(self) -> None:
+ base: int = 0x50000000
+
+ def fmt12(x: int) -> str:
+ s = fmt32(x)
+ return s[:-12] + "_" + s[-12:]
+
+ print(
+ box(
+ "RP2040 DMA memory-mapped registers",
+ f"""
+
+ 8 4 0
+ ┌──┴───┴───┤
+INTR : {fmt12(read_mmreg(base + 0x400))} Raw
+ │ │ │ │
+INTE0: {fmt12(read_mmreg(base + 0x404))} IRQ_DMA_0 Enable
+INTF0: {fmt12(read_mmreg(base + 0x408))} IRQ_DMA_0 Force
+INTS0: {fmt12(read_mmreg(base + 0x40c))} IRQ_DMA_0 Status
+ │ │ │ │
+INTE1: {fmt12(read_mmreg(base + 0x414))} IRQ_DMA_1 Enable
+INTF1: {fmt12(read_mmreg(base + 0x418))} IRQ_DMA_1 Force
+INTS1: {fmt12(read_mmreg(base + 0x41c))} IRQ_DMA_1 Status
+""",
+ )
+ )
+
+
+RP2040ShowInterrupts()
+
+
+class RP2040ShowDMA(gdb.Command):
+ """Show the RP2040's DMA control registers."""
+
+ def __init__(self) -> None:
+ super().__init__("rp2040-show-dma", gdb.COMMAND_USER)
+
+ def invoke(self, arg: str, from_tty: bool) -> None:
+ base: int = 0x50000000
+ u32_size: int = 4
+
+ nchan = read_mmreg(base + 0x448)
+
+ def chreg(
+ ch: int,
+ name: typing.Literal[
+ "read_addr",
+ "write_addr",
+ "trans_count",
+ "ctrl",
+ "dbg_ctdreq",
+ "dbg_tcr",
+ ],
+ ) -> int:
+ fieldcnt: int = 4 * 4
+ fieldnum: int
+ debug = False
+ match name:
+ case "read_addr":
+ fieldnum = 0
+ case "write_addr":
+ fieldnum = 1
+ case "trans_count":
+ fieldnum = 2
+ case "ctrl":
+ fieldnum = 4
+ case "dbg_ctdreq":
+ fieldnum = 0
+ debug = True
+ case "dbg_tcr":
+ fieldnum = 1
+ debug = True
+ return read_mmreg(
+ base
+ + (0x800 if debug else 0)
+ + (ch * u32_size * fieldcnt)
+ + (u32_size * fieldnum)
+ )
+
+ def ctrl(ch: int) -> str:
+ s = fmt32(chreg(ch, "ctrl"))
+ return s[:10] + "_" + s[10:]
+
+ def chaddr(ch: int, name: typing.Literal["read", "write"]) -> str:
+ val = chreg(ch, name + "_addr") # type: ignore
+ if val == 0:
+ return "NULL "
+ return f"0x{val:08x}"
+
+ ret = """
+ ╓sniff_enable
+ ║╓bswap
+ ║║╓irq_quiet
+ ║║║ ┌treq_sel
+ ║║║ │ ┌chain_to
+ ║║║ │ │ ╓ring_sel
+ ║║║ │ │ ║ ┌ring_size
+ ║║║ │ │ ║ │ ╓incr_write
+ busy╖ ║║║ │ │ ║ │ ║╓incr_read
+write_err╖ ║ ║║║ │ │ ║ │ ║║┌data_size
+read_err╖║ ║ ║║║ │ │ ║ │ ║║│ ╓high_priority
+ahb_err╖║║ ║ ║║║ │ │ ║ │ ║║│ ║╓enable
+ ║║║ ║ ║║║ │ │ ║ │ ║║│ ║║ trans_cnt
+ ║║║ ║ ║║║┌─┴──┐┌┴─┐║┌┴─┐║║├┐║║ read_addr write_addr cur/reload
+"""
+ for ch in range(0, nchan):
+ ret += f"{ch: 3}: {ctrl(ch)} {chaddr(ch, 'read')} {chaddr(ch, 'write')} {chreg(ch, 'trans_count')}/{chreg(ch, 'dbg_tcr')}\n"
+ print(box("RP2040 DMA channels", ret))
+
+
+RP2040ShowDMA()