Skip to content

Commit

Permalink
feat: USB hotswapping for replay storage (#39)
Browse files Browse the repository at this point in the history
* initial hotswap impl

the general methodology of this change is as follows

1. copy the technique from HID.c to set up a thread and addition to main loop to manage the IOCTLs and keep track of devices being inserted or removed
2. copy enough of libogc/usb.c and libogc/usbstorage.c in to mount inserted devices
3. modify SlippiFileWriter logic to enable hotswap for us

* replays drive LED option

* clear cbw_buffer

I think we got away with this before cuz all __cycle invocations used the same command length. Not sure if this can actually cause errors but for correctness...
  • Loading branch information
jmlee337 authored Sep 26, 2024
1 parent 594fb6d commit 6deec6c
Show file tree
Hide file tree
Showing 17 changed files with 669 additions and 177 deletions.
2 changes: 2 additions & 0 deletions common/config/MeleeCodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@

// Indicates the maximum number possible for ID. This is important in case the line items change,
// we don't want to persist setting values for a different line item
// THIS AFFECTS sizeof(NIN_CFG).
// IF YOU CHANGE THIS NUMBER, YOU MUST BUMP NIN_CFG_VERSION AND UPDATE LoadNinCFG.
#define MELEE_CODES_MAX_ID 7

#define MELEE_CODES_LINE_ITEM_COUNT 8
Expand Down
12 changes: 8 additions & 4 deletions common/include/CommonConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
#include "Metadata.h"
#include "../config/MeleeCodes.h"

#define NIN_CFG_VERSION 0x0000000C
#define NIN_CFG_VERSION 0x0000000D

#define NIN_CFG_MAXPAD 4

// IF YOU CHANGE THE SIZE OF THIS struct YOU MUST BUMP NIN_CFG_VERSION AND UPDATE LoadNinCFG.
typedef struct NIN_CFG
{
unsigned int Magicbytes; // 0x01070CF6
Expand All @@ -24,6 +25,7 @@ typedef struct NIN_CFG
unsigned char Unused;
unsigned int UseUSB; // 0 for SD, 1 for USB
unsigned int MeleeCodeOptions[MELEE_CODES_MAX_ID + 1]; // IDs are 0 indexed so add 1
unsigned int ReplaysLED; // 0: On, 1: Off
} NIN_CFG;

enum ninconfigbitpos
Expand All @@ -44,7 +46,7 @@ enum ninconfigbitpos
NIN_CFG_BIT_MC_MULTI = (11),
NIN_CFG_BIT_SKIP_IPL = (12), // Disabled in Slippi Nintendont
NIN_CFG_BIT_NETWORK = (13),
NIN_CFG_BIT_SLIPPI_FILE_WRITE = (14),
NIN_CFG_BIT_SLIPPI_REPLAYS = (14),
NIN_CFG_BIT_SLIPPI_PORT_A = (15),

// Internal kernel settings.
Expand All @@ -68,7 +70,7 @@ enum ninconfig
NIN_CFG_MC_MULTI = (1<<NIN_CFG_BIT_MC_MULTI),
NIN_CFG_SKIP_IPL = (1<<NIN_CFG_BIT_SKIP_IPL),
NIN_CFG_NETWORK = (1<<NIN_CFG_BIT_NETWORK),
NIN_CFG_SLIPPI_FILE_WRITE = (1<<NIN_CFG_BIT_SLIPPI_FILE_WRITE),
NIN_CFG_SLIPPI_REPLAYS = (1<<NIN_CFG_BIT_SLIPPI_REPLAYS),
NIN_CFG_SLIPPI_PORT_A = (1<<NIN_CFG_BIT_SLIPPI_PORT_A),

NIN_CFG_MC_SLOTB = (1<<NIN_CFG_BIT_MC_SLOTB),
Expand All @@ -92,7 +94,9 @@ enum ninextrasettings
enum ninslippisettings
{
NIN_SLIPPI_NETWORKING,
NIN_SLIPPI_FILE_WRITE,
NIN_SLIPPI_REPLAYS,
NIN_SLIPPI_REPLAYS_LED,
NIN_SLIPPI_BLANK_0,
NIN_SLIPPI_PORT_A,
NIN_SLIPPI_CUSTOM_CODES,
NIN_SLIPPI_BLANK_1,
Expand Down
5 changes: 5 additions & 0 deletions kernel/Config.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,9 @@ static inline u32 ConfigGetMeleeCodeValue(int identifier)
return ncfg->MeleeCodeOptions[identifier];
}

static inline u32 ConfigGetReplaysLED(void)
{
return ncfg->ReplaysLED;
}

#endif
4 changes: 2 additions & 2 deletions kernel/DI.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

#include "ff_utf8.h"
static u8 DummyBuffer[0x1000] __attribute__((aligned(32)));
extern u32 s_cnt;
extern u32 usb_s_cnt;

#ifndef DEBUG_DI
#define dbgprintf(...)
Expand Down Expand Up @@ -865,7 +865,7 @@ u32 DIReadThread(void *arg)
case IOS_IOCTL:
if(di_msg->ioctl.command == 2)
{
USBStorage_ReadSectors(read32(HW_TIMER) % s_cnt, 1, DummyBuffer);
USBStorage_ReadSectors(read32(HW_TIMER) % usb_s_cnt, 1, DummyBuffer);
mqueue_ack( di_msg, 0 );
break;
}
Expand Down
123 changes: 102 additions & 21 deletions kernel/SlippiFileWriter.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
#include "net.h"

#include "Config.h"
#include "usbstorage.h"

// Game can transfer at most 784 bytes / frame
// That means 4704 bytes every 100 ms. Let's aim to handle
// double that, making our read buffer 10000 bytes
#define READ_BUF_SIZE 10000
#define THREAD_CYCLE_TIME_MS 100
#define THREAD_ERROR_TIME_MS 2000
#define LED_FLASH_TIME_MS 1000

#define FOOTER_BUFFER_LENGTH 200

Expand All @@ -34,13 +37,24 @@ u32 gameStartTime;
// timer for drive led
u32 driveTimer;

// flag for drive led timer
bool driveTimerSet;

// replays LED setting
bool replaysLED;

void SlippiFileWriterInit()
{
// Move to a more appropriate place later
// Enables Drive LED
set32(HW_GPIO_ENABLE, GPIO_SLOT_LED);
clear32(HW_GPIO_DIR, GPIO_SLOT_LED);
clear32(HW_GPIO_OWNER, GPIO_SLOT_LED);
replaysLED = ConfigGetReplaysLED() == 0;
if (replaysLED)
{
// Move to a more appropriate place later
// Enables Drive LED
set32(HW_GPIO_ENABLE, GPIO_SLOT_LED);
clear32(HW_GPIO_DIR, GPIO_SLOT_LED);
clear32(HW_GPIO_OWNER, GPIO_SLOT_LED);
}

Slippi_Thread = do_thread_create(
SlippiHandlerThread,
((u32 *)&__slippi_stack_addr),
Expand All @@ -49,11 +63,30 @@ void SlippiFileWriterInit()
thread_continue(Slippi_Thread);
}

void SlippiFileWriterUpdateRegisters()
{
if (driveTimerSet && TimerDiffMs(driveTimer) >= LED_FLASH_TIME_MS)
{
clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimerSet = false;
}
}

void SlippiFileWriterShutdown()
{
thread_cancel(Slippi_Thread, 0);
}

void flashLED()
{
driveTimer = read32(HW_TIMER);
if (!driveTimerSet)
{
set32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimerSet = true;
}
}

//we cant include time.h so hardcode what we need
struct tm
{
Expand Down Expand Up @@ -154,17 +187,12 @@ void completeFile(FIL *file, SlpGameReader *reader, u32 writtenByteCount)

// Write footer
u32 wrote;
u32 res;
f_write(file, footer, writePos, &wrote);
f_sync(file);

f_lseek(file, 11);
f_write(file, &writtenByteCount, 4, &wrote);
res = f_sync(file);
if (res == 0) {
set32(HW_GPIO_OUT, GPIO_SLOT_LED);
driveTimer = read32(HW_TIMER);
}
FRESULT fileWriteResult = f_write(file, &writtenByteCount, 4, &wrote);
f_sync(file);
}

static u32 SlippiHandlerThread(void *arg)
Expand All @@ -177,16 +205,50 @@ static u32 SlippiHandlerThread(void *arg)

u32 writtenByteCount = 0;
driveTimer = read32(HW_TIMER);
driveTimerSet = false;

FATFS device;
bool failedToMount = false;
bool hasFile = false;
bool mounted = true;
const bool use_usb = ConfigGetUseUSB() != 1;

while (1)
{
// Cycle time, look at const definition for more info
mdelay(THREAD_CYCLE_TIME_MS);

if (TimerDiffMs(driveTimer) > 1000) {
clear32(HW_GPIO_OUT, GPIO_SLOT_LED);
}
if (use_usb)
{
if (!USBStorage_IsInserted_SlippiThread())
{
if (mounted)
f_mount_char(NULL, "usb:", 1);

// TODO: Ensure connection to USB is correct
failedToMount = false;
hasFile = false;
mounted = false;
continue;
}
else if (!mounted && !failedToMount)
{
if (f_mount_char(&device, "usb:", 1) == FR_OK)
{
// ignore anything already in the buffer. users should not expect to record a
// game if the usb device is inserted after game start.
memReadPos = SlippiRestoreReadPos();

mounted = true;
}
else
{
// only attempt to mount once, user can retry by re-inserting the device.
failedToMount = true;
}
}
if (!mounted)
continue;
}

// Read from memory and write to file
SlpMemError err = SlippiMemoryRead(&reader, readBuf, READ_BUF_SIZE, memReadPos);
Expand All @@ -195,7 +257,7 @@ static u32 SlippiHandlerThread(void *arg)
if (err == SLP_READ_OVERFLOW)
memReadPos = SlippiRestoreReadPos();

mdelay(1000);
mdelay(LED_FLASH_TIME_MS + 1000); // we always want LED visibly off if this happens

// For specific errors, bytes will still be read. Not continueing to deal with those
}
Expand All @@ -209,27 +271,45 @@ static u32 SlippiHandlerThread(void *arg)

dbgprintf("Creating File...\r\n");
char *fileName = generateFileName(true);
// Need to open with FA_READ if network thread is going to share &currentFile
// Maybe can remove FA_READ since network thread doesn't share &currentFile
FRESULT fileOpenResult = f_open_secondary_drive(&currentFile, fileName, FA_CREATE_ALWAYS | FA_WRITE | FA_READ);
if (fileOpenResult != FR_OK)
{
dbgprintf("Slippi: failed to open file: %s, errno: %d\r\n", fileName, fileOpenResult);
mdelay(1000);
mdelay(LED_FLASH_TIME_MS - THREAD_CYCLE_TIME_MS - 100); // short enough so we can recover with running out of LED time.
continue;
}
if (replaysLED)
flashLED();

// dbgprintf("Bytes written: %d/%d...\r\n", wrote, currentBuffer->len);
hasFile = true;
writtenByteCount = 0;
writeHeader(&currentFile);
}

if (reader.lastReadResult.bytesRead == 0)
{
if (replaysLED)
flashLED();
continue;
}

// dbgprintf("Bytes read: %d\r\n", reader.lastReadResult.bytesRead);

if (!hasFile)
{
// we can reach this state if the user inserts a usb device during a game.
// skip over and don't write anything until we see the start of a new game
if (replaysLED)
flashLED();
memReadPos += reader.lastReadResult.bytesRead;
continue;
}

UINT wrote;
f_write(&currentFile, readBuf, reader.lastReadResult.bytesRead, &wrote);
FRESULT writeResult = f_write(&currentFile, readBuf, reader.lastReadResult.bytesRead, &wrote);
if (replaysLED && writeResult == FR_OK && wrote > 0)
flashLED();
f_sync(&currentFile);

if (wrote == 0)
Expand All @@ -244,6 +324,7 @@ static u32 SlippiHandlerThread(void *arg)
dbgprintf("Completing File...\r\n");
completeFile(&currentFile, &reader, writtenByteCount);
f_close(&currentFile);
hasFile = false;
}
}

