mirror of
https://git.planet-casio.com/Lephenixnoir/gint.git
synced 2024-12-28 04:23:36 +01:00
Merge remote-tracking branch 'redoste/gdb' into dev (#27)
This commit is contained in:
commit
faead4bc1d
14 changed files with 1208 additions and 10 deletions
|
@ -70,6 +70,8 @@ set(SOURCES
|
|||
src/fs/fugue/fugue_rmdir.c
|
||||
src/fs/fugue/fugue_unlink.c
|
||||
src/fs/fugue/util.c
|
||||
# GDB remote serial protocol
|
||||
src/gdb/gdb.c
|
||||
# Gray engine
|
||||
src/gray/engine.c
|
||||
src/gray/gclear.c
|
||||
|
@ -227,6 +229,9 @@ set(SOURCES
|
|||
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
|
||||
|
|
|
@ -144,6 +144,19 @@ typedef struct {
|
|||
/* GINT_CALL_NULL: Empty function call */
|
||||
#define GINT_CALL_NULL ((gint_call_t){ .function = NULL, .args = {} })
|
||||
|
||||
/* GINT_CALL_FLAG(): Build an indirect call to an odd address
|
||||
|
||||
This is used as a workaround to have an extra flag in gint_call_t structures
|
||||
without altering their size and exploits the fact that SuperH code has to be
|
||||
aligned on even addresses.
|
||||
It is up to the caller to notice the presence of the flag and realign the
|
||||
address properly.
|
||||
|
||||
This flag is currently only checked by gint_inth_callback to specify that the
|
||||
called function will take the interrupt context as its first argument. */
|
||||
#define GINT_CALL_FLAG(func, ...) \
|
||||
GINT_CALL((void*)((uint32_t)(func) | 1), __VA_ARGS__)
|
||||
|
||||
/* gint_call(): Perform an indirect call */
|
||||
static GINLINE int gint_call(gint_call_t cb)
|
||||
{
|
||||
|
|
79
include/gint/gdb.h
Normal file
79
include/gint/gdb.h
Normal file
|
@ -0,0 +1,79 @@
|
|||
//---
|
||||
// gint:gdb - GDB remote serial protocol
|
||||
//---
|
||||
|
||||
#ifndef GINT_GDB
|
||||
#define GINT_GDB
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* Error codes for GDB functions */
|
||||
enum {
|
||||
GDB_NO_INTERFACE = -1,
|
||||
GDB_ALREADY_STARTED = -2,
|
||||
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
|
||||
block until the program is resumed from the connected debugger.
|
||||
It currently only supports USB communication and will fail if USB is already
|
||||
in use.*/
|
||||
int gdb_start(void);
|
||||
|
||||
/* gdb_main(): Main GDB loop
|
||||
|
||||
This function handles GDB messages sent over USB and will mutate the cpu_state
|
||||
struct, memory and the UBC configuration accordingly. */
|
||||
void gdb_main(gdb_cpu_state_t* cpu_state);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* GINT_GDB */
|
|
@ -155,10 +155,29 @@ static GINLINE void *gint_inthandler(int code, void const *h, size_t size) {
|
|||
.callback:
|
||||
.long _gint_inth_callback
|
||||
|
||||
The gint_call_t object can be built with GINT_CALL_FLAG to specify that the
|
||||
called function should get a pointer to a gint_inth_callback_context_t
|
||||
strcture as its first argument.
|
||||
|
||||
@call Address of a gint_call_t object
|
||||
Returns the return value of the callback. */
|
||||
extern int (*gint_inth_callback)(gint_call_t const *call);
|
||||
|
||||
/* gint_inth_callback_context_t: Context of the interrupted function when an
|
||||
interrupt is handled by gint_inth_callback. */
|
||||
typedef struct {
|
||||
uint32_t ssr;
|
||||
uint32_t spc;
|
||||
uint32_t r7;
|
||||
uint32_t r6;
|
||||
uint32_t r5;
|
||||
uint32_t r4;
|
||||
uint32_t r3;
|
||||
uint32_t r2;
|
||||
uint32_t r1;
|
||||
uint32_t r0;
|
||||
} gint_inth_callback_context_t;
|
||||
|
||||
/* gint_set_quit_handler(): Setup a call to be invoked when leaving the add-in
|
||||
|
||||
This function sets up the provided GINT_CALL() to be invoked when the
|
||||
|
|
|
@ -130,6 +130,11 @@ void *intc_handler(int event_code, void const *handler, size_t size);
|
|||
the numerous constraints of intc_handler(), at the cost of always going back
|
||||
to userspace and a small time overhead.
|
||||
|
||||
Since gint_inth_callback is used to do the heavy lifting of setting up a sane
|
||||
context for C code, the gint_call_t object can be built with GINT_CALL_FLAG
|
||||
if the called function expects a gint_inth_callback_context_t structure as its
|
||||
first argument.
|
||||
|
||||
@event_code Identifier of the interrupt block
|
||||
@function Function to use as a handler
|
||||
Returns true on success, false if the event code is invalid. */
|
||||
|
|
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 */
|
58
include/gint/ubc.h
Normal file
58
include/gint/ubc.h
Normal file
|
@ -0,0 +1,58 @@
|
|||
//---
|
||||
// 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 break
|
||||
will be ignored 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_dbh_lock: Lock set by ubc_dbh() when a UBC break is currently being
|
||||
handled. */
|
||||
extern uint8_t ubc_dbh_lock;
|
||||
|
||||
/* 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 */
|
|
@ -517,6 +517,14 @@ uint16_t usb_dc_string(uint16_t const *literal, size_t len);
|
|||
This is mostly used by the driver to answer GET_DESCRIPTOR requests. */
|
||||
usb_dc_string_t *usb_dc_string_get(uint16_t id);
|
||||
|
||||
//---
|
||||
// USB interrupts
|
||||
//---
|
||||
|
||||
/* usb_interrupt_context: Context of the function interrupted by a USB interrupt
|
||||
The pointer is set back to NULL when the interrupt is finished being handled. */
|
||||
extern gint_inth_callback_context_t* usb_interrupt_context;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
644
src/gdb/gdb.c
Normal file
644
src/gdb/gdb.c
Normal file
|
@ -0,0 +1,644 @@
|
|||
#include <gint/cpu.h>
|
||||
#include <gint/exc.h>
|
||||
#include <gint/gdb.h>
|
||||
#include <gint/ubc.h>
|
||||
#include <gint/usb-ff-bulk.h>
|
||||
#include <gint/usb.h>
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void gdb_hexlify(char* output_string, const uint8_t* input_buffer, size_t input_size)
|
||||
{
|
||||
const char* hex = "0123456789ABCDEF";
|
||||
for (size_t i = 0; i < input_size; i++) {
|
||||
uint8_t byte = input_buffer[i];
|
||||
output_string[i*2 + 0] = hex[(byte & 0xF0) >> 4];
|
||||
output_string[i*2 + 1] = hex[byte & 0x0F];
|
||||
}
|
||||
}
|
||||
|
||||
// TODO : bug in fxlibc ? strtoul doesn't support uppercase
|
||||
static uint32_t gdb_unhexlify_sized(const char* input_string, size_t input_length)
|
||||
{
|
||||
uint32_t ret = 0;
|
||||
for (size_t i = 0; i < input_length; i++) {
|
||||
uint8_t nibble_hex = tolower(input_string[i]);
|
||||
uint8_t nibble = nibble_hex >= 'a' && nibble_hex <= 'f' ? nibble_hex - 'a' + 10 :
|
||||
nibble_hex >= '0' && nibble_hex <= '9' ? nibble_hex - '0' : 0;
|
||||
ret = (ret << 4) | nibble;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint32_t gdb_unhexlify(const char* input_string)
|
||||
{
|
||||
return gdb_unhexlify_sized(input_string, strlen(input_string));
|
||||
}
|
||||
|
||||
static enum {
|
||||
GDB_STATE_STOPPED,
|
||||
GDB_STATE_FIRST_BREAK,
|
||||
GDB_STATE_STARTED,
|
||||
} GPACKEDENUM gdb_state = GDB_STATE_STOPPED;
|
||||
|
||||
static void gdb_send(const char *data, size_t size)
|
||||
{
|
||||
usb_fxlink_header_t header;
|
||||
usb_fxlink_fill_header(&header, "gdb", "remote", size);
|
||||
|
||||
int pipe = usb_ff_bulk_output();
|
||||
usb_write_sync(pipe, &header, sizeof(header), false);
|
||||
usb_write_sync(pipe, data, size, false);
|
||||
usb_commit_sync(pipe);
|
||||
}
|
||||
|
||||
static char *gdb_recv_buffer = NULL;
|
||||
static const size_t gdb_recv_buffer_capacity = 256;
|
||||
static size_t gdb_recv_buffer_size = 0;
|
||||
static ssize_t gdb_recv(char *buffer, size_t buffer_size)
|
||||
{
|
||||
if (gdb_recv_buffer_size >= buffer_size) {
|
||||
memcpy(buffer, gdb_recv_buffer, buffer_size);
|
||||
memmove(gdb_recv_buffer, &gdb_recv_buffer[buffer_size], gdb_recv_buffer_size - buffer_size);
|
||||
gdb_recv_buffer_size -= buffer_size;
|
||||
return buffer_size;
|
||||
}
|
||||
|
||||
usb_fxlink_header_t header;
|
||||
while (!usb_fxlink_handle_messages(&header)) {
|
||||
sleep();
|
||||
}
|
||||
|
||||
// TODO : should we abort or find a way to gracefully shutdown the debugger ?
|
||||
if (strncmp(header.application, "gdb", 16) == 0
|
||||
&& strncmp(header.type, "remote", 16) == 0) {
|
||||
if (header.size > gdb_recv_buffer_capacity - gdb_recv_buffer_size) {
|
||||
abort();
|
||||
}
|
||||
usb_read_sync(usb_ff_bulk_input(), &gdb_recv_buffer[gdb_recv_buffer_size], header.size, false);
|
||||
gdb_recv_buffer_size += header.size;
|
||||
return gdb_recv(buffer, buffer_size);
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t gdb_recv_packet(char* buffer, size_t buffer_size)
|
||||
{
|
||||
char read_char;
|
||||
|
||||
// Waiting for packet start '$'
|
||||
do {
|
||||
if (gdb_recv(&read_char, 1) != 1) {
|
||||
return -1;
|
||||
}
|
||||
} while (read_char != '$');
|
||||
|
||||
uint8_t checksum = 0;
|
||||
size_t packet_len = 0;
|
||||
while (true) {
|
||||
if (gdb_recv(&read_char, 1) != 1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (read_char != '#') {
|
||||
// -1 to ensure space for a NULL terminator
|
||||
if (packet_len >= (buffer_size - 1)) {
|
||||
return -1;
|
||||
}
|
||||
buffer[packet_len++] = read_char;
|
||||
checksum += read_char;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
buffer[packet_len] = '\0';
|
||||
|
||||
char read_checksum_hex[3];
|
||||
if (gdb_recv(read_checksum_hex, 2) != 2) {
|
||||
return -1;
|
||||
}
|
||||
read_checksum_hex[2] = '\0';
|
||||
uint8_t read_checksum = gdb_unhexlify(read_checksum_hex);
|
||||
|
||||
if (read_checksum != checksum) {
|
||||
read_char = '-';
|
||||
gdb_send(&read_char, 1);
|
||||
return -1;
|
||||
} else {
|
||||
read_char = '+';
|
||||
gdb_send(&read_char, 1);
|
||||
return packet_len;
|
||||
}
|
||||
}
|
||||
|
||||
static ssize_t gdb_send_packet(const char* packet, size_t packet_length)
|
||||
{
|
||||
if (packet == NULL || packet_length == 0) {
|
||||
// Empty packet
|
||||
gdb_send("$#00", 4);
|
||||
return 4;
|
||||
}
|
||||
|
||||
size_t buffer_length = packet_length + 1 + 4;
|
||||
// TODO : find if it's more efficient to malloc+copy on each packet or send 3 small fxlink messages
|
||||
char* buffer = malloc(buffer_length);
|
||||
|
||||
uint8_t checksum = 0;
|
||||
for (size_t i = 0; i < packet_length; i++) {
|
||||
checksum += packet[i];
|
||||
}
|
||||
|
||||
buffer[0] = '$';
|
||||
memcpy(&buffer[1], packet, packet_length);
|
||||
snprintf(&buffer[buffer_length - 4], 4, "#%02X", checksum);
|
||||
|
||||
// -1 to not send the NULL terminator of snprintf
|
||||
gdb_send(buffer, buffer_length - 1);
|
||||
free(buffer);
|
||||
return buffer_length;
|
||||
}
|
||||
|
||||
static void gdb_send_stop_reply(void)
|
||||
{
|
||||
gdb_send_packet("S05", 3); // SIGTRAP
|
||||
}
|
||||
|
||||
static void gdb_handle_qXfer_packet(const char* packet, const char* data, size_t data_size)
|
||||
{
|
||||
char offset_hex[16] = {0}, length_hex[16] = {0};
|
||||
for (size_t i = 0; i < sizeof(offset_hex); i++) {
|
||||
offset_hex[i] = *(packet++); // consume offset
|
||||
if (*packet == ',') break;
|
||||
}
|
||||
packet++; // consume ','
|
||||
for (size_t i = 0; i < sizeof(length_hex); i++) {
|
||||
length_hex[i] = *(packet++); // consume length
|
||||
if (*packet == '\0') break;
|
||||
}
|
||||
|
||||
size_t offset = (size_t)gdb_unhexlify(offset_hex);
|
||||
size_t length = (size_t)gdb_unhexlify(length_hex);
|
||||
|
||||
if (offset >= data_size) {
|
||||
gdb_send_packet("l", 1);
|
||||
} else if (offset + length >= data_size) {
|
||||
char *reply_buffer = malloc(data_size - offset + 1);
|
||||
reply_buffer[0] = 'l';
|
||||
memcpy(&reply_buffer[1], &data[offset], data_size - offset);
|
||||
gdb_send_packet(reply_buffer, data_size - offset + 1);
|
||||
free(reply_buffer);
|
||||
} else {
|
||||
char *reply_buffer = malloc(length + 1);
|
||||
reply_buffer[0] = 'm';
|
||||
memcpy(&reply_buffer[1], &data[offset], length);
|
||||
gdb_send_packet(reply_buffer, length + 1);
|
||||
free(reply_buffer);
|
||||
}
|
||||
}
|
||||
|
||||
/* We implement the memory-map qXfer extension to mark add-in memory as read-only
|
||||
* and enforce hardware breakpoints.
|
||||
* See : https://sourceware.org/gdb/onlinedocs/gdb/Memory-Map-Format.html
|
||||
* https://sourceware.org/gdb/onlinedocs/gdb/Set-Breaks.html
|
||||
*/
|
||||
// TODO : Should we mark other regions as ROM ?
|
||||
static const char gdb_memory_map_xml[] = "<?xml version=\"1.0\"?>"
|
||||
"<!DOCTYPE memory-map PUBLIC \"+//IDN gnu.org//DTD GDB Memory Map V1.0//EN\" \"http://sourceware.org/gdb/gdb-memory-map.dtd\">"
|
||||
"<memory-map>"
|
||||
"<memory type=\"rom\" start=\"0x00300000\" length=\"0x00200000\"/>" // add-in rom
|
||||
"<memory type=\"ram\" start=\"0x08100000\" length=\"0x00080000\"/>" // add-in ram
|
||||
"<memory type=\"ram\" start=\"0x8c000000\" length=\"0x01000000\"/>" // fx-CG50 stack
|
||||
"<memory type=\"ram\" start=\"0x88000000\" length=\"0x01000000\"/>" // Prizm stack
|
||||
"</memory-map>";
|
||||
|
||||
static void gdb_handle_query_packet(const char* packet)
|
||||
{
|
||||
if (strncmp("qSupported", packet, 10) == 0) {
|
||||
const char* qsupported_ans = "PacketSize=255;qXfer:memory-map:read+";
|
||||
gdb_send_packet(qsupported_ans, strlen(qsupported_ans));
|
||||
} else if (strncmp("qXfer:memory-map:read::", packet, 23) == 0) {
|
||||
// -1 to not send the NULL terminator
|
||||
gdb_handle_qXfer_packet(&packet[23], gdb_memory_map_xml, sizeof(gdb_memory_map_xml) - 1);
|
||||
} else {
|
||||
gdb_send_packet(NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void gdb_handle_read_general_registers(gdb_cpu_state_t* cpu_state)
|
||||
{
|
||||
char reply_buffer[23*8];
|
||||
if (!cpu_state) {
|
||||
memset(reply_buffer, 'x', sizeof(reply_buffer));
|
||||
memcpy(&reply_buffer[offsetof(gdb_cpu_state_t, reg.pc)*2],
|
||||
"A0000000", 8); // pc needs to be set to make GDB happy
|
||||
} else {
|
||||
gdb_hexlify(reply_buffer, (uint8_t*)cpu_state->regs,
|
||||
sizeof(cpu_state->regs));
|
||||
}
|
||||
gdb_send_packet(reply_buffer, sizeof(reply_buffer));
|
||||
}
|
||||
|
||||
static void gdb_handle_read_register(gdb_cpu_state_t* cpu_state, const char* packet)
|
||||
{
|
||||
uint8_t register_id = gdb_unhexlify(&packet[1]);
|
||||
char reply_buffer[8];
|
||||
if (!cpu_state || register_id >= sizeof(cpu_state->regs)/sizeof(uint32_t)) {
|
||||
memset(reply_buffer, 'x', sizeof(reply_buffer));
|
||||
} else {
|
||||
gdb_hexlify(reply_buffer, (uint8_t*)&cpu_state->regs[register_id],
|
||||
sizeof(cpu_state->regs[register_id]));
|
||||
}
|
||||
gdb_send_packet(reply_buffer, sizeof(reply_buffer));
|
||||
}
|
||||
|
||||
static void gdb_handle_write_general_registers(gdb_cpu_state_t* cpu_state, const char* packet)
|
||||
{
|
||||
if (!cpu_state) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
packet++; // consume 'G'
|
||||
|
||||
// Let's not handle incomplete 'G' packets as they're rarely used anyway
|
||||
if (strlen(packet) != sizeof(cpu_state->regs)*2) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sizeof(cpu_state->regs)/sizeof(uint32_t); i++) {
|
||||
cpu_state->regs[i] = gdb_unhexlify_sized(&packet[i * sizeof(uint32_t) * 2],
|
||||
sizeof(uint32_t) * 2);
|
||||
}
|
||||
gdb_send_packet("OK", 2);
|
||||
}
|
||||
|
||||
static void gdb_handle_write_register(gdb_cpu_state_t* cpu_state, const char* packet)
|
||||
{
|
||||
if (!cpu_state) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
char register_id_hex[16] = {0}, value_hex[16] = {0};
|
||||
|
||||
packet++; // consume 'P'
|
||||
for (size_t i = 0; i < sizeof(register_id_hex); i++) {
|
||||
register_id_hex[i] = *(packet++); // consume register id
|
||||
if (*packet == '=') break;
|
||||
}
|
||||
packet++; // consume '='
|
||||
for (size_t i = 0; i < sizeof(value_hex); i++) {
|
||||
value_hex[i] = *(packet++); // consume register value
|
||||
if (*packet == '\0') break;
|
||||
}
|
||||
|
||||
uint32_t register_id = gdb_unhexlify(register_id_hex);
|
||||
uint32_t value = gdb_unhexlify(value_hex);
|
||||
|
||||
if (register_id >= sizeof(cpu_state->regs)/sizeof(uint32_t)) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
} else {
|
||||
cpu_state->regs[register_id] = value;
|
||||
gdb_send_packet("OK", 2);
|
||||
}
|
||||
}
|
||||
|
||||
static volatile bool gdb_tlbh_enable = false;
|
||||
static volatile bool gdb_tlbh_caught = false;
|
||||
|
||||
static void gdb_handle_read_memory(const char* packet)
|
||||
{
|
||||
char address_hex[16] = {0}, size_hex[16] = {0};
|
||||
uint8_t* read_address;
|
||||
size_t read_size;
|
||||
|
||||
packet++; // consume 'm'
|
||||
for (size_t i = 0; i < sizeof(address_hex); i++) {
|
||||
address_hex[i] = *(packet++); // consume address
|
||||
if (*packet == ',') break;
|
||||
}
|
||||
packet++; // consume ','
|
||||
for (size_t i = 0; i < sizeof(size_hex); i++) {
|
||||
size_hex[i] = *(packet++); // consume size
|
||||
if (*packet == '\0') break;
|
||||
}
|
||||
|
||||
read_address = (uint8_t*) gdb_unhexlify(address_hex);
|
||||
read_size = (size_t) gdb_unhexlify(size_hex);
|
||||
|
||||
char *reply_buffer = malloc(read_size * 2);
|
||||
|
||||
gdb_tlbh_enable = true;
|
||||
gdb_tlbh_caught = false;
|
||||
for (size_t i = 0; i < read_size && !gdb_tlbh_caught; i++) {
|
||||
gdb_hexlify(&reply_buffer[i * 2], &read_address[i], 1);
|
||||
}
|
||||
gdb_tlbh_enable = false;
|
||||
|
||||
if (gdb_tlbh_caught) {
|
||||
gdb_send_packet("E22", 3); // EINVAL
|
||||
gdb_tlbh_caught = false;
|
||||
} else {
|
||||
gdb_send_packet(reply_buffer, read_size * 2);
|
||||
}
|
||||
free(reply_buffer);
|
||||
}
|
||||
|
||||
static void gdb_handle_write_memory(const char* packet)
|
||||
{
|
||||
char address_hex[16] = {0}, size_hex[16] = {0};
|
||||
uint8_t* read_address;
|
||||
size_t read_size;
|
||||
|
||||
packet++; // consume 'M'
|
||||
for (size_t i = 0; i < sizeof(address_hex); i++) {
|
||||
address_hex[i] = *(packet++); // consume address
|
||||
if (*packet == ',') break;
|
||||
}
|
||||
packet++; // consume ','
|
||||
for (size_t i = 0; i < sizeof(size_hex); i++) {
|
||||
size_hex[i] = *(packet++); // consume size
|
||||
if (*packet == ':') break;
|
||||
}
|
||||
packet++; // consume ':'
|
||||
|
||||
read_address = (uint8_t*) gdb_unhexlify(address_hex);
|
||||
read_size = (size_t) gdb_unhexlify(size_hex);
|
||||
|
||||
gdb_tlbh_enable = true;
|
||||
gdb_tlbh_caught = false;
|
||||
for (size_t i = 0; i < read_size && !gdb_tlbh_caught; i++) {
|
||||
read_address[i] = (uint8_t)gdb_unhexlify_sized(&packet[i * 2], 2);
|
||||
}
|
||||
gdb_tlbh_enable = false;
|
||||
|
||||
if (gdb_tlbh_caught) {
|
||||
gdb_send_packet("E22", 3); // EINVAL
|
||||
gdb_tlbh_caught = false;
|
||||
} else {
|
||||
gdb_send_packet("OK", 2);
|
||||
}
|
||||
}
|
||||
|
||||
static bool gdb_parse_hardware_breakpoint_packet(const char* packet, void** read_address)
|
||||
{
|
||||
packet++; // consume 'z' or 'Z'
|
||||
if (*packet != '1') { // hardware breakpoint
|
||||
return false;
|
||||
}
|
||||
packet++; // consume '1'
|
||||
packet++; // consume ','
|
||||
|
||||
char address_hex[16] = {0}, kind_hex[16] = {0};
|
||||
for (size_t i = 0; i < sizeof(address_hex); i++) {
|
||||
address_hex[i] = *(packet++); // consume address
|
||||
if (*packet == ',') break;
|
||||
}
|
||||
packet++; // consume ','
|
||||
for (size_t i = 0; i < sizeof(kind_hex); i++) {
|
||||
kind_hex[i] = *(packet++); // consume kind
|
||||
if (*packet == '\0' || *packet == ';') break;
|
||||
}
|
||||
|
||||
*read_address = (void*) gdb_unhexlify(address_hex);
|
||||
uint32_t read_kind = gdb_unhexlify(kind_hex);
|
||||
|
||||
if (read_kind != 2) { // SuperH instructions are 2 bytes long
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gdb_handle_insert_hardware_breakpoint(const char* packet)
|
||||
{
|
||||
void* read_address;
|
||||
if (!gdb_parse_hardware_breakpoint_packet(packet, &read_address)) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
void *channel0_addr, *channel1_addr;
|
||||
bool channel0_used = ubc_get_break_address(0, &channel0_addr);
|
||||
bool channel1_used = ubc_get_break_address(1, &channel1_addr);
|
||||
|
||||
/* As stated by GDB doc : "the operations should be implemented in an idempotent way."
|
||||
* Thus we first check if the breakpoint is already placed in one of the UBC channel.
|
||||
*/
|
||||
if ((channel0_used && channel0_addr == read_address) ||
|
||||
(channel1_used && channel1_addr == read_address)) {
|
||||
gdb_send_packet("OK", 2);
|
||||
} else if (!channel0_used) {
|
||||
ubc_set_breakpoint(0, read_address, UBC_BREAK_BEFORE);
|
||||
gdb_send_packet("OK", 2);
|
||||
} else if (!channel1_used) {
|
||||
ubc_set_breakpoint(1, read_address, UBC_BREAK_BEFORE);
|
||||
gdb_send_packet("OK", 2);
|
||||
} else {
|
||||
/* TODO : We should find a proper way to inform GDB that we are
|
||||
* limited by the number of UBC channels.
|
||||
*/
|
||||
gdb_send_packet(NULL, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void gdb_handle_remove_hardware_breakpoint(const char* packet)
|
||||
{
|
||||
void* read_address;
|
||||
if (!gdb_parse_hardware_breakpoint_packet(packet, &read_address)) {
|
||||
gdb_send_packet(NULL, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
void *channel0_addr, *channel1_addr;
|
||||
bool channel0_used = ubc_get_break_address(0, &channel0_addr);
|
||||
bool channel1_used = ubc_get_break_address(1, &channel1_addr);
|
||||
|
||||
if (channel0_used && channel0_addr == read_address) {
|
||||
ubc_disable_channel(0);
|
||||
}
|
||||
if (channel1_used && channel1_addr == read_address) {
|
||||
ubc_disable_channel(1);
|
||||
}
|
||||
gdb_send_packet("OK", 2);
|
||||
}
|
||||
|
||||
static struct {
|
||||
bool single_stepped;
|
||||
bool channel0_used;
|
||||
bool channel1_used;
|
||||
void* channel0_addr;
|
||||
void* channel1_addr;
|
||||
} gdb_single_step_backup = { false };
|
||||
static void gdb_handle_single_step(uint32_t pc, ubc_break_mode_t break_mode)
|
||||
{
|
||||
gdb_single_step_backup.channel0_used = ubc_get_break_address(0, &gdb_single_step_backup.channel0_addr);
|
||||
gdb_single_step_backup.channel1_used = ubc_get_break_address(1, &gdb_single_step_backup.channel1_addr);
|
||||
|
||||
ubc_disable_channel(0);
|
||||
ubc_set_breakpoint(1, (void*)pc, break_mode);
|
||||
|
||||
gdb_single_step_backup.single_stepped = true;
|
||||
}
|
||||
|
||||
void gdb_main(gdb_cpu_state_t* cpu_state)
|
||||
{
|
||||
if (gdb_single_step_backup.single_stepped) {
|
||||
if (gdb_single_step_backup.channel0_used) {
|
||||
ubc_set_breakpoint(0, gdb_single_step_backup.channel0_addr, UBC_BREAK_BEFORE);
|
||||
} else {
|
||||
ubc_disable_channel(0);
|
||||
}
|
||||
if (gdb_single_step_backup.channel1_used) {
|
||||
ubc_set_breakpoint(1, gdb_single_step_backup.channel1_addr, UBC_BREAK_BEFORE);
|
||||
} else {
|
||||
ubc_disable_channel(1);
|
||||
}
|
||||
|
||||
gdb_single_step_backup.single_stepped = false;
|
||||
}
|
||||
|
||||
if (cpu_state != NULL) {
|
||||
gdb_send_stop_reply();
|
||||
}
|
||||
|
||||
while (1) {
|
||||
char packet_buffer[256];
|
||||
ssize_t packet_size = gdb_recv_packet(packet_buffer, sizeof(packet_buffer));
|
||||
if (packet_size <= 0) {
|
||||
// TODO : Should we break or log on recv error ?
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (packet_buffer[0]) {
|
||||
case '?': // Halt reason
|
||||
gdb_send_stop_reply();
|
||||
break;
|
||||
case 'q':
|
||||
gdb_handle_query_packet(packet_buffer);
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
gdb_handle_read_general_registers(cpu_state);
|
||||
break;
|
||||
case 'p':
|
||||
gdb_handle_read_register(cpu_state, packet_buffer);
|
||||
break;
|
||||
case 'm':
|
||||
gdb_handle_read_memory(packet_buffer);
|
||||
break;
|
||||
case 'G':
|
||||
gdb_handle_write_general_registers(cpu_state, packet_buffer);
|
||||
break;
|
||||
case 'P':
|
||||
gdb_handle_write_register(cpu_state, packet_buffer);
|
||||
break;
|
||||
case 'M':
|
||||
gdb_handle_write_memory(packet_buffer);
|
||||
break;
|
||||
|
||||
case 'k': // Kill request
|
||||
abort();
|
||||
return;
|
||||
|
||||
case 'Z':
|
||||
gdb_handle_insert_hardware_breakpoint(packet_buffer);
|
||||
break;
|
||||
case 'z':
|
||||
gdb_handle_remove_hardware_breakpoint(packet_buffer);
|
||||
break;
|
||||
|
||||
case 's':
|
||||
gdb_handle_single_step(cpu_state->reg.pc, UBC_BREAK_AFTER);
|
||||
return;
|
||||
case 'c': // Continue
|
||||
return;
|
||||
|
||||
default: // Unsupported packet
|
||||
gdb_send_packet(NULL, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gdb_notifier_function(void)
|
||||
{
|
||||
// We ignore fxlink notifications when we're already inside GDB code.
|
||||
if (ubc_dbh_lock || gdb_state != GDB_STATE_STARTED)
|
||||
return;
|
||||
|
||||
// We make sure we are called during a USB interrupt.
|
||||
if (usb_interrupt_context == NULL)
|
||||
return;
|
||||
|
||||
// And we make sure an other step break is not already set up.
|
||||
if (gdb_single_step_backup.single_stepped)
|
||||
return;
|
||||
|
||||
gdb_handle_single_step(usb_interrupt_context->spc, UBC_BREAK_AFTER);
|
||||
}
|
||||
|
||||
static int gdb_panic_handler(uint32_t code)
|
||||
{
|
||||
// If we are currently expecting to handle TLB misses
|
||||
if (gdb_tlbh_enable) {
|
||||
// We only handle TLB miss reads (0x040) and writes (0x060)
|
||||
if (code != 0x040 && code != 0x060)
|
||||
return 1;
|
||||
|
||||
gdb_tlbh_caught = true;
|
||||
|
||||
// We skip the offending instruction and continue
|
||||
gint_exc_skip(1);
|
||||
return 0;
|
||||
}
|
||||
// If we are in user code, let's break
|
||||
else if (!ubc_dbh_lock && gdb_state == GDB_STATE_STARTED) {
|
||||
// We make sure an other step break is not already set up
|
||||
if (gdb_single_step_backup.single_stepped)
|
||||
return 1;
|
||||
|
||||
uint32_t spc;
|
||||
__asm__("stc spc, %0" : "=r"(spc));
|
||||
gdb_handle_single_step(spc, UBC_BREAK_BEFORE);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
int gdb_start(void)
|
||||
{
|
||||
if (gdb_state != GDB_STATE_STOPPED) {
|
||||
return GDB_ALREADY_STARTED;
|
||||
}
|
||||
|
||||
if (usb_is_open()) {
|
||||
return GDB_NO_INTERFACE;
|
||||
}
|
||||
|
||||
if (!gdb_recv_buffer) {
|
||||
gdb_recv_buffer = malloc(gdb_recv_buffer_capacity);
|
||||
}
|
||||
|
||||
usb_interface_t const *interfaces[] = { &usb_ff_bulk, NULL };
|
||||
if (usb_open(interfaces, GINT_CALL_NULL) < 0) {
|
||||
return GDB_USB_ERROR;
|
||||
}
|
||||
usb_open_wait();
|
||||
usb_fxlink_set_notifier(gdb_notifier_function);
|
||||
|
||||
// TODO : Should we detect if other panic or debug handlers are setup ?
|
||||
gint_exc_catch(gdb_panic_handler);
|
||||
|
||||
ubc_set_debug_handler(gdb_main);
|
||||
|
||||
gdb_state = GDB_STATE_FIRST_BREAK;
|
||||
gdb_main(NULL);
|
||||
gdb_state = GDB_STATE_STARTED;
|
||||
return 0;
|
||||
}
|
|
@ -79,6 +79,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code)
|
|||
if(code == 0x1040) name = "Add-in too large";
|
||||
if(code == 0x1060) name = "Memory init failed";
|
||||
if(code == 0x1080) name = "Stack overflow";
|
||||
if(code == 0x10a0) name = "UBC in bank 1 code";
|
||||
|
||||
if(name[0]) dtext(1, 9, name);
|
||||
else dprint(1, 9, "%03x", code);
|
||||
|
@ -119,6 +120,7 @@ GNORETURN static void gint_default_panic(GUNUSED uint32_t code)
|
|||
if(code == 0x1040) name = "Add-in not fully mapped (too large)";
|
||||
if(code == 0x1060) name = "Memory initialization failed (heap)";
|
||||
if(code == 0x1080) name = "Stack overflow during world switch";
|
||||
if(code == 0x10a0) name = "UBC break in register bank 1 code";
|
||||
|
||||
dprint(6, 25, "%03x %s", code, name);
|
||||
|
||||
|
|
|
@ -210,11 +210,15 @@ _gint_inth_callback_reloc:
|
|||
stc.l r7_bank, @-r15
|
||||
stc.l spc, @-r15
|
||||
stc.l ssr, @-r15
|
||||
/* We backup the address of the gint_inth_callback_context_t built on
|
||||
the stack to the user bank. */
|
||||
ldc r15, r4_bank
|
||||
|
||||
stc.l sr, @-r15
|
||||
|
||||
/* Save some values to user bank; once we enable interrupts, the kernel
|
||||
bank might be overwritten at any moment. */
|
||||
ldc r4, r0_bank
|
||||
ldc r4, r3_bank
|
||||
|
||||
/* Enable interrupts and go back to user bank. On SH4, SR.IMASK is set
|
||||
to the level of the current interrupt, which makes sure we can only
|
||||
|
@ -244,15 +248,25 @@ _gint_inth_callback_reloc:
|
|||
.load_sr:
|
||||
ldc r1, sr
|
||||
|
||||
/* We are now in the user bank with r0 set. Perform the call. We want
|
||||
/* We are now in the user bank with r3 set. Perform the call. We want
|
||||
to forward the return value to kernel bank, but this bank can be
|
||||
changed at any moment since interrupts are enabled. */
|
||||
sts.l pr, @-r15
|
||||
mov.l @(4, r0), r4
|
||||
mov.l @(8, r0), r5
|
||||
mov.l @(12, r0), r6
|
||||
mov.l @(16, r0), r7
|
||||
mov.l @r0, r0
|
||||
|
||||
/* We only set r4 to the value of the first argument in the gint_call_t
|
||||
if the address is even (i.e. GINT_CALL_FLAG() was not used). */
|
||||
mov.l @r3, r0
|
||||
tst #1, r0
|
||||
bf .do_not_set_r4
|
||||
mov.l @(4, r3), r4
|
||||
.do_not_set_r4:
|
||||
/* And we make sure to realign the address in case it was odd. */
|
||||
mov #0xfe, r2
|
||||
and r2, r0
|
||||
|
||||
mov.l @(8, r3), r5
|
||||
mov.l @(12, r3), r6
|
||||
mov.l @(16, r3), r7
|
||||
jsr @r0
|
||||
nop
|
||||
lds.l @r15+, pr
|
||||
|
|
127
src/ubc/ubc.S
Normal file
127
src/ubc/ubc.S
Normal file
|
@ -0,0 +1,127 @@
|
|||
.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 don't support breaking in a context where register bank 1 is used */
|
||||
stc ssr, r0
|
||||
mov.l .sr_rb1_mask, r1
|
||||
tst r0, r1
|
||||
bf .dbh_panic
|
||||
|
||||
/* 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
|
||||
|
||||
/* We set the ubc_dbh_lock before enabling interrupts */
|
||||
mov.l .ubc_dbh_lock, r0
|
||||
mov #1, r1
|
||||
mov.b r1, @r0
|
||||
|
||||
/* 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
|
||||
|
||||
/* We can release the ubc_dbh_lock now that interrupts have been
|
||||
disabled */
|
||||
mov.l .ubc_dbh_lock, r0
|
||||
mov #0, r1
|
||||
mov.b r1, @r0
|
||||
|
||||
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
|
||||
|
||||
.dbh_panic:
|
||||
stc sr, r1
|
||||
mov.l .sr_mask, r0
|
||||
and r0, r1
|
||||
ldc r1, sr
|
||||
|
||||
mov.l .panic_code, r4
|
||||
|
||||
mov.l .panic, r0
|
||||
mov.l @r0, r0
|
||||
jmp @r0
|
||||
nop
|
||||
|
||||
.align 4
|
||||
.handler: .long _ubc_debug_handler
|
||||
.ubc_dbh_lock: .long _ubc_dbh_lock
|
||||
.panic_code: .long 0x10a0
|
||||
.panic: .long _gint_exc_panic
|
||||
.sr_rb1_mask: .long (1 << 29)
|
||||
.sr_mask: .long ~0x300000f0 /* IMASK = 0 : mask no interrupts
|
||||
BL = 0 : do not block interrupts
|
||||
RB = 0 : use register BANK0
|
||||
*/
|
||||
|
||||
.data
|
||||
.global _ubc_dbh_lock
|
||||
_ubc_dbh_lock: .byte 0
|
131
src/ubc/ubc.c
Normal file
131
src/ubc/ubc.c
Normal file
|
@ -0,0 +1,131 @@
|
|||
#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);
|
||||
}
|
||||
// For now we will ignore breaks when no debug handler is set
|
||||
// TODO : Should we log or panic when no debug handler is set ?
|
||||
}
|
||||
|
||||
// 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);
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
#define USB SH7305_USB
|
||||
|
||||
static void usb_interrupt_handler(void);
|
||||
static void usb_interrupt_handler(gint_inth_callback_context_t* interrupt_context);
|
||||
|
||||
/* Shorthand to clear a bit in INTSTS0 */
|
||||
#define INTSTS0_clear(field_name) { \
|
||||
|
@ -213,7 +213,7 @@ int usb_open(usb_interface_t const **interfaces, gint_call_t callback)
|
|||
USB.NRDYSTS = 0x0000;
|
||||
USB.BEMPSTS = 0x0000;
|
||||
|
||||
intc_handler_function(0xa20, GINT_CALL(usb_interrupt_handler));
|
||||
intc_handler_function(0xa20, GINT_CALL_FLAG(usb_interrupt_handler));
|
||||
intc_priority(INTC_USB, 8);
|
||||
|
||||
/* Pull D+ up to 3.3V, notifying connection when possible. Read
|
||||
|
@ -251,8 +251,12 @@ void usb_close(void)
|
|||
// Userspace interrupt handler
|
||||
//---
|
||||
|
||||
static void usb_interrupt_handler(void)
|
||||
gint_inth_callback_context_t* usb_interrupt_context;
|
||||
|
||||
static void usb_interrupt_handler(gint_inth_callback_context_t* interrupt_context)
|
||||
{
|
||||
usb_interrupt_context = interrupt_context;
|
||||
|
||||
GUNUSED static char const * const device_st[] = {
|
||||
"powered", "default", "address", "configured",
|
||||
"suspended-powered", "suspended-default", "suspended-address",
|
||||
|
@ -315,6 +319,8 @@ static void usb_interrupt_handler(void)
|
|||
|
||||
/* Restore PIPESEL which can have been used for transfers */
|
||||
USB.PIPESEL.word = pipesel;
|
||||
|
||||
usb_interrupt_context = NULL;
|
||||
}
|
||||
|
||||
//---
|
||||
|
|
Loading…
Reference in a new issue