From cf77e2db67ed6d37268ffca1c53696e6f09d5362 Mon Sep 17 00:00:00 2001 From: CrashOveride95 Date: Fri, 16 Apr 2021 20:02:56 -0400 Subject: [PATCH] Fix up printing and begin GDB --- Makefile | 8 +- sm64.ld | 8 +- src/audio/heap.c | 7 +- src/audio/load.c | 10 +- src/audio/port_eu.c | 5 + src/game/decompress.s | 95 +---- src/game/rumble_init.c | 2 + src/usb/debug.h | 4 +- src/usb/debugger.c | 933 +++++++++++++++++++++++++++++++++++++++++ src/usb/debugger.h | 60 +++ src/usb/serial.c | 519 +++++++++++++++++++++++ src/usb/serial.h | 44 ++ 12 files changed, 1587 insertions(+), 108 deletions(-) create mode 100644 src/usb/debugger.c create mode 100644 src/usb/debugger.h create mode 100644 src/usb/serial.c create mode 100644 src/usb/serial.h diff --git a/Makefile b/Makefile index ca6af533..aaab3d93 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ default: all DEFINES := SRC_DIRS := +USE_DEBUG := 0 #==============================================================================# # Build Options # @@ -170,6 +171,7 @@ $(eval $(call validate-option,UNF,0 1)) ifeq ($(UNF),1) DEFINES += UNF=1 SRC_DIRS += src/usb + USE_DEBUG := 1 endif @@ -181,6 +183,10 @@ ISVPRINT ?= 0 $(eval $(call validate-option,ISVPRINT,0 1)) ifeq ($(ISVPRINT),1) DEFINES += ISVPRINT=1 + USE_DEBUG := 1 +endif + +ifeq ($(USE_DEBUG),1) ULTRALIB := ultra_d else ULTRALIB := ultra_rom @@ -738,7 +744,7 @@ $(ELF): $(O_FILES) $(YAY0_OBJ_FILES) $(SEG_FILES) $(BUILD_DIR)/$(LD_SCRIPT) unde # Build ROM $(ROM): $(ELF) $(call print,Building ROM:,$<,$@) - $(V)$(OBJCOPY) --pad-to=0x100000 --gap-fill=0xFF $< $@ -O binary + $(V)$(OBJCOPY) --pad-to=0x800000 --gap-fill=0xFF $< $@ -O binary $(V)$(N64CKSUM) $@ $(BUILD_DIR)/$(TARGET).objdump: $(ELF) diff --git a/sm64.ld b/sm64.ld index 5e86b935..f2aaeabb 100755 --- a/sm64.ld +++ b/sm64.ld @@ -132,7 +132,7 @@ SECTIONS BUILD_DIR/src/usb*.o(.text); #endif BUILD_DIR/src/audio*.o(.text); -#if defined(ISVPRINT) +#if defined(ISVPRINT) || defined(UNF) */libultra_d.a:*.o(.text); #else */libultra_rom.a:*.o(.text); @@ -158,7 +158,7 @@ SECTIONS #ifdef GZIP */libz.a:*.o(.*data*); #endif -#if defined(ISVPRINT) +#if defined(ISVPRINT) || defined(UNF) */libultra_d.a:*.o(.*data*); #else */libultra_rom.a:*.o(.*data*); @@ -178,7 +178,7 @@ SECTIONS BUILD_DIR/src/usb*.o(.rodata*); #endif BUILD_DIR/src/audio*.o(.rodata*); -#if defined(ISVPRINT) +#if defined(ISVPRINT) || defined(UNF) */libultra_d.a:*.o(.*rodata*); #else */libultra_rom.a:*.o(.*rodata*); @@ -208,7 +208,7 @@ SECTIONS BUILD_DIR/src/gzip*.o(.bss*); #endif BUILD_DIR/src/audio*.o(.*bss*); -#if defined(ISVPRINT) +#if defined(ISVPRINT) || defined(UNF) */libultra_d.a:*.o(COMMON); */libultra_d.a:*.o(.scommon); */libultra_d.a:*.o(.*bss*); diff --git a/src/audio/heap.c b/src/audio/heap.c index 430e4105..a81c8b44 100644 --- a/src/audio/heap.c +++ b/src/audio/heap.c @@ -216,8 +216,10 @@ void discard_bank(s32 bankId) { #else if (note->priority >= NOTE_PRIORITY_MIN) { #endif +#if defined(VERSION_EU) eu_stubbed_printf_3("Kill Voice %d (ID %d) %d\n", note->waveId, bankId, note->priority); +#endif eu_stubbed_printf_0("Warning: Running Sequence's data disappear!\n"); note->parentLayer->enabled = FALSE; // is 0x48, should be 0x44 note->parentLayer->finished = TRUE; @@ -760,7 +762,9 @@ void *alloc_bank_or_seq(struct SoundMultiPool *arg0, s32 arg1, s32 size, s32 arg #ifdef VERSION_SH case 0: #endif +#ifdef VERSION_EU eu_stubbed_printf_1("MEMORY:StayHeap OVERFLOW (REQ:%d)", arg1 * size); +#endif return NULL; } } @@ -1118,8 +1122,9 @@ void audio_reset_session(void) { #else struct SynthesisReverb *reverb; #endif +#ifdef VERSION_EU eu_stubbed_printf_1("Heap Reconstruct Start %x\n", gAudioResetPresetIdToLoad); - +#endif #if defined(VERSION_JP) || defined(VERSION_US) if (gAudioLoadLock != AUDIO_LOCK_UNINITIALIZED) { decrease_reverb_gain(); diff --git a/src/audio/load.c b/src/audio/load.c index dd629fc3..054a0221 100644 --- a/src/audio/load.c +++ b/src/audio/load.c @@ -1977,11 +1977,11 @@ void audio_init() { gAudioLoadLock = AUDIO_LOCK_NOT_LOADING; // Should probably contain the sizes of the data banks, but those aren't // easily accessible from here. - eu_stubbed_printf_0("---------- Init Completed. ------------\n"); - eu_stubbed_printf_1(" Syndrv :[%6d]\n", 0); // gSoundDataADSR - eu_stubbed_printf_1(" Seqdrv :[%6d]\n", 0); // gMusicData - eu_stubbed_printf_1(" audiodata :[%6d]\n", 0); // gSoundDataRaw - eu_stubbed_printf_0("---------------------------------------\n"); + osSyncPrintf("---------- Init Completed. ------------\n"); + osSyncPrintf(" Syndrv :[%6d]\n", gSoundDataADSR); // gSoundDataADSR + osSyncPrintf(" Seqdrv :[%6d]\n", gMusicData); // gMusicData + osSyncPrintf(" audiodata :[%6d]\n", gSoundDataRaw); // gSoundDataRaw + osSyncPrintf("---------------------------------------\n"); #endif } diff --git a/src/audio/port_eu.c b/src/audio/port_eu.c index 3cabfc1a..f591792c 100644 --- a/src/audio/port_eu.c +++ b/src/audio/port_eu.c @@ -7,11 +7,16 @@ #ifdef VERSION_EU +#if defined(ISVPRINT) || defined(UNF) +#define stubbed_printf osSyncPrintf +#else + #ifdef __sgi #define stubbed_printf #else #define stubbed_printf(...) #endif +#endif #define SAMPLES_TO_OVERPRODUCE 0x10 #define EXTRA_BUFFERED_AI_SAMPLES_TARGET 0x40 diff --git a/src/game/decompress.s b/src/game/decompress.s index 1399179d..2c13a164 100644 --- a/src/game/decompress.s +++ b/src/game/decompress.s @@ -11,7 +11,6 @@ # This file is handwritten. glabel decompress -.if VERSION_SH == 1 lw $a3, 8($a0) lw $t9, 0xc($a0) lw $t8, 4($a0) @@ -54,96 +53,4 @@ glabel decompress bne $a1, $t8, .L802772C0 addi $a2, $a2, -1 jr $ra - nop -.elseif VERSION_EU == 1 - lw $a3, 8($a0) - lw $t9, 0xc($a0) - lw $t8, 4($a0) - add $a3, $a3, $a0 - add $t9, $t9, $a0 - move $a2, $zero - addi $a0, $a0, 0x10 - add $t8, $t8, $a1 -.L8026ED80: - bnezl $a2, .L8026ED98 - slt $t1, $t0, $zero - lw $t0, ($a0) - li $a2, 32 - addi $a0, $a0, 4 - slt $t1, $t0, $zero -.L8026ED98: - beql $t1, $zero, .L8026EDB8 - lhu $t2, ($a3) - lb $t2, ($t9) - addi $t9, $t9, 1 - addi $a1, $a1, 1 - b .L8026EDE4 - sb $t2, -1($a1) - lhu $t2, ($a3) -.L8026EDB8: - addi $a3, $a3, 2 - srl $t3, $t2, 0xc - andi $t2, $t2, 0xfff - sub $t1, $a1, $t2 - addi $t3, $t3, 3 -.L8026EDCC: - lb $t2, -1($t1) - addi $t3, $t3, -1 - addi $t1, $t1, 1 - addi $a1, $a1, 1 - bnez $t3, .L8026EDCC - sb $t2, -1($a1) -.L8026EDE4: - sll $t0, $t0, 1 - bne $a1, $t8, .L8026ED80 - addi $a2, $a2, -1 - jr $ra - nop -.else - lw $t8, 4($a0) - lw $a3, 8($a0) - lw $t9, 0xc($a0) - move $a2, $zero - add $t8, $t8, $a1 - add $a3, $a3, $a0 - add $t9, $t9, $a0 - addi $a0, $a0, 0x10 -.L8027EF50: - bnez $a2, .L8027EF64 - nop - lw $t0, ($a0) - li $a2, 32 - addi $a0, $a0, 4 -.L8027EF64: - slt $t1, $t0, $zero - beqz $t1, .L8027EF88 - nop - lb $t2, ($t9) - addi $t9, $t9, 1 - sb $t2, ($a1) - addi $a1, $a1, 1 - b .L8027EFBC - nop -.L8027EF88: - lhu $t2, ($a3) - addi $a3, $a3, 2 - srl $t3, $t2, 0xc - andi $t2, $t2, 0xfff - sub $t1, $a1, $t2 - addi $t3, $t3, 3 -.L8027EFA0: - lb $t2, -1($t1) - addi $t3, $t3, -1 - addi $t1, $t1, 1 - sb $t2, ($a1) - addi $a1, $a1, 1 - bnez $t3, .L8027EFA0 - nop -.L8027EFBC: - sll $t0, $t0, 1 - addi $a2, $a2, -1 - bne $a1, $t8, .L8027EF50 - nop - jr $ra - nop -.endif + nop \ No newline at end of file diff --git a/src/game/rumble_init.c b/src/game/rumble_init.c index f7629397..b616cbc6 100644 --- a/src/game/rumble_init.c +++ b/src/game/rumble_init.c @@ -236,9 +236,11 @@ void func_sh_8024CA04(void) { static void thread6_rumble_loop(UNUSED void *a0) { OSMesg msg; + osSyncPrintf("start motor thread\n"); cancel_rumble(); sRumblePakThreadActive = TRUE; + osSyncPrintf("go motor thread\n"); while (TRUE) { // Block until VI diff --git a/src/usb/debug.h b/src/usb/debug.h index e77a76ea..ebe7ea69 100644 --- a/src/usb/debug.h +++ b/src/usb/debug.h @@ -9,10 +9,8 @@ #define DEBUG_MODE 1 // Enable/Disable debug mode #define DEBUG_INIT_MSG 1 // Print a message when debug mode has initialized #define USE_FAULTTHREAD 1 // Create a fault detection thread (libultra only) - #define OVERWRITE_OSPRINT 0 // Replaces osSyncPrintf calls with debug_printf (libultra only) + #define OVERWRITE_OSPRINT 1 // Replaces osSyncPrintf calls with debug_printf (libultra only) #define MAX_COMMANDS 25 // The max amount of user defined commands possible - - #define osSyncPrintf debug_printf // Temporary until libultra_d is linked instead // Fault thread definitions (libultra only) #define FAULT_THREAD_ID 13 diff --git a/src/usb/debugger.c b/src/usb/debugger.c new file mode 100644 index 00000000..2deec7f5 --- /dev/null +++ b/src/usb/debugger.c @@ -0,0 +1,933 @@ + +#include "debugger.h" +#include +#include +#include + +#define MAX_PACKET_SIZE 0x4000 +#define MAX_DEBUGGER_THREADS 8 + +#define GDB_ANY_THREAD 0 +#define GDB_ALL_THREADS -1 + +#define GDB_STACKSIZE 0x400 +#define GDB_DEBUGGER_THREAD_ID 0xDBDB +#define GDB_POLL_DELAY (OS_CPU_COUNTER / 2) +#define GDB_QUICK_POLL_DELAY (OS_CPU_COUNTER / 100) +#define GDB_QUICK_POLL_COUNT 20 + +#define GDB_BRANCH_DELAY 0x80000000 + +#define GDB_IS_ATTACHED (1 << 0) +#define GDB_IS_WAITING_STOP (1 << 1) + +#define GDB_TRAP_IS_BREAK_CODE 0x123 + +#define GDB_BREAK_INSTRUCTION(code) (0x0000000D | (((code) & 0xfffff) << 6)) +#define GDB_TRAP_INSTRUCTION(code) (0x00000034 | (((code) & 0x3ff) << 6)) +#define GDB_GET_TRAP_CODE(instr) (((instr) >> 6) & 0x3ff) + +#define GDB_GET_EXC_CODE(cause) (((cause) >> 2) & 0x1f) + +extern OSThread * __osGetCurrFaultedThread(void); +extern OSThread * __osGetNextFaultedThread(OSThread *); + +// defined by makerom +extern char _codeSegmentDataStart[]; +extern char _codeSegmentTextStart[]; + +#define strStartsWith(str, constStr) (strncmp(str, constStr, sizeof constStr - 1) == 0) + +static OSThread* gdbTargetThreads[MAX_DEBUGGER_THREADS]; +static OSId gdbCurrentThreadG; +static OSId gdbCurrentThreadg; +static OSId gdbCurrentThreadc; +static char gdbPacketBuffer[MAX_PACKET_SIZE]; +static char gdbOutputBuffer[MAX_PACKET_SIZE]; +static int gdbNextBufferTarget; +static int gdbNextSearchIndex; +static int gdbRunFlags; +static int gdbQuickPollCount; + +static OSThread gdbDebuggerThread; +static u64 gdbDebuggerThreadStack[GDB_STACKSIZE/sizeof(u64)]; + +static OSTimer gdbPollTimer; +static OSMesgQueue gdbPollMesgQ; +static OSMesg gdbPollMesgQMessage; + +static struct GDBBreakpoint gdbBreakpoints[GDB_MAX_BREAK_POINTS]; + +void __gdbSetWatch(u32 value); +u32 __gdbGetWatch(); + +static int gdbSignals[32] = { + 2, // SIGINT + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 11, // SIGSEGV + 10, // SIGBUS + 10, // SIGBUS + 12, // SIGSYS + 5, // SIGTRAP + 4, // SIGILL + 30, // SIGUSR1 + 8, // SIGFPE + 5, // SIGTRAP + 0, // reserved + 8, // SIGFPE + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 5, // SIGTRAP + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved + 0, // reserved +}; + +u32 gdbGetFaultAddress(OSThread* thread) { + if (thread->context.cause & GDB_BRANCH_DELAY) { + // pc points to a branch instruction + return thread->context.pc + 4; + } else { + return thread->context.pc; + } +} + +OSThread* gdbFindThread(OSId id) { + int i; + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (gdbTargetThreads[i] && ( + id == GDB_ANY_THREAD || + osGetThreadId(gdbTargetThreads[i]) == id + )) { + return gdbTargetThreads[i]; + } + } + return NULL; +} + +OSThread* gdbNextThread(OSThread* curr, OSId id) { + if (id == GDB_ALL_THREADS) { + int i; + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (curr == NULL && gdbTargetThreads[i]) { + return gdbTargetThreads[i]; + } else if (curr != NULL && gdbTargetThreads[i] == curr) { + curr = NULL; + } + } + + return NULL; + } else { + if (curr) { + return NULL; + } else { + return gdbFindThread(id); + } + } +} + +int gdbReadHexDigit(char character) { + if (character >= 'a' && character <= 'f') { + return 10 + character - 'a'; + } else if (character >= 'A' && character <= 'F') { + return 10 + character - 'A'; + } else if (character >= '0' && character <= '9') { + return character - '0'; + } else { + return -1; + } +} + +u32 gdbParseHex(char* src, u32 maxBytes) { + u32 result = 0; + int currentChar; + u32 maxCharacters = maxBytes * 2; + + for (currentChar = 0; currentChar < maxCharacters; ++currentChar) { + int digit = gdbReadHexDigit(*src); + + if (digit != -1) { + result = (result << 4) + digit; + } else { + break; + } + + ++src; + } + + return result; +} + +OSId gdbParseThreadId(char* src) { + if (src[0] == '-') { + return GDB_ALL_THREADS; + } else { + return gdbParseHex(src, 4); + } +} + +static char gdbHexLetters[16] = "0123456789abcdef"; + +char* gdbWriteHex(char* target, u8* src, u32 bytes) { + u32 i; + for (i = 0; i < bytes; ++i) { + *target++ = gdbHexLetters[(*src) >> 4]; + *target++ = gdbHexLetters[(*src) & 0xF]; + ++src; + } + return target; +} + +char* gdbReadHex(u8* target, char* src, u32 maxBytes) { + u32 i; + for (i = 0; i < maxBytes; ++i) { + int firstDigit = gdbReadHexDigit(*src++); + + if (firstDigit == -1) { + return src; + } else { + int secondDigit = gdbReadHexDigit(*src++); + + if (secondDigit == -1) { + *target++ = firstDigit << 4; + return src; + } else { + *target++ = (firstDigit << 4) | secondDigit; + } + } + } + return src; +} + +void* gdbTranslateAddr(void* in) { + u32 physicalAddr = osVirtualToPhysical(in); + + if (physicalAddr >= osMemSize) { + return 0; + } else { + return (void*)(PHYS_TO_K0(physicalAddr)); + } +} + +void gdbCopy(char* dst, char* src, int len) +{ + while (len) + { + *dst++ = *src++; + --len; + } +} + +int gdbIsAlphaNum(int chr) +{ + return chr >= 'a' && chr <= 'z' || chr >= 'A' && chr <= 'Z' || chr >= '0' && chr <= '9'; +} + +int gdbApplyChecksum(char* message) +{ + char* messageStart = message; + if (*message == '$') { + ++message; + } + + u8 checksum = 0; + while (*message) + { + if (*message == '#') { + ++message; + break; + } + + checksum += (u8)*message; + ++message; + } + + sprintf(message, "%02x", checksum); + + return (message - messageStart) + 2; + return strlen(messageStart); +} + +void gdbWriteInstruction(u32 addr, u32 value) { + *((u32*)addr) = value; + osWritebackDCache((void*)addr, sizeof(u32)); + osInvalICache((void*)addr, sizeof(u32)); +} + +struct GDBBreakpoint* gdbFindBreakpoint(u32 addr) { + int i; + struct GDBBreakpoint* firstEmpty = NULL; + for (i = 0; i < GDB_MAX_BREAK_POINTS; ++i) { + if (!firstEmpty && gdbBreakpoints[i].type == GDBBreakpointTypeNone) { + firstEmpty = &gdbBreakpoints[i]; + } else if (gdbBreakpoints[i].type != GDBBreakpointTypeNone && gdbBreakpoints[i].addr == addr) { + return &gdbBreakpoints[i]; + } + } + + return firstEmpty; +} + +struct GDBBreakpoint* gdbInsertBreakPoint(u32 addr, enum GDBBreakpointType type) { + struct GDBBreakpoint* result = gdbFindBreakpoint(addr); + + if (result) { + if (result->type == GDBBreakpointTypeNone) { + result->prevValue = *((u32*)addr); + gdbWriteInstruction(addr, GDB_TRAP_INSTRUCTION(GDB_TRAP_IS_BREAK_CODE)); + result->type = type; + } else if (result->type < type) { + result->type = type; + } + result->addr = addr; + } + + return result; +} + +void gdbDisableBreakpoint(struct GDBBreakpoint* breakpoint) { + if (breakpoint && breakpoint->type == GDBBreakpointTypeUser) { + gdbWriteInstruction(breakpoint->addr, breakpoint->prevValue); + breakpoint->type = GDBBreakpointTypeUserUnapplied; + } +} + +void gdbRenableBreakpoint(struct GDBBreakpoint* breakpoint) { + if (breakpoint && breakpoint->type == GDBBreakpointTypeUserUnapplied) { + breakpoint->prevValue = *((u32*)breakpoint->addr); + gdbWriteInstruction(breakpoint->addr, GDB_TRAP_INSTRUCTION(GDB_TRAP_IS_BREAK_CODE)); + breakpoint->type = GDBBreakpointTypeUser; + } +} + +void gdbRemoveBreakpoint(struct GDBBreakpoint* brk) { + if (brk && brk->type != GDBBreakpointTypeNone) { + if (brk->type != GDBBreakpointTypeUserUnapplied) { + gdbWriteInstruction(brk->addr, brk->prevValue); + } + brk->prevValue = 0; + brk->addr = 0; + brk->type = GDBBreakpointTypeNone; + } +} + +enum GDBError gdbParsePacket(char* input, u32 len, char **commandStart, char **packetEnd) +{ + char* stringEnd = input + len; + while (input < stringEnd) + { + if (*input == '$') { + ++input; + *commandStart = input; + break; + } + ++input; + } + + while (input < stringEnd) { + if (*input == '#') { + *packetEnd = input; + ++input; + return GDBErrorNone; + } + else + { + ++input; + } + } + + return GDBErrorBadPacket; +} + +void gdbWaitForStop() { + gdbRunFlags |= GDB_IS_WAITING_STOP; +} + +enum GDBError gdbSendStopReply(OSThread* thread) { + char* current = gdbOutputBuffer; + int excCode = GDB_GET_EXC_CODE(thread->context.cause); + current += sprintf(current, "$T%02x", gdbSignals[excCode]); + u32 breakAddr = gdbGetFaultAddress(thread); + + u32 instr = *((u32*)breakAddr); + u32 trapCode = GDB_GET_TRAP_CODE(instr); + + // check for breakpoint or trap + if ( + // breakpoint + excCode == 9 || + // breakpoint simulated with trap code + excCode == 13 && trapCode == GDB_TRAP_IS_BREAK_CODE && GDB_TRAP_INSTRUCTION(trapCode) == instr) { + current += sprintf(current, "swbreak:"); + } + + current += sprintf(current, "thread:%d;", osGetThreadId(thread)); + *current++ = '#'; + *current++ = '\0'; + + int i; + for (i = 0; i < GDB_MAX_BREAK_POINTS; ++i) { + gdbRenableBreakpoint(&gdbBreakpoints[i]); + } + + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); +} + +void gdbResumeThread(OSThread* thread) { + if (thread->context.pc == (u32)gdbBreak) { + // hacky way to skip breakpoint instruction + thread->context.pc = (u32)thread->context.ra; + } + if ((GDB_GET_EXC_CODE(thread->context.cause) & CAUSE_EXCMASK) == EXC_WATCH) { + // TODO restore watch point + __gdbSetWatch(0); + } + gdbDisableBreakpoint(gdbFindBreakpoint(thread->context.pc)); + // clear fault flag + thread->flags &= ~OS_FLAG_FAULT; + + // step commands will frequently string to together a lot + // of nearby breakpoints. This is to make sure stepping + // doesn't cause a huge delay + gdbQuickPollCount = GDB_QUICK_POLL_COUNT; + osStartThread(thread); +} + +enum GDBError gdbReplyRegisters() { + char* current = gdbOutputBuffer; + *current++ = '$'; + + OSThread* thread = gdbFindThread(gdbCurrentThreadg); + + if (thread) { + /* 0~ GPR0-31(yes, include zero),[32]PS(status),LO,HI,BadVAddr,Cause,PC,[38]FPR0-31,[70]fpcs,fpir,[72]..(dsp?),[90]end */ + current += sprintf(current, "%08x%08x", 0, 0); // zero + current = gdbWriteHex(current, (u8*)&thread->context, offsetof(__OSThreadContext, gp)); + current += sprintf(current, "%08x%08x", 0, 0); // k0 + current += sprintf(current, "%08x%08x", 0, 0); // k1 + current = gdbWriteHex(current, (u8*)&thread->context.gp, offsetof(__OSThreadContext, lo) - offsetof(__OSThreadContext, gp)); + + current += sprintf(current, "%08x%08x", 0, thread->context.sr); + current = gdbWriteHex(current, (u8*)&thread->context.lo, sizeof(u64) * 2); + current += sprintf(current, "%08x%08x", 0, thread->context.badvaddr); + current += sprintf(current, "%08x%08x", 0, thread->context.cause); + if (thread->context.pc == (u32)gdbBreak) { + // when inside gdbBreak, report the breakpoint to be at where the function was called + current += sprintf(current, "%08x%08x", 0, (u32)thread->context.ra); + } else { + current += sprintf(current, "%08x%08x", 0, thread->context.pc); + } + + current = gdbWriteHex(current, (u8*)&thread->context.fp0, sizeof(__OSThreadContext) - offsetof(__OSThreadContext, fp0)); + current += sprintf(current, "%08x%08x", 0, thread->context.fpcsr); + } + + *current++ = '#'; + *current++ = '\0'; + + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); +} + +enum GDBError gdbWriteRegisters(char* commandStart, char *packetEnd) { + + OSThread* thread = gdbFindThread(gdbCurrentThreadg); + + char* current = commandStart + 1; + u32 messageLength = (u32)(packetEnd - current); + + if (messageLength != 880) { + return GDBErrorBadPacket; + } + + if (thread) { + current += 16; // zero register + current = gdbReadHex((u8*)&thread->context, current, offsetof(__OSThreadContext, gp)); + current += 32; // k0 and k1 + current = gdbReadHex((u8*)&thread->context.gp, current, offsetof(__OSThreadContext, lo) - offsetof(__OSThreadContext, gp)); + current += 8; current = gdbReadHex((u8*)&thread->context.sr, current, sizeof(u32)); + current = gdbReadHex((u8*)&thread->context.lo, current, sizeof(u64) * 2); + current += 8; current = gdbReadHex((u8*)&thread->context.badvaddr, current, sizeof(u32)); + current += 8; current = gdbReadHex((u8*)&thread->context.cause, current, sizeof(u32)); + current += 8; current = gdbReadHex((u8*)&thread->context.pc, current, sizeof(u32)); + current = gdbReadHex((u8*)&thread->context.fp0, current, sizeof(__OSThreadContext) - offsetof(__OSThreadContext, fp0)); + current += 8; current = gdbReadHex((u8*)&thread->context.fpcsr, current, sizeof(u32)); + } + + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); +} + +enum GDBError gdbReplyMemory(char* commandStart, char *packetEnd) { + u32 prevWatch = __gdbGetWatch(); + // clear the watch so the debugger thread doesn't get the interrupt + __gdbSetWatch(0); + char* current = gdbOutputBuffer; + *current++ = '$'; + + char* lenText = commandStart + 1; + + while (*lenText != ',') { + if (lenText == packetEnd) { + return GDBErrorBadPacket; + } + ++lenText; + } + + u8* dataSrc = gdbTranslateAddr((u8*)gdbParseHex(commandStart + 1, 4)); + u32 len = gdbParseHex(lenText + 1, 4); + + if ((u32)dataSrc < K0BASE) { + while ((u32)dataSrc < K0BASE && len > 0) { + *current++ = '0'; + *current++ = '0'; + ++dataSrc; + --len; + } + } + + if (len > 0) { + u32 maxLen; + if ((u32)dataSrc < osMemSize + K0BASE) { + maxLen = (osMemSize + K0BASE) - (u32)dataSrc; + } + + if (len > maxLen) { + len = maxLen; + } + + char* strEnd = gdbWriteHex(current, dataSrc, len); + + current = strEnd; + } + + *current++ = '#'; + *current++ = '\0'; + __gdbSetWatch(prevWatch); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); +} + +enum GDBError gdbWriteMemory(char *commandStart, char* packetEnd) { + u32 prevWatch = __gdbGetWatch(); + // clear the watch so the debugger thread doesn't get the interrupt + __gdbSetWatch(0); + char* current = gdbOutputBuffer; + *current++ = '$'; + + char* lenText = commandStart + 1; + + while (*lenText != ',') { + if (lenText == packetEnd) { + return GDBErrorBadPacket; + } + ++lenText; + } + + char* dataText = lenText + 1; + + while (*dataText != ':') { + if (dataText == packetEnd) { + return GDBErrorBadPacket; + } + ++dataText; + } + + ++dataText; + + u8* dataTarget = gdbTranslateAddr((u8*)gdbParseHex(commandStart + 1, 4)); + if (dataTarget) { + u32 len = gdbParseHex(lenText + 1, 4); + gdbReadHex(dataTarget, dataText, len); + } + __gdbSetWatch(prevWatch); + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); +} + +enum GDBError gdbHandleQuery(char* commandStart, char *packetEnd) { + if (strStartsWith(commandStart, "qSupported")) { + strcpy(gdbOutputBuffer, "$PacketSize=4000;vContSupported+;swbreak+#"); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qTStatus")) { + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + } else if (strStartsWith(commandStart, "qfThreadInfo")) { + strcpy(gdbOutputBuffer, "$m"); + char* outputWrite = gdbOutputBuffer + 2; + int i; + int first = 1; + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (gdbTargetThreads[i]) { + if (first) { + first = 0; + outputWrite += sprintf(outputWrite, "%x", osGetThreadId(gdbTargetThreads[i])); + } else { + outputWrite += sprintf(outputWrite, ",%x", osGetThreadId(gdbTargetThreads[i])); + } + } + } + *outputWrite++ = '#'; + *outputWrite++ = '\0'; + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qsThreadInfo")) { + strcpy(gdbOutputBuffer, "$l#"); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qAttached")) { + strcpy(gdbOutputBuffer, "$0#"); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qC")) { + strcpy(gdbOutputBuffer, "$QC"); + char* outputWrite = gdbOutputBuffer + 3; + int i; + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (gdbTargetThreads[i]) { + outputWrite += sprintf(outputWrite, "%x", osGetThreadId(gdbTargetThreads[i])); + break; + } + } + + if (i == MAX_DEBUGGER_THREADS) { + strcpy(gdbOutputBuffer, "$E00#"); + } else { + *outputWrite++ = '#'; + *outputWrite++ = '\0'; + } + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qTfV")) { + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + } else if (strStartsWith(commandStart, "qTfP")) { + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + } else if (strStartsWith(commandStart, "qOffsets")) { + // 0x20 is a magic number. I don't know why but it makes the addresses line up correctly + sprintf(gdbOutputBuffer, "$Text=%x;Data=%x;Bss=%x#", 0, 0, 0); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else if (strStartsWith(commandStart, "qSymbol")) { + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); + } else if (strStartsWith(commandStart, "qThreadExtraInfo")) { + OSId threadId = gdbParseHex(commandStart + sizeof("qThreadExtraInfo"), 4); + + OSThread* thread = gdbFindThread(threadId); + + if (thread) { + int strLen = sprintf(gdbOutputBuffer + 0x200, "state %d priority %d", thread->state, thread->priority); + gdbOutputBuffer[0] = '$'; + char* nextOut = gdbWriteHex(gdbOutputBuffer + 1, gdbOutputBuffer + 0x200, strLen); + *nextOut++ = '#'; + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else { + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + } + } + + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); +} + +enum GDBError gdbHandleV(char* commandStart, char *packetEnd) { + if (strStartsWith(commandStart, "vMustReplyEmpty")) { + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + } else if (strStartsWith(commandStart, "vCont")) { + if (commandStart[5] == '?') { + strcpy(gdbOutputBuffer, "$c;t#"); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } else { + OSId threadId; + + char* idLoc = commandStart + 6; + while (idLoc < packetEnd && *idLoc++ != ':'); + + if (idLoc < packetEnd && *idLoc != '#') { + threadId = gdbParseThreadId(idLoc); + } else { + threadId = GDB_ALL_THREADS; + } + + OSThread *thread = NULL; + + while ((thread = gdbNextThread(thread, threadId))) { + switch (commandStart[6]) + { + case 'c': + { + gdbResumeThread(thread); + break; + } + case 's': + { + // TODO + break; + } + case 't': + { + osStopThread(thread); + break; + } + case 'r': + { + // TODO + break; + } + } + } + + gdbWaitForStop(); + return GDBErrorNone; + } + } else if (strStartsWith(commandStart, "vKill")) { + int i; + for (i = 0; i < GDB_MAX_BREAK_POINTS; ++i) { + gdbRemoveBreakpoint(&gdbBreakpoints[i]); + } + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (gdbTargetThreads[i] && gdbTargetThreads[i]->state == OS_STATE_STOPPED) { + gdbResumeThread(gdbTargetThreads[i]); + } + } + gdbRunFlags &= ~GDB_IS_ATTACHED; + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); + } + + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); +} + +enum GDBError gdbHandlePacket(char* commandStart, char *packetEnd) { + switch (*commandStart) { + case 'q': + return gdbHandleQuery(commandStart, packetEnd); + case 'v': + return gdbHandleV(commandStart, packetEnd); + case 'H': + { + OSId threadId; + + if (commandStart[2] == '-') { + threadId = -1; + } else { + threadId = gdbParseHex(commandStart + 2, 4); + } + + switch (commandStart[1]) { + case 'G': + gdbCurrentThreadG = threadId; + break; + case 'g': + gdbCurrentThreadg = threadId; + break; + case 'c': + gdbCurrentThreadc = threadId; + break; + } + + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); + } + case '!': + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); + case '?': + return gdbSendStopReply(gdbFindThread(GDB_ANY_THREAD)); + case 'g': + return gdbReplyRegisters(); + case 'G': + return gdbWriteRegisters(commandStart, packetEnd); + case 'm': + return gdbReplyMemory(commandStart, packetEnd); + case 'M': + return gdbWriteMemory(commandStart, packetEnd); + case 'D': + gdbRunFlags &= ~GDB_IS_ATTACHED; + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); + case 'z': + case 'Z': + { + if (commandStart[1] == '0') { + u32 addr = gdbParseHex(&commandStart[3], 4); + + if (*commandStart == 'z') { + gdbRemoveBreakpoint(gdbFindBreakpoint(addr)); + } else { + struct GDBBreakpoint* brk = gdbInsertBreakPoint(addr, GDBBreakpointTypeUser); + + if (!brk) { + strcpy(gdbOutputBuffer, "$E00#"); + return gdbSendMessage(GDBDataTypeGDB, gdbOutputBuffer, gdbApplyChecksum(gdbOutputBuffer)); + } + } + + return gdbSendMessage(GDBDataTypeGDB, "$OK#9a", strlen("$OK#9a")); + } else { + break; + } + } + } + + return gdbSendMessage(GDBDataTypeGDB, "$#00", strlen("$#00")); +} + +enum GDBError gdbCheckForPacket() { + char* commandStart; + char* packetEnd; + + if (gdbSerialCanRead()) { + enum GDBDataType type; + u32 len; + enum GDBError err = gdbPollHeader(&type, &len); + if (err != GDBErrorNone) return err; + gdbPacketBuffer[len] = '\0'; + err = gdbReadData(gdbPacketBuffer, len, &len); + if (err != GDBErrorNone) return err; + err = gdbFinishRead(); + if (err != GDBErrorNone) return err; + + if (type == GDBDataTypeGDB) { + if (*gdbPacketBuffer == 0x03) { + if (gdbRunFlags & GDB_IS_WAITING_STOP) { + OSThread* targetThread = gdbFindThread(GDB_ANY_THREAD); + + if (targetThread) { + osStopThread(targetThread); + gdbRunFlags &= ~GDB_IS_WAITING_STOP; + gdbSendStopReply(targetThread); + } + } + } else { + err = gdbParsePacket(gdbPacketBuffer, len, &commandStart, &packetEnd); + if (err != GDBErrorNone) return err; + + err = gdbSendMessage(GDBDataTypeGDB, "+", strlen("+")); + if (err != GDBErrorNone) return err; + + err = gdbHandlePacket(commandStart, packetEnd); + if (err != GDBErrorNone) return err; + } + } + + return GDBErrorNone; + } + + return GDBErrorUSBNoData; +} + +void gdbErrorHandler(s16 code, s16 numArgs, ...) { + va_list valist; + + gdbSendMessage( + GDBDataTypeText, + gdbPacketBuffer, + sprintf(gdbPacketBuffer, "code %04X args %04X", code, numArgs) + ); +} + +void gdbDebuggerLoop(void *arg) { + OSMesg msg; + osCreateMesgQueue(&gdbPollMesgQ, &gdbPollMesgQMessage, 1); + + // give time for the main thead to hit the starting breakpiont + osSetTimer(&gdbPollTimer, GDB_POLL_DELAY, 0, &gdbPollMesgQ, NULL); + osRecvMesg(&gdbPollMesgQ, &msg, OS_MESG_BLOCK); + + gdbRunFlags |= GDB_IS_ATTACHED; + while (gdbRunFlags & GDB_IS_ATTACHED) { + while (gdbCheckForPacket() == GDBErrorNone); + + if (gdbRunFlags & GDB_IS_WAITING_STOP) { + osSetTimer(&gdbPollTimer, gdbQuickPollCount ? GDB_QUICK_POLL_DELAY : GDB_POLL_DELAY, 0, &gdbPollMesgQ, NULL); + + if (gdbQuickPollCount > 0) { + --gdbQuickPollCount; + } + + int i; + // while program is running, decrease polling rate + osRecvMesg(&gdbPollMesgQ, &msg, OS_MESG_BLOCK); + + for (i = 0; i < MAX_DEBUGGER_THREADS; ++i) { + if (gdbTargetThreads[i] && (gdbTargetThreads[i]->flags & OS_FLAG_FAULT)) { + gdbRunFlags &= ~GDB_IS_WAITING_STOP; + gdbSendStopReply(gdbTargetThreads[i]); + break; + } + } + } + } + osDestroyThread(&gdbDebuggerThread); +} + +enum GDBError gdbInitDebugger(OSPiHandle* handler, OSMesgQueue* dmaMessageQ, OSThread** forThreads, u32 forThreadsLen) +{ + enum GDBError err = gdbSerialInit(handler, dmaMessageQ); + if (err != GDBErrorNone) return err; + + OSThread* primaryThread = NULL; + OSId currThread = osGetThreadId(NULL); + + int i; + for (i = 0; i < forThreadsLen && i < MAX_DEBUGGER_THREADS; ++i) { + gdbTargetThreads[i] = forThreads[i]; + + if (osGetThreadId(forThreads[i]) == currThread) { + primaryThread = forThreads[i]; + } + } + + for (;i < MAX_DEBUGGER_THREADS; ++i) { + gdbTargetThreads[i] = NULL; + } + + osCreateThread(&gdbDebuggerThread, GDB_DEBUGGER_THREAD_ID, gdbDebuggerLoop, NULL, gdbDebuggerThreadStack + GDB_STACKSIZE/sizeof(u64), 11); + osStartThread(&gdbDebuggerThread); + + // The main thread needs to be paused before interrupts work + // I'm not sure why + if (primaryThread != NULL) { + osStopThread(primaryThread); + gdbBreak(); + } + + return GDBErrorNone; + +} + +void* getWatchPoint() { + return (void*)(__gdbGetWatch() & ~0x7); +} + +void gdbSetWatchPoint(void* addr, int read, int write) { + __gdbSetWatch(((u32)addr & 0x1ffffff8) | (read ? 0x2 : 0) | (write ? 0x1 : 0)); +} + +void gdbClearWatchPoint() { + __gdbSetWatch(0); +} + +/** + * Implement gdbBreak in assembly to ensure that `teq` is the first instruction of the function + */ +asm( +".global gdbBreak\n" +".balign 4\n" +"gdbBreak:\n" + "teq $0, $0\n" + "jr $ra\n" + "nop\n" + +".global __gdbSetWatch\n" +".balign 4\n" +"__gdbSetWatch:\n" + "MTC0 $a0, $18\n" + "jr $ra\n" + "nop\n" + +".global __gdbGetWatch\n" +".balign 4\n" +"__gdbGetWatch:\n" + "MFC0 $v0, $18\n" + "jr $ra\n" + "nop\n" +); \ No newline at end of file diff --git a/src/usb/debugger.h b/src/usb/debugger.h new file mode 100644 index 00000000..8373ef97 --- /dev/null +++ b/src/usb/debugger.h @@ -0,0 +1,60 @@ + +#ifndef __DEBUGGER_H +#define __DEBUGGER_H + +#include +#include "serial.h" + +enum GDBBreakpointType { + GDBBreakpointTypeNone, + GDBBreakpointTypeTemporary, + GDBBreakpointTypeUser, + GDBBreakpointTypeUserUnapplied, +}; + +struct GDBBreakpoint { + u32 addr; + u32 prevValue; + enum GDBBreakpointType type; +}; + +#define GDB_MAX_BREAK_POINTS 32 + +/** + * Initializes the debugger + * @param handler Pi Handler used for DMA, this is return value + * of osCartRomInit + * @param dmaMessageQ The message queue used to coordinate use of + * the DMA. This should be the same message queue used for DMA + * actions in the rest of your program + * @param forThreads an array of threads you want the debugger to + * watch connect to. If a thread isn't included in this array + * it will be ignored by the debugger + * @param forThreadsLen the length of the forThreads array + */ +enum GDBError gdbInitDebugger(OSPiHandle* handler, OSMesgQueue* dmaMessageQ, OSThread** forThreads, u32 forThreadsLen); +enum GDBError gdbCheckForPacket(); + +/** + * A hard coded breakpoint you can include in compiled code + */ +void gdbBreak(); + +/** + * Gets the address of the current memory watch point + */ +void* getWatchPoint(); +/** + * Sets a watch point. When a thread reads or writes to the given + * address. It will pause in the debugger + * @param addr The address to watch + * @param read a non zero value signals that a thread will break when addr is read + * @param write a non zero value signals that a thread will break when addr is written + */ +void gdbSetWatchPoint(void* addr, int read, int write); +/** + * Removes the current watch point this is the same as doing gdbSetWatchPoint(0, 0, 0) + */ +void gdbClearWatchPoint(); + +#endif \ No newline at end of file diff --git a/src/usb/serial.c b/src/usb/serial.c new file mode 100644 index 00000000..2e4d164e --- /dev/null +++ b/src/usb/serial.c @@ -0,0 +1,519 @@ + +#include "serial.h" + +u8 (*gdbSerialCanRead)(); + +#if USE_UNF_LOADER +#include "usb.h" + +u32 gdbPendingUNFHeader; +u32 gdbPendingUNFData; + +u8 gdbSerialCanRead_UNF() { + if (gdbPendingUNFHeader == 0) { + gdbPendingUNFHeader = usb_poll(); + } + + return gdbPendingUNFHeader != 0; +} + +enum GDBError gdbSerialInit(OSPiHandle* handler, OSMesgQueue* dmaMessageQ) { + usb_initialize(); + gdbSerialCanRead = gdbSerialCanRead_UNF; + return GDBErrorNone; +} + +enum GDBError gdbSendMessage(enum GDBDataType type, char* src, u32 len) { + usb_write(type, src, len); + return GDBErrorNone; +} + +enum GDBError gdbPollHeader(enum GDBDataType* type, u32* len) { + if (gdbSerialCanRead_UNF()) { + *type = USBHEADER_GETTYPE(gdbPendingUNFHeader); + gdbPendingUNFData = USBHEADER_GETSIZE(gdbPendingUNFHeader); + *len = gdbPendingUNFData; + gdbPendingUNFHeader = 0; + return GDBErrorNone; + } else { + return GDBErrorUSBNoData; + } +} + +enum GDBError gdbReadData(char* target, u32 len, u32* dataRead) { + if (len > gdbPendingUNFData) { + len = gdbPendingUNFData; + } + gdbPendingUNFData -= len; + usb_read(target, len); + *dataRead = len; +} + +enum GDBError gdbFinishRead() { + usb_skip(gdbPendingUNFData); + gdbPendingUNFData = 0; +} + +#else // USE_UNF_LOADER + +#include + +#define GDB_USB_SERIAL_SIZE 512 + +static OSMesgQueue* __gdbDmaMessageQ; + +#define KSEG0 0x80000000 +#define KSEG1 0xA0000000 + +#define REG_BASE 0x1F800000 +#define REG_ADDR(reg) (KSEG1 | REG_BASE | (reg)) + +#define USB_LE_CFG 0x8000 +#define USB_LE_CTR 0x4000 + +#define USB_CFG_ACT 0x0200 +#define USB_CFG_RD 0x0400 +#define USB_CFG_WR 0x0000 + +#define USB_STA_ACT 0x0200 +#define USB_STA_RXF 0x0400 +#define USB_STA_TXE 0x0800 +#define USB_STA_PWR 0x1000 +#define USB_STA_BSY 0x2000 + +#define USB_CMD_RD_NOP (USB_LE_CFG | USB_LE_CTR | USB_CFG_RD) +#define USB_CMD_RD (USB_LE_CFG | USB_LE_CTR | USB_CFG_RD | USB_CFG_ACT) +#define USB_CMD_WR_NOP (USB_LE_CFG | USB_LE_CTR | USB_CFG_WR) +#define USB_CMD_WR (USB_LE_CFG | USB_LE_CTR | USB_CFG_WR | USB_CFG_ACT) + +#define HEADER_TEXT_LENGTH 4 +#define MESSAGE_HEADER_SIZE 8 +#define MESSAGE_FOOTER_SIZE 4 + +#define GDB_IS_READING 1 + +#define ALIGN_16_BYTES(input) (((input) + 0xF) & ~0xF) +#define ALIGN_8_BYTES(input) (((input) + 0x7) & ~0x7) +#define ALIGN_2_BYTES(input) (((input) + 0x1) & ~0x1) + +#define USB_MIN_SIZE 16 + +// used to ensure that the memory buffers are aligned to 8 bytes +long long __gdbAlignAndFlags; +char gdbSerialSendBuffer[GDB_USB_SERIAL_SIZE]; +char gdbSerialReadBuffer[GDB_USB_SERIAL_SIZE]; +static OSPiHandle gdbSerialHandle; +static OSMesgQueue gdbSerialSemaphore; +static OSMesg gdbSerialSemaphoreMsg; + +static char gdbHeaderText[] = "DMA@"; +static char gdbFooterText[] = "CMPH"; + +enum GDBError (*gdbSerialRead)(char* target, u32 len); +enum GDBError (*gdbSerialWrite)(char* src, u32 len); +enum GDBCartType gdbCartType; + +enum GDBEVRegister { + GDB_EV_REGISTER_USB_CFG = 0x0004, + GDB_EV_REGISTER_USB_TIMER = 0x000C, + GDB_EV_REGISTER_VERSION = 0x0014, + GDB_EV_REGISTER_USB_DATA = 0x0400, + GDB_EV_REGISTER_SYS_CFG = 0x8000, + GDB_EV_REGISTER_KEY = 0x8004, +}; + +enum GDBError gdbDMARead(void* ram, u32 piAddress, u32 len) { + OSIoMesg dmaIoMesgBuf; + + dmaIoMesgBuf.hdr.pri = OS_MESG_PRI_NORMAL; + dmaIoMesgBuf.hdr.retQueue = __gdbDmaMessageQ; + dmaIoMesgBuf.dramAddr = ram; + dmaIoMesgBuf.devAddr = piAddress & 0x1FFFFFFF; + dmaIoMesgBuf.size = len; + + osInvalDCache(ram, len); + if (osEPiStartDma(&gdbSerialHandle, &dmaIoMesgBuf, OS_READ) == -1) + { + return GDBErrorDMA; + } + + osRecvMesg(__gdbDmaMessageQ, NULL, OS_MESG_BLOCK); + + return GDBErrorNone; +} + +enum GDBError gdbDMAWrite(void* ram, u32 piAddress, u32 len) { + OSIoMesg dmaIoMesgBuf; + + dmaIoMesgBuf.hdr.pri = OS_MESG_PRI_NORMAL; + dmaIoMesgBuf.hdr.retQueue = __gdbDmaMessageQ; + dmaIoMesgBuf.dramAddr = ram; + dmaIoMesgBuf.devAddr = piAddress & 0x1FFFFFFF; + dmaIoMesgBuf.size = len; + + osWritebackDCache(ram, len); + if (osEPiStartDma(&gdbSerialHandle, &dmaIoMesgBuf, OS_WRITE) == -1) + { + return GDBErrorDMA; + } + + osRecvMesg(__gdbDmaMessageQ, NULL, OS_MESG_BLOCK); + + return GDBErrorNone; +} + +u32 gdbReadReg(enum GDBEVRegister reg) { + return *((u32*)REG_ADDR(reg)); +} + +void gdbWriteReg(enum GDBEVRegister reg, u32 value) { + *((u32*)REG_ADDR(reg)) = value; +} + +enum GDBError gdbUsbBusy() { + u32 tout = 0; + enum GDBError err; + u32 registerValue; + + do { + if (tout++ > 8192) { + gdbWriteReg(GDB_EV_REGISTER_USB_CFG, USB_CMD_RD_NOP); + return GDBErrorUSBTimeout; + } + registerValue = gdbReadReg(GDB_EV_REGISTER_USB_CFG); + } while ((registerValue & USB_STA_ACT) != 0); + + return GDBErrorNone; +} + +u8 gdbSerialCanRead_X7() { + return (gdbReadReg(GDB_EV_REGISTER_USB_CFG) & (USB_STA_PWR | USB_STA_RXF)) == USB_STA_PWR; +} + +enum GDBError gdbSerialRead_X7(char* target, u32 len) { + while (len) { + int chunkSize = GDB_USB_SERIAL_SIZE; + if (chunkSize > len) { + chunkSize = len; + } + int baddr = GDB_USB_SERIAL_SIZE - chunkSize; + + gdbWriteReg(GDB_EV_REGISTER_USB_CFG, USB_CMD_RD | baddr); + + enum GDBError err = gdbUsbBusy(); + if (err != GDBErrorNone) return err; + + err = gdbDMARead(target, REG_ADDR(GDB_EV_REGISTER_USB_DATA + baddr), chunkSize); + if (err != GDBErrorNone) return err; + + target += chunkSize; + len -= chunkSize; + } + + return GDBErrorNone; +} + +u8 gdbSerialCanWrite() { + return (gdbReadReg(GDB_EV_REGISTER_USB_CFG) & (USB_STA_PWR | USB_STA_TXE)) == USB_STA_PWR; +} + +enum GDBError gdbWaitForWritable() { + u32 timeout = 0; + + while (!gdbSerialCanWrite()) { + if (++timeout == 8192) { + return GDBErrorUSBTimeout; + } + } + + return GDBErrorNone; +} + +enum GDBError gdbSerialWrite_X7(char* src, u32 len) { + enum GDBError err = gdbWaitForWritable(); + if (err != GDBErrorNone) return err; + + gdbWriteReg(GDB_EV_REGISTER_USB_CFG, USB_CMD_WR_NOP); + + while (len) { + int chunkSize = GDB_USB_SERIAL_SIZE; + if (chunkSize > len) { + chunkSize = len; + } + int baddr = GDB_USB_SERIAL_SIZE - chunkSize; + err = gdbDMAWrite(src, REG_ADDR(GDB_EV_REGISTER_USB_DATA + baddr), chunkSize); + if (err != GDBErrorNone) return err; + + gdbWriteReg(GDB_EV_REGISTER_USB_CFG, USB_CMD_WR | baddr); + + err = gdbUsbBusy(); + if (err != GDBErrorNone) return err; + + src += chunkSize; + len -= chunkSize; + } + + return GDBErrorNone; +} + +u8 gdbSerialCanRead_cen64() { + return *((volatile u32*)(0xA0000000 | 0x18000004)); +} + +enum GDBError gdbSerialRead_cen64(char* target, u32 len) { + while (len--) { + *target++ = *((volatile u32*)(0xA0000000 | 0x18000000)); + } + return GDBErrorNone; +} + +enum GDBError gdbSerialWrite_cen64(char* target, u32 len) { + while (len--) { + *((volatile u32*)(0xA0000000 | 0x18000000)) = *target++; + } + return GDBErrorNone; +} + +enum GDBError gdbSerialInit(OSPiHandle* handler, OSMesgQueue* dmaMessageQ) +{ + gdbSerialHandle = *handler; + + volatile u32* cen64Check = (volatile u32*)(0xA0000000 | 0x18000008); + if (*cen64Check == 0xcece) { + gdbSerialCanRead = gdbSerialCanRead_cen64; + gdbSerialRead = gdbSerialRead_cen64; + gdbSerialWrite = gdbSerialWrite_cen64; + gdbCartType = GDBCartTypeCen64; + } else { + gdbSerialCanRead = gdbSerialCanRead_X7; + gdbSerialRead = gdbSerialRead_X7; + gdbSerialWrite = gdbSerialWrite_X7; + gdbCartType = GDBCartTypeX7; + + gdbSerialHandle.latency = 0x04; + gdbSerialHandle.pulse = 0x0C; + + OSIntMask prev = osGetIntMask(); + osSetIntMask(0); + gdbSerialHandle.next = __osPiTable; + __osPiTable = &gdbSerialHandle; + osSetIntMask(prev); + + __gdbDmaMessageQ = dmaMessageQ; + + osCreateMesgQueue(&gdbSerialSemaphore, &gdbSerialSemaphoreMsg, 1); + + gdbWriteReg(GDB_EV_REGISTER_KEY, 0xAA55); + gdbWriteReg(GDB_EV_REGISTER_SYS_CFG, 0); + gdbWriteReg(GDB_EV_REGISTER_USB_CFG, USB_CMD_RD_NOP); + } + + return GDBErrorNone; +} + +enum GDBError __gdbSendMessage(enum GDBDataType type, char* src, u32 len) { + if (len >= 0x1000000) { + return GDBErrorMessageTooLong; + } + + u32 header = (type << 24) | (0xFFFFFF & len); + strcpy(gdbSerialSendBuffer, gdbHeaderText); + memcpy(gdbSerialSendBuffer + HEADER_TEXT_LENGTH, (char*)&header, sizeof(u32)); + + u32 firstChunkLength = len; + + if (firstChunkLength > GDB_USB_SERIAL_SIZE - MESSAGE_HEADER_SIZE) { + firstChunkLength = GDB_USB_SERIAL_SIZE - MESSAGE_HEADER_SIZE; + } + + enum GDBError err; + + memcpy(gdbSerialSendBuffer + MESSAGE_HEADER_SIZE, src, firstChunkLength); + + if (GDB_USB_SERIAL_SIZE >= MESSAGE_HEADER_SIZE + firstChunkLength + MESSAGE_FOOTER_SIZE) { + // entire message fits into a single 512 byte buffer + strcpy(gdbSerialSendBuffer + MESSAGE_HEADER_SIZE + firstChunkLength, gdbFooterText); + err = gdbSerialWrite(gdbSerialSendBuffer, ALIGN_2_BYTES(MESSAGE_HEADER_SIZE + firstChunkLength + MESSAGE_FOOTER_SIZE)); + if (err != GDBErrorNone) return err; + } else { + // header partially fits + if (GDB_USB_SERIAL_SIZE > MESSAGE_HEADER_SIZE + firstChunkLength) { + memcpy(gdbSerialSendBuffer + MESSAGE_HEADER_SIZE + firstChunkLength, gdbFooterText, GDB_USB_SERIAL_SIZE - MESSAGE_HEADER_SIZE - firstChunkLength); + } + + err = gdbSerialWrite(gdbSerialSendBuffer, GDB_USB_SERIAL_SIZE); + if (err != GDBErrorNone) return err; + src += firstChunkLength; + len -= firstChunkLength; + + while (len >= GDB_USB_SERIAL_SIZE) { + if ((int)src == ((int)src & !0x7)) { + err = gdbSerialWrite(src, GDB_USB_SERIAL_SIZE); + } else { + memcpy(gdbSerialSendBuffer, src, GDB_USB_SERIAL_SIZE); + gdbSerialWrite(gdbSerialSendBuffer, GDB_USB_SERIAL_SIZE); + } + if (err != GDBErrorNone) return err; + src += GDB_USB_SERIAL_SIZE; + len -= GDB_USB_SERIAL_SIZE; + } + + if (len) { + memcpy(gdbSerialSendBuffer, src, len); + } + + if (len + MESSAGE_FOOTER_SIZE <= GDB_USB_SERIAL_SIZE) { + strcpy(gdbSerialSendBuffer + len, gdbFooterText); + err = gdbSerialWrite(gdbSerialSendBuffer, len + MESSAGE_FOOTER_SIZE); + if (err != GDBErrorNone) return err; + } else { + memcpy(gdbSerialSendBuffer + len, gdbFooterText, GDB_USB_SERIAL_SIZE - len); + err = gdbSerialWrite(gdbSerialSendBuffer, GDB_USB_SERIAL_SIZE); + if (err != GDBErrorNone) return err; + strcpy(gdbSerialSendBuffer, &gdbFooterText[GDB_USB_SERIAL_SIZE - len]); + err = gdbSerialWrite(gdbSerialSendBuffer, MESSAGE_FOOTER_SIZE + len - GDB_USB_SERIAL_SIZE); + if (err != GDBErrorNone) return err; + } + } + + return GDBErrorNone; +} + +enum GDBError gdbSendMessage(enum GDBDataType type, char* src, u32 len) { + // usb_write(type, src, len); + // return GDBErrorNone; + OSMesg msg = 0; + // osSendMesg(&gdbSerialSemaphore, msg, OS_MESG_BLOCK); + enum GDBError result = __gdbSendMessage(type, src, len); + // osRecvMesg(&gdbSerialSemaphore, &msg, OS_MESG_NOBLOCK); + return result; +} + +static u32 gdbReadHead; +static u32 gdbMaxReadHead; +static u32 gdbRemainingLen; + +enum GDBError gdbPollHeader(enum GDBDataType* type, u32* len) { + if (!gdbSerialCanRead()) { + return GDBErrorUSBNoData; + } + + enum GDBError err = gdbSerialRead(gdbSerialReadBuffer, USB_MIN_SIZE); + if (err != GDBErrorNone) return err; + + if (strncmp(gdbSerialReadBuffer, gdbHeaderText, HEADER_TEXT_LENGTH) == 0) { + *type = gdbSerialReadBuffer[4]; + gdbRemainingLen = 0xFFFFFF & *((u32*)&gdbSerialReadBuffer[4]); + *len = gdbRemainingLen; + gdbReadHead = MESSAGE_HEADER_SIZE; + gdbMaxReadHead = USB_MIN_SIZE; + __gdbAlignAndFlags |= GDB_IS_READING; + + u32 dataWithLength = gdbRemainingLen + MESSAGE_HEADER_SIZE + MESSAGE_FOOTER_SIZE; + return GDBErrorNone; + } + + return GDBErrorUSBNoData; +} + +enum GDBError gdbReadData(char* target, u32 len, u32* dataRead) { + if (len > gdbRemainingLen) { + len = gdbRemainingLen; + } + + u32 pendingData = gdbMaxReadHead - gdbReadHead; + *dataRead = 0; + + if (len <= pendingData) { + memcpy(target, &gdbSerialReadBuffer[gdbReadHead], len); + gdbReadHead += len; + gdbRemainingLen -= len; + *dataRead += len; + return GDBErrorNone; + } + + if (pendingData > 0) { + memcpy(target, &gdbSerialReadBuffer[gdbReadHead], pendingData); + target += pendingData; + len -= pendingData; + *dataRead += pendingData; + gdbRemainingLen -= pendingData; + } + + gdbReadHead = 0; + gdbMaxReadHead = 0; + + enum GDBError err; + + while (len > GDB_USB_SERIAL_SIZE) { + // if data is aligned, read directly into the buffer + if ((u32)target == ALIGN_8_BYTES((u32)target)) { + u32 bytesToRead = len & ~0x1FF; + err = gdbSerialRead(target, bytesToRead); + if (err != GDBErrorNone) return err; + len -= bytesToRead; + gdbRemainingLen -= bytesToRead; + *dataRead += bytesToRead; + target += bytesToRead; + } else { + err = gdbSerialRead(gdbSerialReadBuffer, GDB_USB_SERIAL_SIZE); + if (err != GDBErrorNone) return err; + memcpy(target, gdbSerialReadBuffer, GDB_USB_SERIAL_SIZE); + len -= GDB_USB_SERIAL_SIZE; + gdbRemainingLen -= GDB_USB_SERIAL_SIZE; + *dataRead += GDB_USB_SERIAL_SIZE; + target += GDB_USB_SERIAL_SIZE; + } + } + + u32 dataToRead = ALIGN_16_BYTES(gdbRemainingLen + MESSAGE_FOOTER_SIZE); + + if (dataToRead > GDB_USB_SERIAL_SIZE) { + dataToRead = GDB_USB_SERIAL_SIZE; + } + + err = gdbSerialRead(gdbSerialReadBuffer, dataToRead); + if (err != GDBErrorNone) return err; + + gdbMaxReadHead = dataToRead; + + memcpy(target, gdbSerialReadBuffer, len); + gdbReadHead += len; + gdbRemainingLen -= len; + *dataRead += len; + + if (gdbRemainingLen == 0 && (__gdbAlignAndFlags & GDB_IS_READING)) { + gdbRemainingLen = MESSAGE_FOOTER_SIZE; + // this prevents an infinite loop when reading the footer + __gdbAlignAndFlags &= ~GDB_IS_READING; + char footerCheck[4]; + u32 footerDataRead; + err = gdbReadData(footerCheck, MESSAGE_FOOTER_SIZE, &footerDataRead); + if (err != GDBErrorNone) return err; + gdbRemainingLen = 0; + + if (footerDataRead == MESSAGE_FOOTER_SIZE && + strncmp(gdbFooterText, footerCheck, MESSAGE_FOOTER_SIZE) != 0) { + return GDBErrorBadFooter; + } + } + + return GDBErrorNone; +} + +enum GDBError gdbFinishRead() { + u32 actuallyRead; + enum GDBError err; + while (gdbRemainingLen > 0) { + if (gdbRemainingLen > GDB_USB_SERIAL_SIZE) { + err = gdbReadData(gdbSerialReadBuffer, GDB_USB_SERIAL_SIZE, &actuallyRead); + if (err != GDBErrorNone) return err; + } else { + err = gdbReadData(gdbSerialReadBuffer, gdbRemainingLen, &actuallyRead); + if (err != GDBErrorNone) return err; + } + } + return GDBErrorNone; +} + +#endif // USE_UNF_LOADER \ No newline at end of file diff --git a/src/usb/serial.h b/src/usb/serial.h new file mode 100644 index 00000000..7523b470 --- /dev/null +++ b/src/usb/serial.h @@ -0,0 +1,44 @@ + +#ifndef __SERIAL_H +#define __SERIAL_H + +#include + +// #define USE_UNF_LOADER 1 + +enum GDBError { + GDBErrorNone, + GDBErrorUSBTimeout, + GDBErrorUSBNoData, + GDBErrorBadPacket, + GDBErrorMessageTooLong, + GDBErrorBadFooter, + GDBErrorDMA, + GDBErrorBufferTooSmall, +}; + +enum GDBDataType { + GDBDataTypeNone, + GDBDataTypeText, + GDBDataTypeRawBinary, + GDBDataTypeScreenshot, + GDBDataTypeGDB, +}; + +enum GDBCartType { + GDBCartTypeNone, + GDBCartTypeX7, + GDBCartTypeCen64, +}; + +extern u8 (*gdbSerialCanRead)(); + +enum GDBError gdbSerialInit(OSPiHandle* handler, OSMesgQueue* dmaMessageQ); + +enum GDBError gdbSendMessage(enum GDBDataType type, char* src, u32 len); + +enum GDBError gdbPollHeader(enum GDBDataType* type, u32* len); +enum GDBError gdbReadData(char* target, u32 len, u32* dataRead); +enum GDBError gdbFinishRead(); + +#endif \ No newline at end of file