Expand Down
1 change: 1 addition & 0 deletions kernel/SlippiFileWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include "global.h"

void SlippiFileWriterInit();
void SlippiFileWriterUpdateRegisters();
void SlippiFileWriterShutdown();

#endif
1 change: 0 additions & 1 deletion kernel/SlippiNetwork.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
#include "string.h"
#include "debug.h"
#include "net.h"
#include "ff_utf8.h"
#include "Config.h"


Expand Down
12 changes: 9 additions & 3 deletions kernel/diskio.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@
#include "common.h"

extern bool access_led;
u32 s_size; // Sector size.
u32 s_cnt; // Sector count.
u32 usb_s_cnt; // USB Sector count.
u32 usb_s_size; // USB Sector size.
u32 sd_s_cnt; // SD Sector count.


/*-----------------------------------------------------------------------*/
Expand Down Expand Up @@ -213,7 +214,12 @@ DRESULT disk_ioctl (
)
{
if(cmd == GET_SECTOR_SIZE)
*(WORD*)buff = s_size;
{
if (pdrv == DEV_SD)
*(WORD*)buff = PAGE_SIZE512;
else if (pdrv == DEV_USB)
*(WORD*)buff = usb_s_size;
}

return RES_OK;
}
Expand Down
3 changes: 3 additions & 0 deletions kernel/kernel.ld
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ __slippi_network_broadcast_stack_addr = __slippi_stack_addr + __slippi_network_b
__slippi_debug_stack_size = 0x400;
__slippi_debug_stack_addr = __slippi_network_broadcast_stack_addr + __slippi_debug_stack_size;

__ven_change_stack_size = 0x400;
__ven_change_stack_addr = __slippi_debug_stack_addr + __ven_change_stack_size;

MEMORY {

code : ORIGIN = 0x12F00000, LENGTH = 0x60000
Expand Down
Loading

0 comments on commit 6deec6c

Please sign in to comment.