mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
ubc: basic User Break Controller driver
This commit is contained in:
parent
0bea485f4b
commit
aa0ff7b10b
8 changed files with 407 additions and 52 deletions
|
@ -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
|
||||
|
|
|
@ -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
87
include/gint/mpu/ubc.h
Normal 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
54
include/gint/ubc.h
Normal 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 */
|
|
@ -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";
|
||||
|
|
|
@ -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
89
src/ubc/ubc.S
Normal 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
132
src/ubc/ubc.c
Normal 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);
|
Loading…
Reference in a new issue