Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: USB hotswapping for replay storage #39

Merged
merged 7 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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: Stealth, 2: None
} 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
128 changes: 107 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, 0: always on, 1: flash on insert and file end, 2: do not use
u32 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();
if (replaysLED < 2)
{
// 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,14 @@ 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);
if (replaysLED == 1 && fileWriteResult == FR_OK)
flashLED();
f_sync(file);
}

static u32 SlippiHandlerThread(void *arg)
Expand All @@ -177,16 +207,53 @@ 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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

u32 UseUSB = ConfigGetUseUSB(); // Returns 0 for SD, 1 for USB

based on the line above, shouldn't this be const bool use_usb = ConfigGetUseUSB() (== 1);?

Copy link
Author

@jmlee337 jmlee337 Sep 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ConfigGetUseUSB refers to ISO device, so file write device is 'opposite'


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;

if (replaysLED == 1)
flashLED();
}
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 +262,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 +276,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;
}
else if (replaysLED == 0)
flashLED();

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

if (reader.lastReadResult.bytesRead == 0)
{
if (replaysLED == 0)
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 == 0)
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 == 0 && writeResult == FR_OK && wrote > 0)
flashLED();
f_sync(&currentFile);

if (wrote == 0)
Expand All @@ -244,6 +329,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
Loading