diff --git a/.make_hackeroot.mk b/.make_hackeroot.mk index 52600a5c2..f754df398 100644 --- a/.make_hackeroot.mk +++ b/.make_hackeroot.mk @@ -251,3 +251,15 @@ endif # same as above and start listening to the IS-Viewer sc64v: sc64 $(SC64_DEPLOYER) debug --isv 0x03FF0000 + +### UNFLoader Settings ### + +# TODO: download this automatically +UNFLOADER ?= UNFLoader + +# upload the build +unf: rom +ifeq ($(UNFLOADER),) + $(error sc64deployer path not set. Set UNFLOADER in the Makefile or define it as an environment variable) +endif + $(UNFLOADER) -r $(ROM) -d diff --git a/include/config/config_debug.h b/include/config/config_debug.h index 184652b35..51e4622a0 100644 --- a/include/config/config_debug.h +++ b/include/config/config_debug.h @@ -84,4 +84,10 @@ */ #define ENABLE_DEBUG_BOOT true +/** + * Enable UNFLoader support + * This is required to use the command system. + */ +#define ENABLE_UNF true + #endif diff --git a/include/config/config_safeguards.h b/include/config/config_safeguards.h index e83cc9e5e..4ee725797 100644 --- a/include/config/config_safeguards.h +++ b/include/config/config_safeguards.h @@ -40,6 +40,7 @@ #undef ENABLE_MOTION_BLUR_DEBUG #undef ENABLE_HACKER_DEBUG #undef ENABLE_PROFILER + #undef ENABLE_UNF #define SKIP_N64_BOOT_LOGO false #define BOOT_TO_SCENE false @@ -67,6 +68,7 @@ #define ENABLE_MOTION_BLUR_DEBUG false #define ENABLE_HACKER_DEBUG false #define ENABLE_PROFILER false + #define ENABLE_UNF false #ifndef NDEBUG #define NDEBUG diff --git a/include/segment_symbols.h b/include/segment_symbols.h index dfc031221..9c5cd0ef4 100644 --- a/include/segment_symbols.h +++ b/include/segment_symbols.h @@ -92,6 +92,12 @@ DECLARE_ROM_SEGMENT(debug) DECLARE_BSS_SEGMENT(debug) #endif +#if ENABLE_UNF +DECLARE_SEGMENT(usb) +DECLARE_ROM_SEGMENT(usb) +DECLARE_BSS_SEGMENT(usb) +#endif + // N64-only, these are not wrapped in an `#if PLATFORM_N64` // so that the N64DD code can always be built. DECLARE_SEGMENT(n64dd) diff --git a/include/usb/debug.h b/include/usb/debug.h new file mode 100644 index 000000000..0a6ba9b2e --- /dev/null +++ b/include/usb/debug.h @@ -0,0 +1,180 @@ +#ifndef UNFL_DEBUG_H +#define UNFL_DEBUG_H + + /********************************* + Settings macros + *********************************/ + + // Enable/Disable debug + #ifndef DEBUG_MODE + #define DEBUG_MODE 1 // Enable/Disable debug mode + #endif + + // Settings + #define DEBUG_INIT_MSG 1 // Print a message when debug mode has initialized + #define AUTOPOLL_ENABLED 1 // Automatically poll the USB on a timer + #define AUTOPOLL_TIME 200 // Time (in milliseconds) between auto polls + #define USE_FAULTTHREAD 1 // Create a fault detection thread (libultra only) + #define USE_RDBTHREAD 0 // Create a remote debugger thread + #define OVERWRITE_OSPRINT 1 // Replaces osSyncPrintf calls with debug_printf (libultra only) + #define MAX_COMMANDS 25 // The max amount of user defined commands possible + + // USB thread definitions (libultra only) + #define USB_THREAD_ID 14 + #define USB_THREAD_PRI 126 + #define USB_THREAD_STACK 0x2000 + + // Fault thread definitions (libultra only) + #define FAULT_THREAD_ID 13 + #define FAULT_THREAD_PRI 125 + #define FAULT_THREAD_STACK 0x2000 + + // Remote debugger thread definitions (libultra only) + #define RDB_THREAD_ID 15 + #define RDB_THREAD_PRI 124 + #define RDB_THREAD_STACK 0x2000 + + + /********************************* + Debug Functions + *********************************/ + + #if DEBUG_MODE + + /*============================== + debug_initialize + Initializes the debug and USB library. + Should be called last during game initialization. + ==============================*/ + + extern void debug_initialize(); + + + /*============================== + debug_printf + Prints a formatted message to the developer's command prompt. + Supports up to 256 characters. + @param A string to print + @param variadic arguments to print as well + ==============================*/ + + extern void debug_printf(const char* message, ...); + + + /*============================== + debug_dumpbinary + Dumps a binary file through USB + @param The file to dump + @param The size of the file + ==============================*/ + + extern void debug_dumpbinary(void* file, int size); + + + /*============================== + debug_screenshot + Sends the currently displayed framebuffer through USB. + DOES NOT PAUSE DRAWING THREAD! Using outside the drawing + thread may lead to a screenshot with visible tearing + ==============================*/ + + extern void debug_screenshot(); + + + /*============================== + debug_assert + Halts the program if the expression fails. + @param The expression to test + ==============================*/ + + #define debug_assert(expr) (expr) ? ((void)0) : _debug_assert(#expr, __FILE__, __LINE__) + + + /*============================== + debug_64drivebutton + Assigns a function to be executed when the 64drive button is pressed. + @param The function pointer to execute + @param Whether or not to execute the function only on pressing (ignore holding the button down) + ==============================*/ + + extern void debug_64drivebutton(void(*execute)(), char onpress); + + + /*============================== + debug_pollcommands + Check the USB for incoming commands. + ==============================*/ + + extern void debug_pollcommands(); + + + /*============================== + debug_addcommand + Adds a command for the USB to read. + @param The command name + @param The command description + @param The function pointer to execute + ==============================*/ + + extern void debug_addcommand(char* command, char* description, char*(*execute)()); + + + /*============================== + debug_parsecommand + Stores the next part of the incoming command into the provided buffer. + Make sure the buffer can fit the amount of data from debug_sizecommand! + If you pass NULL, it skips this command. + @param The buffer to store the data in + ==============================*/ + + extern void debug_parsecommand(void* buffer); + + + /*============================== + debug_sizecommand + Returns the size of the data from this part of the command. + @return The size of the data in bytes, or 0 + ==============================*/ + + extern int debug_sizecommand(); + + + /*============================== + debug_printcommands + Prints a list of commands to the developer's command prompt. + ==============================*/ + + extern void debug_printcommands(); + + + // Ignore this, use the macro instead + extern void _debug_assert(const char* expression, const char* file, int line); + + // Include usb.h automatically + #include "usb.h" + + #else + + // Overwrite library functions with useless macros if debug mode is disabled + #define debug_initialize() + #define debug_printf (void) + #define debug_screenshot(a, b, c) + #define debug_assert(a) + #define debug_pollcommands() + #define debug_addcommand(a, b, c) + #define debug_parsecommand(a) NULL + #define debug_sizecommand() 0 + #define debug_printcommands() + #define debug_64drivebutton(a, b) + #define usb_initialize() 0 + #define usb_getcart() 0 + #define usb_write(a, b, c) + #define usb_poll() 0 + #define usb_read(a, b) + #define usb_skip(a) + #define usb_rewind(a) + #define usb_purge() + + #endif + +#endif diff --git a/include/usb/usb.h b/include/usb/usb.h new file mode 100644 index 000000000..8fadee075 --- /dev/null +++ b/include/usb/usb.h @@ -0,0 +1,139 @@ +#ifndef UNFL_USB_H +#define UNFL_USB_H + + /********************************* + DataType macros + *********************************/ + + // UNCOMMENT THE #DEFINE IF USING LIBDRAGON + //#define LIBDRAGON + + // Settings + #define USE_OSRAW 0 // Use if you're doing USB operations without the PI Manager (libultra only) + #define DEBUG_ADDRESS_SIZE 8*1024*1024 // Max size of USB I/O. The bigger this value, the more ROM you lose! + #define CHECK_EMULATOR 0 // Stops the USB library from working if it detects an emulator to prevent problems + + // Cart definitions + #define CART_NONE 0 + #define CART_64DRIVE 1 + #define CART_EVERDRIVE 2 + #define CART_SC64 3 + + // Data types defintions + #define DATATYPE_TEXT 0x01 + #define DATATYPE_RAWBINARY 0x02 + #define DATATYPE_HEADER 0x03 + #define DATATYPE_SCREENSHOT 0x04 + #define DATATYPE_HEARTBEAT 0x05 + #define DATATYPE_RDBPACKET 0x06 + + + /********************************* + Convenience macros + *********************************/ + + // Use these to conveniently read the header from usb_poll() + #define USBHEADER_GETTYPE(header) (((header) & 0xFF000000) >> 24) + #define USBHEADER_GETSIZE(header) (((header) & 0x00FFFFFF)) + + + /********************************* + USB Functions + *********************************/ + + /*============================== + usb_initialize + Initializes the USB buffers and pointers + @return 1 if the USB initialization was successful, 0 if not + ==============================*/ + + extern char usb_initialize(void); + + + /*============================== + usb_getcart + Returns which flashcart is currently connected + @return The CART macro that corresponds to the identified flashcart + ==============================*/ + + extern char usb_getcart(void); + + + /*============================== + usb_write + Writes data to the USB. + Will not write if there is data to read from USB + @param The DATATYPE that is being sent + @param A buffer with the data to send + @param The size of the data being sent + ==============================*/ + + extern void usb_write(int datatype, const void* data, int size); + + + /*============================== + usb_poll + Returns the header of data being received via USB + The first byte contains the data type, the next 3 the number of bytes left to read + @return The data header, or 0 + ==============================*/ + + extern unsigned long usb_poll(void); + + + /*============================== + usb_read + Reads bytes from USB into the provided buffer + @param The buffer to put the read data in + @param The number of bytes to read + ==============================*/ + + extern void usb_read(void* buffer, int nbytes); + + /*============================== + usb_skip + Skips a USB read by the specified amount of bytes + @param The number of bytes to skip + ==============================*/ + + extern void usb_skip(int nbytes); + + + /*============================== + usb_rewind + Rewinds a USB read by the specified amount of bytes + @param The number of bytes to rewind + ==============================*/ + + extern void usb_rewind(int nbytes); + + + /*============================== + usb_purge + Purges the incoming USB data + ==============================*/ + + extern void usb_purge(void); + + + /*============================== + usb_timedout + Checks if the USB timed out recently + @return 1 if the USB timed out, 0 if not + ==============================*/ + + extern char usb_timedout(void); + + + /*============================== + usb_sendheartbeat + Sends a heartbeat packet to the PC + This is done once automatically at initialization, + but can be called manually to ensure that the + host side tool is aware of the current USB protocol + version. + ==============================*/ + + extern void usb_sendheartbeat(void); + +#endif diff --git a/spec/code_libultra_gc.inc b/spec/code_libultra_gc.inc index 25839bc91..eb698b031 100644 --- a/spec/code_libultra_gc.inc +++ b/spec/code_libultra_gc.inc @@ -53,6 +53,7 @@ include "$(BUILD_DIR)/src/libultra/gu/cosf.o" include "$(BUILD_DIR)/src/libultra/gu/libm_vals.o" include "$(BUILD_DIR)/src/libultra/gu/coss.o" + include "$(BUILD_DIR)/src/libultra/os/settime.o" include "$(BUILD_DIR)/src/libultra/io/visetevent.o" #if DEBUG_FEATURES include "$(BUILD_DIR)/src/libultra/io/pfsisplug.o" diff --git a/spec/spec b/spec/spec index 5df5149b6..f0c1f97c6 100644 --- a/spec/spec +++ b/spec/spec @@ -877,6 +877,19 @@ beginseg include "$(BUILD_DIR)/src/debug/profiler.o" #endif include "$(BUILD_DIR)/src/debug/inventory_editor.o" +#if ENABLE_UNF + include "$(BUILD_DIR)/src/debug/commands.o" +#endif +endseg +#endif + +#if ENABLE_UNF +beginseg + name "usb" + compress + include "$(BUILD_DIR)/src/usb/missing_libultra_functions.o" + include "$(BUILD_DIR)/src/usb/usb.o" + include "$(BUILD_DIR)/src/usb/debug.o" endseg #endif diff --git a/src/boot/idle.c b/src/boot/idle.c index b1c8162bd..f0fea9115 100644 --- a/src/boot/idle.c +++ b/src/boot/idle.c @@ -70,6 +70,14 @@ void Main_ThreadEntry(void* arg) { PRINTF("[HackerOoT:Info]: Completed!\n"); #endif +#if ENABLE_UNF + PRINTF("[HackerOoT:Info]: Loading 'usb' segment...\n"); + DMA_REQUEST_SYNC(_usbSegmentStart, (uintptr_t)_usbSegmentRomStart, _usbSegmentRomEnd - _usbSegmentRomStart, + __BASE_FILE__, __LINE__); + bzero(_usbSegmentBssStart, _usbSegmentBssEnd - _usbSegmentBssStart); + PRINTF("[HackerOoT:Info]: Completed!\n"); +#endif + Main(arg); PRINTF(T("mainx 実行終了\n", "mainx execution finished\n")); } diff --git a/src/code/graph.c b/src/code/graph.c index c676a7e25..4ae2e5f35 100644 --- a/src/code/graph.c +++ b/src/code/graph.c @@ -474,6 +474,10 @@ void Graph_Update(GraphicsContext* gfxCtx, GameState* gameState) { } } +#if ENABLE_UNF +void Commands_Init(void); +#endif + void Graph_ThreadEntry(void* arg0) { GraphicsContext gfxCtx; GameState* gameState; @@ -489,6 +493,10 @@ void Graph_ThreadEntry(void* arg0) { PRINTF(T("グラフィックスレッド実行開始\n", "Start graphic thread execution\n")); Graph_Init(&gfxCtx); +#if ENABLE_UNF + Commands_Init(); +#endif + while (nextOvl != NULL) { ovl = nextOvl; Overlay_LoadGameState(ovl); diff --git a/src/debug/commands.c b/src/debug/commands.c new file mode 100644 index 000000000..f47ff79ca --- /dev/null +++ b/src/debug/commands.c @@ -0,0 +1,41 @@ +#include "ultra64.h" +#include "array_count.h" +#include "usb/debug.h" +#include "config.h" + +#if ENABLE_UNF + +typedef struct CommandDesc { + char* name; + char* desc; + char* (*callback)(void); +} CommandDesc; + +char* command_example() { + return "The example command executed successfully."; +} + +// to add a command, create the function and add an entry for it here, you shouldn't require any other change +CommandDesc sCommandList[] = { + { "example", "example command", command_example }, +}; + +void Commands_Init(void) { + debug_initialize(); + + if (ARRAY_COUNT(sCommandList) < MAX_COMMANDS) { + u8 i; + + for (i = 0; i < ARRAY_COUNT(sCommandList); i++) { + CommandDesc* cmd = &sCommandList[i]; + + debug_addcommand(cmd->name, cmd->desc, cmd->callback); + } + + debug_printcommands(); + } else { + debug_printf("Too many commands! Ignoring..."); + } +} + +#endif diff --git a/src/libultra/os/initialize.c b/src/libultra/os/initialize.c index 1241a0ac8..53b84d433 100644 --- a/src/libultra/os/initialize.c +++ b/src/libultra/os/initialize.c @@ -1,6 +1,8 @@ #include "ultra64.h" #include "ultra64/bcp.h" +void* __printfunc = NULL; + typedef struct __osExceptionVector { u32 inst1; // lui $k0, %hi(__osException) u32 inst2; // addiu $k0, $k0, %lo(__osException) diff --git a/src/usb/debug.c b/src/usb/debug.c new file mode 100644 index 000000000..4a5f3564c --- /dev/null +++ b/src/usb/debug.c @@ -0,0 +1,1990 @@ +/*************************************************************** + debug.c + +A basic debug library that makes use of the USB library for N64 +flashcarts. +https://github.com/buu342/N64-UNFLoader +***************************************************************/ + +#include "usb/debug.h" +#ifndef LIBDRAGON +#include "ultra64.h" +// #include // Needed for Crash's Linux toolchain +#else +#include +#include +#endif +#include "libc/math.h" +#include "libc/stdarg.h" +#include "libc/stdlib.h" +#include "libc/string.h" +#include "libc64/sprintf.h" + +extern char* strtok(char* string1, const char* string2); +extern void strcpy(char* dest, char* srce); +extern int strncmp(const char* s1, const char* s2, size_t n); +extern s32 __osPiRawReadIo(u32 devAddr, u32* data); +extern s32 __osPiRawWriteIo(u32 devAddr, u32 data); +extern s32 osPiWriteIo(u32 devAddr, u32 data); +extern s32 osPiReadIo(u32 devAddr, u32* data); +extern s32 osPiStartDma(OSIoMesg* mb, s32 priority, s32 direction, u32 devAddr, void* dramAddr, u32 size, + OSMesgQueue* mq); + +#if DEBUG_MODE + +/********************************* + Definitions +*********************************/ + +// USB thread messages +#define MSG_FAULT 0x10 +#define MSG_READ 0x11 +#define MSG_WRITE 0x12 + +#define USBERROR_NONE 0 +#define USBERROR_NOTTEXT 1 +#define USBERROR_UNKNOWN 2 +#define USBERROR_TOOMUCH 3 +#define USBERROR_CUSTOM 4 + +// RDB thread messages (Libultra) +#ifndef LIBDRAGON +#define MSG_RDB_PACKET 0x10 +#define MSG_RDB_BPHIT 0x11 +#define MSG_RDB_PAUSE 0x12 +#endif + +// Breakpoints +#define BPOINT_COUNT 10 +#define MAKE_BREAKPOINT_INDEX(indx) (0x0000000D | ((indx) << 6)) +#define GET_BREAKPOINT_INDEX(addr) ((((addr) >> 6) & 0x0000FFFF)) + +// Helpful stuff +#define HASHTABLE_SIZE 7 +#define COMMAND_TOKENS 10 +#define BUFFER_SIZE 256 +#define REGISTER_COUNT 72 // 32 GPRs + 6 SPRs + 16 FPRs + fsr + fir (fcr0) +#define REGISTER_SIZE 16 // GDB expects the registers to be 64-bits + +/********************************* + Libultra types (for libdragon) +*********************************/ + +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif + +#ifdef LIBDRAGON +#define OS_PHYSICAL_TO_K0(x) (void*)(((u32)(x) + 0x80000000)) +#define OS_PHYSICAL_TO_K1(x) (void*)(((u32)(x) + 0xa0000000)) + +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned long u32; +typedef unsigned long long u64; + +typedef signed char s8; +typedef short s16; +typedef long s32; +typedef long long s64; + +typedef volatile unsigned char vu8; +typedef volatile unsigned short vu16; +typedef volatile unsigned long vu32; +typedef volatile unsigned long long vu64; + +typedef volatile signed char vs8; +typedef volatile short vs16; +typedef volatile long vs32; +typedef volatile long long vs64; + +typedef float f32; +typedef double f64; + +typedef void* OSMesg; +typedef exception_t OSThread; +#endif + +/********************************* + Structs +*********************************/ + +// Register struct +typedef struct { + u32 mask; + u32 value; + char* string; +} regDesc; + +// Because of the thread context's messy struct, this'll come in handy +typedef struct { + int size; + void* ptr; +} regType; + +// Thread message struct +typedef struct { + int msgtype; + int datatype; + void* buff; + int size; +} usbMesg; + +// Debug command struct +typedef struct { + char* command; + char* description; + char* (*execute)(); + void* next; +} debugCommand; + +// Remote debugger packet lookup table +typedef struct { + char* command; + void (*func)(); +} RDBPacketLUT; + +// Breakpoint struct +typedef struct { + u32* addr; + u32 instruction; +} bPoint; + +/********************************* + Function Prototypes +*********************************/ + +// Threads +static void debug_thread_usb(void* arg); +#ifndef LIBDRAGON +#if USE_FAULTTHREAD +static void debug_thread_fault(void* arg); +#endif +#else +#if AUTOPOLL_ENABLED +static void debug_timer_usb(int overflow); +#endif +#endif +#if USE_RDBTHREAD +#ifndef LIBDRAGON +static void debug_thread_rdb(void* arg); +#else +static void debug_thread_rdb(exception_t* arg); +static void debug_thread_rdb_pause(); +#endif +static void debug_thread_rdb_loop(OSThread* t); +static void debug_rdb_qsupported(OSThread* t); +static void debug_rdb_haltreason(OSThread* t); +static void debug_rdb_dumpregisters(OSThread* t); +static void debug_rdb_writeregisters(OSThread* t); +static void debug_rdb_readmemory(OSThread* t); +static void debug_rdb_writememory(OSThread* t); +static void debug_rdb_addbreakpoint(OSThread* t); +static void debug_rdb_removebreakpoint(OSThread* t); +static void debug_rdb_continue(OSThread* t); +static void debug_rdb_pause(OSThread* t); +#endif + +// Other +#ifndef LIBDRAGON +#if OVERWRITE_OSPRINT +static void* debug_osSyncPrintf_implementation(void* unused, const char* str, size_t len); +#endif +#endif +static inline void debug_handle_64drivebutton(); + +/********************************* + Globals +*********************************/ + +// Function pointers +#ifndef LIBDRAGON +extern int _Printf(void* (*copyfunc)(void*, const char*, size_t), void*, const char*, va_list); +#if OVERWRITE_OSPRINT +extern void* __printfunc; +#endif +#endif + +// Debug globals +static char debug_initialized = 0; +static char debug_buffer[BUFFER_SIZE]; + +// Commands hashtable related +static debugCommand* debug_commands_hashtable[HASHTABLE_SIZE]; +static debugCommand debug_commands_elements[MAX_COMMANDS]; +static int debug_commands_count = 0; + +// Command parsing related +static int debug_command_current = 0; +static int debug_command_totaltokens = 0; +static int debug_command_incoming_start[COMMAND_TOKENS]; +static int debug_command_incoming_size[COMMAND_TOKENS]; +static char* debug_command_error = NULL; + +// Assertion globals +static int assert_line = 0; +static const char* assert_file = NULL; +static const char* assert_expr = NULL; + +// 64Drive button functions +static void (*debug_64dbut_func)() = NULL; +static u64 debug_64dbut_debounce = 0; +static u64 debug_64dbut_hold = 0; + +#ifndef LIBDRAGON + +// USB thread globals +static OSMesgQueue usbMessageQ; +static OSMesg usbMessageBuf; +static OSThread usbThread; +static u64 usbThreadStack[USB_THREAD_STACK / sizeof(u64)]; +#if AUTOPOLL_ENABLED +static OSTimer usbThreadTimer; +#endif + +// Fault thread globals +#if USE_FAULTTHREAD +static OSMesgQueue faultMessageQ; +static OSMesg faultMessageBuf; +static OSThread faultThread; +static u64 faultThreadStack[FAULT_THREAD_STACK / sizeof(u64)]; + +// List of error causes +static regDesc causeDesc[] = { { CAUSE_BD, CAUSE_BD, "BD" }, + { CAUSE_IP8, CAUSE_IP8, "IP8" }, + { CAUSE_IP7, CAUSE_IP7, "IP7" }, + { CAUSE_IP6, CAUSE_IP6, "IP6" }, + { CAUSE_IP5, CAUSE_IP5, "IP5" }, + { CAUSE_IP4, CAUSE_IP4, "IP4" }, + { CAUSE_IP3, CAUSE_IP3, "IP3" }, + { CAUSE_SW2, CAUSE_SW2, "IP2" }, + { CAUSE_SW1, CAUSE_SW1, "IP1" }, + { CAUSE_EXCMASK, EXC_INT, "Interrupt" }, + { CAUSE_EXCMASK, EXC_MOD, "TLB modification exception" }, + { CAUSE_EXCMASK, EXC_RMISS, "TLB exception on load or instruction fetch" }, + { CAUSE_EXCMASK, EXC_WMISS, "TLB exception on store" }, + { CAUSE_EXCMASK, EXC_RADE, "Address error on load or instruction fetch" }, + { CAUSE_EXCMASK, EXC_WADE, "Address error on store" }, + { CAUSE_EXCMASK, EXC_IBE, "Bus error exception on instruction fetch" }, + { CAUSE_EXCMASK, EXC_DBE, "Bus error exception on data reference" }, + { CAUSE_EXCMASK, EXC_SYSCALL, "System call exception" }, + { CAUSE_EXCMASK, EXC_BREAK, "Breakpoint exception" }, + { CAUSE_EXCMASK, EXC_II, "Reserved instruction exception" }, + { CAUSE_EXCMASK, EXC_CPU, "Coprocessor unusable exception" }, + { CAUSE_EXCMASK, EXC_OV, "Arithmetic overflow exception" }, + { CAUSE_EXCMASK, EXC_TRAP, "Trap exception" }, + { CAUSE_EXCMASK, EXC_VCEI, "Virtual coherency exception on intruction fetch" }, + { CAUSE_EXCMASK, EXC_FPE, "Floating point exception (see fpcsr)" }, + { CAUSE_EXCMASK, EXC_WATCH, "Watchpoint exception" }, + { CAUSE_EXCMASK, EXC_VCED, "Virtual coherency exception on data reference" }, + { 0, 0, "" } }; + +// List of register descriptions +static regDesc srDesc[] = { { SR_CU3, SR_CU3, "CU3" }, + { SR_CU2, SR_CU2, "CU2" }, + { SR_CU1, SR_CU1, "CU1" }, + { SR_CU0, SR_CU0, "CU0" }, + { SR_RP, SR_RP, "RP" }, + { SR_FR, SR_FR, "FR" }, + { SR_RE, SR_RE, "RE" }, + { SR_BEV, SR_BEV, "BEV" }, + { SR_TS, SR_TS, "TS" }, + { SR_SR, SR_SR, "SR" }, + { SR_CH, SR_CH, "CH" }, + { SR_CE, SR_CE, "CE" }, + { SR_DE, SR_DE, "DE" }, + { SR_IBIT8, SR_IBIT8, "IM8" }, + { SR_IBIT7, SR_IBIT7, "IM7" }, + { SR_IBIT6, SR_IBIT6, "IM6" }, + { SR_IBIT5, SR_IBIT5, "IM5" }, + { SR_IBIT4, SR_IBIT4, "IM4" }, + { SR_IBIT3, SR_IBIT3, "IM3" }, + { SR_IBIT2, SR_IBIT2, "IM2" }, + { SR_IBIT1, SR_IBIT1, "IM1" }, + { SR_KX, SR_KX, "KX" }, + { SR_SX, SR_SX, "SX" }, + { SR_UX, SR_UX, "UX" }, + { SR_KSU_MASK, SR_KSU_USR, "USR" }, + { SR_KSU_MASK, SR_KSU_SUP, "SUP" }, + { SR_KSU_MASK, SR_KSU_KER, "KER" }, + { SR_ERL, SR_ERL, "ERL" }, + { SR_EXL, SR_EXL, "EXL" }, + { SR_IE, SR_IE, "IE" }, + { 0, 0, "" } }; + +// List of floating point registers descriptions +static regDesc fpcsrDesc[] = { { FPCSR_FS, FPCSR_FS, "FS" }, + { FPCSR_C, FPCSR_C, "C" }, + { FPCSR_CE, FPCSR_CE, "Unimplemented operation" }, + { FPCSR_CV, FPCSR_CV, "Invalid operation" }, + { FPCSR_CZ, FPCSR_CZ, "Division by zero" }, + { FPCSR_CO, FPCSR_CO, "Overflow" }, + { FPCSR_CU, FPCSR_CU, "Underflow" }, + { FPCSR_CI, FPCSR_CI, "Inexact operation" }, + { FPCSR_EV, FPCSR_EV, "EV" }, + { FPCSR_EZ, FPCSR_EZ, "EZ" }, + { FPCSR_EO, FPCSR_EO, "EO" }, + { FPCSR_EU, FPCSR_EU, "EU" }, + { FPCSR_EI, FPCSR_EI, "EI" }, + { FPCSR_FV, FPCSR_FV, "FV" }, + { FPCSR_FZ, FPCSR_FZ, "FZ" }, + { FPCSR_FO, FPCSR_FO, "FO" }, + { FPCSR_FU, FPCSR_FU, "FU" }, + { FPCSR_FI, FPCSR_FI, "FI" }, + { FPCSR_RM_MASK, FPCSR_RM_RN, "RN" }, + { FPCSR_RM_MASK, FPCSR_RM_RZ, "RZ" }, + { FPCSR_RM_MASK, FPCSR_RM_RP, "RP" }, + { FPCSR_RM_MASK, FPCSR_RM_RM, "RM" }, + { 0, 0, "" } }; +#endif +#endif + +#if USE_RDBTHREAD +// Remote debugger thread globals +#ifndef LIBDRAGON +static OSMesgQueue rdbMessageQ; +static OSMesg rdbMessageBuf; +static OSThread rdbThread; +static u64 rdbThreadStack[RDB_THREAD_STACK / sizeof(u64)]; +#endif + +// RDB status globals +static vu8 debug_rdbpaused = FALSE; +#ifndef LIBDRAGON +static OSTime debug_pausetime = 0; +#else +static u32 debug_pausetime = 0; +static u8 debug_ismanualpause = FALSE; +#endif +static bPoint debug_bpoints[BPOINT_COUNT]; + +// Remote debugger packet lookup table +RDBPacketLUT lut_rdbpackets[] = { + // Due to the use of strncmp, the order of strings matters! + { "qSupported", debug_rdb_qsupported }, + { "?", debug_rdb_haltreason }, + { "g", debug_rdb_dumpregisters }, + { "G", debug_rdb_writeregisters }, + { "m", debug_rdb_readmemory }, + { "M", debug_rdb_writememory }, + { "Z0", debug_rdb_addbreakpoint }, + { "z0", debug_rdb_removebreakpoint }, + { "c", debug_rdb_continue }, + { "\x03", debug_rdb_pause }, +}; +#endif + +/********************************* + Debug functions +*********************************/ + +/*============================== + debug_initialize + Initializes the debug library +==============================*/ + +void debug_initialize() { + // Initialize the USB functions + if (!usb_initialize()) { + return; + } + + // Initialize globals + memset(debug_commands_hashtable, 0, sizeof(debugCommand*) * HASHTABLE_SIZE); + memset(debug_commands_elements, 0, sizeof(debugCommand) * MAX_COMMANDS); + +// Libultra functions +#ifndef LIBDRAGON +// Overwrite osSyncPrintf +#if OVERWRITE_OSPRINT + __printfunc = (void*)debug_osSyncPrintf_implementation; +#endif + + // Initialize the USB thread + osCreateThread(&usbThread, USB_THREAD_ID, debug_thread_usb, 0, (usbThreadStack + USB_THREAD_STACK / sizeof(u64)), + USB_THREAD_PRI); + osStartThread(&usbThread); +#if AUTOPOLL_ENABLED + osSetTimer(&usbThreadTimer, 0, OS_USEC_TO_CYCLES(AUTOPOLL_TIME * 1000), &usbMessageQ, (OSMesg)NULL); +#endif + +// Initialize the fault thread +#if USE_FAULTTHREAD + osCreateThread(&faultThread, FAULT_THREAD_ID, debug_thread_fault, 0, + (faultThreadStack + FAULT_THREAD_STACK / sizeof(u64)), FAULT_THREAD_PRI); + osStartThread(&faultThread); +#endif + +// Initialize the remote debugger thread +#if USE_RDBTHREAD + osCreateThread(&rdbThread, RDB_THREAD_ID, debug_thread_rdb, (void*)osGetThreadId(NULL), + (rdbThreadStack + RDB_THREAD_STACK / sizeof(u64)), RDB_THREAD_PRI); + osStartThread(&rdbThread); + + // Initialize breakpoints + osSetEventMesg(OS_EVENT_CPU_BREAK, &rdbMessageQ, (OSMesg)MSG_RDB_BPHIT); + memset(debug_bpoints, 0, BPOINT_COUNT * sizeof(bPoint)); + + // Pause the main thread + usb_purge(); + usb_write(DATATYPE_TEXT, "Pausing main thread until GDB connects and resumes\n", 51 + 1); + osSendMesg(&rdbMessageQ, (OSMesg)MSG_RDB_PAUSE, OS_MESG_BLOCK); +#endif +#else + timer_init(); // If the timer subsystem has been initialized already, it's not a problem to call it again. +#if AUTOPOLL_ENABLED + new_timer(TIMER_TICKS(AUTOPOLL_TIME * 1000), TF_CONTINUOUS, debug_timer_usb); +#endif +#if USE_RDBTHREAD + memset(debug_bpoints, 0, BPOINT_COUNT * sizeof(bPoint)); + register_exception_handler(debug_thread_rdb); + usb_purge(); + usb_write(DATATYPE_TEXT, "Pausing main thread until GDB connects and resumes\n", 51 + 1); + debug_thread_rdb_pause(); +#endif +#endif + + // Mark the debug mode as initialized + debug_initialized = 1; +#if DEBUG_INIT_MSG + debug_printf("Debug mode initialized!\n\n"); +#endif +} + +#ifndef LIBDRAGON +/*============================== + printf_handler + Handles printf memory copying + @param The buffer to copy the partial string to + @param The string to copy + @param The length of the string + @returns The end of the buffer that was written to +==============================*/ + +static void* printf_handler(void* buf, const char* str, size_t len) { + return ((char*)memcpy(buf, str, len) + len); +} +#endif + +/*============================== + debug_printf + Prints a formatted message to the developer's command prompt. + Supports up to 256 characters. + @param A string to print + @param variadic arguments to print as well +==============================*/ + +void debug_printf(const char* message, ...) { + int len = 0; + usbMesg msg; + va_list args; + + // Stop if debug mode isn't initialized + if (!debug_initialized) { + return; + } + + // Use the internal libultra printf function to format the string + va_start(args, message); +#ifndef LIBDRAGON + len = _Printf(&printf_handler, debug_buffer, message, args); +#else + len = vsprintf(debug_buffer, message, args); +#endif + va_end(args); + + // Attach the '\0' if necessary + if (0 <= len) { + debug_buffer[len] = '\0'; + } + + // Send the printf to the usb thread + msg.msgtype = MSG_WRITE; + msg.datatype = DATATYPE_TEXT; + msg.buff = debug_buffer; + msg.size = len + 1; +#ifndef LIBDRAGON + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); +#else + debug_thread_usb(&msg); +#endif +} + +/*============================== + debug_dumpbinary + Dumps a binary file through USB + @param The file to dump + @param The size of the file +==============================*/ + +void debug_dumpbinary(void* file, int size) { + usbMesg msg; + + // Stop if debug mode isn't initialized + if (!debug_initialized) { + return; + } + + // Send the binary file to the usb thread + msg.msgtype = MSG_WRITE; + msg.datatype = DATATYPE_RAWBINARY; + msg.buff = file; + msg.size = size; +#ifndef LIBDRAGON + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); +#else + debug_thread_usb(&msg); +#endif +} + +/*============================== + debug_screenshot + Sends the currently displayed framebuffer through USB. + DOES NOT PAUSE DRAWING THREAD! Using outside the drawing + thread may lead to a screenshot with visible tearing +==============================*/ + +void debug_screenshot() { + usbMesg msg; + int data[4]; + + // These addresses were obtained from http://en64.shoutwiki.com/wiki/VI_Registers_Detailed + void* frame = (void*)(0x80000000 | (*(u32*)0xA4400004)); // Same as calling osViGetCurrentFramebuffer() in libultra + u32 yscale = (*(u32*)0xA4400034); + u32 w = (*(u32*)0xA4400008); + u32 h = ((((*(u32*)0xA4400028) & 0x3FF) - (((*(u32*)0xA4400028) >> 16) & 0x3FF)) * yscale) / 2048; + u8 depth = (((*(u32*)0xA4400000) & 0x03) == 0x03) ? 4 : 2; + + // Ensure debug mode is initialized + if (!debug_initialized) { + return; + } + + // Create the data header to send + data[0] = DATATYPE_SCREENSHOT; + data[1] = depth; + data[2] = w; + data[3] = h; + + // Send the header to the USB thread + msg.msgtype = MSG_WRITE; + msg.datatype = DATATYPE_HEADER; + msg.buff = data; + msg.size = sizeof(data); +#ifndef LIBDRAGON + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); +#else + debug_thread_usb(&msg); +#endif + + // Send the framebuffer to the USB thread + msg.msgtype = MSG_WRITE; + msg.datatype = DATATYPE_SCREENSHOT; + msg.buff = frame; + msg.size = depth * w * h; +#ifndef LIBDRAGON + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); +#else + debug_thread_usb(&msg); +#endif +} + +/*============================== + _debug_assert + Halts the program (assumes expression failed) + @param The expression that was tested + @param The file where the exception ocurred + @param The line number where the exception ocurred +==============================*/ + +void _debug_assert(const char* expression, const char* file, int line) { + volatile char crash; + + // Stop if debug mode isn't initialized + if (!debug_initialized) { + return; + } + + // Set the assert data + assert_expr = expression; + assert_line = line; + assert_file = file; + +// If on libdragon, print where the assertion failed +#ifdef LIBDRAGON + debug_printf("Assertion failed in file '%s', line %d.\n", assert_file, assert_line); +#endif + +// Intentionally cause a TLB exception on load/instruction fetch +#ifdef LIBDRAGON +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif + crash = *(volatile char*)1; +#ifdef LIBDRAGON +#pragma GCC diagnostic pop +#endif + (void)crash; +} + +/*============================== + debug_64drivebutton + Assigns a function to be executed when the 64drive button is pressed. + @param The function pointer to execute + @param Whether or not to execute the function only on pressing (ignore holding the button down) +==============================*/ + +void debug_64drivebutton(void (*execute)(), char onpress) { + debug_64dbut_func = execute; + debug_64dbut_debounce = 0; + debug_64dbut_hold = !onpress; +} + +/*============================== + debug_addcommand + Adds a command for the USB to listen for + @param The command name + @param The command description + @param The function pointer to execute +==============================*/ + +void debug_addcommand(char* command, char* description, char* (*execute)()) { + int entry = command[0] % HASHTABLE_SIZE; + debugCommand* slot = debug_commands_hashtable[entry]; + + // Ensure debug mode is initialized + if (!debug_initialized) { + return; + } + + // Ensure we haven't hit the command limit + if (debug_commands_count == MAX_COMMANDS) { + debug_printf("Max commands exceeded!\n"); + return; + } + + // Look for an empty spot in the hash table + if (slot != NULL) { + while (slot->next != NULL) { + slot = slot->next; + } + slot->next = &debug_commands_elements[debug_commands_count]; + } else { + debug_commands_hashtable[entry] = &debug_commands_elements[debug_commands_count]; + } + + // Fill this spot with info about this command + debug_commands_elements[debug_commands_count].command = command; + debug_commands_elements[debug_commands_count].description = description; + debug_commands_elements[debug_commands_count].execute = execute; + debug_commands_count++; +} + +/*============================== + debug_printcommands + Prints a list of commands to the developer's command prompt. +==============================*/ + +void debug_printcommands() { + int i; + + // Ensure debug mode is initialized + if (!debug_initialized) { + return; + } + + // Ensure there are commands to print + if (debug_commands_count == 0) { + return; + } + + // Print the commands + debug_printf("Available USB commands\n----------------------\n"); + for (i = 0; i < debug_commands_count; i++) { + debug_printf("%d. %s\n\t%s\n", i + 1, debug_commands_elements[i].command, + debug_commands_elements[i].description); + } + debug_printf("\n"); +} + +/*============================== + debug_pollcommands + Check the USB for incoming commands +==============================*/ + +void debug_pollcommands() { + usbMesg msg; + + // Ensure debug mode is initialized + if (!debug_initialized) { + return; + } + + // Send a read message to the USB thread + msg.msgtype = MSG_READ; +#ifndef LIBDRAGON + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); +#else + debug_thread_usb(&msg); +#endif +} + +/*============================== + debug_handle_64drivebutton + Handles the 64Drive's button logic +==============================*/ + +static inline void debug_handle_64drivebutton() { + static u32 held = 0; + + // If we own a 64Drive + if (usb_getcart() == CART_64DRIVE && debug_64dbut_func != NULL) { + u64 curtime; +#ifndef LIBDRAGON + curtime = osGetTime(); +#else + curtime = timer_ticks(); +#endif + + // And the debounce time on the 64Drive's button has elapsed + if (debug_64dbut_debounce < curtime) { + s32 bpoll; +#ifndef LIBDRAGON + osPiReadIo(0xB80002F8, &bpoll); +#else + bpoll = io_read(0xB80002F8); +#endif + bpoll = (bpoll & 0xFFFF0000) >> 16; + + // If the 64Drive's button has been pressed, then execute the assigned function and set the debounce timer + if (bpoll == 0 && (debug_64dbut_hold || !held)) { + u64 nexttime; +#ifndef LIBDRAGON + nexttime = OS_USEC_TO_CYCLES(100000); +#else + nexttime = TIMER_TICKS(100000); +#endif + debug_64dbut_debounce = curtime + nexttime; + debug_64dbut_func(); + held = 1; + } else if (bpoll != 0 && held) { + held = 0; + } + } + } +} + +/*============================== + debug_sizecommand + Returns the size of the data from this part of the command + @return The size of the data in bytes, or 0 +==============================*/ + +int debug_sizecommand() { + // If we're out of commands to read, return 0 + if (debug_command_current == debug_command_totaltokens) { + return 0; + } + + // Otherwise, return the amount of data to read + return debug_command_incoming_size[debug_command_current]; +} + +/*============================== + debug_parsecommand + Stores the next part of the incoming command into the provided buffer. + Make sure the buffer can fit the amount of data from debug_sizecommand! + If you pass NULL, it skips this command. + @param The buffer to store the data in +==============================*/ + +void debug_parsecommand(void* buffer) { + u8 curr = debug_command_current; + + // Skip this command if no buffer exists + if (buffer == NULL) { + debug_command_current++; + return; + } + + // If we're out of commands to read, do nothing + if (curr == debug_command_totaltokens) { + return; + } + + // Read from the correct offset + usb_skip(debug_command_incoming_start[curr]); + usb_read(buffer, debug_command_incoming_size[curr]); + usb_rewind(debug_command_incoming_size[curr] + debug_command_incoming_start[curr]); + debug_command_current++; +} + +/*============================== + debug_commands_setup + Reads the entire incoming string and breaks it into parts for + debug_parsecommand and debug_sizecommand +==============================*/ + +static void debug_commands_setup() { + int i; + int datasize = USBHEADER_GETSIZE(usb_poll()); + int dataleft = datasize; + int filesize = 0; + char filestep = 0; + + // Initialize the starting offsets at -1 + memset(debug_command_incoming_start, -1, COMMAND_TOKENS * sizeof(int)); + + // Read data from USB in blocks + while (dataleft > 0) { + int readsize = BUFFER_SIZE; + if (readsize > dataleft) { + readsize = dataleft; + } + + // Read a block from USB + memset(debug_buffer, 0, BUFFER_SIZE); + usb_read(debug_buffer, readsize); + + // Parse the block + for (i = 0; i < readsize && dataleft > 0; i++) { + // If we're not reading a file + int offset = datasize - dataleft; + u8 tok = debug_command_totaltokens; + + // Decide what to do based on the current character + switch (debug_buffer[i]) { + case ' ': + case '\0': + if (filestep < 2) { + if (debug_command_incoming_start[tok] != -1) { + debug_command_incoming_size[tok] = offset - debug_command_incoming_start[tok]; + debug_command_totaltokens++; + } + + if (debug_buffer[i] == '\0') { + dataleft = 0; + } + break; + } + case '@': + filestep++; + if (filestep < 3) { + break; + } + default: + // Decide what to do based on the file handle + if (filestep == 0 && debug_command_incoming_start[tok] == -1) { + // Store the data offsets and sizes in the global command buffers + debug_command_incoming_start[tok] = offset; + } else if (filestep == 1) { + // Get the filesize + filesize = filesize * 10 + debug_buffer[i] - '0'; + } else if (filestep > 1) { + // Store the file offsets and sizes in the global command buffers + debug_command_incoming_start[tok] = offset; + debug_command_incoming_size[tok] = filesize; + debug_command_totaltokens++; + + // Skip a bunch of bytes + if ((readsize - i) - filesize < 0) { + usb_skip(filesize - (readsize - i)); + } + dataleft -= filesize; + i += filesize; + filesize = 0; + filestep = 0; + } + break; + } + dataleft--; + } + } + + // Rewind the USB fully + usb_rewind(datasize); +} + +#ifdef LIBDRAGON +#if AUTOPOLL_ENABLED +/*============================== + debug_timer_usb + A function that's called by the auto-poll timer + @param How many ticks the timer overflew by (unused) +==============================*/ + +static void debug_timer_usb(int overflow) { + usbMesg msg; + (void)overflow; // To prevent unused variable errors + msg.msgtype = MSG_READ; + debug_thread_usb(&msg); +} +#endif +#endif + +/*============================== + debug_thread_usb + Handles the USB thread + @param Arbitrary data that the thread can receive +==============================*/ + +static void debug_thread_usb(void* arg) { + char errortype = USBERROR_NONE; + usbMesg* threadMsg; + +#ifndef LIBDRAGON + // Create the message queue for the USB message + osCreateMesgQueue(&usbMessageQ, &usbMessageBuf, 1); +#else + // Set the received thread message to the argument + threadMsg = (usbMesg*)arg; +#endif + + // Thread loop + while (1) { +#ifndef LIBDRAGON + // Wait for a USB message to arrive + osRecvMesg(&usbMessageQ, (OSMesg*)&threadMsg, OS_MESG_BLOCK); +#endif + + // Ensure there's no data in the USB (which handles MSG_READ) + while (usb_poll() != 0) { + int header = usb_poll(); + debugCommand* entry; + +// RDB packets should be rerouted to the RDB thread +#if USE_RDBTHREAD + if (USBHEADER_GETTYPE(header) == DATATYPE_RDBPACKET) { +#ifndef LIBDRAGON + osSendMesg(&rdbMessageQ, (OSMesg)MSG_RDB_PACKET, OS_MESG_BLOCK); +#else + + // Exceptional case, handle pausing through CTRL+C + char packetstart; + usb_read(&packetstart, 1); + if (packetstart == '\x03') { + usb_rewind(1); + debug_thread_rdb_pause(); + } else + debug_thread_rdb_loop(NULL); +#endif + continue; + } +#endif + + // Ensure we're receiving a text command + if (USBHEADER_GETTYPE(header) != DATATYPE_TEXT) { + errortype = USBERROR_NOTTEXT; + usb_purge(); + break; + } + + // Initialize the command trackers + debug_command_totaltokens = 0; + debug_command_current = 0; + + // Break the USB command into parts + debug_commands_setup(); + + // Ensure we don't read past our buffer + if (debug_sizecommand() > BUFFER_SIZE) { + errortype = USBERROR_TOOMUCH; + usb_purge(); + break; + } + + // Read from the USB to retrieve the command name + debug_parsecommand(debug_buffer); + + // Iterate through the hashtable to see if we find the command + entry = debug_commands_hashtable[debug_buffer[0] % HASHTABLE_SIZE]; + while (entry != NULL) { + // If we found the command + if (!strncmp(debug_buffer, entry->command, debug_command_incoming_size[0])) { + // Execute the command function and exit the while loop + debug_command_error = entry->execute(); + if (debug_command_error != NULL) { + errortype = USBERROR_CUSTOM; + } + usb_purge(); + break; + } + entry = entry->next; + } + + // If no command was found + if (entry == NULL) { + // Purge the USB contents and print unknown command + usb_purge(); + errortype = USBERROR_UNKNOWN; + } + } + + // Handle 64Drive button polling + debug_handle_64drivebutton(); + + // Spit out an error if there was one during the command parsing + if (errortype != USBERROR_NONE) { + switch (errortype) { + case USBERROR_NOTTEXT: + usb_write(DATATYPE_TEXT, "Error: USB data was not text\n", 29 + 1); + break; + case USBERROR_UNKNOWN: + usb_write(DATATYPE_TEXT, "Error: Unknown command\n", 23 + 1); + break; + case USBERROR_TOOMUCH: + usb_write(DATATYPE_TEXT, "Error: Command too large\n", 25 + 1); + break; + case USBERROR_CUSTOM: + usb_write(DATATYPE_TEXT, debug_command_error, strlen(debug_command_error) + 1); + usb_write(DATATYPE_TEXT, "\n", 1 + 1); + break; + } + errortype = USBERROR_NONE; + } + + // Handle the other USB messages + if (threadMsg != NULL) { + switch (threadMsg->msgtype) { + case MSG_WRITE: + if (usb_timedout()) { + usb_sendheartbeat(); + } + usb_write(threadMsg->datatype, threadMsg->buff, threadMsg->size); + break; + } + } + +// If we're in libdragon, break out of the loop as we don't need it +#ifdef LIBDRAGON + break; +#endif + } +} + +#ifndef LIBDRAGON +#if OVERWRITE_OSPRINT + +/*============================== + debug_osSyncPrintf_implementation + Overwrites osSyncPrintf calls with this one + @param Unused + @param The buffer with the string + @param The amount of characters to write + @returns The end of the buffer that was written to +==============================*/ + +static void* debug_osSyncPrintf_implementation(void* unused, const char* str, size_t len) { + void* ret; + usbMesg msg; + + // Clear the debug buffer and copy the formatted string to it + memset(debug_buffer, 0, len + 1); + ret = ((char*)memcpy(debug_buffer, str, len) + len); + + // Send the printf to the usb thread + msg.msgtype = MSG_WRITE; + msg.datatype = DATATYPE_TEXT; + msg.buff = debug_buffer; + msg.size = len + 1; + osSendMesg(&usbMessageQ, (OSMesg)&msg, OS_MESG_BLOCK); + + // Return the end of the buffer + return ret; +} + +#endif +#endif + +#ifndef LIBDRAGON +#if USE_FAULTTHREAD + +/*============================== + debug_printreg + Prints info about a register + @param The value of the register + @param The name of the register + @param The registry description to use +==============================*/ + +static void debug_printreg(u32 value, char* name, regDesc* desc) { + char first = 1; + debug_printf("%s\t\t0x%16x <", name, value); + while (desc->mask != 0) { + if ((value & desc->mask) == desc->value) { + (first) ? (first = 0) : ((void)debug_printf(",")); + debug_printf("%s", desc->string); + } + desc++; + } + debug_printf(">\n"); +} + +/*============================== + debug_thread_fault + Handles the fault thread + @param Arbitrary data that the thread can receive +==============================*/ + +static void debug_thread_fault(void* arg) { + OSMesg msg; + OSThread* curr; + + // Create the message queue for the fault message + osCreateMesgQueue(&faultMessageQ, &faultMessageBuf, 1); + osSetEventMesg(OS_EVENT_FAULT, &faultMessageQ, (OSMesg)MSG_FAULT); + + // Thread loop + while (1) { + // Wait for a fault message to arrive + osRecvMesg(&faultMessageQ, (OSMesg*)&msg, OS_MESG_BLOCK); + + // Get the faulted thread + curr = (OSThread*)__osGetCurrFaultedThread(); + if (curr != NULL) { + __OSThreadContext* context = &curr->context; + + // If the debug or rdb thread crashed, restart it + if (curr->id == USB_THREAD_ID) { + osCreateThread(&usbThread, USB_THREAD_ID, debug_thread_usb, 0, + (usbThreadStack + USB_THREAD_STACK / sizeof(u64)), USB_THREAD_PRI); + osStartThread(&usbThread); + } +#if USE_RDBTHREAD + else if (curr->id == RDB_THREAD_ID) { + osCreateThread(&rdbThread, RDB_THREAD_ID, debug_thread_rdb, (void*)osGetThreadId(NULL), + (rdbThreadStack + RDB_THREAD_STACK / sizeof(u64)), RDB_THREAD_PRI); + osStartThread(&rdbThread); + } +#endif + + // Print the basic info + debug_printf("Fault in thread: %d\n\n", curr->id); + debug_printf("pc\t\t0x%16x\n", context->pc); + if (assert_file == NULL) { + debug_printreg(context->cause, "cause", causeDesc); + } else { + debug_printf("cause\t\tAssertion failed in file '%s', line %d.\n", assert_file, assert_line); + } + debug_printreg(context->sr, "sr", srDesc); + debug_printf("badvaddr\t0x%16x\n\n", context->badvaddr); + + // Print the registers + debug_printf("at 0x%016llx v0 0x%016llx v1 0x%016llx\n", context->at, context->v0, context->v1); + debug_printf("a0 0x%016llx a1 0x%016llx a2 0x%016llx\n", context->a0, context->a1, context->a2); + debug_printf("a3 0x%016llx t0 0x%016llx t1 0x%016llx\n", context->a3, context->t0, context->t1); + debug_printf("t2 0x%016llx t3 0x%016llx t4 0x%016llx\n", context->t2, context->t3, context->t4); + debug_printf("t5 0x%016llx t6 0x%016llx t7 0x%016llx\n", context->t5, context->t6, context->t7); + debug_printf("s0 0x%016llx s1 0x%016llx s2 0x%016llx\n", context->s0, context->s1, context->s2); + debug_printf("s3 0x%016llx s4 0x%016llx s5 0x%016llx\n", context->s3, context->s4, context->s5); + debug_printf("s6 0x%016llx s7 0x%016llx t8 0x%016llx\n", context->s6, context->s7, context->t8); + debug_printf("t9 0x%016llx gp 0x%016llx sp 0x%016llx\n", context->t9, context->gp, context->sp); + debug_printf("s8 0x%016llx ra 0x%016llx\n\n", context->s8, context->ra); + + // Print the floating point registers + debug_printreg(context->fpcsr, "fpcsr", fpcsrDesc); + debug_printf("\n"); + debug_printf("d0 %.15e\td2 %.15e\n", context->fp0.d, context->fp2.d); + debug_printf("d4 %.15e\td6 %.15e\n", context->fp4.d, context->fp6.d); + debug_printf("d8 %.15e\td10 %.15e\n", context->fp8.d, context->fp10.d); + debug_printf("d12 %.15e\td14 %.15e\n", context->fp12.d, context->fp14.d); + debug_printf("d16 %.15e\td18 %.15e\n", context->fp16.d, context->fp18.d); + debug_printf("d20 %.15e\td22 %.15e\n", context->fp20.d, context->fp22.d); + debug_printf("d24 %.15e\td26 %.15e\n", context->fp24.d, context->fp26.d); + debug_printf("d28 %.15e\td30 %.15e\n", context->fp28.d, context->fp30.d); + } + } +} +#endif +#endif + +#if USE_RDBTHREAD +#ifndef LIBDRAGON +/*============================== + debug_thread_rdb + Handles the remote debugger thread (Libultra) + @param Arbitrary data that the thread can receive. + Used for passing the main thread ID. +==============================*/ + +static void debug_thread_rdb(void* arg) { + OSId mainid = (OSId)arg; + OSThread* mainthread = &rdbThread; + + // Find the main thread pointer given its ID + while (mainthread->id != mainid) + mainthread = mainthread->tlnext; + + // Create the message queue for the rdb messages + osCreateMesgQueue(&rdbMessageQ, &rdbMessageBuf, 1); + + // Thread loop + while (1) { + OSMesg msg; + OSThread* affected = NULL; + + // Wait for an rdb message to arrive + osRecvMesg(&rdbMessageQ, &msg, OS_MESG_BLOCK); + + // Exceptional case, handle pausing through CTRL+C + if (USBHEADER_GETTYPE(usb_poll()) == DATATYPE_RDBPACKET) { + char packetstart; + usb_read(&packetstart, 1); + if (packetstart == '\x03') + msg = (OSMesg)MSG_RDB_PAUSE; + usb_rewind(1); + } + + // Check what message we received + switch ((s32)msg) { + case MSG_RDB_PACKET: + break; // Do nothing + case MSG_RDB_BPHIT: + affected = mainthread; + debug_rdbpaused = TRUE; + debug_pausetime = osGetTime(); + + // Find out which thread hit the bp exception + while (1) { + if (affected->flags & OS_FLAG_CPU_BREAK) + break; + affected = affected->tlnext; + } + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "T05swbreak:;", 12 + 1); + break; + case MSG_RDB_PAUSE: + affected = mainthread; + debug_rdbpaused = TRUE; + debug_pausetime = osGetTime(); + break; + } + + // Do the RDB thread main loop + debug_thread_rdb_loop(affected); + } +} +#else + +/*============================== + debug_thread_rdb_pause + "Pauses" the program execution in Libdragon. + @param The received exception +==============================*/ + +static void debug_thread_rdb_pause() { + debug_ismanualpause = TRUE; + debug_pausetime = C0_COUNT(); + asm volatile("break"); // Jank workaround because I can't "message" the exception handler "thread" +} + +/*============================== + debug_thread_rdb + Handles the remote debugger logic (Libdragon) + @param The received exception +==============================*/ + +static void debug_thread_rdb(exception_t* exc) { + switch (exc->code) { + case EXCEPTION_CODE_BREAKPOINT: + debug_rdbpaused = TRUE; + if (!debug_ismanualpause) { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "T05swbreak:;", 12 + 1); + } + debug_pausetime = C0_COUNT(); + debug_thread_rdb_loop(exc); + if (debug_ismanualpause) { + debug_ismanualpause = FALSE; + exc->regs->epc += 4; // Gotta increment PC otherwise the program will likely hit the breakpoint again in + // debug_initialize + } + break; + default: + exception_default_handler(exc); + break; + } +} +#endif + +/*============================== + debug_thread_rdb_loop + Handles the loop of the remote debugger thread + @param (Libultra) A pointer to the affected thread + (Libdragon) A pointer to the exception struct +==============================*/ + +static void debug_thread_rdb_loop(OSThread* affected) { + // Handle the RDB packet + do { + int usbheader = usb_poll(); + if (USBHEADER_GETTYPE(usbheader) == DATATYPE_RDBPACKET) { + int i; + u8 found = FALSE; + u32 size = USBHEADER_GETSIZE(usbheader); + + // Read the GDB packet from USB + memset(debug_buffer, 0, BUFFER_SIZE); + usb_read(&debug_buffer, (size <= BUFFER_SIZE) ? size : BUFFER_SIZE); + + // Run a function based on what we received + for (i = 0; i < (sizeof(lut_rdbpackets) / sizeof(lut_rdbpackets[0])); i++) { + if (!strncmp(lut_rdbpackets[i].command, debug_buffer, strlen(lut_rdbpackets[i].command))) { + found = TRUE; + lut_rdbpackets[i].func(affected); + break; + } + } + + // If we didn't find a supported command, then reply back with nothing + if (!found) { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "\0", 1); + } + } + usb_purge(); + } while (debug_rdbpaused); // Loop forever while we are paused +} + +/*============================== + debug_rdb_qsupported + Responds to GDB with the supported features + @param The affected thread, if any +==============================*/ + +static void debug_rdb_qsupported(OSThread* t) { + sprintf(debug_buffer, "swbreak+"); + usb_purge(); + usb_write(DATATYPE_RDBPACKET, debug_buffer, strlen(debug_buffer) + 1); +} + +/*============================== + debug_rdb_haltreason + Responds to GDB with the halt reason + @param The affected thread, if any +==============================*/ + +static void debug_rdb_haltreason(OSThread* t) { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "S02", 3 + 1); +} + +#ifndef LIBDRAGON +/*============================== + register_fromindex + Gets a register type from a given index + @param The affected thread context + @param The register index + @returns The regType that matches the given index +==============================*/ + +static regType register_fromindex(__OSThreadContext* context, int index) { + regType reg = { 0, NULL }; + switch (index) { + case 0: // Zero register + case 26: // K0 + case 27: // K1 + case 71: // FCR0 + return reg; + case 32: + reg.size = 4; + reg.ptr = ((u32*)&context->sr); + return reg; + case 33: + reg.size = 8; + reg.ptr = ((u64*)&context->lo); + return reg; + case 34: + reg.size = 8; + reg.ptr = ((u64*)&context->hi); + return reg; + case 35: + reg.size = 4; + reg.ptr = ((u32*)&context->badvaddr); + return reg; + case 36: + reg.size = 4; + reg.ptr = ((u32*)&context->cause); + return reg; + case 37: + reg.size = 4; + reg.ptr = ((u32*)&context->pc); + return reg; + case 70: + reg.size = 4; + reg.ptr = ((u32*)&context->fpcsr); + return reg; + default: + if (index < 32) { + reg.size = 8; + if (index > 27) + reg.ptr = ((u64*)&context->gp) + (index - 28); + else + reg.ptr = ((u64*)&context->at) + (index - 1); + return reg; + } else { + reg.size = 8; + reg.ptr = ((u64*)&context->fp0) + (((u32)(index - 38)) / 2); + return reg; + } + } +} +#else +/*============================== + register_fromindex + Gets a register type from a given index + @param The affected thread context + @param The register index + @returns The regType that matches the given index +==============================*/ + +static regType register_fromindex(reg_block_t* context, int index) { + regType reg = { 0, NULL }; + switch (index) { + case 0: // Zero register + case 26: // K0 + case 27: // K1 + case 71: // FCR0 + case 35: // BadVAddr + case 37: // pc + return reg; + case 32: + reg.size = 4; + reg.ptr = ((u32*)&context->sr); + return reg; + case 33: + reg.size = 8; + reg.ptr = ((u64*)&context->lo); + return reg; + case 34: + reg.size = 8; + reg.ptr = ((u64*)&context->hi); + return reg; + case 36: + reg.size = 4; + reg.ptr = ((u32*)&context->cr); + return reg; + case 70: + reg.size = 4; + reg.ptr = ((u32*)&context->fc31); + return reg; + default: + if (index < 32) { + reg.size = 8; + reg.ptr = (u64*)&context->gpr[index - 1]; + return reg; + } else { + reg.size = 8; + reg.ptr = (u64*)&context->fpr[index - 38]; + return reg; + } + } + return reg; +} +#endif + +/*============================== + debug_rdb_printreg_rle + Sprintf's a register value into a buffer, + compressed with Run-Length Encoding + @param The buffer to write to + @param The register type + @returns The number of bytes written +==============================*/ + +static u32 debug_rdb_printreg_rle(char* buf, regType reg) { + if (reg.ptr != NULL) { + int i; + u8 count; + u32 totalwrote = 0; + char last; + char temp[REGISTER_SIZE + 1]; + + // Read the register value into a string + if (reg.size == 8) + sprintf(temp, "%016llx", (u64)(*(u64*)reg.ptr)); + else + sprintf(temp, "%016llx", (u64)(*(u32*)reg.ptr)); + last = temp[0]; + count = 1; + + // Find repeated characters + for (i = 1; i < (REGISTER_SIZE + 1); i++) { + if (temp[i] != last) { + // If the repeat was more than 3, then it's worth RLE'ing + if (count > 3) { + // Because 6 (#) and 7 ($) are special characters in GDB, we have to do them differently + if (count == 7) + totalwrote += sprintf(buf + totalwrote, "%c*\"%c", last, last); + else if (count == 8) + totalwrote += sprintf(buf + totalwrote, "%c*\"%c%c", last, last, last); + else + totalwrote += sprintf(buf + totalwrote, "%c*%c", last, ' ' + (count - 4)); + } else { + int j; + for (j = 0; j < count; j++) + *(buf + totalwrote + j) = last; + *(buf + totalwrote + j) = '\0'; + totalwrote += j; + } + last = temp[i]; + count = 1; + } else + count++; + } + return totalwrote; + } + return sprintf(buf, "x*,"); +} + +/*============================== + debug_rdb_dumpregisters + Responds to GDB with a dump of all registers + @param The affected thread, if any +==============================*/ + +static void debug_rdb_dumpregisters(OSThread* t) { + if (t != NULL) { + int i; + u32 chunkcount = 2 + (REGISTER_COUNT * REGISTER_SIZE) / BUFFER_SIZE; + u32 header[2]; + u32 offset = 0; +#ifndef LIBDRAGON + __OSThreadContext* context = &t->context; +#else + reg_block_t* context = t->regs; +#endif + + // Start by sending a HEADER packet with the chunk count + header[0] = DATATYPE_RDBPACKET; + header[1] = chunkcount; + usb_purge(); + usb_write(DATATYPE_HEADER, &header, sizeof(u32) * 2); + + // Perform the humpty dumpty + offset += sprintf(debug_buffer + offset, "0*,"); // Zero register + for (i = 1; i < REGISTER_COUNT; i++) { + if (i == 71) + offset += sprintf(debug_buffer + offset, "0*&800b11"); // FCR0 is an edge case +#ifdef LIBDRAGON + else if (i == 35) + offset += sprintf(debug_buffer + offset, "%016llx", (s64)C0_BADVADDR()); + else if (i == 37) + offset += sprintf(debug_buffer + offset, "%016llx", (s64)((s32)context->epc)); +#endif + else { + regType reg = register_fromindex(context, i); + offset += debug_rdb_printreg_rle(debug_buffer + offset, reg); + } + + // Send a chunk if we're about to overrun the debug buffer + if ((strlen(debug_buffer) + REGISTER_SIZE + 1) > BUFFER_SIZE || i == (REGISTER_COUNT - 1)) { + usb_write(DATATYPE_RDBPACKET, debug_buffer, strlen(debug_buffer) + 1); + memset(debug_buffer, 0, BUFFER_SIZE); + offset = 0; + chunkcount--; + } + } + + // Finish sending the other chunks + for (i = chunkcount; i >= 0; i--) + usb_write(DATATYPE_RDBPACKET, "\0", 1); + } else { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "E00", 3 + 1); + } +} + +/*============================== + hex2u64 + Converts a string containing a hexadecimal value + into a number. This exists because strtol is broken + on ModernSDK. + @param The string with the hexadecimal number + @returns The converted value +==============================*/ + +static u64 hex2u64(char* addr) { + int i = 0; + u64 ret = 0; + while (addr[i] != '\0') { + u32 val; + if (addr[i] <= '9') + val = addr[i] - '0'; + else if (addr[i] <= 'F') + val = 10 + addr[i] - 'A'; + else + val = 10 + addr[i] - 'a'; + ret = (ret << 4) | (val & 0xF); + i++; + } + return ret; +} + +/*============================== + debug_rdb_writeregisters + Writes a set of registers from a GDB packet + @param The affected thread, if any +==============================*/ + +static void debug_rdb_writeregisters(OSThread* t) { + if (t != NULL) { + int i; +#ifndef LIBDRAGON + __OSThreadContext* context = &t->context; +#else + reg_block_t* context = t->regs; +#endif + + // The incoming data probably won't fit in the buffer, so we'll go bit by bit + usb_rewind(BUFFER_SIZE); + + // Skip the 'G' at the start of the command + usb_skip(1); + + // Do the writing + for (i = 0; i < REGISTER_COUNT; i++) { + char val[REGISTER_SIZE + 1]; + regType reg = register_fromindex(context, i); + + // Stop if there's no more register values to read + if (USBHEADER_GETSIZE(usb_poll()) < REGISTER_SIZE) + break; + + // Read the register and get it's value + usb_read(val, REGISTER_SIZE); + val[REGISTER_SIZE] = '\0'; + if (val[0] != 'x' && reg.ptr != NULL) { + if (reg.size == 4) + (*(vu32*)reg.ptr) = (u32)hex2u64(val); + else + (*(vu64*)reg.ptr) = (u64)hex2u64(val); + } + } + + // Done + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); + } else { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "E00", 3 + 1); + } +} + +/*============================== + debug_rdb_translateaddr + Translates an address from GDB into a valid value + @param The address that we received + @returns The corrected address +==============================*/ + +static u32 debug_rdb_translateaddr(u32 addr) { + if ((addr & 0xFF000000) != 0xA4000000 && (addr & 0xFF000000) != 0x04000000) { +#ifndef LIBDRAGON + addr = (u32)osVirtualToPhysical((u32*)addr); +#else + u32 osMemSize = get_memory_size(); + addr = (u32)PhysicalAddr((u32*)addr); +#endif + addr = (addr <= osMemSize) ? (u32)OS_PHYSICAL_TO_K0(addr) : 0; + } else + addr = (u32)OS_PHYSICAL_TO_K1(addr & 0x0FFFFFFF); + return addr; +} + +/*============================== + debug_rdb_readmemory + Responds to GDB with a memory read + @param The affected thread, if any +==============================*/ + +static void debug_rdb_readmemory(OSThread* t) { + int i; + u32 written = 0; + u32 read = 0; + u32 addr; + u32 size; + u32 chunkcount; + u32 header[2]; + u8 validaddress = FALSE; + char command[32]; + char* commandp = &command[0]; +#ifdef LIBDRAGON + u32 osMemSize = get_memory_size(); +#endif + strcpy(commandp, debug_buffer); + + // Skip the 'm' at the start of the command + commandp++; + + // Extract the address value + strtok(commandp, ","); + addr = (u32)hex2u64(commandp); + + // Extract the size value + commandp = strtok(NULL, ","); + size = (u32)hex2u64(commandp); + chunkcount = 2 + size / 128; + + // Start by sending a HEADER packet with the chunk count + header[0] = DATATYPE_RDBPACKET; + header[1] = chunkcount; + usb_purge(); + usb_write(DATATYPE_HEADER, &header, sizeof(u32) * 2); + + // We need to translate the address before trying to read it + addr = debug_rdb_translateaddr(addr); + + // Ensure we are reading a valid memory address + if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize) { +#ifndef LIBDRAGON + osWritebackDCache((u32*)addr, size); +#else + data_cache_hit_writeback((u32*)addr, size); +#endif + validaddress = TRUE; + } + + // Read the memory address, one byte at a time + while (read < size) { + u8 val = 0; + if (validaddress) + val = *((vu8*)(addr + read)); + written += sprintf(debug_buffer + written, "%02x", val); + + // Send the partial address dump if we're almost overrunning the buffer, or if we've finished + read++; + if (written + 3 >= BUFFER_SIZE || read == size) { + usb_write(DATATYPE_RDBPACKET, &debug_buffer, strlen(debug_buffer) + 1); + written = 0; + chunkcount--; + } + } + + // Finish sending the other chunks + for (i = chunkcount; i >= 0; i--) + usb_write(DATATYPE_RDBPACKET, "\0", 1); +} + +/*============================== + debug_rdb_writememory + Writes the memory from a GDB packet + @param The affected thread, if any +==============================*/ + +static void debug_rdb_writememory(OSThread* t) { + int i; + u32 addr; + u32 size; +#ifdef LIBDRAGON + u32 osMemSize = get_memory_size(); +#endif + char* commandp = &debug_buffer[0]; + + // Skip the 'M' at the start of the command + commandp++; + + // Extract the address value + strtok(commandp, ","); + addr = (u32)hex2u64(commandp); + + // Extract the size value + commandp = strtok(NULL, ":"); + size = (u32)hex2u64(commandp); + + // Finally, point to the data we're actually gonna write + commandp = strtok(NULL, "\0"); + + // We need to translate the address before trying to read it + addr = debug_rdb_translateaddr(addr); + + // Ensure we are writing to a valid memory address + if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize) { + // Read the memory address, one byte at a time + for (i = 0; i < size; i++) { + char byte[3]; + sprintf(byte, "%.2s", commandp + (i * 2)); + *(((vu8*)addr) + i) = (u8)hex2u64(byte); + } + +// Done +#ifndef LIBDRAGON + osWritebackDCache((u32*)addr, size); +#else + data_cache_hit_writeback((u32*)addr, size); +#endif + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); + } else { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "E00", 3 + 1); + } +} + +/*============================== + debug_rdb_addbreakpoint + Enables a breakpoint + @param The affected thread, if any +==============================*/ + +void debug_rdb_addbreakpoint(OSThread* t) { + int i; + u32 addr; + char command[32]; + char* token = &command[0]; +#ifdef LIBDRAGON + u32 osMemSize = get_memory_size(); +#endif + strcpy(command, debug_buffer); + + // Skip the Z0 at the start + token = strtok(command, ","); + + // Extract the address value + token = strtok(NULL, ","); + addr = (u32)hex2u64(token); + + // There's still one more byte left (the breakpoint kind) which we can ignore + + // We need to translate the address before trying to read it + addr = debug_rdb_translateaddr(addr); + + // Find an empty slot in our breakpoint array and store the breakpoint info there + if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize) { + for (i = 0; i < BPOINT_COUNT; i++) { + if (debug_bpoints[i].addr == (u32*)addr) // No need to re-add the bp if it already exists + { + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); + return; + } + if (debug_bpoints[i].addr == NULL) { + // Store the address and the instruction (the value in its memory) before we overwrite it with a + // breakpoint + debug_bpoints[i].addr = (u32*)addr; + debug_bpoints[i].instruction = *((u32*)addr); + + // A breakpoint on the R4300 is any invalid instruction (It's an exception). + // The first 6 bits of the opcodes are reserved for the instruction itself. + // So since we have a range of values, we can encode the index into the instruction itself, in the + // middle 20 bits + *((vu32*)addr) = MAKE_BREAKPOINT_INDEX(i + 1); +#ifndef LIBDRAGON + osWritebackDCache((u32*)addr, 4); + osInvalICache((u32*)addr, 4); +#else + data_cache_hit_writeback((u32*)addr, 4); + inst_cache_hit_invalidate((u32*)addr, 4); +#endif + + // Tell GDB we succeeded + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); + return; + } + } + } + + // Some failure happend + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "E00", 3 + 1); +} + +/*============================== + debug_rdb_removebreakpoint + Disables a breakpoint + @param The affected thread, if any +==============================*/ + +static void debug_rdb_removebreakpoint(OSThread* t) { + int index; + u32 addr; + char command[32]; + char* commandp = &command[0]; +#ifdef LIBDRAGON + u32 osMemSize = get_memory_size(); +#endif + strcpy(commandp, debug_buffer); + + // Skip the Z0 at the start + strtok(commandp, ","); + + // Extract the address value + commandp = strtok(NULL, ","); + addr = (u32)hex2u64(commandp); + + // There's still one more byte left (the breakpoint kind) which we can ignore + + // We need to translate the address before trying to read it + addr = debug_rdb_translateaddr(addr); + + // Ensure the address has a valid breakpoint + if (addr >= 0x80000000 && addr < 0x80000000 + osMemSize) { + index = GET_BREAKPOINT_INDEX(*((u32*)addr)) - 1; + if (debug_bpoints[index].addr == (u32*)addr) { + int i; + + // Remove the breakpoint + *((vu32*)addr) = debug_bpoints[index].instruction; +#ifndef LIBDRAGON + osWritebackDCache((u32*)addr, 4); + osInvalICache((u32*)addr, 4); +#else + data_cache_hit_writeback((u32*)addr, 4); + inst_cache_hit_invalidate((u32*)addr, 4); +#endif + + // free the breakpoint + debug_bpoints[index].addr = NULL; + debug_bpoints[index].instruction = 0; + + // Tell GDB we succeeded + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); + return; + } + } + + // Some failure happend + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "E00", 3 + 1); +} + +/*============================== + debug_rdb_continue + Handles continue + @param The affected thread, if any +==============================*/ + +static void debug_rdb_continue(OSThread* t) { + debug_rdbpaused = FALSE; + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "OK", 2 + 1); +#ifndef LIBDRAGON + osSetTime(debug_pausetime); +#else + C0_WRITE_COUNT(debug_pausetime); +#endif +} + +/*============================== + debug_rdb_pause + Handles pausing from CTRL+C + @param The affected thread, if any +==============================*/ + +static void debug_rdb_pause(OSThread* t) { + debug_rdbpaused = TRUE; + usb_purge(); + usb_write(DATATYPE_RDBPACKET, "S02", 3 + 1); +#ifndef LIBDRAGON + debug_pausetime = osGetTime(); +#else + debug_pausetime = C0_COUNT(); +#endif +} +#endif +#endif diff --git a/src/usb/missing_libultra_functions.c b/src/usb/missing_libultra_functions.c new file mode 100644 index 000000000..a04831044 --- /dev/null +++ b/src/usb/missing_libultra_functions.c @@ -0,0 +1,146 @@ +#include "ultra64.h" +#include "ultra64/internal.h" + +char* strpbrk(const char* string1, const char* string2) { + char* b; + + while (*string1) { + b = (char*)string2; + while (*b) { + if (*b++ == *string1) { + return (char*)string1; + } + } + string1++; + } + return NULL; +} + +char* strtok(char* string1, const char* string2) { + static char* ptr = NULL; + char* a; + + if (string1 != NULL) { + ptr = string1; + } + if (ptr == NULL) { + return NULL; + } + a = ptr; + ptr = strpbrk(ptr, string2); + if (ptr != NULL) { + *ptr++ = '\0'; + } + return a; +} + +char* strcpy(char* str1, const char* str2) { + char* p; + p = str1; + while (*str2) { + *p++ = *str2++; + } + *p = '\0'; + return str1; +} + +int strncmp(const char* s1, const char* s2, size_t n) { + while (n && *s1 && (*s1 == *s2)) { + ++s1; + ++s2; + --n; + } + if (n == 0) { + return 0; + } else { + return (*(unsigned char*)s1 - *(unsigned char*)s2); + } +} + +#define WAIT_ON_IOBUSY(stat) \ + { \ + stat = IO_READ(PI_STATUS_REG); \ + while (stat & (PI_STATUS_IO_BUSY | PI_STATUS_DMA_BUSY)) \ + stat = IO_READ(PI_STATUS_REG); \ + } \ + (void)0 + +s32 __osPiRawReadIo(u32 devAddr, u32* data) { + register u32 stat; + + WAIT_ON_IOBUSY(stat); + *data = IO_READ((u32)osRomBase | devAddr); + + return 0; +} + +s32 __osPiRawWriteIo(u32 devAddr, u32 data) { + register u32 stat; + + WAIT_ON_IOBUSY(stat); + IO_WRITE((u32)osRomBase | devAddr, data); + + return 0; +} + +s32 osPiWriteIo(u32 devAddr, u32 data) { + register s32 ret; + +#ifdef _DEBUG + if (devAddr & 0x3) { + __osError(ERR_OSPIWRITEIO, 1, devAddr); + return -1; + } +#endif + + __osPiGetAccess(); + ret = __osPiRawWriteIo(devAddr, data); + __osPiRelAccess(); + + return ret; +} + +s32 osPiReadIo(u32 devAddr, u32* data) { + register s32 ret; + +#ifdef _DEBUG + if (devAddr & 0x3) { + __osError(ERR_OSPIREADIO, 1, devAddr); + return -1; + } +#endif + + __osPiGetAccess(); + ret = __osPiRawReadIo(devAddr, data); + __osPiRelAccess(); + + return ret; +} + +s32 osPiStartDma(OSIoMesg* mb, s32 priority, s32 direction, u32 devAddr, void* dramAddr, u32 size, OSMesgQueue* mq) { + register s32 ret; + if (!__osPiDevMgr.active) { + return -1; + } + + if (direction == OS_READ) { + mb->hdr.type = OS_MESG_TYPE_DMAREAD; + } else { + mb->hdr.type = OS_MESG_TYPE_DMAWRITE; + } + + mb->hdr.pri = priority; + mb->hdr.retQueue = mq; + mb->dramAddr = dramAddr; + mb->devAddr = devAddr; + mb->size = size; + mb->piHandle = NULL; + + if (priority == OS_MESG_PRI_HIGH) { + ret = osJamMesg(osPiGetCmdQueue(), (OSMesg)mb, OS_MESG_NOBLOCK); + } else { + ret = osSendMesg(osPiGetCmdQueue(), (OSMesg)mb, OS_MESG_NOBLOCK); + } + + return ret; +} diff --git a/src/usb/usb.c b/src/usb/usb.c new file mode 100644 index 000000000..467478e21 --- /dev/null +++ b/src/usb/usb.c @@ -0,0 +1,1444 @@ +/*************************************************************** + usb.c + +Allows USB communication between an N64 flashcart and the PC +using UNFLoader. +https://github.com/buu342/N64-UNFLoader +***************************************************************/ + +#include "usb/usb.h" +#ifndef LIBDRAGON +#include "ultra64.h" +#else +#include +#endif +#include "libc/string.h" + +extern int strncmp(const char* s1, const char* s2, size_t n); +extern s32 __osPiRawReadIo(u32 devAddr, u32* data); +extern s32 __osPiRawWriteIo(u32 devAddr, u32 data); +extern s32 osPiWriteIo(u32 devAddr, u32 data); +extern s32 osPiReadIo(u32 devAddr, u32* data); +extern s32 osPiStartDma(OSIoMesg* mb, s32 priority, s32 direction, u32 devAddr, void* dramAddr, u32 size, + OSMesgQueue* mq); + +/********************************* + Data macros +*********************************/ + +// Input/Output buffer size. Always keep it at 512 +#define BUFFER_SIZE 512 + +// USB Memory location +#define DEBUG_ADDRESS \ + (0x04000000 - DEBUG_ADDRESS_SIZE) // Put the debug area at the 64MB - DEBUG_ADDRESS_SIZE area in ROM space + +// Data header related +#define USBHEADER_CREATE(type, left) ((((type) << 24) | ((left)&0x00FFFFFF))) + +// Protocol related +#define USBPROTOCOL_VERSION 2 +#define HEARTBEAT_VERSION 1 + +/********************************* + Libultra macros for libdragon +*********************************/ + +// Useful +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif +#ifndef ALIGN +#define ALIGN(value, align) (((value) + ((typeof(value))(align)-1)) & ~((typeof(value))(align)-1)) +#endif +#ifndef TRUE +#define TRUE 1 +#endif +#ifndef FALSE +#define FALSE 0 +#endif +#ifndef NULL +#define NULL 0 +#endif + +#ifdef LIBDRAGON +// MIPS addresses +#define KSEG0 0x80000000 +#define KSEG1 0xA0000000 + +// Memory translation stuff +#define PHYS_TO_K1(x) ((u32)(x) | KSEG1) +#define IO_WRITE(addr, data) (*(vu32*)PHYS_TO_K1(addr) = (u32)(data)) +#define IO_READ(addr) (*(vu32*)PHYS_TO_K1(addr)) +#endif + +// Data alignment +#define OS_DCACHE_ROUNDUP_ADDR(x) (void*)(((((u32)(x) + 0xf) / 0x10) * 0x10)) +#define OS_DCACHE_ROUNDUP_SIZE(x) (u32)(((((u32)(x) + 0xf) / 0x10) * 0x10)) + +/********************************* + 64Drive macros +*********************************/ + +#define D64_COMMAND_TIMEOUT 1000 +#define D64_WRITE_TIMEOUT 1000 + +#define D64_BASE 0x10000000 +#define D64_REGS_BASE 0x18000000 +#define D64_REGS_BASE_EXTENDED 0x1F800000 + +#define D64_REG_STATUS (usb_64drive_get_baseaddr() + 0x0200) +#define D64_REG_COMMAND (usb_64drive_get_baseaddr() + 0x0208) + +#define D64_REG_MAGIC (usb_64drive_get_baseaddr() + 0x02EC) + +#define D64_REG_USBCOMSTAT (usb_64drive_get_baseaddr() + 0x0400) +#define D64_REG_USBP0R0 (usb_64drive_get_baseaddr() + 0x0404) +#define D64_REG_USBP1R1 (usb_64drive_get_baseaddr() + 0x0408) + +#define D64_CI_BUSY 0x1000 + +#define D64_MAGIC 0x55444556 + +#define D64_CI_ENABLE_ROMWR 0xF0 +#define D64_CI_DISABLE_ROMWR 0xF1 + +#define D64_CI_ENABLE_EXTADDR 0xF8 +#define D64_CI_DISABLE_EXTADDR 0xF9 + +#define D64_CUI_ARM 0x0A +#define D64_CUI_DISARM 0x0F +#define D64_CUI_WRITE 0x08 + +#define D64_CUI_ARM_MASK 0x0F +#define D64_CUI_ARM_IDLE 0x00 +#define D64_CUI_ARM_UNARMED_DATA 0x02 + +#define D64_CUI_WRITE_MASK 0xF0 +#define D64_CUI_WRITE_IDLE 0x00 +#define D64_CUI_WRITE_BUSY 0xF0 + +/********************************* + EverDrive macros +*********************************/ + +#define ED_TIMEOUT 1000 + +#define ED_BASE 0x10000000 +#define ED_BASE_ADDRESS 0x1F800000 + +#define ED_REG_USBCFG (ED_BASE_ADDRESS | 0x0004) +#define ED_REG_VERSION (ED_BASE_ADDRESS | 0x0014) +#define ED_REG_USBDAT (ED_BASE_ADDRESS | 0x0400) +#define ED_REG_SYSCFG (ED_BASE_ADDRESS | 0x8000) +#define ED_REG_KEY (ED_BASE_ADDRESS | 0x8004) + +#define ED_USBMODE_RDNOP 0xC400 +#define ED_USBMODE_RD 0xC600 +#define ED_USBMODE_WRNOP 0xC000 +#define ED_USBMODE_WR 0xC200 + +#define ED_USBSTAT_ACT 0x0200 +#define ED_USBSTAT_RXF 0x0400 +#define ED_USBSTAT_TXE 0x0800 +#define ED_USBSTAT_POWER 0x1000 +#define ED_USBSTAT_BUSY 0x2000 + +#define ED_REGKEY 0xAA55 + +#define ED25_VERSION 0xED640007 // V2.5 +#define ED3_VERSION 0xED640008 // V3 +#define EDX_VERSION 0xED640013 // X7, X5 + +/********************************* + SC64 macros +*********************************/ + +#define SC64_WRITE_TIMEOUT 1000 + +#define SC64_BASE 0x10000000 +#define SC64_REGS_BASE 0x1FFF0000 + +#define SC64_REG_SR_CMD (SC64_REGS_BASE + 0x00) +#define SC64_REG_DATA_0 (SC64_REGS_BASE + 0x04) +#define SC64_REG_DATA_1 (SC64_REGS_BASE + 0x08) +#define SC64_REG_IDENTIFIER (SC64_REGS_BASE + 0x0C) +#define SC64_REG_KEY (SC64_REGS_BASE + 0x10) + +#define SC64_SR_CMD_ERROR (1 << 30) +#define SC64_SR_CMD_BUSY (1 << 31) + +#define SC64_V2_IDENTIFIER 0x53437632 + +#define SC64_KEY_RESET 0x00000000 +#define SC64_KEY_UNLOCK_1 0x5F554E4C +#define SC64_KEY_UNLOCK_2 0x4F434B5F + +#define SC64_CMD_CONFIG_SET 'C' +#define SC64_CMD_USB_WRITE_STATUS 'U' +#define SC64_CMD_USB_WRITE 'M' +#define SC64_CMD_USB_READ_STATUS 'u' +#define SC64_CMD_USB_READ 'm' + +#define SC64_CFG_ROM_WRITE_ENABLE 1 + +#define SC64_USB_WRITE_STATUS_BUSY (1 << 31) +#define SC64_USB_READ_STATUS_BUSY (1 << 31) + +/********************************* + Libultra types (for libdragon) +*********************************/ + +#ifdef LIBDRAGON +typedef uint8_t u8; +typedef uint16_t u16; +typedef uint32_t u32; +typedef uint64_t u64; + +typedef int8_t s8; +typedef int16_t s16; +typedef int32_t s32; +typedef int64_t s64; + +typedef volatile uint8_t vu8; +typedef volatile uint16_t vu16; +typedef volatile uint32_t vu32; +typedef volatile uint64_t vu64; + +typedef volatile int8_t vs8; +typedef volatile int16_t vs16; +typedef volatile int32_t vs32; +typedef volatile int64_t vs64; + +typedef float f32; +typedef double f64; +#endif + +/********************************* + Function Prototypes +*********************************/ + +static void usb_findcart(void); +static u32 usb_getaddr(); + +static void usb_64drive_write(int datatype, const void* data, int size); +static u32 usb_64drive_poll(void); +static void usb_64drive_read(void); +static void usb_64drive_set_extendedaddress(u8 enable); +static u32 usb_64drive_get_baseaddr(); + +static void usb_everdrive_write(int datatype, const void* data, int size); +static u32 usb_everdrive_poll(void); +static void usb_everdrive_read(void); + +static void usb_sc64_write(int datatype, const void* data, int size); +static u32 usb_sc64_poll(void); +static void usb_sc64_read(void); + +/********************************* + Globals +*********************************/ + +// Function pointers +void (*funcPointer_write)(int datatype, const void* data, int size); +u32 (*funcPointer_poll)(void); +void (*funcPointer_read)(void); + +// USB globals +static s8 usb_cart = CART_NONE; +static u8 usb_buffer_align[BUFFER_SIZE + + 16]; // IDO doesn't support GCC's __attribute__((aligned(x))), so this is a workaround +static u8* usb_buffer; +static char usb_didtimeout = FALSE; +static int usb_datatype = 0; +static int usb_datasize = 0; +static int usb_dataleft = 0; +static int usb_readblock = -1; + +// Cart specific globals +static u8 d64_extendedaddr = FALSE; + +#ifndef LIBDRAGON +// Message globals +#if !USE_OSRAW +OSMesg dmaMessageBuf; +OSIoMesg dmaIOMessageBuf; +OSMesgQueue dmaMessageQ; +#endif + +// osPiRaw +#if USE_OSRAW +extern s32 __osPiRawWriteIo(u32, u32); +extern s32 __osPiRawReadIo(u32, u32*); +extern s32 __osPiRawStartDma(s32, u32, void*, u32); + +#define osPiRawWriteIo(a, b) __osPiRawWriteIo(a, b) +#define osPiRawReadIo(a, b) __osPiRawReadIo(a, b) +#define osPiRawStartDma(a, b, c, d) __osPiRawStartDma(a, b, c, d) +#endif +#endif + +/********************************* + I/O Wrapper Functions +*********************************/ + +/*============================== + usb_io_read + Reads a 32-bit value from a + given address using the PI. + @param The address to read from + @return The 4 byte value that was read +==============================*/ + +static inline u32 usb_io_read(u32 pi_address) { +#ifndef LIBDRAGON + u32 value; +#if USE_OSRAW + osPiRawReadIo(pi_address, &value); +#else + osPiReadIo(pi_address, &value); +#endif + return value; +#else + return io_read(pi_address); +#endif +} + +/*============================== + usb_io_write + Writes a 32-bit value to a + given address using the PI. + @param The address to write to + @param The 4 byte value to write +==============================*/ + +static inline void usb_io_write(u32 pi_address, u32 value) { +#ifndef LIBDRAGON +#if USE_OSRAW + osPiRawWriteIo(pi_address, value); +#else + osPiWriteIo(pi_address, value); +#endif +#else + io_write(pi_address, value); +#endif +} + +/*============================== + usb_dma_read + Reads arbitrarily sized data from a + given address using DMA. + @param The buffer to read into + @param The address to read from + @param The size of the data to read +==============================*/ + +static inline void usb_dma_read(void* ram_address, u32 pi_address, size_t size) { +#ifndef LIBDRAGON + osWritebackDCache(ram_address, size); + osInvalDCache(ram_address, size); +#if USE_OSRAW + osPiRawStartDma(OS_READ, pi_address, ram_address, size); +#else + osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, pi_address, ram_address, size, &dmaMessageQ); + while (osRecvMesg(&dmaMessageQ, NULL, OS_MESG_NOBLOCK) != 0) { + ; + } +#endif +#else + data_cache_hit_writeback_invalidate(ram_address, size); + dma_read(ram_address, pi_address, size); +#endif +} + +/*============================== + usb_dma_write + writes arbitrarily sized data to a + given address using DMA. + @param The buffer to read from + @param The address to write to + @param The size of the data to write +==============================*/ + +static inline void usb_dma_write(void* ram_address, u32 pi_address, size_t size) { +#ifndef LIBDRAGON + osWritebackDCache(ram_address, size); +#if USE_OSRAW + osPiRawStartDma(OS_WRITE, pi_address, ram_address, size); +#else + osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE, pi_address, ram_address, size, &dmaMessageQ); + while (osRecvMesg(&dmaMessageQ, NULL, OS_MESG_NOBLOCK) != 0) { + ; + } +#endif +#else + data_cache_hit_writeback(ram_address, size); + dma_write(ram_address, pi_address, size); +#endif +} + +/********************************* + Timeout helpers +*********************************/ + +/*============================== + usb_timeout_start + Returns current value of COUNT coprocessor 0 register + @return C0_COUNT value +==============================*/ + +static u32 usb_timeout_start(void) { +#ifndef LIBDRAGON + return osGetCount(); +#else + return TICKS_READ(); +#endif +} + +/*============================== + usb_timeout_check + Checks if timeout occurred + @param Starting value obtained from usb_timeout_start + @param Timeout duration specified in milliseconds + @return TRUE if timeout occurred, otherwise FALSE +==============================*/ + +static char usb_timeout_check(u32 start_ticks, u32 duration) { +#ifndef LIBDRAGON + u64 current_ticks = (u64)osGetCount(); + u64 timeout_ticks = OS_USEC_TO_CYCLES((u64)duration * 1000); +#else + u64 current_ticks = (u64)TICKS_READ(); + u64 timeout_ticks = (u64)TICKS_FROM_MS(duration); +#endif + if (current_ticks < start_ticks) { + current_ticks += 0x100000000ULL; + } + if (current_ticks >= (start_ticks + timeout_ticks)) { + return TRUE; + } + return FALSE; +} + +/********************************* + USB functions +*********************************/ + +/*============================== + usb_initialize + Initializes the USB buffers and pointers + @returns 1 if the USB initialization was successful, 0 if not +==============================*/ + +char usb_initialize(void) { + // Initialize the debug related globals + usb_buffer = (u8*)OS_DCACHE_ROUNDUP_ADDR(usb_buffer_align); + memset(usb_buffer, 0, BUFFER_SIZE); + +#ifndef LIBDRAGON +// Create the message queue +#if !USE_OSRAW + osCreateMesgQueue(&dmaMessageQ, &dmaMessageBuf, 1); +#endif +#endif + + // Find the flashcart + usb_findcart(); + + // Set the function pointers based on the flashcart + switch (usb_cart) { + case CART_64DRIVE: + funcPointer_write = usb_64drive_write; + funcPointer_poll = usb_64drive_poll; + funcPointer_read = usb_64drive_read; + break; + case CART_EVERDRIVE: + funcPointer_write = usb_everdrive_write; + funcPointer_poll = usb_everdrive_poll; + funcPointer_read = usb_everdrive_read; + break; + case CART_SC64: + funcPointer_write = usb_sc64_write; + funcPointer_poll = usb_sc64_poll; + funcPointer_read = usb_sc64_read; + break; + default: + return 0; + } + + // Send a heartbeat + usb_sendheartbeat(); + return 1; +} + +/*============================== + usb_findcart + Checks if the game is running on a 64Drive, EverDrive or a SC64. +==============================*/ + +static void usb_findcart(void) { + u32 buff; + +// Before we do anything, check that we are using an emulator +#if CHECK_EMULATOR + // Check the RDP clock register. + // Always zero on emulators + if (IO_READ(0xA4100010) == 0) // DPC_CLOCK_REG in Libultra + return; + + // Fallback, harder emulator check. + // The VI has an interesting quirk where its values are mirrored every 0x40 bytes + // It's unlikely that emulators handle this, so we'll write to the VI_TEST_ADDR register and readback 0x40 bytes + // from its address If they don't match, we probably have an emulator + buff = (*(u32*)0xA4400038); + (*(u32*)0xA4400038) = 0x6ABCDEF9; + if ((*(u32*)0xA4400038) != (*(u32*)0xA4400078)) { + (*(u32*)0xA4400038) = buff; + return; + } + (*(u32*)0xA4400038) = buff; +#endif + + // Read the cartridge and check if we have a 64Drive. + if (usb_io_read(D64_REG_MAGIC) == D64_MAGIC) { + usb_cart = CART_64DRIVE; + return; + } + + // Since we didn't find a 64Drive let's assume we have an EverDrive + // Write the key to unlock the registers, then read the version register + usb_io_write(ED_REG_KEY, ED_REGKEY); + buff = usb_io_read(ED_REG_VERSION); + + // EverDrive 2.5 not compatible + if (buff == ED25_VERSION) { + return; + } + + // Check if we have an EverDrive + if (buff == EDX_VERSION || buff == ED3_VERSION) { + // Set the USB mode + usb_io_write(ED_REG_SYSCFG, 0); + usb_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + + // If the USB unit is powered off, it means that this is a + // X variant without USB support (X5). + if ((usb_io_read(ED_REG_USBCFG) & ED_USBSTAT_POWER) == 0) { + return; + } + + // Set the cart to EverDrive + usb_cart = CART_EVERDRIVE; + return; + } + + // Since we didn't find an EverDrive either let's assume we have a SC64 + // Write the key sequence to unlock the registers, then read the identifier register + usb_io_write(SC64_REG_KEY, SC64_KEY_RESET); + usb_io_write(SC64_REG_KEY, SC64_KEY_UNLOCK_1); + usb_io_write(SC64_REG_KEY, SC64_KEY_UNLOCK_2); + + // Check if we have a SC64 + if (usb_io_read(SC64_REG_IDENTIFIER) == SC64_V2_IDENTIFIER) { + // Set the cart to SC64 + usb_cart = CART_SC64; + return; + } +} + +/*============================== + usb_getcart + Returns which flashcart is currently connected + @return The CART macro that corresponds to the identified flashcart +==============================*/ + +char usb_getcart(void) { + return usb_cart; +} + +/*============================== + usb_getaddr + Gets the base address for the USB data to be stored in + @return The base data address +==============================*/ + +u32 usb_getaddr() { + if (usb_cart == CART_64DRIVE && d64_extendedaddr) { + return 0x10000000 - DEBUG_ADDRESS_SIZE; + } else { + return DEBUG_ADDRESS; + } +} + +/*============================== + usb_write + Writes data to the USB. + Will not write if there is data to read from USB + @param The DATATYPE that is being sent + @param A buffer with the data to send + @param The size of the data being sent +==============================*/ + +void usb_write(int datatype, const void* data, int size) { + // If no debug cart exists, stop + if (usb_cart == CART_NONE) { + return; + } + + // If there's data to read first, stop + if (usb_dataleft != 0) { + return; + } + + // Call the correct write function + funcPointer_write(datatype, data, size); +} + +/*============================== + usb_poll + Returns the header of data being received via USB + The first byte contains the data type, the next 3 the number of bytes left to read + @return The data header, or 0 +==============================*/ + +u32 usb_poll(void) { + // If no debug cart exists, stop + if (usb_cart == CART_NONE) { + return 0; + } + + // If we're out of USB data to read, we don't need the header info anymore + if (usb_dataleft <= 0) { + usb_dataleft = 0; + usb_datatype = 0; + usb_datasize = 0; + usb_readblock = -1; + } + + // If there's still data that needs to be read, return the header with the data left + if (usb_dataleft != 0) { + return USBHEADER_CREATE(usb_datatype, usb_dataleft); + } + + // Call the correct read function + return funcPointer_poll(); +} + +/*============================== + usb_read + Reads bytes from USB into the provided buffer + @param The buffer to put the read data in + @param The number of bytes to read +==============================*/ + +void usb_read(void* buffer, int nbytes) { + int read = 0; + int left = nbytes; + int offset = usb_datasize - usb_dataleft; + int copystart = offset % BUFFER_SIZE; + int block = BUFFER_SIZE - copystart; + int blockoffset = (offset / BUFFER_SIZE) * BUFFER_SIZE; + + // If no debug cart exists, stop + if (usb_cart == CART_NONE) { + return; + } + + // If there's no data to read, stop + if (usb_dataleft == 0) { + return; + } + + // Read chunks from ROM + while (left > 0) { + // Ensure we don't read too much data + if (left > usb_dataleft) { + left = usb_dataleft; + } + if (block > left) { + block = left; + } + + // Call the read function if we're reading a new block + if (usb_readblock != blockoffset) { + usb_readblock = blockoffset; + funcPointer_read(); + } + + // Copy from the USB buffer to the supplied buffer + memcpy((void*)(((u32)buffer) + read), usb_buffer + copystart, block); + + // Increment/decrement all our counters + read += block; + left -= block; + usb_dataleft -= block; + blockoffset += BUFFER_SIZE; + block = BUFFER_SIZE; + copystart = 0; + } +} + +/*============================== + usb_skip + Skips a USB read by the specified amount of bytes + @param The number of bytes to skip +==============================*/ + +void usb_skip(int nbytes) { + // Subtract the amount of bytes to skip to the data pointers + usb_dataleft -= nbytes; + if (usb_dataleft < 0) { + usb_dataleft = 0; + } +} + +/*============================== + usb_rewind + Rewinds a USB read by the specified amount of bytes + @param The number of bytes to rewind +==============================*/ + +void usb_rewind(int nbytes) { + // Add the amount of bytes to rewind to the data pointers + usb_dataleft += nbytes; + if (usb_dataleft > usb_datasize) { + usb_dataleft = usb_datasize; + } +} + +/*============================== + usb_purge + Purges the incoming USB data +==============================*/ + +void usb_purge(void) { + usb_dataleft = 0; + usb_datatype = 0; + usb_datasize = 0; + usb_readblock = -1; +} + +/*============================== + usb_timedout + Checks if the USB timed out recently + @return 1 if the USB timed out, 0 if not +==============================*/ + +char usb_timedout() { + return usb_didtimeout; +} + +/*============================== + usb_sendheartbeat + Sends a heartbeat packet to the PC + This is done once automatically at initialization, + but can be called manually to ensure that the + host side tool is aware of the current USB protocol + version. +==============================*/ + +void usb_sendheartbeat(void) { + u8 buffer[4]; + + // First two bytes describe the USB library protocol version + buffer[0] = (u8)(((USBPROTOCOL_VERSION) >> 8) & 0xFF); + buffer[1] = (u8)(((USBPROTOCOL_VERSION)) & 0xFF); + + // Next two bytes describe the heartbeat packet version + buffer[2] = (u8)(((HEARTBEAT_VERSION) >> 8) & 0xFF); + buffer[3] = (u8)(((HEARTBEAT_VERSION)) & 0xFF); + + // Send through USB + usb_write(DATATYPE_HEARTBEAT, buffer, sizeof(buffer) / sizeof(buffer[0])); +} + +/********************************* + 64Drive functions +*********************************/ + +/*============================== + usb_64drive_wait + Wait until the 64Drive CI is ready + @return FALSE if success or TRUE if failure +==============================*/ + +#ifndef LIBDRAGON +static char usb_64drive_wait(void) +#else +char usb_64drive_wait(void) +#endif +{ + u32 timeout; + + // Wait until the cartridge interface is ready + timeout = usb_timeout_start(); + do { + // Took too long, abort + if (usb_timeout_check(timeout, D64_COMMAND_TIMEOUT)) { + usb_didtimeout = TRUE; + return TRUE; + } + } while (usb_io_read(D64_REG_STATUS) & D64_CI_BUSY); + + // Success + usb_didtimeout = FALSE; + return FALSE; +} + +/*============================== + usb_64drive_set_writable + Set the CARTROM write mode on the 64Drive + @param A boolean with whether to enable or disable +==============================*/ + +static void usb_64drive_set_writable(u32 enable) { + // Wait until CI is not busy + usb_64drive_wait(); + + // Send enable/disable CARTROM writes command + usb_io_write(D64_REG_COMMAND, enable ? D64_CI_ENABLE_ROMWR : D64_CI_DISABLE_ROMWR); + + // Wait until operation is finished + usb_64drive_wait(); +} + +/*============================== + usb_64drive_get_baseaddr + Gets the 64Drive's base address for CI commands + @return The CI base address +==============================*/ + +static u32 usb_64drive_get_baseaddr() { + return d64_extendedaddr ? D64_REGS_BASE_EXTENDED : D64_REGS_BASE; +} + +/*============================== + usb_64drive_set_extendedaddress + Enables or disables 64Drive's extended address mode + @param Enables/disables extended address mode +==============================*/ + +static void usb_64drive_set_extendedaddress(u8 enable) { + // Wait until CI is not busy + usb_64drive_wait(); + + // Send enable extended address command + usb_io_write(D64_REG_COMMAND, enable ? D64_CI_ENABLE_EXTADDR : D64_CI_DISABLE_EXTADDR); + d64_extendedaddr = enable; + + // Wait until operation is finished + usb_64drive_wait(); +} + +/*============================== + usb_64drive_cui_write + Writes data from buffer in the 64drive through USB + @param Data type + @param Offset in CARTROM memory space + @param Transfer size +==============================*/ + +static void usb_64drive_cui_write(u8 datatype, u32 offset, u32 size) { + u32 timeout; + + // Start USB write + usb_io_write(D64_REG_USBP0R0, offset >> 1); + usb_io_write(D64_REG_USBP1R1, + USBHEADER_CREATE(datatype, ALIGN(size, 4))); // Align size to 32-bits due to bugs in the firmware + usb_io_write(D64_REG_USBCOMSTAT, D64_CUI_WRITE); + + // Spin until the write buffer is free + timeout = usb_timeout_start(); + do { + // Took too long, abort + if (usb_timeout_check(timeout, D64_WRITE_TIMEOUT)) { + usb_didtimeout = TRUE; + return; + } + } while ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_WRITE_MASK) != D64_CUI_WRITE_IDLE); +} + +/*============================== + usb_64drive_cui_poll + Checks if there is data waiting to be read from USB FIFO + @return TRUE if data is waiting, FALSE if otherwise +==============================*/ + +static char usb_64drive_cui_poll(void) { + // Check if we have data waiting in buffer + if ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_ARM_MASK) == D64_CUI_ARM_UNARMED_DATA) { + return TRUE; + } + return FALSE; +} + +/*============================== + usb_64drive_cui_read + Reads data from USB FIFO to buffer in the 64drive + @param Offset in CARTROM memory space + @return USB header (datatype + size) +==============================*/ + +static u32 usb_64drive_cui_read(u32 offset) { + u32 header; + u32 left; + u32 datatype; + u32 size; + + // Arm USB FIFO with 8 byte sized transfer + usb_io_write(D64_REG_USBP0R0, offset >> 1); + usb_io_write(D64_REG_USBP1R1, 8); + usb_io_write(D64_REG_USBCOMSTAT, D64_CUI_ARM); + + // Wait until data is received + while ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_ARM_MASK) != D64_CUI_ARM_UNARMED_DATA) { + ; + } + + // Get datatype and bytes remaining + header = usb_io_read(D64_REG_USBP0R0); + left = usb_io_read(D64_REG_USBP1R1) & 0x00FFFFFF; + datatype = header & 0xFF000000; + size = header & 0x00FFFFFF; + + // Determine if we need to read more data + if (left > 0) { + // Arm USB FIFO with known transfer size + usb_io_write(D64_REG_USBP0R0, (offset + 8) >> 1); + usb_io_write(D64_REG_USBP1R1, left); + usb_io_write(D64_REG_USBCOMSTAT, D64_CUI_ARM); + + // Wait until data is received + while ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_ARM_MASK) != D64_CUI_ARM_UNARMED_DATA) { + ; + } + + // Calculate total transfer length + size += left; + } + + // Disarm USB FIFO + usb_io_write(D64_REG_USBCOMSTAT, D64_CUI_DISARM); + + // Wait until USB FIFO is disarmed + while ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_ARM_MASK) != D64_CUI_ARM_IDLE) { + ; + } + + // Due to a 64drive bug, we need to ignore the last 512 bytes of the transfer if it's larger than 512 bytes + if (size > 512) { + size -= 512; + } + + // Return data header (datatype and size) + return (datatype | size); +} + +/*============================== + usb_64drive_write + Sends data through USB from the 64Drive + Will not write if there is data to read from USB + @param The DATATYPE that is being sent + @param A buffer with the data to send + @param The size of the data being sent +==============================*/ + +static void usb_64drive_write(int datatype, const void* data, int size) { + s32 left = size; + u32 pi_address = D64_BASE + usb_getaddr(); + + // Return if previous transfer timed out + if ((usb_io_read(D64_REG_USBCOMSTAT) & D64_CUI_WRITE_MASK) == D64_CUI_WRITE_BUSY) { + usb_didtimeout = TRUE; + return; + } + + // Set the cartridge to write mode + usb_64drive_set_writable(TRUE); + + // Write data to SDRAM until we've finished + while (left > 0) { + // Calculate transfer size + u32 block = MIN(left, BUFFER_SIZE); + + // Copy data to PI DMA aligned buffer + memcpy(usb_buffer, data, block); + + // Pad the buffer with zeroes if it wasn't 4 byte aligned + while (block % 4) { + usb_buffer[block++] = 0; + } + + // Copy block of data from RDRAM to SDRAM + usb_dma_write(usb_buffer, pi_address, ALIGN(block, 2)); + + // Update pointers and variables + data = (void*)(((u32)data) + block); + left -= block; + pi_address += block; + } + + // Disable write mode + usb_64drive_set_writable(FALSE); + + // Send the data through USB + usb_64drive_cui_write(datatype, usb_getaddr(), size); + usb_didtimeout = FALSE; +} + +/*============================== + usb_64drive_poll + Returns the header of data being received via USB on the 64Drive + The first byte contains the data type, the next 3 the number of bytes left to read + @return The data header, or 0 +==============================*/ + +static u32 usb_64drive_poll(void) { + u32 header; + + // If there's data to service + if (usb_64drive_cui_poll()) { + // Read data to the buffer in 64drive SDRAM memory + header = usb_64drive_cui_read(usb_getaddr()); + + // Get the data header + usb_datatype = USBHEADER_GETTYPE(header); + usb_dataleft = USBHEADER_GETSIZE(header); + usb_datasize = usb_dataleft; + usb_readblock = -1; + + // Return the data header + return USBHEADER_CREATE(usb_datatype, usb_datasize); + } + + // Return 0 if there's no data + return 0; +} + +/*============================== + usb_64drive_read + Reads bytes from the 64Drive ROM into the global buffer with the block offset +==============================*/ + +static void usb_64drive_read(void) { + // Set up DMA transfer between RDRAM and the PI + usb_dma_read(usb_buffer, D64_BASE + usb_getaddr() + usb_readblock, BUFFER_SIZE); +} + +/********************************* + EverDrive functions +*********************************/ + +/*============================== + usb_everdrive_usbbusy + Spins until the USB is no longer busy + @return FALSE on success, TRUE on failure +==============================*/ + +static char usb_everdrive_usbbusy(void) { + u32 val; + u32 timeout = usb_timeout_start(); + do { + val = usb_io_read(ED_REG_USBCFG); + if (usb_timeout_check(timeout, ED_TIMEOUT)) { + usb_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + usb_didtimeout = TRUE; + return TRUE; + } + } while ((val & ED_USBSTAT_ACT) != 0); + return FALSE; +} + +/*============================== + usb_everdrive_canread + Checks if the EverDrive's USB can read + @return TRUE if it can read, FALSE if not +==============================*/ + +static char usb_everdrive_canread(void) { + u32 val; + u32 status = ED_USBSTAT_POWER; + + // Read the USB register and check its status + val = usb_io_read(ED_REG_USBCFG); + status = val & (ED_USBSTAT_POWER | ED_USBSTAT_RXF); + if (status == ED_USBSTAT_POWER) { + return TRUE; + } + return FALSE; +} + +/*============================== + usb_everdrive_readusb + Reads from the EverDrive USB buffer + @param The buffer to put the read data in + @param The number of bytes to read +==============================*/ + +static void usb_everdrive_readusb(void* buffer, int size) { + u16 block, addr; + + while (size) { + // Get the block size + block = BUFFER_SIZE; + if (block > size) { + block = size; + } + addr = BUFFER_SIZE - block; + + // Request to read from the USB + usb_io_write(ED_REG_USBCFG, ED_USBMODE_RD | addr); + + // Wait for the FPGA to transfer the data to its internal buffer, or stop on timeout + if (usb_everdrive_usbbusy()) { + return; + } + + // Read from the internal buffer and store it in our buffer + usb_dma_read(buffer, ED_REG_USBDAT + addr, block); + buffer = (char*)buffer + block; + size -= block; + } +} + +/*============================== + usb_everdrive_write + Sends data through USB from the EverDrive + Will not write if there is data to read from USB + @param The DATATYPE that is being sent + @param A buffer with the data to send + @param The size of the data being sent +==============================*/ + +static void usb_everdrive_write(int datatype, const void* data, int size) { + char wrotecmp = 0; + char cmp[] = { 'C', 'M', 'P', 'H' }; + int read = 0; + int left = size; + int offset = 8; + u32 header = (size & 0x00FFFFFF) | (datatype << 24); + + // Put in the DMA header along with length and type information in the global buffer + usb_buffer[0] = 'D'; + usb_buffer[1] = 'M'; + usb_buffer[2] = 'A'; + usb_buffer[3] = '@'; + usb_buffer[4] = (header >> 24) & 0xFF; + usb_buffer[5] = (header >> 16) & 0xFF; + usb_buffer[6] = (header >> 8) & 0xFF; + usb_buffer[7] = header & 0xFF; + + // Write data to USB until we've finished + while (left > 0) { + int block = left; + int blocksend, baddr; + if (block + offset > BUFFER_SIZE) { + block = BUFFER_SIZE - offset; + } + + // Copy the data to the next available spots in the global buffer + memcpy(usb_buffer + offset, (void*)((char*)data + read), block); + + // Restart the loop to write the CMP signal if we've finished + if (!wrotecmp && read + block >= size) { + left = 4; + offset = block + offset; + data = cmp; + wrotecmp = 1; + read = 0; + continue; + } + + // Ensure the data is 2 byte aligned and the block address is correct + blocksend = ALIGN((block + offset), 2); + baddr = BUFFER_SIZE - blocksend; + + // Set USB to write mode and send data through USB + usb_io_write(ED_REG_USBCFG, ED_USBMODE_WRNOP); + usb_dma_write(usb_buffer, ED_REG_USBDAT + baddr, blocksend); + + // Set USB to write mode with the new address and wait for USB to end (or stop if it times out) + usb_io_write(ED_REG_USBCFG, ED_USBMODE_WR | baddr); + if (usb_everdrive_usbbusy()) { + usb_didtimeout = TRUE; + return; + } + + // Keep track of what we've read so far + left -= block; + read += block; + offset = 0; + } + usb_didtimeout = FALSE; +} + +/*============================== + usb_everdrive_poll + Returns the header of data being received via USB on the EverDrive + The first byte contains the data type, the next 3 the number of bytes left to read + @return The data header, or 0 +==============================*/ + +static u32 usb_everdrive_poll(void) { + int len; + int offset = 0; + unsigned char buffaligned[32]; + unsigned char* buff = (unsigned char*)OS_DCACHE_ROUNDUP_ADDR(buffaligned); + + // Wait for the USB to be ready + if (usb_everdrive_usbbusy()) { + return 0; + } + + // Check if the USB is ready to be read + if (!usb_everdrive_canread()) { + return 0; + } + + // Read the first 8 bytes that are being received and check if they're valid + usb_everdrive_readusb(buff, 8); + if (buff[0] != 'D' || buff[1] != 'M' || buff[2] != 'A' || buff[3] != '@') { + return 0; + } + + // Store information about the incoming data + usb_datatype = buff[4]; + usb_datasize = (buff[5] << 16) | (buff[6] << 8) | (buff[7] << 0); + usb_dataleft = usb_datasize; + usb_readblock = -1; + + // Get the aligned data size. Must be 2 byte aligned + len = ALIGN(usb_datasize, 2); + + // While there's data to service + while (len > 0) { + u32 bytes_do = BUFFER_SIZE; + if (len < BUFFER_SIZE) { + bytes_do = len; + } + + // Read a chunk from USB and store it into our temp buffer + usb_everdrive_readusb(usb_buffer, bytes_do); + + // Copy received block to ROM + usb_dma_write(usb_buffer, ED_BASE + usb_getaddr() + offset, bytes_do); + offset += bytes_do; + len -= bytes_do; + } + + // Read the CMP Signal + if (usb_everdrive_usbbusy()) { + return 0; + } + usb_everdrive_readusb(buff, 4); + if (buff[0] != 'C' || buff[1] != 'M' || buff[2] != 'P' || buff[3] != 'H') { + // Something went wrong with the data + usb_datatype = 0; + usb_datasize = 0; + usb_dataleft = 0; + usb_readblock = -1; + return 0; + } + + // Return the data header + return USBHEADER_CREATE(usb_datatype, usb_datasize); +} + +/*============================== + usb_everdrive_read + Reads bytes from the EverDrive ROM into the global buffer with the block offset +==============================*/ + +static void usb_everdrive_read(void) { + // Set up DMA transfer between RDRAM and the PI + usb_dma_read(usb_buffer, ED_BASE + usb_getaddr() + usb_readblock, BUFFER_SIZE); +} + +/********************************* + SC64 functions +*********************************/ + +/*============================== + usb_sc64_execute_cmd + Executes specified command in SC64 controller + @param Command ID to execute + @param 2 element array of 32 bit arguments to pass with command, use NULL when argument values are not needed + @param 2 element array of 32 bit values to read command result, use NULL when result values are not needed + @return TRUE if there was error during command execution, otherwise FALSE +==============================*/ + +#ifndef LIBDRAGON +static char usb_sc64_execute_cmd(u8 cmd, u32* args, u32* result) +#else +char usb_sc64_execute_cmd(u8 cmd, u32* args, u32* result) +#endif +{ + u32 sr; + + // Write arguments if provided + if (args != NULL) { + usb_io_write(SC64_REG_DATA_0, args[0]); + usb_io_write(SC64_REG_DATA_1, args[1]); + } + + // Start execution + usb_io_write(SC64_REG_SR_CMD, cmd); + + // Wait for completion + do { + sr = usb_io_read(SC64_REG_SR_CMD); + } while (sr & SC64_SR_CMD_BUSY); + + // Read result if provided + if (result != NULL) { + result[0] = usb_io_read(SC64_REG_DATA_0); + result[1] = usb_io_read(SC64_REG_DATA_1); + } + + // Return error status + if (sr & SC64_SR_CMD_ERROR) { + return TRUE; + } + return FALSE; +} + +/*============================== + usb_sc64_set_writable + Enable ROM (SDRAM) writes in SC64 + @param A boolean with whether to enable or disable + @return Previous value of setting +==============================*/ + +static u32 usb_sc64_set_writable(u32 enable) { + u32 args[2]; + u32 result[2]; + + args[0] = SC64_CFG_ROM_WRITE_ENABLE; + args[1] = enable; + if (usb_sc64_execute_cmd(SC64_CMD_CONFIG_SET, args, result)) { + return 0; + } + + return result[1]; +} + +/*============================== + usb_sc64_write + Sends data through USB from the SC64 + @param The DATATYPE that is being sent + @param A buffer with the data to send + @param The size of the data being sent +==============================*/ + +static void usb_sc64_write(int datatype, const void* data, int size) { + u32 left = size; + u32 pi_address = SC64_BASE + usb_getaddr(); + u32 writable_restore; + u32 timeout; + u32 args[2]; + u32 result[2]; + + // Return if previous transfer timed out + usb_sc64_execute_cmd(SC64_CMD_USB_WRITE_STATUS, NULL, result); + if (result[0] & SC64_USB_WRITE_STATUS_BUSY) { + usb_didtimeout = TRUE; + return; + } + + // Enable SDRAM writes and get previous setting + writable_restore = usb_sc64_set_writable(TRUE); + + while (left > 0) { + // Calculate transfer size + u32 block = MIN(left, BUFFER_SIZE); + + // Copy data to PI DMA aligned buffer + memcpy(usb_buffer, data, block); + + // Copy block of data from RDRAM to SDRAM + usb_dma_write(usb_buffer, pi_address, ALIGN(block, 2)); + + // Update pointers and variables + data = (void*)(((u32)data) + block); + left -= block; + pi_address += block; + } + + // Restore previous SDRAM writable setting + usb_sc64_set_writable(writable_restore); + + // Start sending data from buffer in SDRAM + args[0] = SC64_BASE + usb_getaddr(); + args[1] = USBHEADER_CREATE(datatype, size); + if (usb_sc64_execute_cmd(SC64_CMD_USB_WRITE, args, NULL)) { + usb_didtimeout = TRUE; + return; // Return if USB write was unsuccessful + } + + // Wait for transfer to end + timeout = usb_timeout_start(); + do { + // Took too long, abort + if (usb_timeout_check(timeout, SC64_WRITE_TIMEOUT)) { + usb_didtimeout = TRUE; + return; + } + usb_sc64_execute_cmd(SC64_CMD_USB_WRITE_STATUS, NULL, result); + } while (result[0] & SC64_USB_WRITE_STATUS_BUSY); + usb_didtimeout = FALSE; +} + +/*============================== + usb_sc64_poll + Returns the header of data being received via USB on the SC64 + The first byte contains the data type, the next 3 the number of bytes left to read + @return The data header, or 0 +==============================*/ + +static u32 usb_sc64_poll(void) { + u8 datatype; + u32 size; + u32 args[2]; + u32 result[2]; + + // Get read status and extract packet info + usb_sc64_execute_cmd(SC64_CMD_USB_READ_STATUS, NULL, result); + datatype = result[0] & 0xFF; + size = result[1] & 0xFFFFFF; + + // Return 0 if there's no data + if (size == 0) { + return 0; + } + + // Fill USB read data variables + usb_datatype = datatype; + usb_dataleft = size; + usb_datasize = usb_dataleft; + usb_readblock = -1; + + // Start receiving data to buffer in SDRAM + args[0] = SC64_BASE + usb_getaddr(); + args[1] = size; + if (usb_sc64_execute_cmd(SC64_CMD_USB_READ, args, NULL)) { + return 0; // Return 0 if USB read was unsuccessful + } + + // Wait for completion + do { + usb_sc64_execute_cmd(SC64_CMD_USB_READ_STATUS, NULL, result); + } while (result[0] & SC64_USB_READ_STATUS_BUSY); + + // Return USB header + return USBHEADER_CREATE(datatype, size); +} + +/*============================== + usb_sc64_read + Reads bytes from the SC64 SDRAM into the global buffer with the block offset +==============================*/ + +static void usb_sc64_read(void) { + // Set up DMA transfer between RDRAM and the PI + usb_dma_read(usb_buffer, SC64_BASE + usb_getaddr() + usb_readblock, BUFFER_SIZE); +}