From aa0ff7b10b5fefa31994c75894f54483548cf236 Mon Sep 17 00:00:00 2001 From: redoste Date: Wed, 24 May 2023 17:24:08 +0200 Subject: [PATCH] ubc: basic User Break Controller driver --- CMakeLists.txt | 3 + include/gint/gdb.h | 42 +++++++++++++ include/gint/mpu/ubc.h | 87 +++++++++++++++++++++++++++ include/gint/ubc.h | 54 +++++++++++++++++ src/gdb/gdb.c | 2 - src/gdb/gdb_private.h | 50 ---------------- src/ubc/ubc.S | 89 +++++++++++++++++++++++++++ src/ubc/ubc.c | 132 +++++++++++++++++++++++++++++++++++++++++ 8 files changed, 407 insertions(+), 52 deletions(-) create mode 100644 include/gint/mpu/ubc.h create mode 100644 include/gint/ubc.h delete mode 100644 src/gdb/gdb_private.h create mode 100644 src/ubc/ubc.S create mode 100644 src/ubc/ubc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 5449fb4..bc90558 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -124,6 +124,9 @@ set(SOURCES_COMMON src/tmu/inth-tmu.s src/tmu/sleep.c src/tmu/tmu.c + # UBC driver + src/ubc/ubc.c + src/ubc/ubc.S # USB driver src/usb/asyncio.c src/usb/classes/ff-bulk.c diff --git a/include/gint/gdb.h b/include/gint/gdb.h index 41b6e3c..acdedc8 100644 --- a/include/gint/gdb.h +++ b/include/gint/gdb.h @@ -9,6 +9,8 @@ extern "C" { #endif +#include + /* Error codes for GDB functions */ enum { GDB_NO_INTERFACE = -1, @@ -16,6 +18,46 @@ enum { GDB_USB_ERROR = -3, }; +/* gdb_cpu_state_t: State of the CPU when breaking + This struct keep the same register indices as those declared by GDB to allow + easy R/W without needing a "translation" table. + See : https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/sh-tdep.c; + h=c402961b80a0b4589243023ea5362d43f644a9ec;hb=4f3e26ac6ee31f7bc4b04abd + 8bdb944e7f1fc5d2#l327 + */ +// TODO : Should we expose r*b*, ssr, spc ? are they double-saved when breaking +// inside an interrupt handler ? +typedef struct { + union { + struct { + uint32_t r0; + uint32_t r1; + uint32_t r2; + uint32_t r3; + uint32_t r4; + uint32_t r5; + uint32_t r6; + uint32_t r7; + uint32_t r8; + uint32_t r9; + uint32_t r10; + uint32_t r11; + uint32_t r12; + uint32_t r13; + uint32_t r14; + uint32_t r15; + uint32_t pc; + uint32_t pr; + uint32_t gbr; + uint32_t vbr; + uint32_t mach; + uint32_t macl; + uint32_t sr; + } reg; + uint32_t regs[23]; + }; +} gdb_cpu_state_t; + /* gdb_start(): Start the GDB remote serial protocol server This function will start the GDB remote serial protocol implementation and diff --git a/include/gint/mpu/ubc.h b/include/gint/mpu/ubc.h new file mode 100644 index 0000000..4dacb87 --- /dev/null +++ b/include/gint/mpu/ubc.h @@ -0,0 +1,87 @@ +//--- +// gint:mpu:ubc - User Break Controller +//--- + +#ifndef GINT_MPU_UBC +#define GINT_MPU_UBC + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +typedef volatile struct +{ + lword_union(CBR0, /* Match condition setting 0 */ + uint32_t MFE :1; /* Match Flag Enable */ + uint32_t AIE :1; /* ASID Enable */ + uint32_t MFI :6; /* Match Flag Specify */ + uint32_t AIV :8; /* ASID Specify */ + uint32_t :1; + uint32_t SZ :3; /* Operand Size Select */ + uint32_t :4; + uint32_t CD :2; /* Bus Select */ + uint32_t ID :2; /* Instruction Fetch / Operand Access Select */ + uint32_t :1; + uint32_t RW :2; /* Bus Command Select */ + uint32_t CE :1; /* Channel Enable */ + ); + lword_union(CRR0, /* Match operation setting 0 */ + uint32_t :30; + uint32_t PCB :1; /* PC Break Select */ + uint32_t BIE :1; /* Break Enable */ + ); + uint32_t CAR0; /* Match address setting 0 */ + uint32_t CAMR0; /* Match address mask setting 0 */ + pad(0x10); + + + lword_union(CBR1, /* Match condition setting 1 */ + uint32_t MFE :1; /* Match Flag Enable */ + uint32_t AIE :1; /* ASID Enable */ + uint32_t MFI :6; /* Match Flag Specify */ + uint32_t AIV :8; /* ASID Specify */ + uint32_t DBE :1; /* Data Value Enable */ + uint32_t SZ :3; /* Operand Size Select */ + uint32_t ETBE :1; /* Execution Count Value Enable */ + uint32_t :3; + uint32_t CD :2; /* Bus Select */ + uint32_t ID :2; /* Instruction Fetch / Operand Access Select */ + uint32_t :1; + uint32_t RW :2; /* Bus Command Select */ + uint32_t CE :1; /* Channel Enable */ + ); + lword_union(CRR1, /* Match operation setting 1 */ + uint32_t :30; + uint32_t PCB :1; /* PC Break Select */ + uint32_t BIE :1; /* Break Enable */ + ); + uint32_t CAR1; /* Match address setting 1 */ + uint32_t CAMR1; /* Match address mask setting 1 */ + uint32_t CDR1; /* Match data setting 1 */ + uint32_t CDMR1; /* Match data mask setting 1 */ + lword_union(CETR1, /* Execution count break 1 */ + uint32_t :20; + uint32_t CET :12; /* Execution Count */ + ); + pad(0x5c4); + + lword_union(CCMFR, /* Channel match flag */ + uint32_t :30; + uint32_t MF1 :1; /* Channel 1 Condition Match Flag */ + uint32_t MF0 :1; /* Channel 0 Condition Match Flag */ + ); + pad(0x1c); + lword_union(CBCR, /* Break control */ + uint32_t :31; + uint32_t UBDE :1; /* User Break Debugging Support Function Enable */ + ); +} GPACKED(4) sh7305_ubc_t; +#define SH7305_UBC (*(sh7305_ubc_t *)0xff200000) + +#ifdef __cplusplus +} +#endif + +#endif /* GINT_MPU_UBC */ diff --git a/include/gint/ubc.h b/include/gint/ubc.h new file mode 100644 index 0000000..d858948 --- /dev/null +++ b/include/gint/ubc.h @@ -0,0 +1,54 @@ +//--- +// gint:ubc - User Break Controller driver +//--- + +#ifndef GINT_UBC +#define GINT_UBC + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Read and write the DBR register */ +void ubc_setDBR(void* DBR); +void* ubc_getDBR(void); + +/* ubc_dbh(): Low level UBC debug handler + The handler will backup the current CPU state and call ubc_debug_handler(). */ +void ubc_dbh(void); +/* ubc_debug_handler(): C level UBC debug handler + The execution will be redirected to the handler set by the user or the add-in + will panic in case no handler has been set. */ +void ubc_debug_handler(gdb_cpu_state_t* cpu_state); +/* ubc_set_debug_handler(): Set user debug handler + Set a custom debug handler that will be called when a break condition from + the UBC is reached. */ +void ubc_set_debug_handler(void (*h)(gdb_cpu_state_t*)); + +/* UBC Breakpoint types */ +typedef enum { + UBC_BREAK_BEFORE, /* Break before the instruction is executed */ + UBC_BREAK_AFTER, /* Break after the instruction is executed : + at this point PC will point to the next instruction. */ +} ubc_break_mode_t; + +/* ubc_set_breakpoint(): Set a breakpoint in a UBC channel and enable it + Return false when an invalid channel number is provided, true if the + breakpoint was correctly set up. */ +bool ubc_set_breakpoint(int channel, void* break_address, ubc_break_mode_t break_mode); +/* ubc_get_break_address(): Get a breakpoint address if it's enabled + If the channel is disabled the function will return false and *break_address + will not be updated. */ +bool ubc_get_break_address(int channel, void** break_address); +/* ubc_disable_channel(): Disable a UBC channel + Return true on success. If an invalid channel number is provided, it will + return false. */ +bool ubc_disable_channel(int channel); + +#ifdef __cplusplus +} +#endif + +#endif /* GINT_UBC */ diff --git a/src/gdb/gdb.c b/src/gdb/gdb.c index 5b95416..68782ab 100644 --- a/src/gdb/gdb.c +++ b/src/gdb/gdb.c @@ -8,8 +8,6 @@ #include #include -#include "gdb_private.h" - static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size) { const char* hex = "0123456789ABCDEF"; diff --git a/src/gdb/gdb_private.h b/src/gdb/gdb_private.h deleted file mode 100644 index 04474ee..0000000 --- a/src/gdb/gdb_private.h +++ /dev/null @@ -1,50 +0,0 @@ -//--- -// gint:gdb:gdb-private - Private definitions for the GDB implementation -//--- - -#ifndef GINT_GDB_PRIVATE -#define GINT_GDB_PRIVATE - -#include - -/* gdb_cpu_state_t: State of the CPU when breaking - This struct keep the same register indices as those declared by GDB to allow - easy R/W without needing a "translation" table. - See : https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=gdb/sh-tdep.c; - h=c402961b80a0b4589243023ea5362d43f644a9ec;hb=4f3e26ac6ee31f7bc4b04abd - 8bdb944e7f1fc5d2#l327 - */ -// TODO : Should we expose r*b*, ssr, spc ? are they double-saved when breaking -// inside an interrupt handler ? -typedef struct { - union { - struct { - uint32_t r0; - uint32_t r1; - uint32_t r2; - uint32_t r3; - uint32_t r4; - uint32_t r5; - uint32_t r6; - uint32_t r7; - uint32_t r8; - uint32_t r9; - uint32_t r10; - uint32_t r11; - uint32_t r12; - uint32_t r13; - uint32_t r14; - uint32_t r15; - uint32_t pc; - uint32_t pr; - uint32_t gbr; - uint32_t vbr; - uint32_t mach; - uint32_t macl; - uint32_t sr; - } reg; - uint32_t regs[23]; - }; -} gdb_cpu_state_t; - -#endif /* GINT_GDB_PRIVATE */ diff --git a/src/ubc/ubc.S b/src/ubc/ubc.S new file mode 100644 index 0000000..da31041 --- /dev/null +++ b/src/ubc/ubc.S @@ -0,0 +1,89 @@ +.global _ubc_setDBR +.global _ubc_getDBR +.text + +_ubc_setDBR: + ldc r4, dbr + rts + nop + +_ubc_getDBR: + stc dbr, r0 + rts + nop + +.global _ubc_dbh +_ubc_dbh: + /* We backup registers in the correct order to build gdb_cpu_state_t */ + stc.l ssr, @-r15 + sts.l macl, @-r15 + sts.l mach, @-r15 + stc.l vbr, @-r15 + stc.l gbr, @-r15 + sts.l pr, @-r15 + stc.l spc, @-r15 + stc.l sgr, @-r15 + mov.l r14, @-r15 + mov.l r13, @-r15 + mov.l r12, @-r15 + mov.l r11, @-r15 + mov.l r10, @-r15 + mov.l r9, @-r15 + mov.l r8, @-r15 + stc.l R7_BANK, @-r15 + stc.l R6_BANK, @-r15 + stc.l R5_BANK, @-r15 + stc.l R4_BANK, @-r15 + stc.l R3_BANK, @-r15 + stc.l R2_BANK, @-r15 + stc.l R1_BANK, @-r15 + stc.l R0_BANK, @-r15 + + /* Enable interrupts and switch register bank + Original SR is kept in r8 */ + stc sr, r8 + mov r8, r1 + mov.l .sr_mask, r0 + and r0, r1 + ldc r1, sr + + mov r15, r4 + mov.l .handler, r0 + jsr @r0 + nop + + /* Restore original SR to access the correct register bank */ + ldc r8, sr + + ldc.l @r15+, R0_BANK + ldc.l @r15+, R1_BANK + ldc.l @r15+, R2_BANK + ldc.l @r15+, R3_BANK + ldc.l @r15+, R4_BANK + ldc.l @r15+, R5_BANK + ldc.l @r15+, R6_BANK + ldc.l @r15+, R7_BANK + mov.l @r15+, r8 + mov.l @r15+, r9 + mov.l @r15+, r10 + mov.l @r15+, r11 + mov.l @r15+, r12 + mov.l @r15+, r13 + mov.l @r15+, r14 + ldc.l @r15+, sgr + ldc.l @r15+, spc + lds.l @r15+, pr + ldc.l @r15+, gbr + ldc.l @r15+, vbr + lds.l @r15+, mach + lds.l @r15+, macl + ldc.l @r15+, ssr + rte + nop + +.align 4 +.handler: .long _ubc_debug_handler +.sr_mask: .long ~0x300000f0 /* IMASK = 0 : mask no interrupts + BL = 0 : do not block interrupts + RB = 0 : use register BANK0 + */ diff --git a/src/ubc/ubc.c b/src/ubc/ubc.c new file mode 100644 index 0000000..26b8021 --- /dev/null +++ b/src/ubc/ubc.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include +#include +#include + +#define UBC SH7305_UBC +#define POWER SH7305_POWER + +static bool hpowered(void) +{ + return POWER.MSTPCR0.UDB == 0; +} + +static void hpoweron(void) +{ + // Power on the UBC via MSTPCR0 + POWER.MSTPCR0.UDB = 0; + + // Set the DBR register + ubc_setDBR(ubc_dbh); + + // Disable break channel 0 and 1 + UBC.CBR0.CE = 0; + UBC.CBR1.CE = 0; + + // Enable user break debugging support (i.e. usage of the DBR register) + UBC.CBCR.UBDE = 1; +} + +static void hpoweroff(void) +{ + POWER.MSTPCR0.UDB = 1; + + UBC.CBR0.CE = 0; + UBC.CBR1.CE = 0; +} + +#define UBC_BREAK_CHANNEL(CBR, CRR, CAR, CAMR) do { \ + UBC.CBR.MFE = 0; /* Don't include Match Flag in match condition */ \ + UBC.CBR.AIE = 0; /* Don't include ASID check in match condition */ \ + UBC.CBR.MFI = 0; /* Default value of MFI is reserved, make it legal */ \ + UBC.CBR.SZ = 0; /* Match on any operand size */ \ + UBC.CBR.CD = 0; /* Match on operand bus access */ \ + UBC.CBR.ID = 1; /* Match on instruction fetch */ \ + UBC.CBR.RW = 1; /* Match on read cycle */ \ + \ + UBC.CRR.PCB = pcb; /* Set PC break {before,after} instruction execution */ \ + UBC.CRR.BIE = 1; /* Break when channel matches */ \ + \ + UBC.CAR = (uint32_t)break_address; /* Match address */ \ + UBC.CAMR = 0; /* Match mask (0 bits are included) */ \ + UBC.CBR.CE = 1; /* Enable channel */ \ +} while (0) +bool ubc_set_breakpoint(int channel, void* break_address, ubc_break_mode_t break_mode) +{ + if (!hpowered()) + hpoweron(); + + uint32_t pcb = break_mode == UBC_BREAK_AFTER ? 1 : 0; + if (channel == 0) { + UBC_BREAK_CHANNEL(CBR0, CRR0, CAR0, CAMR0); + return true; + } else if (channel == 1) { + UBC_BREAK_CHANNEL(CBR1, CRR1, CAR1, CAMR1); + return true; + } else { + return false; + } +} +#undef UBC_BREAK_CHANNEL + +bool ubc_get_break_address(int channel, void** break_address) +{ + if (!hpowered()) + hpoweron(); + + if (channel == 0 && UBC.CBR0.CE) { + *break_address = (void*) UBC.CAR0; + return true; + } else if (channel == 1 && UBC.CBR1.CE) { + *break_address = (void*) UBC.CAR1; + return true; + } else { + return false; + } +} + +bool ubc_disable_channel(int channel) +{ + if (!hpowered()) + hpoweron(); + + if (channel == 0) { + UBC.CBR0.CE = 0; + return true; + } else if (channel == 1) { + UBC.CBR1.CE = 0; + return true; + } else { + return false; + } +} + +static void (*ubc_application_debug_handler)(gdb_cpu_state_t*) = NULL; +void ubc_debug_handler(gdb_cpu_state_t* cpu_state) +{ + // Clear match flags + UBC.CCMFR.lword = 0; + + if (ubc_application_debug_handler != NULL) { + ubc_application_debug_handler(cpu_state); + } else { + // TODO : Should we add a custom panic code ? + gint_panic(-1); + } +} + +// TODO : Should we use the struct designed for GDB or make it more generic ? +void ubc_set_debug_handler(void (*h)(gdb_cpu_state_t*)) { + ubc_application_debug_handler = h; +} + +gint_driver_t drv_ubc = { + .name = "UBC", + .hpowered = hpowered, + .hpoweron = hpoweron, + .hpoweroff = hpoweroff, + .flags = GINT_DRV_SHARED, +}; +GINT_DECLARE_DRIVER(30, drv_ubc);