1457 lines
41 KiB
C
Executable File
1457 lines
41 KiB
C
Executable File
/***************************************************************
|
|
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 <ultra64.h>
|
|
#else
|
|
#include <libdragon.h>
|
|
#endif
|
|
#include <string.h>
|
|
|
|
|
|
/*********************************
|
|
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);
|
|
}
|