diff options
Diffstat (limited to 'gdb-helpers/rp2040.py')
-rw-r--r-- | gdb-helpers/rp2040.py | 295 |
1 files changed, 295 insertions, 0 deletions
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() |