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

View file

@ -9,6 +9,8 @@
extern "C" {
#endif
#include <stdint.h>
/* 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

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 <string.h>
#include "gdb_private.h"
static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size)
{
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);