diff --git a/libdragon/.gitignore b/libdragon/.gitignore new file mode 100644 index 0000000..eccfb94 --- /dev/null +++ b/libdragon/.gitignore @@ -0,0 +1,3 @@ +build +gpsp.z64 +filesystem/rom.gba diff --git a/libdragon/Makefile b/libdragon/Makefile new file mode 100644 index 0000000..832cec5 --- /dev/null +++ b/libdragon/Makefile @@ -0,0 +1,74 @@ +TARGET := gpsp.z64 +SOURCES := n64main.c unf/usb.c unf/debug.c + +#OPTFLAG = -O0 -ggdb +OPTFLAG = -O3 -flto=auto +CFLAGS := -Wall -Wno-unused-function -DFULLSCREEN +LDFLAGS := -flto=auto + +#CFLAGS += -DTRACE_INSTRUCTIONS +#CFLAGS += -DTRACE_REGISTERS +#CFLAGS += -DTRACE_EVENTS +#CFLAGS += -DNO_PRINT_ONLY_TRACE + +CORE_DIR := $(abspath ..) + +HAVE_DYNAREC := 0 + +ifeq ($(HAVE_DYNAREC),1) + CFLAGS += -DHAVE_DYNAREC +endif +CFLAGS += -DMIPS_ARCH -DUSE_RGBA5551_FORMAT -DROM_BUFFER_SIZE=1 +CFLAGS += -DHAVE_STRINGS_H -DHAVE_STDINT_H -DHAVE_INTTYPES_H -D__LIBRETRO__ -DINLINE=inline +CPU_ARCH := mips + +include $(CORE_DIR)/Makefile.common +SOURCES_C := $(filter-out $(CORE_DIR)/libretro/libretro.c,$(SOURCES_C)) + +CFLAGS += $(INCFLAGS) + +BUILD_DIR = build +CXXFLAGS := $(CFLAGS) +ASFLAGS := $(CFLAGS) -Wa,-I$(CORE_DIR) -DN64 + +include $(N64_INST)/include/n64.mk + +N64_CFLAGS += $(OPTFLAG) +N64_CXXFLAGS += $(OPTFLAG) + +OBJS := $(addprefix $(BUILD_DIR)/,$(SOURCES:.c=.o)) +OBJS += $(addprefix $(BUILD_DIR)/,$(SOURCES_C:.c=.o)) +OBJS += $(addprefix $(BUILD_DIR)/,$(SOURCES_CC:.cc=.o)) +OBJS += $(addprefix $(BUILD_DIR)/,$(SOURCES_ASM:.S=.o)) + +all: $(TARGET) + +clean: + $(RM) -rf $(BUILD_DIR) + +.PHONY: all clean debug everdrive + +$(BUILD_DIR)/%.o: $(SOURCE_DIR)/%.cc + @mkdir -p $(dir $@) + @echo " [CXX] $<" + $(CXX) -c $(CXXFLAGS) -o $@ $< + +$(TARGET): N64_ROM_TITLE = "gameplaySP64" +$(TARGET): $(BUILD_DIR)/$(TARGET:.z64=.dfs) + +$(BUILD_DIR)/$(TARGET:.z64=.elf): $(OBJS) + +$(BUILD_DIR)/$(TARGET:.z64=.dfs): $(wildcard filesystem/*) + +debug: $(TARGET) + ares --setting DebugServer/Enabled=true \ + --setting Boot/Debugger=true \ + --setting General/HomebrewMode=true \ + $(TARGET) | grep -vE '^CPU ffffffff[^a]' & + gdb $(BUILD_DIR)/$(TARGET:.z64=.elf) -ex 'target remote tcp::9123' + +everdrive: $(TARGET) + usb64 -rom=$(TARGET) + usb64 -start + +-include $(OBJS:.o=.d) diff --git a/libdragon/README.md b/libdragon/README.md new file mode 100644 index 0000000..1171eac --- /dev/null +++ b/libdragon/README.md @@ -0,0 +1,25 @@ +build with libdragon unstable. https://libdragon.dev/ + +requires real hardware or ares-emu to run. https://ares-emu.net/ + +because the n64's cpu needs *ten instructions* to byte-swap a word (and eight +to byte-swap a half-word), we just leave memory in word-wise big-endian and +simply invert the lower bits of addresses whenever we access bytes or halfwords +at runtime (just one XOR instruction, and no need to touch word accesses - +the most common by a landslide - at all!) + +for this scheme to work, the ROM must also already be in word-wise big-endian, +which you can achieve with binutils like so: + +``` +objcopy -I binary -O binary --reverse-bytes=4 .../game.gba .../gpsp/libdragon/filesystem/rom.gba +``` + +dynarec doesn't quite work yet - crashes for reasons i don't yet understand! +to attempt to use it and/or debug, change the variables in the Makefile. +(hint: there's also a make target `debug` that works if you have ares and +multiarch gdb, but it doesn't change the build flags - that still needs to be +done by hand in the Makefile) + +ppu emulation is still done in software on the main cpu - i am hoping to make +a renderer that utilizes the RCP, but that may take some time to implement. diff --git a/libdragon/filesystem/rom.gba b/libdragon/filesystem/rom.gba new file mode 100644 index 0000000..e69de29 diff --git a/libdragon/n64main.c b/libdragon/n64main.c new file mode 100644 index 0000000..b81d491 --- /dev/null +++ b/libdragon/n64main.c @@ -0,0 +1,189 @@ +#include +#include +#include + +#include +#include +#include + +#include "unf/usb.h" +#include "unf/debug.h" + +#include "../common.h" +#include "../memmap.h" + +#include "../gba_memory.h" +#include "../gba_cc_lut.h" + +// Usually 59.72750057 Hz, unless GBC_RATE is overclocked (for 60FPS) +#define GBA_FPS ((float) GBC_BASE_RATE) / (308 * 228 * 4) + +//static s16 audio_sample_buffer[(audio_samples_per_frame + 1) * 2]; +static s16 audio_sample_buffer[2194]; +static float audio_samples_per_frame = (float)(GBA_SOUND_FREQUENCY) / (float)(GBA_FPS); +static float audio_samples_accumulator = 0.0f; + +static int16_t g_joypads[JOYPAD_PORT_COUNT][RETRO_DEVICE_ID_JOYPAD_R+1]; + +u32 skip_next_frame = 0; +boot_mode selected_boot_mode = boot_game; + +int dynarec_enable; +int sprite_limit = 1; + +u32 idle_loop_target_pc = 0xFFFFFFFF; +u32 translation_gate_target_pc[MAX_TRANSLATION_GATES]; +u32 translation_gate_targets = 0; + +surface_t* display_surface = NULL; +size_t gba_screen_pixel_offset = 0; + +static void audio_run(void) +{ + u32 samples_to_read; + u32 samples_produced; + + /* audio_samples_per_frame is decimal; + * get integer component */ + samples_to_read = (u32)audio_samples_per_frame; + + /* Account for fractional component */ + audio_samples_accumulator += audio_samples_per_frame - + (float)samples_to_read; + + if (audio_samples_accumulator >= 1.0f) + { + samples_to_read++; + audio_samples_accumulator -= 1.0f; + } + + samples_produced = sound_read_samples(audio_sample_buffer, samples_to_read); + audio_push(audio_sample_buffer, samples_produced, false); +} + +static void allocate_video_surface() { + display_surface = display_get(); + + gba_screen_pixels = display_surface->buffer; + gba_screen_pixels += gba_screen_pixel_offset; +} + +static void video_run(void) +{ + if (display_surface != NULL) { + display_show(display_surface); + } + allocate_video_surface(); +} + +static void setup_screen_geometry() { + resolution_t res; + filter_options_t filt; + +#ifdef FULLSCREEN + res.width = GBA_SCREEN_WIDTH; + res.height = GBA_SCREEN_WIDTH * 3 / 4; + filt = FILTERS_RESAMPLE_ANTIALIAS; +#else // integer scaled, centered + res.width = 320; + res.height = 240; + filt = FILTERS_RESAMPLE; +#endif + // TODO: do any of the interlace options give us anything like interframe blending? + res.interlaced = INTERLACE_OFF; + + gba_screen_pitch = res.width; + // center in screen + gba_screen_pixel_offset = ((res.width - GBA_SCREEN_WIDTH) + (res.width * (res.height - GBA_SCREEN_HEIGHT))) / 2; + + display_init(res, DEPTH_16_BPP, 3, GAMMA_NONE, filt); +} + +int16_t input_state(unsigned port, unsigned device, unsigned index, unsigned id) { + if (port < JOYPAD_PORT_COUNT && device == RETRO_DEVICE_JOYPAD) { + return g_joypads[port][id]; + } else { + return 0; + } +} + +static void input_poll() { + joypad_poll(); + JOYPAD_PORT_FOREACH(i) { + joypad_buttons_t inp = joypad_get_buttons(i); + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_A] = inp.a; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_B] = inp.b; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_UP] = inp.d_up; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_DOWN] = inp.d_down; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_LEFT] = inp.d_left; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_RIGHT] = inp.d_right; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_START] = inp.start; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_SELECT] = inp.z; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_L] = inp.l; + g_joypads[i][RETRO_DEVICE_ID_JOYPAD_R] = inp.r; + } +} + +void set_fastforward_override(bool fastforward) {} + +int stdio_stderr_redirect(char* buf, unsigned len) { + usb_write(DATATYPE_TEXT, buf, len); + return write(2, buf, len); +} + +int main() { + // UNFLoader interface plumbing for flash carts + stdio_t console_calls = { 0, stdio_stderr_redirect, 0 }; + //debug_initialize(); + usb_initialize(); + hook_stdio_calls(&console_calls); + printf("UNFLoader debug initialized!\n"); + + // ISViewer (i.e. ares-emu) + debug_init_isviewer(); + printf("ISViewer debug initialized!\n"); + + dfs_init(DFS_DEFAULT_LOCATION); + + setup_screen_geometry(); + allocate_video_surface(); + + audio_init(GBA_SOUND_FREQUENCY, 4); + joypad_init(); + + // gpsp input.c implements this directly. + retro_set_input_state(input_state); + + memcpy(bios_rom, open_gba_bios_rom, sizeof(bios_rom)); + + init_gamepak_buffer(); + init_sound(); + + memset(gamepak_backup, 0xff, sizeof(gamepak_backup)); + if (load_gamepak(NULL, "rom:/rom.gba", 0, 0) != 0) + { + printf("Could not load the game file."); + return -1; + } + + reset_gba(); + while(true) { + input_poll(); + update_input(); + + /* This runs just a frame */ + #ifdef HAVE_DYNAREC + if (dynarec_enable) + execute_arm_translate(execute_cycles); + else + #endif + { + /* Sticky bits only used in interpreter */ + clear_gamepak_stickybits(); + execute_arm(execute_cycles); + } + + audio_run(); + video_run(); + } +} diff --git a/libdragon/unf/debug.c b/libdragon/unf/debug.c new file mode 100755 index 0000000..2bfadec --- /dev/null +++ b/libdragon/unf/debug.c @@ -0,0 +1,2100 @@ +/*************************************************************** + debug.c + +A basic debug library that makes use of the USB library for N64 +flashcarts. +https://github.com/buu342/N64-UNFLoader +***************************************************************/ + +#include "debug.h" +#ifndef LIBDRAGON + #include + #include // Needed for Crash's Linux toolchain +#else + #include + #include +#endif +#include +#include +#include +#include + +#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) + *********************************/ + + #ifdef LIBDRAGON + #ifndef TRUE + #define TRUE 1 + #endif + #ifndef FALSE + #define FALSE 0 + #endif + #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; + + // 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; + + // 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; + + // 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>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 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)); + } + + + /*============================== + 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; jcontext; + #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; iepc)); + #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= 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= 0x80000000 && addr < 0x80000000 + osMemSize) + { + for (i=0; i= 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 + + // Move all the breakpoints in front of it back + for (i=index; i +#else + #include +#endif +#include + + +/********************************* + 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 +*********************************/ + +#ifdef 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 + + // 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)) + + // 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)) +#endif + + +/********************************* + 64Drive macros +*********************************/ + +#define D64_COMMAND_TIMEOUT 1000 +#define D64_WRITE_TIMEOUT 1000 + +#define D64_BASE 0x10000000 +#define D64_REGS_BASE 0x18000000 + +#define D64_REG_STATUS (D64_REGS_BASE + 0x0200) +#define D64_REG_COMMAND (D64_REGS_BASE + 0x0208) + +#define D64_REG_MAGIC (D64_REGS_BASE + 0x02EC) + +#define D64_REG_USBCOMSTAT (D64_REGS_BASE + 0x0400) +#define D64_REG_USBP0R0 (D64_REGS_BASE + 0x0404) +#define D64_REG_USBP1R1 (D64_REGS_BASE + 0x0408) + +#define D64_CI_BUSY 0x1000 + +#define D64_MAGIC 0x55444556 + +#define D64_CI_ENABLE_ROMWR 0xF0 +#define D64_CI_DISABLE_ROMWR 0xF1 + +#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 +#define ED3_VERSION 0xED640008 +#define ED7_VERSION 0xED640013 + + +/********************************* + 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 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_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; + +#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); + osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); + #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 == ED7_VERSION || buff == ED3_VERSION) + { + // Set the USB mode + usb_io_write(ED_REG_SYSCFG, 0); + usb_io_write(ED_REG_USBCFG, ED_USBMODE_RDNOP); + + // 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_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_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 + DEBUG_ADDRESS; + + // 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, DEBUG_ADDRESS, 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(DEBUG_ADDRESS); + + // 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 + DEBUG_ADDRESS + 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 + DEBUG_ADDRESS + 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 + DEBUG_ADDRESS + 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 + DEBUG_ADDRESS; + 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 + DEBUG_ADDRESS; + 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 + DEBUG_ADDRESS; + 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 + DEBUG_ADDRESS + usb_readblock, BUFFER_SIZE); +} diff --git a/libdragon/unf/usb.h b/libdragon/unf/usb.h new file mode 100755 index 0000000..a62f2c0 --- /dev/null +++ b/libdragon/unf/usb.h @@ -0,0 +1,140 @@ +#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 size); + + + /*============================== + 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