# gdb-helpers/rp2040.py - GDB helpers for the RP2040 CPU. # # Copyright (C) 2024-2025 Luke T. Shumaker # SPDX-License-Identifier: AGPL-3.0-or-later import typing import gdb 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.ljust(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(RP2040ShowInterrupts, self).__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 and 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) } {{Application,Execution,Interrupt}} 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(RP2040ShowDMA, self).__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 = f""" ╓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()