ubc: basic User Break Controller driver

This commit is contained in:
redoste 2023-05-24 17:24:08 +02:00
parent 0bea485f4b
commit aa0ff7b10b
No known key found for this signature in database
8 changed files with 407 additions and 52 deletions

View file

@ -124,6 +124,9 @@ set(SOURCES_COMMON
src/tmu/inth-tmu.s src/tmu/inth-tmu.s
src/tmu/sleep.c src/tmu/sleep.c
src/tmu/tmu.c src/tmu/tmu.c
# UBC driver
src/ubc/ubc.c
src/ubc/ubc.S
# USB driver # USB driver
src/usb/asyncio.c src/usb/asyncio.c
src/usb/classes/ff-bulk.c src/usb/classes/ff-bulk.c

View file

@ -9,6 +9,8 @@
extern "C" { extern "C" {
#endif #endif
#include <stdint.h>
/* Error codes for GDB functions */ /* Error codes for GDB functions */
enum { enum {
GDB_NO_INTERFACE = -1, GDB_NO_INTERFACE = -1,
@ -16,6 +18,46 @@ enum {
GDB_USB_ERROR = -3, 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 /* gdb_start(): Start the GDB remote serial protocol server
This function will start the GDB remote serial protocol implementation and This function will start the GDB remote serial protocol implementation and

87
include/gint/mpu/ubc.h Normal file
View file

@ -0,0 +1,87 @@
//---
// gint:mpu:ubc - User Break Controller
//---
#ifndef GINT_MPU_UBC
#define GINT_MPU_UBC
#ifdef __cplusplus
extern "C" {
#endif
#include <gint/defs/types.h>
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 */

54
include/gint/ubc.h Normal file
View file

@ -0,0 +1,54 @@
//---
// gint:ubc - User Break Controller driver
//---
#ifndef GINT_UBC
#define GINT_UBC
#ifdef __cplusplus
extern "C" {
#endif
#include <gint/gdb.h>
/* 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 */

View file

@ -8,8 +8,6 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include "gdb_private.h"
static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size) static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size)
{ {
const char* hex = "0123456789ABCDEF"; const char* hex = "0123456789ABCDEF";

View file

@ -1,50 +0,0 @@
//---
// gint:gdb:gdb-private - Private definitions for the GDB implementation
//---
#ifndef GINT_GDB_PRIVATE
#define GINT_GDB_PRIVATE
#include <stdint.h>
/* 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 */

89
src/ubc/ubc.S Normal file
View file

@ -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
*/

132
src/ubc/ubc.c Normal file
View file

@ -0,0 +1,132 @@
#include <gint/drivers.h>
#include <gint/exc.h>
#include <gint/gdb.h>
#include <gint/mpu/power.h>
#include <gint/mpu/ubc.h>
#include <gint/ubc.h>
#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);