/*************************************************************** usb.c Allows USB communication between an N64 flashcart and the PC using UNFLoader. https://github.com/buu342/N64-UNFLoader ***************************************************************/ #include #include #include "usb.h" #define ALIGN(s, align) (((u32)(s) + ((align)-1)) & ~((align)-1)) /********************************* 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 63MB area in ROM space // Data header related #define USBHEADER_CREATE(type, left) (((type<<24) | (left & 0x00FFFFFF))) /********************************* Parallel Interface macros *********************************/ #define N64_PI_ADDRESS 0xA4600000 #define N64_PI_RAMADDRESS 0x00 #define N64_PI_PIADDRESS 0x04 #define N64_PI_READLENGTH 0x08 #define N64_PI_WRITELENGTH 0x0C #define N64_PI_STATUS 0x10 /********************************* 64Drive macros *********************************/ // Cartridge Interface definitions. Obtained from 64Drive's Spec Sheet #define D64_BASE_ADDRESS 0xB0000000 #define D64_CIREG_ADDRESS 0x08000000 #define D64_CIBASE_ADDRESS 0xB8000000 #define D64_REGISTER_STATUS 0x00000200 #define D64_REGISTER_COMMAND 0x00000208 #define D64_REGISTER_LBA 0x00000210 #define D64_REGISTER_LENGTH 0x00000218 #define D64_REGISTER_RESULT 0x00000220 #define D64_REGISTER_MAGIC 0x000002EC #define D64_REGISTER_VARIANT 0x000002F0 #define D64_REGISTER_BUTTON 0x000002F8 #define D64_REGISTER_REVISION 0x000002FC #define D64_REGISTER_USBCOMSTAT 0x00000400 #define D64_REGISTER_USBP0R0 0x00000404 #define D64_REGISTER_USBP1R1 0x00000408 #define D64_ENABLE_ROMWR 0xF0 #define D64_DISABLE_ROMWR 0xF1 #define D64_COMMAND_WRITE 0x08 // Cartridge Interface return values #define D64_MAGIC 0x55444556 #define D64_USB_IDLE 0x00 #define D64_USB_IDLEUNARMED 0x00 #define D64_USB_ARMED 0x01 #define D64_USB_DATA 0x02 #define D64_USB_ARM 0x0A #define D64_USB_BUSY 0x0F #define D64_USB_DISARM 0x0F #define D64_USB_ARMING 0x0F #define D64_CI_IDLE 0x00 #define D64_CI_BUSY 0x10 #define D64_CI_WRITE 0x20 /********************************* EverDrive macros *********************************/ #define ED_BASE 0x10000000 #define ED_BASE_ADDRESS 0x1F800000 #define ED_GET_REGADD(reg) (0xA0000000 | ED_BASE_ADDRESS | (reg)) #define ED_REG_USBCFG 0x0004 #define ED_REG_VERSION 0x0014 #define ED_REG_USBDAT 0x0400 #define ED_REG_SYSCFG 0x8000 #define ED_REG_KEY 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 ED3_VERSION 0xED640008 #define ED7_VERSION 0xED640013 /********************************* SummerCart64 macros *********************************/ #define SC64_SDRAM_BASE 0x10000000 #define SC64_BANK_ROM 1 #define SC64_REGS_BASE 0x1E000000 #define SC64_REG_SCR (SC64_REGS_BASE + 0x00) #define SC64_REG_VERSION (SC64_REGS_BASE + 0x08) #define SC64_REG_USB_SCR (SC64_REGS_BASE + 0x10) #define SC64_REG_USB_DMA_ADDR (SC64_REGS_BASE + 0x14) #define SC64_REG_USB_DMA_LEN (SC64_REGS_BASE + 0x18) #define SC64_MEM_BASE (SC64_REGS_BASE + 0x1000) #define SC64_MEM_USB_FIFO_BASE (SC64_MEM_BASE + 0x0000) #define SC64_MEM_USB_FIFO_LEN (4 * 1024) #define SC64_SCR_SDRAM_WRITE_EN (1 << 0) #define SC64_VERSION_A 0x53363461 #define SC64_USB_STATUS_BUSY (1 << 0) #define SC64_USB_STATUS_READY (1 << 1) #define SC64_USB_CONTROL_START (1 << 0) #define SC64_USB_CONTROL_FIFO_FLUSH (1 << 2) #define SC64_USB_BANK_ADDR(b, a) ((((b) & 0xF) << 28) | ((a) & 0x3FFFFFF)) #define SC64_USB_LENGTH(l) (ALIGN((l), 4) / 4) #define SC64_USB_DMA_MAX_LEN (2 * 1024 * 1024) #define SC64_USB_FIFO_ITEMS(s) (((s) >> 3) & 0x7FF) /********************************* Function Prototypes *********************************/ static void usb_findcart(); static void usb_64drive_write(int datatype, const void* data, int size); static u32 usb_64drive_poll(); static void usb_64drive_read(); static void usb_everdrive_readreg(u32 reg, u32* result); static void usb_everdrive_write(int datatype, const void* data, int size); static u32 usb_everdrive_poll(); static void usb_everdrive_read(); static void usb_everdrive_writereg(u64 reg, u32 value); static void usb_sc64_write(int datatype, const void* data, int size); static u32 usb_sc64_poll(); static void usb_sc64_read(); /********************************* Globals *********************************/ // Function pointers void (*funcPointer_write)(int datatype, const void* data, int size); u32 (*funcPointer_poll)(); void (*funcPointer_read)(); // USB globals static s8 usb_cart = CART_NONE; static u8 usb_buffer[BUFFER_SIZE*3] __attribute__((aligned(16))); int usb_datatype = 0; int usb_datasize = 0; int usb_dataleft = 0; int usb_readblock = -1; // 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 /********************************* USB functions *********************************/ /*============================== usb_initialize Initializes the USB buffers and pointers @returns 1 if the USB initialization was successful, 0 if not ==============================*/ char usb_initialize() { // Initialize the debug related globals memset(usb_buffer, 0, BUFFER_SIZE); // Create the message queue #if !USE_OSRAW osCreateMesgQueue(&dmaMessageQ, &dmaMessageBuf, 1); #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; } return 1; } /*============================== usb_findcart Checks if the game is running on a 64Drive, EverDrive or a SummerCart64. ==============================*/ static void usb_findcart() { u32 buff; // Read the cartridge and check if we have a 64Drive. #if USE_OSRAW osPiRawReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_MAGIC, &buff); #else osPiReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_MAGIC, &buff); #endif if (buff == D64_MAGIC) { usb_cart = CART_64DRIVE; return; } // Read the cartridge and check if we have a SummerCart64. #if USE_OSRAW osPiRawReadIo(SC64_REG_VERSION, &buff); #else osPiReadIo(SC64_REG_VERSION, &buff); #endif if (buff == SC64_VERSION_A) { usb_cart = CART_SC64; return; } // Since we didn't find a 64Drive or SummerCart64, let's assume we have an EverDrive // Write the key to unlock the registers, then read the version register usb_everdrive_writereg(ED_REG_KEY, ED_REGKEY); usb_everdrive_readreg(ED_REG_VERSION, &buff); // Check if we have an EverDrive if (buff == ED7_VERSION || buff == ED3_VERSION) { // Initialize the PI IO_WRITE(PI_STATUS_REG, 3); IO_WRITE(PI_BSD_DOM1_LAT_REG, 0x40); IO_WRITE(PI_BSD_DOM1_PWD_REG, 0x12); IO_WRITE(PI_BSD_DOM1_PGS_REG, 0x07); IO_WRITE(PI_BSD_DOM1_RLS_REG, 0x03); IO_WRITE(PI_BSD_DOM2_LAT_REG, 0x05); IO_WRITE(PI_BSD_DOM2_PWD_REG, 0x0C); IO_WRITE(PI_BSD_DOM2_PGS_REG, 0x0D); IO_WRITE(PI_BSD_DOM2_RLS_REG, 0x02); IO_WRITE(PI_BSD_DOM1_LAT_REG, 0x04); IO_WRITE(PI_BSD_DOM1_PWD_REG, 0x0C); // Set the USB mode usb_everdrive_writereg(ED_REG_SYSCFG, 0); usb_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_RDNOP); // Set the cart to EverDrive usb_cart = CART_EVERDRIVE; return; } } /*============================== usb_getcart Returns which flashcart is currently connected @return The CART macro that corresponds to the identified flashcart ==============================*/ char usb_getcart() { 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() { // 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(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() { usb_dataleft = 0; usb_datatype = 0; usb_datasize = 0; usb_readblock = -1; } /********************************* 64Drive functions *********************************/ /*============================== usb_64drive_wait Wait until the 64Drive is ready @return 0 if success or -1 if failure ==============================*/ static s8 usb_64drive_wait() { u32 ret; u32 timeout = 0; // I wanted to use osGetTime() but that requires the VI manager // Wait until the cartridge interface is ready do { #if USE_OSRAW osPiRawReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_STATUS, &ret); #else osPiReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_STATUS, &ret); #endif // Took too long, abort if((timeout++) > 1000000) return -1; } while((ret >> 8) & D64_CI_BUSY); // Success return 0; } /*============================== usb_64drive_setwritable Set the write mode on the 64Drive @param A boolean with whether to enable or disable ==============================*/ static void usb_64drive_setwritable(u8 enable) { usb_64drive_wait(); #if USE_OSRAW osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_COMMAND, enable ? D64_ENABLE_ROMWR : D64_DISABLE_ROMWR); #else osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_COMMAND, enable ? D64_ENABLE_ROMWR : D64_DISABLE_ROMWR); #endif usb_64drive_wait(); } /*============================== usb_64drive_waitidle Waits for the 64Drive's USB to be idle ==============================*/ static void usb_64drive_waitidle() { u32 status; do { #if USE_OSRAW osPiRawReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #else osPiReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #endif status = (status >> 4) & D64_USB_BUSY; } while(status != D64_USB_IDLE); } /*============================== usb_64drive_armstatus Checks if the 64Drive is armed @return The arming status ==============================*/ static u32 usb_64drive_armstatus() { u32 status; #if USE_OSRAW osPiRawReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #else osPiReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #endif return status & 0xf; } /*============================== usb_64drive_waitdisarmed Waits for the 64Drive's USB to be disarmed ==============================*/ static void usb_64drive_waitdisarmed() { u32 status; do { #if USE_OSRAW osPiRawReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #else osPiReadIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, &status); #endif status &= 0x0F; } while (status != D64_USB_IDLEUNARMED); } /*============================== 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) { int left = size; int read = 0; // Spin until the write buffer is free and then set the cartridge to write mode usb_64drive_waitidle(); usb_64drive_setwritable(TRUE); // Write data to SDRAM until we've finished while (left > 0) { int block = left; if (block > BUFFER_SIZE) block = BUFFER_SIZE; // Copy the data to the global buffer memcpy(usb_buffer, (void*)((char*)data+read), block); // If the data was not 32-bit aligned, pad the buffer if (block < BUFFER_SIZE && size%4 != 0) { u32 i; u32 size_new = (size & ~3)+4; block += size_new-size; for (i=size; i> 1); osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP1R1, (size & 0xFFFFFF) | (datatype << 24)); osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_COMMAND_WRITE); #else osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP0R0, (DEBUG_ADDRESS) >> 1); osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP1R1, (size & 0xFFFFFF) | (datatype << 24)); osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_COMMAND_WRITE); #endif // Spin until the write buffer is free and then disable write mode usb_64drive_waitidle(); usb_64drive_setwritable(FALSE); } /*============================== usb_64drive_arm Arms the 64Drive's USB @param The ROM offset to arm @param The size of the data to transfer ==============================*/ static void usb_64drive_arm(u32 offset, u32 size) { u32 ret = usb_64drive_armstatus(); if (ret != D64_USB_ARMING && ret != D64_USB_ARMED) { usb_64drive_waitidle(); // Arm the 64Drive, using the ROM space as a buffer #if USE_OSRAW osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_USB_ARM); osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP0R0, (offset >> 1)); osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP1R1, (size & 0xFFFFFF)); #else osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_USB_ARM); osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP0R0, (offset >> 1)); osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP1R1, (size & 0xFFFFFF)); #endif } } /*============================== usb_64drive_disarm Disarms the 64Drive's USB ==============================*/ static void usb_64drive_disarm() { // Disarm the USB #if USE_OSRAW osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_USB_DISARM); #else osPiWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBCOMSTAT, D64_USB_DISARM); #endif usb_64drive_waitdisarmed(); } /*============================== 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() { u32 ret; // Arm the USB buffer usb_64drive_waitidle(); usb_64drive_setwritable(TRUE); usb_64drive_arm(DEBUG_ADDRESS, DEBUG_ADDRESS_SIZE); // If there's data to service if (usb_64drive_armstatus() == D64_USB_DATA) { char buff[8]; u32 copyleft; // Read ROM to get the data header osWritebackDCacheAll(); #if USE_OSRAW osPiRawStartDma(OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS, buff, 8); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS, buff, 8, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Ensure we got a USB request and this isn't something else if (buff[0] != 'D' && buff[1] != 'M' && buff[2] != 'A' && buff[3] != '@') return 0; // Get the data header ret = buff[4] << 24 | buff[5] << 16 | buff[6] << 8 | buff[7]; usb_datatype = USBHEADER_GETTYPE(ret); usb_dataleft = USBHEADER_GETSIZE(ret); usb_datasize = usb_dataleft; usb_readblock = -1; copyleft = usb_dataleft; // Copy data from USB to ROM while (copyleft > 0) { u32 block = copyleft%BUFFER_SIZE; if (block == 0) block = BUFFER_SIZE; // Arm the 64Drive's USB usb_64drive_arm(DEBUG_ADDRESS, DEBUG_ADDRESS_SIZE); // Wait for data to arrive while (usb_64drive_armstatus() != D64_USB_DATA) ; // Put the data in the correct ROM offset osWritebackDCacheAll(); #if USE_OSRAW osPiRawStartDma(OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS, usb_buffer, BUFFER_SIZE); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS, usb_buffer, BUFFER_SIZE, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif osWritebackDCacheAll(); #if USE_OSRAW osPiRawStartDma(OS_WRITE, D64_BASE_ADDRESS + DEBUG_ADDRESS + (copyleft-block), usb_buffer, BUFFER_SIZE); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE, D64_BASE_ADDRESS + DEBUG_ADDRESS + (copyleft-block), usb_buffer, BUFFER_SIZE, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif copyleft -= block; } // Return the data header usb_64drive_waitidle(); usb_64drive_setwritable(FALSE); return USBHEADER_CREATE(usb_datatype, usb_datasize); } // Disarm the USB if no data arrived usb_64drive_disarm(); usb_64drive_waitidle(); usb_64drive_setwritable(FALSE); return 0; } /*============================== usb_64drive_read Reads bytes from the 64Drive ROM into the global buffer with the block offset ==============================*/ static void usb_64drive_read() { // Set up DMA transfer between RDRAM and the PI osWritebackDCacheAll(); #if USE_OSRAW osPiRawStartDma(OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS + usb_readblock, usb_buffer, BUFFER_SIZE); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, D64_BASE_ADDRESS + DEBUG_ADDRESS + usb_readblock, usb_buffer, BUFFER_SIZE, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif } /********************************* EverDrive functions *********************************/ /*============================== usb_everdrive_wait_pidma Spins until the EverDrive's DMA is ready ==============================*/ static void usb_everdrive_wait_pidma() { u32 status; do { status = *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_STATUS); status &= (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY); } while (status); } /*============================== usb_everdrive_readdata Reads data from a specific address on the EverDrive @param The buffer with the data @param The register address to write to the PI @param The size of the data ==============================*/ static void usb_everdrive_readdata(void* buff, u32 pi_address, u32 len) { // Correct the PI address pi_address &= 0x1FFFFFFF; // Set up DMA transfer between RDRAM and the PI osInvalDCache(buff, len); #if USE_OSRAW osPiRawStartDma(OS_READ, pi_address, buff, len); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, pi_address, buff, len, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Write the data to the PI usb_everdrive_wait_pidma(); IO_WRITE(PI_STATUS_REG, 3); *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_RAMADDRESS) = (u32)buff; *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_PIADDRESS) = pi_address; *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_READLENGTH) = len-1; usb_everdrive_wait_pidma(); } /*============================== usb_everdrive_readreg Reads data from a specific register on the EverDrive @param The register to read from @param A pointer to write the read value to ==============================*/ static void usb_everdrive_readreg(u32 reg, u32* result) { usb_everdrive_readdata(result, ED_GET_REGADD(reg), sizeof(u32)); } /*============================== usb_everdrive_writedata Writes data to a specific address on the EverDrive @param A buffer with the data to write @param The register address to write to the PI @param The length of the data ==============================*/ static void usb_everdrive_writedata(void* buff, u32 pi_address, u32 len) { // Correct the PI address pi_address &= 0x1FFFFFFF; // Set up DMA transfer between RDRAM and the PI osWritebackDCache(buff, len); #if USE_OSRAW osPiRawStartDma(OS_WRITE, pi_address, buff, len); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE, pi_address, buff, len, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Write the data to the PI usb_everdrive_wait_pidma(); IO_WRITE(PI_STATUS_REG, 3); *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_RAMADDRESS) = (u32)buff; *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_PIADDRESS) = pi_address; *(volatile unsigned long *)(N64_PI_ADDRESS + N64_PI_WRITELENGTH) = len-1; usb_everdrive_wait_pidma(); } /*============================== usb_everdrive_writereg Writes data to a specific register on the EverDrive @param The register to write to @param The value to write to the register ==============================*/ static void usb_everdrive_writereg(u64 reg, u32 value) { usb_everdrive_writedata(&value, ED_GET_REGADD(reg), sizeof(u32)); } /*============================== usb_everdrive_usbbusy Spins until the USB is no longer busy ==============================*/ static void usb_everdrive_usbbusy() { u32 val; do { usb_everdrive_readreg(ED_REG_USBCFG, &val); } while ((val & ED_USBSTAT_ACT) != 0); } /*============================== usb_everdrive_canread Checks if the EverDrive's USB can read @return 1 if it can read, 0 if not ==============================*/ static u8 usb_everdrive_canread() { u32 val, status = ED_USBSTAT_POWER; // Read the USB register and check its status usb_everdrive_readreg(ED_REG_USBCFG, &val); status = val & (ED_USBSTAT_POWER | ED_USBSTAT_RXF); return status == ED_USBSTAT_POWER; } /*============================== 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_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_RD | addr); // Wait for the FPGA to transfer the data to its internal buffer usb_everdrive_usbbusy(); // Read from the internal buffer and store it in our buffer usb_everdrive_readdata(buffer, ED_GET_REGADD(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 16 byte aligned and the block address is correct blocksend = (block+offset)+15 - ((block+offset)+15)%16; baddr = BUFFER_SIZE - blocksend; // Set USB to write mode and send data through USB usb_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_WRNOP); usb_everdrive_writedata(usb_buffer, ED_GET_REGADD(ED_REG_USBDAT + baddr), blocksend); // Set USB to write mode with the new address and wait for USB to end usb_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_WR | baddr); usb_everdrive_usbbusy(); // Keep track of what we've read so far left -= block; read += block; offset = 0; } } /*============================== 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() { char buff[16]; int len; int offset = 0; // Wait for the USB to be ready usb_everdrive_usbbusy(); // 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, 16); if (buff[0] != 'D' || buff[1] != 'M' || buff[2] != 'A' || buff[3] != '@') return 0; // Store information about the incoming data usb_datatype = (int)buff[4]; usb_datasize = (int)buff[5]<<16 | (int)buff[6]<<8 | (int)buff[7]<<0; usb_dataleft = usb_datasize; usb_readblock = -1; // Begin receiving data usb_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_RD | BUFFER_SIZE); len = (usb_datasize + BUFFER_SIZE-usb_datasize%BUFFER_SIZE)/BUFFER_SIZE; // While there's data to service while (len--) { // Wait for the USB to be ready and then read data usb_everdrive_usbbusy(); usb_everdrive_readdata(usb_buffer, ED_GET_REGADD(ED_REG_USBDAT), BUFFER_SIZE); // TODO: Replace with usb_everdrive_readusb? // Tell the FPGA we can receive more data if (len != 0) usb_everdrive_writereg(ED_REG_USBCFG, ED_USBMODE_RD | BUFFER_SIZE); // Copy received block to ROM usb_everdrive_writedata(usb_buffer, ED_BASE + DEBUG_ADDRESS + offset, BUFFER_SIZE); offset += BUFFER_SIZE; } // Read the CMP Signal usb_everdrive_usbbusy(); usb_everdrive_readusb(buff, 16); 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() { // Set up DMA transfer between RDRAM and the PI osWritebackDCacheAll(); #if USE_OSRAW osPiRawStartDma(OS_READ, ED_BASE + DEBUG_ADDRESS + usb_readblock, usb_buffer, BUFFER_SIZE); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, ED_BASE + DEBUG_ADDRESS + usb_readblock, usb_buffer, BUFFER_SIZE, &dmaMessageQ); (void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif } /********************************* SummerCart64 functions *********************************/ /*============================== usb_sc64_read_usb_scr Reads SummerCart64 REG_USB_SCR register @return value of REG_USB_SCR register ==============================*/ static u32 usb_sc64_read_usb_scr(void) { u32 usb_scr; #if USE_OSRAW osPiRawReadIo(SC64_REG_USB_SCR, &usb_scr); #else osPiReadIo(SC64_REG_USB_SCR, &usb_scr); #endif return usb_scr; } /*============================== usb_sc64_read_usb_fifo Loads one element from USB FIFO @return value popped from USB FIFO ==============================*/ static u32 usb_sc64_read_usb_fifo(void) { u32 data; #if USE_OSRAW osPiRawReadIo(SC64_MEM_USB_FIFO_BASE, &data); #else osPiReadIo(SC64_MEM_USB_FIFO_BASE, &data); #endif return data; } /*============================== usb_sc64_waitidle Waits for the SummerCart64 USB interface to be idle @return 0 if interface is ready, -1 if USB cable is not connected ==============================*/ static s8 usb_sc64_waitidle(void) { u32 usb_scr; do { usb_scr = usb_sc64_read_usb_scr(); if (!(usb_scr & SC64_USB_STATUS_READY)) { // Reset usb_cart type if USB cable is not connected usb_cart = CART_NONE; return -1; } } while (usb_scr & SC64_USB_STATUS_BUSY); return 0; } /*============================== usb_sc64_waitdata Waits for the SummerCart64 USB FIFO to contain specified amount of data or for full FIFO @param length in bytes @return number of available bytes in FIFO, -1 if USB cable is not connected ==============================*/ static s32 usb_sc64_waitdata(u32 length) { u32 usb_scr; u32 wait_length = ALIGN(MIN(length, SC64_MEM_USB_FIFO_LEN), 4); u32 bytes = 0; do { usb_scr = usb_sc64_read_usb_scr(); if (!(usb_scr & SC64_USB_STATUS_READY)) { // Reset usb_cart type if USB cable is not connected usb_cart = CART_NONE; return -1; } bytes = SC64_USB_FIFO_ITEMS(usb_scr) * 4; } while (bytes < wait_length); return (s32) bytes; } /*============================== usb_sc64_setwritable Enable ROM (SDRAM) writes in SummerCart64 @param A boolean with whether to enable or disable ==============================*/ static void usb_sc64_setwritable(u8 enable) { u32 scr; #if USE_OSRAW osPiRawReadIo(SC64_REG_SCR, &scr); osPiRawWriteIo(SC64_REG_SCR, enable ? (scr | SC64_SCR_SDRAM_WRITE_EN) : (scr & (~SC64_SCR_SDRAM_WRITE_EN))); #else osPiReadIo(SC64_REG_SCR, &scr); osPiWriteIo(SC64_REG_SCR, enable ? (scr | SC64_SCR_SDRAM_WRITE_EN) : (scr & (~SC64_SCR_SDRAM_WRITE_EN))); #endif } /*============================== usb_sc64_write Sends data through USB from the SummerCart64 @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) { u8 dma[4] = {'D', 'M', 'A', '@'}; u32 header = USBHEADER_CREATE(datatype, size); u8 cmp[4] = {'C', 'M', 'P', 'H'}; u8 wrote_cmp = FALSE; size_t block_size = MIN(BUFFER_SIZE, DEBUG_ADDRESS_SIZE); size_t usb_block_max_size = MIN(DEBUG_ADDRESS_SIZE, SC64_USB_DMA_MAX_LEN); u8* data_ptr = (u8*) data; u32 sdram_address = SC64_SDRAM_BASE + DEBUG_ADDRESS; int offset; int left; u32 transfer_length; // Wait until ready if (usb_sc64_waitidle()) { // Do nothing if USB cable is not connected return; } // Enable SDRAM writes usb_sc64_setwritable(TRUE); // Prepare transfer header memcpy(usb_buffer, dma, sizeof(dma)); memcpy(usb_buffer + sizeof(dma), &header, sizeof(header)); offset = sizeof(dma) + sizeof(header); left = size; transfer_length = 0; while (left > 0) { // Calculate data copy length size_t data_length = MIN(MIN(left, block_size - offset), usb_block_max_size - transfer_length); u32 dma_length; // Fill buffer memcpy(usb_buffer + offset, data_ptr, data_length); // Write CMPH at the end of data if (!wrote_cmp && (left - data_length) <= 0) { wrote_cmp = TRUE; data_ptr = cmp; offset = MIN(offset + data_length, block_size); left = sizeof(cmp); continue; } // Calculate RDRAM -> PI transfer length dma_length = ALIGN(offset + data_length, 4); // Write data to buffer in SDRAM osWritebackDCache(usb_buffer, dma_length); #if USE_OSRAW osPiRawStartDma(OS_WRITE, sdram_address, usb_buffer, dma_length); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE, sdram_address, usb_buffer, dma_length, &dmaMessageQ); osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Update pointers and remaining data tracking data_ptr += data_length; sdram_address += dma_length; offset = 0; left -= data_length; transfer_length = sdram_address - (SC64_SDRAM_BASE + DEBUG_ADDRESS); // Continue filling SDRAM buffer if total length is lower than maximum transfer length or if there's no more data if ((transfer_length < usb_block_max_size) && (left > 0)) { continue; } // Disable SDRAM writes if there's no more data to write if (left <= 0) { usb_sc64_setwritable(FALSE); } // Setup hardware registers #if USE_OSRAW osPiRawWriteIo(SC64_REG_USB_DMA_ADDR, SC64_USB_BANK_ADDR(SC64_BANK_ROM, DEBUG_ADDRESS)); osPiRawWriteIo(SC64_REG_USB_DMA_LEN, SC64_USB_LENGTH(transfer_length)); osPiRawWriteIo(SC64_REG_USB_SCR, SC64_USB_CONTROL_START); #else osPiWriteIo(SC64_REG_USB_DMA_ADDR, SC64_USB_BANK_ADDR(SC64_BANK_ROM, DEBUG_ADDRESS)); osPiWriteIo(SC64_REG_USB_DMA_LEN, SC64_USB_LENGTH(transfer_length)); osPiWriteIo(SC64_REG_USB_SCR, SC64_USB_CONTROL_START); #endif // Wait for transfer to complete if there's more data to send if (left > 0) { if (usb_sc64_waitidle()) { // Disable SDRAM writes usb_sc64_setwritable(FALSE); // Stop sending data if USB cable has been disconnected return; } } // Reset SDRAM address and transfer length sdram_address = SC64_SDRAM_BASE + DEBUG_ADDRESS; transfer_length = 0; } } /*============================== usb_sc64_poll Returns the header of data being received via USB on the SummerCart64 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) { u32 buff, sdram_address; int left; // Load how many 32 bit words are in FIFO u32 fifo_items = SC64_USB_FIFO_ITEMS(usb_sc64_read_usb_scr()); // Check data if there's at least DMA@ and header in FIFO if (fifo_items >= 2) { // Load and check DMA@ identifier buff = usb_sc64_read_usb_fifo(); if (memcmp(&buff, "DMA@", 4) != 0) { // Return if identifier is wrong return 0; } // Load header buff = usb_sc64_read_usb_fifo(); // Fill USB read data variables usb_datatype = USBHEADER_GETTYPE(buff); usb_dataleft = USBHEADER_GETSIZE(buff); usb_datasize = usb_dataleft; usb_readblock = -1; // Calculate copy length, data size + CMPH identifier aligned to 4 bytes left = ALIGN(usb_datasize + 4, 4); // Starting address in SDRAM sdram_address = SC64_SDRAM_BASE + DEBUG_ADDRESS; // Enable SDRAM writes usb_sc64_setwritable(TRUE); // Copy data until finished while (left > 0) { // Calculate transfer length s32 dma_length = MIN(left, BUFFER_SIZE); // Wait for data in FIFO dma_length = usb_sc64_waitdata(dma_length); if (dma_length < 0) { // Disable SDRAM writes usb_sc64_setwritable(FALSE); // Stop waiting for data if USB cable has been disconnected return 0; } // Load data from FIFO to buffer in RDRAM #if USE_OSRAW osPiRawStartDma(OS_READ, SC64_MEM_USB_FIFO_BASE, usb_buffer, dma_length); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, SC64_MEM_USB_FIFO_BASE, usb_buffer, dma_length, &dmaMessageQ); osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Copy data from RDRAM to SDRAM #if USE_OSRAW osPiRawStartDma(OS_WRITE, sdram_address, usb_buffer, dma_length); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE, sdram_address, usb_buffer, dma_length, &dmaMessageQ); osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Update tracking variables left -= dma_length; sdram_address += dma_length; } // Disable SDRAM writes usb_sc64_setwritable(FALSE); // Return USB header return USBHEADER_CREATE(usb_datatype, usb_dataleft); } // Return no USB header if FIFO is empty return 0; } /*============================== usb_sc64_read Reads bytes from the SummerCart64 ROM into the global buffer with the block offset ==============================*/ static void usb_sc64_read(void) { // Calculate address in SDRAM u32 sdram_address = SC64_SDRAM_BASE + DEBUG_ADDRESS + usb_readblock; // Set up DMA transfer between RDRAM and the PI #if USE_OSRAW osPiRawStartDma(OS_READ, sdram_address, usb_buffer, BUFFER_SIZE); #else osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_READ, sdram_address, usb_buffer, BUFFER_SIZE, &dmaMessageQ); osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK); #endif // Invalidate cache osInvalDCache(usb_buffer, BUFFER_SIZE); }