/*************************************************************** usb.c Allows USB communication between an N64 flashcart and the PC using UNFLoader. https://github.com/buu342/N64-UNFLoader ***************************************************************/ #include "usb.h" #ifndef LIBDRAGON #include #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); }