Files
Microtransactions64/src/usb/usb.c
2020-12-09 16:38:26 -05:00

1546 lines
45 KiB
C

/***************************************************************
usb.c
Allows USB communication between an N64 flashcart and the PC
using UNFLoader.
https://github.com/buu342/N64-UNFLoader
***************************************************************/
#include <ultra64.h>
#include <string.h>
#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<size_new; i++)
usb_buffer[i] = 0;
size = size_new;
}
// Spin until the write buffer is free
usb_64drive_waitidle();
// Set up DMA transfer between RDRAM and the PI
osWritebackDCache(usb_buffer, block);
#if USE_OSRAW
osPiRawStartDma(OS_WRITE,
D64_BASE_ADDRESS + DEBUG_ADDRESS + read,
usb_buffer, block);
#else
osPiStartDma(&dmaIOMessageBuf, OS_MESG_PRI_NORMAL, OS_WRITE,
D64_BASE_ADDRESS + DEBUG_ADDRESS + read,
usb_buffer, block, &dmaMessageQ);
(void)osRecvMesg(&dmaMessageQ, NULL, OS_MESG_BLOCK);
#endif
// Keep track of what we've read so far
left -= block;
read += block;
}
// Send the data through USB
#if USE_OSRAW
osPiRawWriteIo(D64_CIBASE_ADDRESS + D64_REGISTER_USBP0R0, (DEBUG_ADDRESS) >> 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);
}