# 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 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().__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()