Initial commit

This commit is contained in:
Gericom
2025-11-22 11:08:28 +01:00
commit 9cf3ffbfcf
358 changed files with 58350 additions and 0 deletions

3
arm7/source/clearFast.h Normal file
View File

@@ -0,0 +1,3 @@
#pragma once
extern "C" void clearFast(void* dst, u32 length);

29
arm7/source/clearFast.s Normal file
View File

@@ -0,0 +1,29 @@
.text
.thumb
.global clearFast
.type clearFast, %function
clearFast:
push {r4-r7,lr}
mov r12, sp
mov sp, r0
add sp, r1
mov r1, #0
mov r2, #0
mov r3, #0
mov r4, #0
mov r5, #0
mov r6, #0
mov r7, #0
mov lr, r7
1:
push {r1-r7,lr}
push {r1-r7,lr}
cmp sp, r0
bne 1b
mov sp, r12
pop {r4-r7}
pop {r3}
bx r3
.end

23
arm7/source/common.h Normal file
View File

@@ -0,0 +1,23 @@
#pragma once
#include <nds/ndstypes.h>
#include "fat/ff.h"
#ifdef __cplusplus
#include "header.h"
#include "logger/ILogger.h"
extern ILogger* gLogger;
#define MAX_COMPILED_LOG_LEVEL LogLevel::All
#define LOG_FATAL(...) if (LogLevel::Fatal < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Fatal, __VA_ARGS__)
#define LOG_ERROR(...) if (LogLevel::Error < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Error, __VA_ARGS__)
#define LOG_WARNING(...) if (LogLevel::Warning < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Warning, __VA_ARGS__)
#define LOG_INFO(...) if (LogLevel::Info < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Info, __VA_ARGS__)
#define LOG_DEBUG(...) if (LogLevel::Debug < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Debug, __VA_ARGS__)
#define LOG_TRACE(...) if (LogLevel::Trace < MAX_COMPILED_LOG_LEVEL) gLogger->Log(LogLevel::Trace, __VA_ARGS__)
#endif
extern FATFS gFatFs;

View File

@@ -0,0 +1,61 @@
#include <nds.h>
#include "picoAgbAdapter.h"
#include "Environment.h"
u32 Environment::_flags;
static bool detectIsNitroEmulator()
{
u32 agbMemoryAddress = *(vu32*)0x027FFF7C;
if (agbMemoryAddress < 0x08000000 || agbMemoryAddress >= 0x0A000000)
return false;
// u32 monitorRomLoadAddress = *(vu32*)0x027FFF68;
// if (monitorRomLoadAddress < 0x02000000 || monitorRomLoadAddress >= 0x02800000)
// return false;
return true;
}
static bool detectNocashPrintSuppport()
{
u32 nocashIdentifier = *(vu32*)0x04FFFA00;
return nocashIdentifier == 0x67246F6E; //no$g
}
static bool detectPicoAgbAdapter()
{
REG_EXMEMSTAT &= ~0xFF;
return PICO_AGB_IDENTIFIER == PICO_AGB_IDENTIFIER_VALUE;
}
void Environment::Initialize(bool dsiMode)
{
_flags = ENVIRONMENT_FLAGS_NONE;
if (dsiMode)
{
_flags |= ENVIRONMENT_FLAGS_DSI_MODE;
}
else
{
if (detectIsNitroEmulator())
{
_flags |= ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR;
_flags |= ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING;
REG_EXMEMSTAT &= ~0xFF;
u32 agbMemoryAddress = *(vu32*)0x027FFF7C;
if (*(vu32*)(agbMemoryAddress + 0x100) == 0x44495349) //ISID
_flags |= ENVIRONMENT_FLAGS_AGB_SEMIHOSTING;
}
else
{
if (detectPicoAgbAdapter())
_flags |= ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER;
}
}
if (!(_flags & ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR))
{
if (detectNocashPrintSuppport())
_flags |= ENVIRONMENT_FLAGS_NOCASH_PRINT;
}
}

View File

@@ -0,0 +1,32 @@
#pragma once
class Environment
{
enum EnvironmentFlags : u32
{
ENVIRONMENT_FLAGS_NONE = 0,
ENVIRONMENT_FLAGS_DSI_MODE = (1 << 0),
ENVIRONMENT_FLAGS_NOCASH_PRINT = (1 << 1),
ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR = (1 << 2),
ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING = (1 << 3),
ENVIRONMENT_FLAGS_AGB_SEMIHOSTING = (1 << 4),
ENVIRONMENT_FLAGS_DLDI = (1 << 5),
ENVIRONMENT_FLAGS_ARGV = (1 << 6),
ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER = (1 << 7)
};
static u32 _flags;
public:
static void Initialize(bool dsiMode);
static inline bool IsDsiMode() { return _flags & ENVIRONMENT_FLAGS_DSI_MODE; }
static inline bool SupportsNocashPrint() { return _flags & ENVIRONMENT_FLAGS_NOCASH_PRINT; }
static inline bool IsIsNitroEmulator() { return _flags & ENVIRONMENT_FLAGS_IS_NITRO_EMULATOR; }
static inline bool SupportsJtagSemihosting() { return _flags & ENVIRONMENT_FLAGS_JTAG_SEMIHOSTING; }
static inline bool SupportsAgbSemihosting() { return _flags & ENVIRONMENT_FLAGS_AGB_SEMIHOSTING; }
static inline bool SupportsDldi() { return _flags & ENVIRONMENT_FLAGS_DLDI; }
static inline bool SupportsArgv() { return _flags & ENVIRONMENT_FLAGS_ARGV; }
static inline bool HasPicoAgbAdapter() { return _flags & ENVIRONMENT_FLAGS_PICO_AGB_ADAPTER; }
};

26
arm7/source/crt0.s Normal file
View File

@@ -0,0 +1,26 @@
.section ".crt0", "ax"
.arm
.global _start
.type _start, %function
_start:
// disable irqs
ldr r0,= 0x04000208
strb r0, [r0]
// clear bss
ldr r0,= __bss_start
ldr r1,= __bss_end
cmp r0, r1
beq bss_done
mov r2, #0
1:
str r2, [r0], #4
cmp r0, r1
bne 1b
bss_done:
ldr sp,= 0x0380FD80
b loaderMain
.pool
.end

View File

@@ -0,0 +1,17 @@
#include "common.h"
#include <stdarg.h>
#include <libtwl/i2c/i2cMcu.h>
#include "ipc.h"
#include "ipcCommands.h"
#include "core/mini-printf.h"
#include "ErrorDisplay.h"
void ErrorDisplay::PrintError(const char* errorFormat, ...)
{
va_list va;
va_start(va, errorFormat);
mini_vsnprintf((char*)0x02000000, 1024, errorFormat, va);
sendToArm9(IPC_COMMAND_ARM9_DISPLAY_ERROR);
va_end(va);
while (true);
}

View File

@@ -0,0 +1,11 @@
#pragma once
/// @brief Class for displaying critical errors on screen.
class ErrorDisplay
{
public:
/// @brief Formats and sends a critical error message to the arm9 to display it on screen.
/// @note This function does not return.
/// @param errorFormat The error message to format.
void PrintError(const char* errorFormat, ...);
};

205
arm7/source/fat/diskio.cpp Normal file
View File

@@ -0,0 +1,205 @@
/*-----------------------------------------------------------------------*/
/* Low level disk I/O module skeleton for FatFs (C)ChaN, 2016 */
/*-----------------------------------------------------------------------*/
/* If a working storage control module is available, it should be */
/* attached to the FatFs via a glue function rather than modifying it. */
/* This is an example of glue functions to attach various exsisting */
/* storage control modules to the FatFs module with a defined API. */
/*-----------------------------------------------------------------------*/
#include <nds/ndstypes.h>
#include <nds/disc_io.h>
#include <string.h>
#include <libtwl/rtos/rtosIrq.h>
#include "core/Environment.h"
// #include <libtwl/rtos/rtosIrq.h>
// #include <libtwl/rtos/rtosEvent.h>
#include "ff.h" /* Obtains integer types */
#include "diskio.h" /* Declarations of disk functions */
#include "dldi.h"
#include "../mmc/sdmmc.h"
// #include "core/semihosting.h"
/* Definitions of physical drive number for each drive */
#define DEV_FAT 0 //dldi
#define DEV_SD 1 //dsi sd
#define DEV_PC 2 //image on pc via semihosting
#define DEV_PC2 3 //image on pc via agb semihosting
// static int sPcFileHandle;
// static rtos_event_t sSemihostingCommandDoneEvent;
//extern "C" int sdmmc_sd_startup();
// extern FN_MEDIUM_STARTUP _DLDI_startup_ptr;
// extern FN_MEDIUM_READSECTORS _DLDI_readSectors_ptr;
// extern FN_MEDIUM_WRITESECTORS _DLDI_writeSectors_ptr;
/*-----------------------------------------------------------------------*/
/* Get Drive Status */
/*-----------------------------------------------------------------------*/
extern "C" DSTATUS disk_status (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
return 0;
}
// static void gbaIrq(u32 mask)
// {
// rtos_signalEvent(&sSemihostingCommandDoneEvent);
// }
/*-----------------------------------------------------------------------*/
/* Inidialize a Drive */
/*-----------------------------------------------------------------------*/
extern "C" DSTATUS disk_initialize (
BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{
if (pdrv == DEV_FAT)
{
return 0;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_init(SDMMC_DEV_CARD);
return 0;
}
else if (pdrv == DEV_PC)
{
// sPcFileHandle = sh_openFile("d:\\Emulators\\No$Debugger 3.0\\DSI-1.SD", SH_OPEN_MODE_R_PLUS_B);
return 0;
}
else if (pdrv == DEV_PC2)
{
// rtos_createEvent(&sSemihostingCommandDoneEvent);
// rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
// rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
// rtos_setIrqFunc(RTOS_IRQ_GBA_IREQ, gbaIrq);
// rtos_enableIrqMask(RTOS_IRQ_GBA_IREQ);
return 0;
}
return STA_NOINIT;
}
/*-----------------------------------------------------------------------*/
/* Read Sector(s) */
/*-----------------------------------------------------------------------*/
extern "C" DRESULT disk_read (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
BYTE *buff, /* Data buffer to store read data */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to read */
)
{
if (pdrv == DEV_FAT)
{
dldi_readSectors(buff, sector, count);
return RES_OK;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_readSectors(SDMMC_DEV_CARD, sector, buff, count);
return RES_OK;
}
else if (pdrv == DEV_PC)
{
// sh_seekFile(sPcFileHandle, sector * 512);
// sh_readFile(sPcFileHandle, buff, count * 512);
// return RES_OK;
}
else if (pdrv == DEV_PC2)
{
// rtos_clearEvent(&sSemihostingCommandDoneEvent);
u32 agbMem = *(u32*)0x027FFF7C;
*(vu16*)(agbMem + 0x10002) = 1;
*(vu32*)(agbMem + 0x10004) = sector;
*(vu32*)(agbMem + 0x10008) = count;
rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
*(vu16*)(agbMem + 0x10000) = 0x55;
while (!(rtos_getIrqFlags() & RTOS_IRQ_GBA_IREQ));
// rtos_waitEvent(&sSemihostingCommandDoneEvent, false, true);
if (*(vu16*)(agbMem + 0x10000) != 0xAA)
return RES_ERROR;
memcpy(buff, (const void*)(agbMem + 0x10020), count * 512);
return RES_OK;
}
return RES_PARERR;
}
/*-----------------------------------------------------------------------*/
/* Write Sector(s) */
/*-----------------------------------------------------------------------*/
#if FF_FS_READONLY == 0
extern "C" DRESULT disk_write (
BYTE pdrv, /* Physical drive nmuber to identify the drive */
const BYTE *buff, /* Data to be written */
DWORD sector, /* Start sector in LBA */
UINT count /* Number of sectors to write */
)
{
if (pdrv == DEV_FAT)
{
dldi_writeSectors(buff, sector, count);
return RES_OK;
}
else if (Environment::IsDsiMode() && pdrv == DEV_SD)
{
pico_SDMMC_writeSectors(SDMMC_DEV_CARD, sector, buff, count);
return RES_OK;
}
else if (pdrv == DEV_PC)
{
// sh_seekFile(sPcFileHandle, sector * 512);
// sh_writeFile(sPcFileHandle, buff, count * 512);
// return RES_OK;
}
else if (pdrv == DEV_PC2)
{
// rtos_clearEvent(&sSemihostingCommandDoneEvent);
u32 agbMem = *(u32*)0x027FFF7C;
memcpy((void*)(agbMem + 0x10020), buff, count * 512);
*(vu16*)(agbMem + 0x10002) = 2;
*(vu32*)(agbMem + 0x10004) = sector;
*(vu32*)(agbMem + 0x10008) = count;
rtos_disableIrqMask(RTOS_IRQ_GBA_IREQ);
rtos_ackIrqMask(RTOS_IRQ_GBA_IREQ);
*(vu16*)(agbMem + 0x10000) = 0x55;
while (!(rtos_getIrqFlags() & RTOS_IRQ_GBA_IREQ));
// rtos_waitEvent(&sSemihostingCommandDoneEvent, false, true);
if (*(vu16*)(agbMem + 0x10000) != 0xAA)
return RES_ERROR;
return RES_OK;
}
return RES_PARERR;
}
#endif
/*-----------------------------------------------------------------------*/
/* Miscellaneous Functions */
/*-----------------------------------------------------------------------*/
extern "C" DRESULT disk_ioctl (
BYTE pdrv, /* Physical drive nmuber (0..) */
BYTE cmd, /* Control code */
void *buff /* Buffer to send/receive control data */
)
{
return RES_OK;
}

77
arm7/source/fat/diskio.h Normal file
View File

@@ -0,0 +1,77 @@
/*-----------------------------------------------------------------------/
/ Low level disk interface modlue include file (C)ChaN, 2014 /
/-----------------------------------------------------------------------*/
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
/* Status of Disk Functions */
typedef BYTE DSTATUS;
/* Results of Disk Functions */
typedef enum {
RES_OK = 0, /* 0: Successful */
RES_ERROR, /* 1: R/W Error */
RES_WRPRT, /* 2: Write Protected */
RES_NOTRDY, /* 3: Not Ready */
RES_PARERR /* 4: Invalid Parameter */
} DRESULT;
/*---------------------------------------*/
/* Prototypes for disk control functions */
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
/* Disk Status Bits (DSTATUS) */
#define STA_NOINIT 0x01 /* Drive not initialized */
#define STA_NODISK 0x02 /* No medium in the drive */
#define STA_PROTECT 0x04 /* Write protected */
/* Command code for disk_ioctrl fucntion */
/* Generic command (Used by FatFs) */
#define CTRL_SYNC 0 /* Complete pending write process (needed at FF_FS_READONLY == 0) */
#define GET_SECTOR_COUNT 1 /* Get media size (needed at FF_USE_MKFS == 1) */
#define GET_SECTOR_SIZE 2 /* Get sector size (needed at FF_MAX_SS != FF_MIN_SS) */
#define GET_BLOCK_SIZE 3 /* Get erase block size (needed at FF_USE_MKFS == 1) */
#define CTRL_TRIM 4 /* Inform device that the data on the block of sectors is no longer used (needed at FF_USE_TRIM == 1) */
/* Generic command (Not used by FatFs) */
#define CTRL_POWER 5 /* Get/Set power status */
#define CTRL_LOCK 6 /* Lock/Unlock media removal */
#define CTRL_EJECT 7 /* Eject media */
#define CTRL_FORMAT 8 /* Create physical format on the media */
/* MMC/SDC specific ioctl command */
#define MMC_GET_TYPE 10 /* Get card type */
#define MMC_GET_CSD 11 /* Get CSD */
#define MMC_GET_CID 12 /* Get CID */
#define MMC_GET_OCR 13 /* Get OCR */
#define MMC_GET_SDSTAT 14 /* Get SD status */
#define ISDIO_READ 55 /* Read data form SD iSDIO register */
#define ISDIO_WRITE 56 /* Write data to SD iSDIO register */
#define ISDIO_MRITE 57 /* Masked write data to SD iSDIO register */
/* ATA/CF specific ioctl command */
#define ATA_GET_REV 20 /* Get F/W revision */
#define ATA_GET_MODEL 21 /* Get model name */
#define ATA_GET_SN 22 /* Get serial number */
#ifdef __cplusplus
}
#endif
#endif

97
arm7/source/fat/dldi.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include "common.h"
#include <string.h>
#include "ipc.h"
#include "ipcCommands.h"
#include "loader/DldiDriver.h"
static u8 sDldiBuffer[16 * 1024] alignas(32);
static DldiDriver sDldiDriver = DldiDriver((dldi_header_t*)sDldiBuffer);
[[gnu::target("thumb")]]
static bool readSectorsWithPatchCode(u32 sector, u32 count, void* buffer)
{
typedef void (*patch_code_read_sd_sectors_t)(u32 srcSector, void* dst, u32 sectorCount);
(*(patch_code_read_sd_sectors_t*)0x037F8000)(sector, buffer, count);
return true;
}
[[gnu::target("thumb")]]
static bool writeSectorsWithPatchCode(u32 sector, u32 count, const void* buffer)
{
typedef void (*patch_code_write_sd_sectors_t)(u32 dstSector, const void* src, u32 sectorCount);
(*(patch_code_write_sd_sectors_t*)0x037F8004)(sector, buffer, count);
return true;
}
bool dldi_init()
{
auto driver = (const dldi_header_t*)gLoaderHeader.dldiDriver;
if (!driver || driver->dldiMagic != DLDI_MAGIC || driver->driverMagic == DLDI_DRIVER_MAGIC_NONE)
{
LOG_DEBUG("No dldi driver found\n");
// Need to initialize before getting the patch code
sendToArm9(IPC_COMMAND_ARM9_INITIALIZE_SD_CARD);
if (!receiveFromArm9())
{
LOG_ERROR("Sd card initialization failed\n");
return false;
}
// Try to get the patch code
sendToArm9(IPC_COMMAND_ARM9_GET_SD_FUNCTIONS);
if (!receiveFromArm9())
{
LOG_ERROR("Getting patch code failed\n");
return false;
}
LOG_DEBUG("Using patch code sd read/write\n");
((dldi_header_t*)sDldiBuffer)->readSectorsFuncAddress = (u32)readSectorsWithPatchCode;
((dldi_header_t*)sDldiBuffer)->writeSectorsFuncAddress = (u32)writeSectorsWithPatchCode;
}
else
{
u32 driverSize = 1 << driver->driverSize;
if (driverSize > sizeof(sDldiBuffer))
{
LOG_ERROR("Not enough space for dldi driver of size %d\n", driverSize);
return false;
}
memcpy(sDldiBuffer, driver, driverSize);
sDldiDriver.Relocate();
sDldiDriver.PrepareForUse();
if (!sDldiDriver.Startup())
{
LOG_ERROR("DLDI startup failed\n");
return false;
}
sendToArm9(IPC_COMMAND_ARM9_INITIALIZE_SD_CARD);
if (!receiveFromArm9())
{
LOG_ERROR("Sd card initialization failed\n");
return false;
}
}
return true;
}
extern "C" bool dldi_readSectors(void* buffer, u32 sector, u32 count)
{
return sDldiDriver.ReadSectors(sector, count, buffer);
}
extern "C" bool dldi_writeSectors(const void* buffer, u32 sector, u32 count)
{
return sDldiDriver.WriteSectors(sector, count, buffer);
}
bool dldi_patchTo(dldi_header_t* stub)
{
return sDldiDriver.PatchTo(stub);
}

16
arm7/source/fat/dldi.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
#include "loader/dldiHeader.h"
bool dldi_init();
bool dldi_patchTo(dldi_header_t* stub);
#ifdef __cplusplus
extern "C" {
#endif
bool dldi_readSectors(void* buffer, u32 sector, u32 count);
bool dldi_writeSectors(const void* buffer, u32 sector, u32 count);
#ifdef __cplusplus
}
#endif

6593
arm7/source/fat/ff.c Normal file

File diff suppressed because it is too large Load Diff

412
arm7/source/fat/ff.h Normal file
View File

@@ -0,0 +1,412 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.13c /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2018, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 86604 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Main development platform */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint16_t WCHAR; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned short WCHAR; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
#endif
#include "math.h"
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition resolution table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Type of path name strings on FatFs API */
#ifndef _INC_TCHAR
#define _INC_TCHAR
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
#endif
/* Type of file size variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#else
typedef DWORD FSIZE_t;
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Associated physical drive */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] flag (b0:dirty) */
BYTE fsi_flag; /* FSINFO flags (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if FF_FS_REENTRANT
FF_SYNC_t sobj; /* Identifier of sync object */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Size of an FAT [sectors] */
DWORD volbase; /* Volume base sector */
DWORD fatbase; /* FAT base sector */
DWORD dirbase; /* Root directory base sector/cluster */
DWORD database; /* Data base sector */
#if FF_FS_EXFAT
DWORD bitbase; /* Allocation bitmap base sector */
#endif
DWORD winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS] __attribute__((aligned(32))); /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS __attribute__((aligned(32)));
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
DWORD sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
DWORD dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS] __attribute__((aligned(32))); /* File private data read/write window */
#endif
} FIL __attribute__((aligned(32)));
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
DWORD sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
DWORD fdirsect;
DWORD fdiroffs;
DWORD fclust;
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Altenative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs module application interface */
DWORD f_clst2sect(FATFS* fs, DWORD clst);
DWORD f_getFat(FIL* fp, DWORD clst);
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE opt, DWORD au, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD* szt, void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
#ifndef EOF
#define EOF (-1)
#endif
/*--------------------------------------------------------------*/
/* Additional user defined functions */
/* RTC function */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void);
#endif
/* LFN support functions */
#if FF_USE_LFN >= 1 /* Code conversion (defined in unicode.c) */
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
/* Sync functions */
#if FF_FS_REENTRANT
int ff_cre_syncobj (BYTE vol, FF_SYNC_t* sobj); /* Create a sync object */
int ff_req_grant (FF_SYNC_t sobj); /* Lock sync object */
void ff_rel_grant (FF_SYNC_t sobj); /* Unlock sync object */
int ff_del_syncobj (FF_SYNC_t sobj); /* Delete a sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and offset address */
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */

288
arm7/source/fat/ffconf.h Normal file
View File

@@ -0,0 +1,288 @@
/*---------------------------------------------------------------------------/
/ FatFs Functional Configurations
/---------------------------------------------------------------------------*/
#define FFCONF_DEF 86604 /* Revision ID */
/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_READONLY 0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/ Read-only configuration removes writing API functions, f_write(), f_sync(),
/ f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/ and optional writing functions as well. */
#define FF_FS_MINIMIZE 0
/* This option defines minimization level to remove some basic API functions.
/
/ 0: Basic functions are fully enabled.
/ 1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/ are removed.
/ 2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/ 3: f_lseek() function is removed in addition to 2. */
#define FF_USE_STRFUNC 0
/* This option switches string functions, f_gets(), f_putc(), f_puts() and f_printf().
/
/ 0: Disable string functions.
/ 1: Enable without LF-CRLF conversion.
/ 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
#define FF_USE_MKFS 0
/* This option switches f_mkfs() function. (0:Disable or 1:Enable) */
#define FF_USE_FASTSEEK 1
/* This option switches fast seek function. (0:Disable or 1:Enable) */
#define FF_USE_EXPAND 0
/* This option switches f_expand function. (0:Disable or 1:Enable) */
#define FF_USE_CHMOD 0
/* This option switches attribute manipulation functions, f_chmod() and f_utime().
/ (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */
#define FF_USE_LABEL 0
/* This option switches volume label functions, f_getlabel() and f_setlabel().
/ (0:Disable or 1:Enable) */
#define FF_USE_FORWARD 0
/* This option switches f_forward() function. (0:Disable or 1:Enable) */
/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 437 //932
/* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure.
/
/ 437 - U.S.
/ 720 - Arabic
/ 737 - Greek
/ 771 - KBL
/ 775 - Baltic
/ 850 - Latin 1
/ 852 - Latin 2
/ 855 - Cyrillic
/ 857 - Turkish
/ 860 - Portuguese
/ 861 - Icelandic
/ 862 - Hebrew
/ 863 - Canadian French
/ 864 - Arabic
/ 865 - Nordic
/ 866 - Russian
/ 869 - Greek 2
/ 932 - Japanese (DBCS)
/ 936 - Simplified Chinese (DBCS)
/ 949 - Korean (DBCS)
/ 950 - Traditional Chinese (DBCS)
/ 0 - Include all code pages above and configured by f_setcp()
*/
#define FF_USE_LFN 1
#define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/ 0: Disable LFN. FF_MAX_LFN has no effect.
/ 1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/ 2: Enable LFN with dynamic working buffer on the STACK.
/ 3: Enable LFN with dynamic working buffer on the HEAP.
/
/ To enable the LFN, ffunicode.c needs to be added to the project. The LFN function
/ requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/ additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/ The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/ be in range of 12 to 255. It is recommended to be set 255 to fully support LFN
/ specification.
/ When use stack for the working buffer, take care on stack overflow. When use heap
/ memory for the working buffer, memory management functions, ff_memalloc() and
/ ff_memfree() in ffsystem.c, need to be added to the project. */
#define FF_LFN_UNICODE 2
/* This option switches the character encoding on the API when LFN is enabled.
/
/ 0: ANSI/OEM in current CP (TCHAR = char)
/ 1: Unicode in UTF-16 (TCHAR = WCHAR)
/ 2: Unicode in UTF-8 (TCHAR = char)
/ 3: Unicode in UTF-32 (TCHAR = DWORD)
/
/ Also behavior of string I/O functions will be affected by this option.
/ When LFN is not enabled, this option has no effect. */
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
/* This set of options defines size of file name members in the FILINFO structure
/ which is used to read out directory items. These values should be suffcient for
/ the file names to read. The maximum possible length of the read file name depends
/ on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 3
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be
/ read/written via those functions.
/
/ 0: ANSI/OEM in current CP
/ 1: Unicode in UTF-16LE
/ 2: Unicode in UTF-16BE
/ 3: Unicode in UTF-8
*/
#define FF_FS_RPATH 2
/* This option configures support for relative path.
/
/ 0: Disable relative path and remove related functions.
/ 1: Enable relative path. f_chdir() and f_chdrive() are available.
/ 2: f_getcwd() function is available in addition to 1.
*/
/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/
#define FF_VOLUMES 4
/* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 1
#define FF_VOLUME_STRS "fat","sd","pc","pc2"//"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/ logical drives. Number of items must not be less than FF_VOLUMES. Valid
/ characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/ compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/ not defined, a user defined volume string table needs to be defined as:
/
/ const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/
#define FF_MULTI_PARTITION 0
/* This option switches support for multiple volumes on the physical drive.
/ By default (0), each logical drive number is bound to the same physical drive
/ number and only an FAT volume found on the physical drive will be mounted.
/ When this function is enabled (1), each logical drive number can be bound to
/ arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/ funciton will be available. */
#define FF_MIN_SS 512
#define FF_MAX_SS 512
/* This set of options configures the range of sector size to be supported. (512,
/ 1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/ harddisk. But a larger value may be required for on-board flash memory and some
/ type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is configured
/ for variable sector size mode and disk_ioctl() function needs to implement
/ GET_SECTOR_SIZE command. */
#define FF_USE_TRIM 0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/ To enable Trim function, also CTRL_TRIM command should be implemented to the
/ disk_ioctl() function. */
#define FF_FS_NOFSINFO 0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/ option, and f_getfree() function at first time after volume mount will force
/ a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/ bit0=0: Use free cluster count in the FSINFO if available.
/ bit0=1: Do not trust free cluster count in the FSINFO.
/ bit1=0: Use last allocated cluster number in the FSINFO if available.
/ bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/
/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/
#define FF_FS_TINY 0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/ At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/ Instead of private sector buffer eliminated from the file object, common sector
/ buffer in the filesystem object (FATFS) is used for the file data transfer. */
#define FF_FS_EXFAT 0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/ To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/ Note that enabling exFAT discards ANSI C (C89) compatibility. */
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2018
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp
/ defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/ To enable timestamp function (FF_FS_NORTC = 0), get_fattime() function need to be
/ added to the project to read current time form real-time clock. FF_NORTC_MON,
/ FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/ These options have no effect at read-only configuration (FF_FS_READONLY = 1). */
#define FF_FS_LOCK 0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/ and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/ is 1.
/
/ 0: Disable file lock function. To avoid volume corruption, application program
/ should avoid illegal open, remove and rename to the open objects.
/ >0: Enable file lock function. The value defines how many files/sub-directories
/ can be opened simultaneously under file lock control. Note that the file
/ lock control is independent of re-entrancy. */
/* #include <somertos.h> // O/S definitions */
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000
#define FF_SYNC_t HANDLE
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/ module itself. Note that regardless of this option, file access to different
/ volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/ and f_fdisk() function, are always not re-entrant. Only file/directory access
/ to the same volume is under control of this function.
/
/ 0: Disable re-entrancy. FF_FS_TIMEOUT and FF_SYNC_t have no effect.
/ 1: Enable re-entrancy. Also user provided synchronization handlers,
/ ff_req_grant(), ff_rel_grant(), ff_del_syncobj() and ff_cre_syncobj()
/ function, must be added to the project. Samples are available in
/ option/syscall.c.
/
/ The FF_FS_TIMEOUT defines timeout period in unit of time tick.
/ The FF_SYNC_t defines O/S dependent sync object type. e.g. HANDLE, ID, OS_EVENT*,
/ SemaphoreHandle_t and etc. A header file for O/S definitions needs to be
/ included somewhere in the scope of ff.h. */
/*--- End of configuration options ---*/

170
arm7/source/fat/ffsystem.c Normal file
View File

@@ -0,0 +1,170 @@
/*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */
/* (C)ChaN, 2018 */
/*------------------------------------------------------------------------*/
#include "ff.h"
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif
#if FF_FS_REENTRANT /* Mutal exclusion */
/*------------------------------------------------------------------------*/
/* Create a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to create a new
/ synchronization object for the volume, such as semaphore and mutex.
/ When a 0 is returned, the f_mount() function fails with FR_INT_ERR.
*/
//const osMutexDef_t Mutex[FF_VOLUMES]; /* Table of CMSIS-RTOS mutex */
int ff_cre_syncobj ( /* 1:Function succeeded, 0:Could not create the sync object */
BYTE vol, /* Corresponding volume (logical drive number) */
FF_SYNC_t* sobj /* Pointer to return the created sync object */
)
{
/* Win32 */
*sobj = CreateMutex(NULL, FALSE, NULL);
return (int)(*sobj != INVALID_HANDLE_VALUE);
/* uITRON */
// T_CSEM csem = {TA_TPRI,1,1};
// *sobj = acre_sem(&csem);
// return (int)(*sobj > 0);
/* uC/OS-II */
// OS_ERR err;
// *sobj = OSMutexCreate(0, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// *sobj = xSemaphoreCreateMutex();
// return (int)(*sobj != NULL);
/* CMSIS-RTOS */
// *sobj = osMutexCreate(&Mutex[vol]);
// return (int)(*sobj != NULL);
}
/*------------------------------------------------------------------------*/
/* Delete a Synchronization Object */
/*------------------------------------------------------------------------*/
/* This function is called in f_mount() function to delete a synchronization
/ object that created with ff_cre_syncobj() function. When a 0 is returned,
/ the f_mount() function fails with FR_INT_ERR.
*/
int ff_del_syncobj ( /* 1:Function succeeded, 0:Could not delete due to an error */
FF_SYNC_t sobj /* Sync object tied to the logical drive to be deleted */
)
{
/* Win32 */
return (int)CloseHandle(sobj);
/* uITRON */
// return (int)(del_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexDel(sobj, OS_DEL_ALWAYS, &err);
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// vSemaphoreDelete(sobj);
// return 1;
/* CMSIS-RTOS */
// return (int)(osMutexDelete(sobj) == osOK);
}
/*------------------------------------------------------------------------*/
/* Request Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on entering file functions to lock the volume.
/ When a 0 is returned, the file function fails with FR_TIMEOUT.
*/
int ff_req_grant ( /* 1:Got a grant to access the volume, 0:Could not get a grant */
FF_SYNC_t sobj /* Sync object to wait */
)
{
/* Win32 */
return (int)(WaitForSingleObject(sobj, FF_FS_TIMEOUT) == WAIT_OBJECT_0);
/* uITRON */
// return (int)(wai_sem(sobj) == E_OK);
/* uC/OS-II */
// OS_ERR err;
// OSMutexPend(sobj, FF_FS_TIMEOUT, &err));
// return (int)(err == OS_NO_ERR);
/* FreeRTOS */
// return (int)(xSemaphoreTake(sobj, FF_FS_TIMEOUT) == pdTRUE);
/* CMSIS-RTOS */
// return (int)(osMutexWait(sobj, FF_FS_TIMEOUT) == osOK);
}
/*------------------------------------------------------------------------*/
/* Release Grant to Access the Volume */
/*------------------------------------------------------------------------*/
/* This function is called on leaving file functions to unlock the volume.
*/
void ff_rel_grant (
FF_SYNC_t sobj /* Sync object to be signaled */
)
{
/* Win32 */
ReleaseMutex(sobj);
/* uITRON */
// sig_sem(sobj);
/* uC/OS-II */
// OSMutexPost(sobj);
/* FreeRTOS */
// xSemaphoreGive(sobj);
/* CMSIS-RTOS */
// osMutexRelease(sobj);
}
#endif

15597
arm7/source/fat/ffunicode.c Normal file

File diff suppressed because it is too large Load Diff

102
arm7/source/globalHeap.cpp Normal file
View File

@@ -0,0 +1,102 @@
#include "common.h"
#include <string.h>
#include "core/heap/tlsf.h"
#include "globalHeap.h"
static tlsf_t sHeap;
extern "C" void* malloc(size_t size)
{
return tlsf_malloc(sHeap, size);
}
extern "C" void* _malloc_r(struct _reent *, size_t size)
{
return malloc(size);
}
extern "C" void free(void* ptr)
{
tlsf_free(sHeap, ptr);
}
extern "C" void _free_r(struct _reent *, void* ptr)
{
free(ptr);
}
extern "C" void* realloc(void* ptr, size_t size)
{
return tlsf_realloc(sHeap, ptr, size);
}
extern "C" void* memalign(size_t alignment, size_t size)
{
return tlsf_memalign(sHeap, alignment, size);
}
void* operator new(std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new(std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void* operator new[](std::size_t blocksize)
{
return malloc(blocksize);
}
void* operator new[](std::size_t size, std::align_val_t al)
{
return memalign(static_cast<std::size_t>(al), size);
}
void operator delete(void* ptr)
{
return free(ptr);
}
void operator delete(void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete(void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr)
{
return free(ptr);
}
void operator delete[](void* ptr, std::align_val_t align)
{
return free(ptr);
}
void operator delete[](void* ptr, std::size_t size, std::align_val_t align)
{
return free(ptr);
}
extern u8 __heap_start;
extern u8 __heap_end;
[[gnu::target("thumb"), gnu::optimize("Os")]]
void heap_init()
{
u32 heapStart = (u32)&__heap_start;
heapStart = (heapStart + 31) & ~31;
u32 heapEnd = (u32)&__heap_end;
heapEnd = heapEnd & ~31;
u32 tlsfSize = tlsf_size();
memset((void*)heapStart, 0, tlsfSize);
memset((u8*)heapStart + tlsfSize, 0xA5, heapEnd - heapStart - tlsfSize);
sHeap = tlsf_create_with_pool((void*)heapStart, heapEnd - heapStart);
}

16
arm7/source/globalHeap.h Normal file
View File

@@ -0,0 +1,16 @@
#pragma once
void* operator new(std::size_t blocksize) noexcept;
void* operator new(std::size_t size, std::align_val_t al) noexcept;
void* operator new[](std::size_t blocksize) noexcept;
void* operator new[](std::size_t size, std::align_val_t al) noexcept;
void operator delete(void* ptr) noexcept;
void operator delete(void* ptr, std::align_val_t align) noexcept;
void operator delete(void* ptr, std::size_t size, std::align_val_t align) noexcept;
void operator delete[](void* ptr) noexcept;
void operator delete[](void* ptr, std::align_val_t align) noexcept;
void operator delete[](void* ptr, std::size_t size, std::align_val_t align) noexcept;
constexpr std::align_val_t cache_align { 32 };
void heap_init();

14
arm7/source/header.cpp Normal file
View File

@@ -0,0 +1,14 @@
#include "common.h"
#include "header.h"
extern "C" void _start();
extern u8 __bss_start[];
extern u8 __bss_size[];
[[gnu::section(".crt0")]]
[[gnu::used]]
pload_header7_t gLoaderHeader
{
.entryPoint = (void*)&_start,
.apiVersion = PICO_LOADER_API_VERSION
};

4
arm7/source/header.h Normal file
View File

@@ -0,0 +1,4 @@
#pragma once
#include "../../include/picoLoader7.h"
extern pload_header7_t gLoaderHeader;

15
arm7/source/ipc.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include <libtwl/ipc/ipcFifo.h>
#include "ipcCommands.h"
static inline void sendToArm9(u32 value)
{
while (ipc_isSendFifoFull());
ipc_sendWordDirect(value);
}
static inline u32 receiveFromArm9()
{
while (ipc_isRecvFifoEmpty());
return ipc_recvWordDirect();
}

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "ApListFactory.h"
ApList* ApListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open ap list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(ApListEntry);
auto entries = std::make_unique_for_overwrite<ApListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(ApListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(ApListEntry))
{
LOG_FATAL("Failed to read ap list file\n");
return nullptr;
}
f_close(&file);
return new ApList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "ApList.h"
/// @brief Factory for creating \see ApList instances.
class ApListFactory
{
public:
/// @brief Creates an \see ApList instance from the file at the given \p path.
/// @param path The ap list file path.
/// @return A pointer to the constructed \see ApList instance, or \c nullptr if construction failed.
ApList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,151 @@
#include "common.h"
#include <libtwl/dma/dmaNitro.h>
#include <libtwl/dma/dmaTwl.h>
#include <libtwl/gfx/gfxStatus.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/sio/sio.h>
#include <libtwl/sound/sound.h>
#include <libtwl/sound/soundCapture.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/sys/sysPower.h>
#include <libtwl/timer/timer.h>
#include "SdmmcDefinitions.h"
#include "core/Environment.h"
#include "Arm7IoRegisterClearer.h"
void Arm7IoRegisterClearer::ClearIoRegisters() const
{
REG_IME = 0;
REG_IE = 0;
if (Environment::IsDsiMode())
{
ClearTwlIoRegisters();
}
ClearNtrIoRegisters();
}
void Arm7IoRegisterClearer::ClearNtrIoRegisters() const
{
REG_DISPSTAT = 0;
REG_TM0CNT_H = 0; // timer 0
REG_TM0CNT_L = 0;
REG_TM1CNT_H = 0; // timer 1
REG_TM1CNT_L = 0;
REG_TM2CNT_H = 0; // timer 2
REG_TM2CNT_L = 0;
REG_TM3CNT_H = 0; // timer 3
REG_TM3CNT_L = 0;
REG_DMA0CNT = 0; // dma 0
REG_DMA0SAD = 0;
REG_DMA0DAD = 0;
REG_DMA1CNT = 0; // dma 1
REG_DMA1SAD = 0;
REG_DMA1DAD = 0;
REG_DMA2CNT = 0; // dma 2
REG_DMA2SAD = 0;
REG_DMA2DAD = 0;
REG_DMA3CNT = 0; // dma 3
REG_DMA3SAD = 0;
REG_DMA3DAD = 0;
REG_RCNT0_L = 0;
for (int i = 0; i < 16; i++)
{
REG_SOUNDxCNT(i) = 0;
REG_SOUNDxSAD(i) = 0;
REG_SOUNDxTMR(i) = 0;
REG_SOUNDxPNT(i) = 0;
REG_SOUNDxLEN(i) = 0;
}
REG_SOUNDCNT = 0;
REG_SNDCAP0CNT = 0;
REG_SNDCAP1CNT = 0;
REG_SNDCAP0DAD = 0;
REG_SNDCAP0LEN = 0;
REG_SNDCAP1DAD = 0;
REG_SNDCAP1LEN = 0;
sys_setWifiPower(false);
sys_setSoundPower(true);
}
void Arm7IoRegisterClearer::ClearTwlIoRegisters() const
{
REG_IE2 = 0;
REG_NDMA0SAD = 0;
REG_NDMA0DAD = 0;
REG_NDMA0TCNT = 0;
REG_NDMA0WCNT = 0;
REG_NDMA0BCNT = 0;
REG_NDMA0FDATA = 0;
REG_NDMA0CNT = 0;
REG_NDMA1SAD = 0;
REG_NDMA1DAD = 0;
REG_NDMA1TCNT = 0;
REG_NDMA1WCNT = 0;
REG_NDMA1BCNT = 0;
REG_NDMA1FDATA = 0;
REG_NDMA1CNT = 0;
REG_NDMA2SAD = 0;
REG_NDMA2DAD = 0;
REG_NDMA2TCNT = 0;
REG_NDMA2WCNT = 0;
REG_NDMA2BCNT = 0;
REG_NDMA2FDATA = 0;
REG_NDMA2CNT = 0;
REG_NDMA3SAD = 0;
REG_NDMA3DAD = 0;
REG_NDMA3TCNT = 0;
REG_NDMA3WCNT = 0;
REG_NDMA3BCNT = 0;
REG_NDMA3FDATA = 0;
REG_NDMA3CNT = 0;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(SDMMC_BASE + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN32) = 512;
*(vu16*)(SDMMC_BASE + REG_SDBLKCOUNT32) = 1;
*(vu16*)(SDMMC_BASE + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(SDMMC_BASE + REG_SDRESET) |= 1u;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(SDMMC_BASE + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS0) = 0;
*(vu16*)(SDMMC_BASE + REG_SDSTATUS1) = 0;
*(vu16*)(SDMMC_BASE + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(SDMMC_BASE + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDCLKCTL) = 0x20;
*(vu16*)(SDMMC_BASE + REG_SDOPT) = 0x40EE;
*(vu16*)(SDMMC_BASE + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(SDMMC_BASE + REG_SDBLKLEN) = 512;
*(vu16*)(SDMMC_BASE + REG_SDSTOP) = 0;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xF7FFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xEFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) |= 0x402u;
*(vu16*)(0x04004A00 + REG_SDDATACTL) = (*(vu16*)(SDMMC_BASE + REG_SDDATACTL) & 0xFFDD) | 2;
*(vu16*)(0x04004A00 + REG_SDDATACTL32) &= 0xFFFFu;
*(vu16*)(0x04004A00 + REG_SDDATACTL) &= 0xFFDFu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN32) = 512;
*(vu16*)(0x04004A00 + REG_SDBLKCOUNT32) = 1;
*(vu16*)(0x04004A00 + REG_SDRESET) &= 0xFFFEu;
*(vu16*)(0x04004A00 + REG_SDRESET) |= 1u;
*(vu16*)(0x04004A00 + REG_SDIRMASK0) |= TMIO_MASK_ALL;
*(vu16*)(0x04004A00 + REG_SDIRMASK1) |= TMIO_MASK_ALL>>16;
*(vu16*)(0x04004A00 + REG_SDSTATUS0) = 0;
*(vu16*)(0x04004A00 + REG_SDSTATUS1) = 0;
*(vu16*)(0x04004A00 + 0x0fc) |= 0xDBu; //SDCTL_RESERVED7
*(vu16*)(0x04004A00 + 0x0fe) |= 0xDBu; //SDCTL_RESERVED8
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDCLKCTL) = 0x20;
*(vu16*)(0x04004A00 + REG_SDOPT) = 0x40EE;
*(vu16*)(0x04004A00 + REG_SDPORTSEL) &= 0xFFFCu;
*(vu16*)(0x04004A00 + REG_SDBLKLEN) = 512;
*(vu16*)(0x04004A00 + REG_SDSTOP) = 0;
}

View File

@@ -0,0 +1,13 @@
#pragma once
/// @brief Class for clearing the arm9 IO registers.
class Arm7IoRegisterClearer
{
public:
/// @brief Clears the arm7 IO registers.
void ClearIoRegisters() const;
private:
void ClearNtrIoRegisters() const;
void ClearTwlIoRegisters() const;
};

View File

@@ -0,0 +1,113 @@
#include "common.h"
#include <bit>
#include "Blowfish.h"
void Blowfish::Encrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Encrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Encrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 0; i < 16; i++)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[16]) | ((u64)(y ^ _keyTable.pTable[17]) << 32);
}
void Blowfish::Decrypt(const void* src, void* dst, u32 length) const
{
const u32* src32 = (const u32*)src;
u32* dst32 = (u32*)dst;
for (u32 i = 0; i < (length / 4); i += 2)
{
u64 value = src32[i] | ((u64)src32[i + 1] << 32);
value = Decrypt(value);
dst32[i] = value & 0xFFFFFFFFu;
dst32[i + 1] = value >> 32;
}
}
u64 Blowfish::Decrypt(u64 value) const
{
u32 y = value & 0xFFFFFFFFu;
u32 x = value >> 32;
for (u32 i = 17; i >= 2; i--)
{
u32 z = _keyTable.pTable[i] ^ x;
u32 a = _keyTable.sBoxes[0][(z >> 24) & 0xFF];
u32 b = _keyTable.sBoxes[1][(z >> 16) & 0xFF];
u32 c = _keyTable.sBoxes[2][(z >> 8) & 0xFF];
u32 d = _keyTable.sBoxes[3][z & 0xFF];
x = (d + (c ^ (b + a))) ^ y;
y = z;
}
return (x ^ _keyTable.pTable[1]) | ((u64)(y ^ _keyTable.pTable[0]) << 32);
}
void Blowfish::TransformTable(u32 idCode, int level, int modulo)
{
u32 keyCode[3] = { idCode, idCode >> 1, idCode << 1 };
if (level >= 1)
{
ApplyKeyCode(&keyCode[0], modulo);
}
if (level >= 2)
{
ApplyKeyCode(&keyCode[0], modulo);
}
keyCode[1] <<= 1;
keyCode[2] >>= 1;
if (level >= 3)
{
ApplyKeyCode(&keyCode[0], modulo);
}
}
void Blowfish::ApplyKeyCode(u32* keyCode, int modulo)
{
Encrypt(&keyCode[1], &keyCode[1], 8);
Encrypt(&keyCode[0], &keyCode[0], 8);
const u32 reversedKeyCode[3] =
{
std::byteswap(keyCode[0]),
std::byteswap(keyCode[1]),
std::byteswap(keyCode[2])
};
int keyCodeIndex = 0;
for (u32 i = 0; i < BLOWFISH_PTABLE_ENTRY_COUNT; i++)
{
_keyTable.pTable[i] ^= reversedKeyCode[keyCodeIndex];
if (++keyCodeIndex == (modulo >> 2))
{
keyCodeIndex = 0;
}
}
u64 scratch = 0;
u64* keyTable = (u64*)&_keyTable;
for (u32 i = 0; i < (sizeof(KeyTable) / 8); i++)
{
scratch = Encrypt(scratch);
keyTable[i] = (scratch >> 32) | ((scratch & 0xFFFFFFFFu) << 32);
}
}

View File

@@ -0,0 +1,67 @@
#pragma once
#include <string.h>
#define BLOWFISH_PTABLE_ENTRY_COUNT 18
#define BLOWFISH_SBOX_COUNT 4
#define BLOWFISH_SBOX_ENTRY_COUNT 256
/// @brief Class for blowfish encryption and decryption.
class Blowfish
{
public:
/// @brief Struct representing a blowfish key table.
struct KeyTable
{
u32 pTable[BLOWFISH_PTABLE_ENTRY_COUNT];
u32 sBoxes[BLOWFISH_SBOX_COUNT][BLOWFISH_SBOX_ENTRY_COUNT];
};
static_assert(sizeof(KeyTable) == 0x1048, "Invalid size of Blowfish::KeyTable.");
/// @brief Constructs an instance of \see Blowfish using the given \p keyTable.
/// @param keyTable The key table to use. A copy will be made into the constructed class.
explicit Blowfish(const KeyTable* keyTable)
: Blowfish(keyTable->pTable, keyTable->sBoxes) { }
/// @brief Constructs an instance of \see Blowfish using the given \p pTable and \p sBoxes.
/// @param pTable The p table to use. A copy will be made into the constructed class.
/// @param sBoxes The s boxes to use. A copy will be made into the constructed class.
Blowfish(const void* pTable, const void* sBoxes)
{
memcpy(_keyTable.pTable, pTable, sizeof(_keyTable.pTable));
memcpy(_keyTable.sBoxes, sBoxes, sizeof(_keyTable.sBoxes));
}
/// @brief Encrypts the given \p length from \p src to \p dst.
/// @param src The source buffer.
/// @param dst The encrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Encrypt(const void* src, void* dst, u32 length) const;
/// @brief Encrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to encrypt.
/// @return The encrypted result.
u64 Encrypt(u64 value) const;
/// @brief Drcrypts the given \p length from \p src to \p dst.
/// @param src The encrypted source buffer.
/// @param dst The decrypted destination buffer.
/// @param length The length of the data to encrypt. Must be a multiple of 8.
void Decrypt(const void* src, void* dst, u32 length) const;
/// @brief Decrypts the given 64-bit \p value and returns the result.
/// @param value The 64-bit value to decrypt.
/// @return The decrypted result.
u64 Decrypt(u64 value) const;
/// @brief Transforms the key table by the given \p idCode, \p level and \p modulo.
/// @param idCode The id code to use.
/// @param level The transform level to use.
/// @param modulo The modulo to use.
void TransformTable(u32 idCode, int level, int modulo);
private:
void ApplyKeyCode(u32* keyCode, int modulo);
KeyTable _keyTable;
};

View File

@@ -0,0 +1,14 @@
#pragma once
/// @brief The Pico Loader boot mode.
enum class BootMode
{
/// @brief Boot a retail or homebrew rom.
Normal,
/// @brief Reboot a retail rom that used OS_ResetSystem.
SdkResetSystem,
/// @brief Boot a multiboot rom that is already loaded into memory.
Multiboot
};

View File

@@ -0,0 +1,107 @@
#include "common.h"
#include <string.h>
#include "SaveList.h"
#include "SaveListFactory.h"
#include "fileInfo.h"
#include "CardSaveArranger.h"
#define SAVE_LIST_PATH "/_pico/savelist.bin"
#define DEFAULT_SAVE_SIZE (512 * 1024)
#define SAVE_FILL_VALUE 0xFF
bool CardSaveArranger::SetupCardSave(u32 gameCode, const TCHAR* savePath) const
{
SaveList* saveList = SaveListFactory().CreateFromFile(SAVE_LIST_PATH);
u32 saveSize = DEFAULT_SAVE_SIZE;
if (saveList)
{
const auto saveListEntry = saveList->FindEntry(gameCode);
if (!saveListEntry)
{
LOG_WARNING("Game code %c%c%c%c not found in save list\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24);
}
else
{
saveSize = saveListEntry->GetSaveSize();
saveListEntry->Dump();
}
delete saveList;
}
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
LOG_DEBUG("Save file: %s\n", savePath);
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), initialSize) != FR_OK)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
auto ffBuffer = std::make_unique<u8[]>(512);
memset(ffBuffer.get(), SAVE_FILL_VALUE, 512);
u32 offset = initialSize;
// Align to 512 bytes
if ((offset & 511) != 0)
{
u32 remainingTo512 = 512 - (offset & 511);
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), remainingTo512, &bytesWritten) != FR_OK ||
bytesWritten != remainingTo512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += remainingTo512;
}
// Write in 512-byte blocks
while (offset < saveSize)
{
UINT bytesWritten = 0;
if (f_write(file.get(), ffBuffer.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to expand save file\n");
return false;
}
offset += 512;
}
}
DWORD* clusterTab = (DWORD*)SHARED_SAVE_FILE_INFO->clusterMap;
clusterTab[0] = sizeof(SHARED_SAVE_FILE_INFO->clusterMap) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make save cluster table. Result: %d\n", seekResult);
return false;
}
SHARED_SAVE_FILE_INFO->clusterShift = __builtin_ctz(file->obj.fs->csize);
SHARED_SAVE_FILE_INFO->database = file->obj.fs->database;
SHARED_SAVE_FILE_INFO->clusterMask = file->obj.fs->csize - 1;
LOG_DEBUG("Made save cluster table\n");
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close save file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
/// @brief Class for setting up the save file for retail card roms.
class CardSaveArranger
{
public:
/// @brief Sets up the save file at \p savePath for a retail card rom with the given \p gameCode.
/// @param gameCode The game code of the retail card rom.
/// @param savePath The desired save file path.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupCardSave(u32 gameCode, const TCHAR* savePath) const;
};

View File

@@ -0,0 +1,356 @@
#include "common.h"
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/sound/twlI2s.h>
#include <libtwl/sound/sound.h>
#include <libtwl/spi/spiCodec.h>
#include <libtwl/spi/spiPmic.h>
#include <libtwl/sys/twlScfg.h>
#include "ipc.h"
#include "gameCode.h"
#include "DSMode.h"
void DSMode::SwitchToDSMode(u32 gameCode) const
{
SwitchToDSTouchAndSoundMode(gameCode);
mcu_writeReg(MCU_REG_MODE, 0);
*(vu16*)0x04004C04 |= (1 << 8); // ntr wifi
REG_SCFG_A9ROM = SCFG_A9ROM_DISABLE_SECURE | SCFG_A9ROM_NITRO;
REG_SCFG_A7ROM = SCFG_A7ROM_DISABLE_SECURE | SCFG_A7ROM_NITRO | SCFG_A7ROM_DISABLE_FUSE;
REG_SCFG_EXT = 0x12A03000u;
sendToArm9(IPC_COMMAND_ARM9_SWITCH_TO_DS_MODE);
receiveFromArm9();
LOG_DEBUG("Switched to ds mode\n");
}
void DSMode::SwitchToDSTouchAndSoundMode(u32 gameCode) const
{
REG_I2SCNT = I2SCNT_MIX_RATIO_DSP_0_NITRO_8 | I2SCNT_FREQUENCY_32728_HZ;
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 21);
}
REG_I2SCNT |= I2SCNT_ENABLE;
SwitchCodecToDSMode(gameCode);
REG_SOUNDCNT = SOUNDCNT_MASTER_ENABLE | SOUNDCNT_MASTER_VOLUME(0x7F);
LOG_DEBUG("Switched to ds touch and sound mode\n");
}
void DSMode::SwitchCodecToDSMode(u32 gameCode) const
{
// 0xAC: special setting (when found special gamecode)
// 0xA7: normal setting (for any other gamecodes)
u8 volLevel = ShouldUseVolumeFix(gameCode) ? 0xAC : 0xA7;
// Touchscreen
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_HPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_SPL_DRIVER);
codec_readRegister(CODEC_REG_PAGE1_MICBIAS);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x0C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0xFF);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0x7F);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4A);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x10);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x00);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x14);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0x00);
codec_readRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x02);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
codec_writeRegister(CODEC_REG_PAGE0_RESET, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_GPI1_GPI2_PIN_CONTROL, 0x66);
}
codec_setPage(CODEC_PAGE_1);
{
codec_readRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0x10);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x81);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x82);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_CLOCK_GEN_MUXING, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_PLL_P_R, 0xA1);
codec_writeRegister(CODEC_REG_PAGE0_PLL_J, 0x15);
codec_writeRegister(CODEC_REG_PAGE0_DAC_NDAC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MDAC_VAL, 0x83);
codec_writeRegister(CODEC_REG_PAGE0_ADC_NADC_VAL, 0x87);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MADC_VAL, 0x83);
}
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK);
codec_writeRegister(CODEC_REG_PAGE3_SCAN_MODE_TIMER_CLOCK, 0x08);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_LEFT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_DAC_RIGHT_VOLUME_CONTROL, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_4);
{
codec_writeRegister(0x08, 0x7F);
codec_writeRegister(0x09, 0xE1);
codec_writeRegister(0x0A, 0x80);
codec_writeRegister(0x0B, 0x1F);
codec_writeRegister(0x0C, 0x7F);
codec_writeRegister(0x0D, 0xC1);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_MIC_PGA, 0x2B);
codec_writeRegister(CODEC_REG_PAGE1_DELTA_SIGMA_MONO_ADC_CHANNEL_FINE_GAIN_INPUT_SELECTION_FOR_P_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_ADC_INPUT_SELECTION_FOR_M_TERMINAL, 0x40);
codec_writeRegister(CODEC_REG_PAGE1_INPUT_CM_SETTINGS, 0x60);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x02);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x10);
codec_readRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0x40);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
}
codec_setPage(CODEC_PAGE_0);
{
codec_readRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC);
codec_readRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DATA_PATH_SETUP, 0xD4);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_DAC_L_DAC_R_OUTPUT_MIXER_ROUTING, 0x44);
codec_writeRegister(CODEC_REG_PAGE1_HEADPHONE_DRIVERS, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_HPL_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_HPR_DRIVER, 0x4E);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_HPL, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_HPR, 0x9E);
codec_writeRegister(CODEC_REG_PAGE1_CLASS_D_SPEAKER_AMPLIFIER, 0xD4);
codec_writeRegister(CODEC_REG_PAGE1_SPL_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_SPR_DRIVER, 0x14);
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, 0xA7);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, 0xA7);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_DAC_VOLUME_CONTROL, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_GPI3_PIN_CONTROL, 0x60);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_LEFT_ANALOG_VOL_TO_SPL, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_RIGHT_ANALOG_VOL_TO_SPR, volLevel);
codec_writeRegister(CODEC_REG_PAGE1_MICBIAS, 0x03);
}
codec_setPage(CODEC_PAGE_3);
{
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_2, 0x00);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_HP_OUTPUT_DRIVERS_POP_REMOVAL_SETTINGS, 0x20);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0xF0);
codec_readRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL);
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x00);
}
codec_setPage(CODEC_PAGE_0);
{
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_VOLUME_CONTROL_FINE_ADJUST, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_DIGITAL_MIC, 0x00);
// Set remaining values
codec_writeRegister(0x03, 0x44);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_DAC_DOSR_VAL_LSB, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_DAC_MINIDSP_INTERPOLATION, 0x08);
codec_writeRegister(CODEC_REG_PAGE0_ADC_AOSR_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_IDAC_VAL, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_ADC_MINIDSP_DECIMATION, 0x04);
codec_writeRegister(CODEC_REG_PAGE0_CLKOUT_M_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_BCLK_N_VAL, 0x01);
codec_writeRegister(CODEC_REG_PAGE0_ADC_FLAG_REGISTER, 0x80);
codec_writeRegister(CODEC_REG_PAGE0_GPIO1_IN_OUT_PIN_CONTROL, 0x34);
codec_writeRegister(CODEC_REG_PAGE0_GPIO2_IN_OUT_PIN_CONTROL, 0x32);
codec_writeRegister(CODEC_REG_PAGE0_SDOUT_OUT_PIN_CONTROL, 0x12);
codec_writeRegister(CODEC_REG_PAGE0_SDIN_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_MISO_OUT_PIN_CONTROL, 0x02);
codec_writeRegister(CODEC_REG_PAGE0_SCLK_IN_PIN_CONTROL, 0x03);
codec_writeRegister(CODEC_REG_PAGE0_DAC_INSTRUCTION_SET, 0x19);
codec_writeRegister(CODEC_REG_PAGE0_ADC_INSTRUCTION_SET, 0x05);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_1, 0x0F);
codec_writeRegister(CODEC_REG_PAGE0_DRC_CONTROL_2, 0x38);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MSB, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_MID, 0x00);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_LENGTH_LSB, 0xEE);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_MSB, 0x10);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_SIN_LSB, 0xD8);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_MSB, 0x7E);
codec_writeRegister(CODEC_REG_PAGE0_BEEP_COS_LSB, 0xE3);
codec_writeRegister(CODEC_REG_PAGE0_AGC_MAXIMUM_GAIN, 0x7F);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_SAR_ADC_VOLUME_CONTROL, 0xD2);
codec_writeRegister(CODEC_REG_PAGE0_VOL_MICDET_PIN_GAIN, 0x2C);
}
codec_setPage(CODEC_PAGE_1);
{
codec_writeRegister(CODEC_REG_PAGE1_OUTPUT_DRIVER_PGA_RAMP_DOWN_PERIOD_CONTROL, 0x70);
codec_writeRegister(CODEC_REG_PAGE1_HP_DRIVER_CONTROL, 0x20);
}
// Finish up!
codec_setPage(CODEC_PAGE_3);
{
codec_readRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1);
codec_writeRegister(CODEC_REG_PAGE3_SAR_ADC_CONTROL_1, 0x98);
}
codec_setPage(CODEC_PAGE_255);
{
codec_writeRegister(CODEC_REG_PAGE255_BACKWARDS_COMPATIBILITY_MODE, CODEC_PAGE255_BACKWARDS_COMPATIBILITY_MODE_ON);
}
pmic_setAmplifierEnable(true);
}
bool DSMode::ShouldUseVolumeFix(u32 gameCode) const
{
switch (gameCode & 0xFFFFFF)
{
case GAMECODE_NO_REGION("A3T"):
case GAMECODE_NO_REGION("A4U"):
case GAMECODE_NO_REGION("A5H"):
case GAMECODE_NO_REGION("A5I"):
case GAMECODE_NO_REGION("A8N"):
case GAMECODE_NO_REGION("ABJ"):
case GAMECODE_NO_REGION("ABN"):
case GAMECODE_NO_REGION("ABX"):
case GAMECODE_NO_REGION("ACC"):
case GAMECODE_NO_REGION("ACL"):
case GAMECODE_NO_REGION("ACZ"):
case GAMECODE_NO_REGION("ADA"):
case GAMECODE_NO_REGION("AHD"):
case GAMECODE_NO_REGION("AJU"):
case GAMECODE_NO_REGION("AKA"):
case GAMECODE_NO_REGION("AKE"):
case GAMECODE_NO_REGION("ALH"):
case GAMECODE_NO_REGION("AMH"):
case GAMECODE_NO_REGION("AN9"):
case GAMECODE_NO_REGION("ANR"):
case GAMECODE_NO_REGION("APA"):
case GAMECODE_NO_REGION("APY"):
case GAMECODE_NO_REGION("ART"):
case GAMECODE_NO_REGION("AV2"):
case GAMECODE_NO_REGION("AV3"):
case GAMECODE_NO_REGION("AV4"):
case GAMECODE_NO_REGION("AV5"):
case GAMECODE_NO_REGION("AV6"):
case GAMECODE_NO_REGION("AVI"):
case GAMECODE_NO_REGION("AVT"):
case GAMECODE_NO_REGION("AWH"):
case GAMECODE_NO_REGION("AWY"):
case GAMECODE_NO_REGION("AXB"):
case GAMECODE_NO_REGION("AXJ"):
case GAMECODE_NO_REGION("AY7"):
case GAMECODE_NO_REGION("AYK"):
case GAMECODE_NO_REGION("AZW"):
case GAMECODE_NO_REGION("CPU"):
case GAMECODE_NO_REGION("YB2"):
case GAMECODE_NO_REGION("YB3"):
case GAMECODE_NO_REGION("YBO"):
case GAMECODE_NO_REGION("YCH"):
case GAMECODE_NO_REGION("YCQ"):
case GAMECODE_NO_REGION("YFE"):
case GAMECODE_NO_REGION("YFS"):
case GAMECODE_NO_REGION("YG8"):
case GAMECODE_NO_REGION("YGD"):
case GAMECODE_NO_REGION("YKR"):
case GAMECODE_NO_REGION("YNZ"):
case GAMECODE_NO_REGION("YO9"):
case GAMECODE_NO_REGION("YON"):
case GAMECODE_NO_REGION("YRM"):
case GAMECODE_NO_REGION("YT3"):
case GAMECODE_NO_REGION("YW2"):
case GAMECODE_NO_REGION("YYK"):
{
return true;
}
default:
{
return false;
}
}
}

View File

@@ -0,0 +1,18 @@
#pragma once
/// @brief Class handling switching from DSi to DS mode.
class DSMode
{
public:
/// @brief Switches to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch to DS mode for.
void SwitchToDSMode(u32 gameCode) const;
/// @brief Switches CODEC to DS mode for the game with the given \p gameCode.
/// @param gameCode The game code of the game to switch CODEC to DS mode for.
void SwitchToDSTouchAndSoundMode(u32 gameCode) const;
private:
void SwitchCodecToDSMode(u32 gameCode) const;
bool ShouldUseVolumeFix(u32 gameCode) const;
};

View File

@@ -0,0 +1,106 @@
#include "common.h"
#include <string.h>
#include "DldiDriver.h"
void DldiDriver::Relocate(u32 targetAddress)
{
u32 currentAddress = _driver->driverStartAddress;
if (currentAddress == targetAddress)
return;
u32 currentEndAddress = _driver->driverEndAddress;
if (_driver->fixFlags & DLDI_FIX_ALL)
{
LOG_WARNING("Dldi driver uses FIX_ALL flag");
u32* ptr = (u32*)((u8*)_driver + sizeof(dldi_header_t));
u32 size = currentEndAddress - currentAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
else
{
// note that we are not going to do those fixes when we did
// FIX_ALL already, since it covers them already
if (_driver->fixFlags & DLDI_FIX_GLUE)
{
u32* ptr = (u32*)((u8*)_driver + _driver->glueStartAddress - currentAddress);
u32 size = _driver->glueEndAddress - _driver->glueStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
if (_driver->fixFlags & DLDI_FIX_GOT)
{
u32* ptr = (u32*)((u8*)_driver + _driver->gotStartAddress - currentAddress);
u32 size = _driver->gotEndAddress - _driver->gotStartAddress;
for (u32 i = 0; i < size; i += 4)
{
u32 word = *ptr;
if (currentAddress <= word && word < currentEndAddress)
*ptr = word - currentAddress + targetAddress;
ptr++;
}
}
}
_driver->driverStartAddress = targetAddress;
_driver->driverEndAddress = _driver->driverEndAddress + targetAddress - currentAddress;
_driver->glueStartAddress = _driver->glueStartAddress + targetAddress - currentAddress;
_driver->glueEndAddress = _driver->glueEndAddress + targetAddress - currentAddress;
_driver->gotStartAddress = _driver->gotStartAddress + targetAddress - currentAddress;
_driver->gotEndAddress = _driver->gotEndAddress + targetAddress - currentAddress;
_driver->bssStartAddress = _driver->bssStartAddress + targetAddress - currentAddress;
_driver->bssEndAddress = _driver->bssEndAddress + targetAddress - currentAddress;
_driver->startupFuncAddress = _driver->startupFuncAddress + targetAddress - currentAddress;
_driver->isInsertedFuncAddress = _driver->isInsertedFuncAddress + targetAddress - currentAddress;
_driver->readSectorsFuncAddress = _driver->readSectorsFuncAddress + targetAddress - currentAddress;
_driver->writeSectorsFuncAddress = _driver->writeSectorsFuncAddress + targetAddress - currentAddress;
_driver->clearStatusFuncAddress = _driver->clearStatusFuncAddress + targetAddress - currentAddress;
_driver->shutdownFuncAddress = _driver->shutdownFuncAddress + targetAddress - currentAddress;
}
void DldiDriver::PrepareForUse()
{
if (_driver->fixFlags & DLDI_FIX_BSS)
{
memset((u8*)_driver + _driver->bssStartAddress - _driver->driverStartAddress, 0,
_driver->bssEndAddress - _driver->bssStartAddress);
}
}
bool DldiDriver::PatchTo(dldi_header_t* stub)
{
if (_driver->dldiMagic != DLDI_MAGIC)
return false;
u32 stubSize = stub->stubSize;
if (stubSize < _driver->driverSize)
{
LOG_ERROR("Dldi stub of size %d is not large enough for driver of size %d\n",
stubSize, _driver->driverSize);
return false;
}
u32 targetAddress = stub->driverStartAddress;
u32 driverSize = 1 << _driver->driverSize;
memcpy(stub, _driver, driverSize);
stub->stubSize = stubSize;
auto newDriver = DldiDriver(stub);
newDriver.Relocate(targetAddress);
newDriver.PrepareForUse();
return true;
}

View File

@@ -0,0 +1,86 @@
#pragma once
#include "dldiHeader.h"
/// @brief Class representing a DLDI driver.
class DldiDriver
{
dldi_header_t* _driver;
public:
explicit DldiDriver(dldi_header_t* driver)
: _driver(driver) { }
/// @brief Returns whether the driver has been relocated to its current ram address.
/// @return True if the driver is relocated to its current ram address, or false otherwise.
bool IsRelocated() const
{
return _driver->driverStartAddress == (u32)_driver;
}
/// @brief Relocates the driver to its current ram address.
void Relocate()
{
Relocate((u32)_driver);
}
/// @brief Relocates the driver to the given targetAddress.
/// @param targetAddress The address to relocate the driver to.
void Relocate(u32 targetAddress);
/// @brief Clears the bss area of the driver if needed, making it ready for use.
/// This method should only be used after placing the driver in a memory region
/// with enough space and relocating it.
void PrepareForUse();
/// @brief Patches this dldi driver into the given stub.
/// @param stub The stub to patch the dldi driver into.
/// @return \c true if patching was successful, or \c false otherwise.
bool PatchTo(dldi_header_t* stub);
/// @brief Calls the startup function of the DLDI driver and returns the result.
/// @return \c true if startup was successful, or \c false otherwise.
bool Startup()
{
return ((dldi_startup_func_t)_driver->startupFuncAddress)();
}
/// @brief Calls the is inserted function of the DLDI driver and returns the result.
/// @return \c true if the storage medium is inserted, or \c false otherwise.
bool IsInserted()
{
return ((dldi_inserted_func_t)_driver->isInsertedFuncAddress)();
}
/// @brief Reads sectors using this DLDI driver.
/// @param sector The sector to start reading at.
/// @param count The number of sectors to read.
/// @param dst The destination buffer.
/// @return \c true if reading was successful, or \c false otherwise.
bool ReadSectors(u32 sector, u32 count, void* dst)
{
return ((dldi_read_func_t)_driver->readSectorsFuncAddress)(sector, count, dst);
}
/// @brief Writes sectors using this DLDI driver.
/// @param sector The sector to start writing at.
/// @param count The number of sectors to write.
/// @param src The source buffer.
/// @return \c true if writing was successful, or \c false otherwise.
bool WriteSectors(u32 sector, u32 count, const void* src)
{
return ((dldi_write_func_t)_driver->writeSectorsFuncAddress)(sector, count, src);
}
/// @brief Calls the clear status function of the DLDI driver and returns the result.
/// @return \c true if clear status was successful, or \c false otherwise.
bool ClearStatus()
{
return ((dldi_clearstatus_func_t)_driver->clearStatusFuncAddress)();
}
/// @brief Calls the shutdown function of the DLDI driver and returns the result.
/// @return \c true if shutdown was successful, or \c false otherwise.
bool Shutdown()
{
return ((dldi_shutdown_func_t)_driver->shutdownFuncAddress)();
}
};

View File

@@ -0,0 +1,39 @@
#pragma once
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_SDMC 0
#define DSI_DEVICELIST_ENTRY_FLAGS_DRIVE_NAND 1
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_PHYSICAL (0 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FILE (1 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_TYPE_FOLDER (2 << 3)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_FIRST (0 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_PARTITION_SECOND (1 << 5)
#define DSI_DEVICELIST_ENTRY_FLAGS_ENCRYPTED (1 << 7)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_READ (1 << 1)
#define DSI_DEVICELIST_ENTRY_ACCESS_RIGHTS_WRITE (1 << 2)
/// @brief Struct representing a DSi device list entry.
struct dsi_devicelist_entry_t
{
char driveLetter;
u8 flags;
u8 accessRights;
u8 padding;
char deviceName[16];
char path[64];
};
static_assert(sizeof(dsi_devicelist_entry_t) == 0x54, "Invalid dsi_devicelist_entry_t size.");
/// @brief Struct representing a DSi device list.
struct dsi_devicelist_t
{
dsi_devicelist_entry_t deviceList[11];
u8 padding[0x24];
char appFileName[64];
};
static_assert(sizeof(dsi_devicelist_t) == 0x400, "Invalid dsi_devicelist_t size.");

View File

@@ -0,0 +1,235 @@
#include "common.h"
#include <string.h>
#include <memory>
#include "DsiWareSaveArranger.h"
bool DsiWareSaveArranger::SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const
{
char path[256];
strcpy(path, romPath);
if (!CreateDeviceListPath(path, result.romFilePath))
{
return false;
}
if (romHeader.twlPrivateSavSize != 0)
{
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'r';
extension[3] = 'v';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPrivateSavSize) ||
!CreateDeviceListPath(path, result.privateSavePath))
{
return false;
}
}
if (romHeader.twlPublicSavSize != 0)
{
strcpy(path, romPath);
char* extension = strrchr(path, '.');
if (!extension)
extension = &path[strlen(path)];
extension[0] = '.';
extension[1] = 'p';
extension[2] = 'u';
extension[3] = 'b';
extension[4] = 0;
if (!SetupDsiWareSaveFile(path, romHeader.twlPublicSavSize) ||
!CreateDeviceListPath(path, result.publicSavePath))
{
return false;
}
}
return true;
}
bool DsiWareSaveArranger::SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const
{
if (saveSize == 0)
{
return true;
}
auto file = std::make_unique<FIL>();
if (f_open(file.get(), savePath, FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
{
LOG_FATAL("Failed to open or create save file\n");
return false;
}
u32 initialSize = f_size(file.get());
if (initialSize < saveSize)
{
if (f_lseek(file.get(), saveSize) != FR_OK ||
f_lseek(file.get(), 0) != FR_OK)
{
LOG_FATAL("Failed to create private save file\n");
return false;
}
auto fatHeader = CreateFatHeader(saveSize);
UINT bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), sizeof(fat_header_t), &bytesWritten) != FR_OK ||
bytesWritten != sizeof(fat_header_t))
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
memset(fatHeader.get(), 0, 512);
while (f_tell(file.get()) < saveSize)
{
bytesWritten = 0;
if (f_write(file.get(), fatHeader.get(), 512, &bytesWritten) != FR_OK ||
bytesWritten != 512)
{
LOG_FATAL("Failed to format private save file\n");
return false;
}
}
}
f_close(file.get());
return true;
}
std::unique_ptr<DsiWareSaveArranger::fat_header_t> DsiWareSaveArranger::CreateFatHeader(u32 saveSize) const
{
// based on https://github.com/Epicpkmn11/NTM/blob/master/arm9/src/sav.c
const u32 maxSectors = saveSize >> 9;
u32 sectorCount = 1;
u32 secPerTrk = 1;
u32 numHeads = 1;
u32 sectorCountNext = 0;
while (sectorCountNext <= maxSectors)
{
sectorCountNext = secPerTrk * (numHeads + 1) * (numHeads + 1);
if (sectorCountNext <= maxSectors)
{
numHeads++;
sectorCount = sectorCountNext;
secPerTrk++;
sectorCountNext = secPerTrk * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
sectorCount = sectorCountNext;
}
}
}
sectorCountNext = (secPerTrk + 1) * numHeads * numHeads;
if (sectorCountNext <= maxSectors)
{
secPerTrk++;
sectorCount = sectorCountNext;
}
u32 sectorsPerCluster;
u32 totalClusters;
if (sectorCount > 8192)
{
sectorsPerCluster = 8;
totalClusters = (sectorCount + 7) >> 3;
}
else if (sectorCount > 1024)
{
sectorsPerCluster = 4;
totalClusters = (sectorCount + 3) >> 2;
}
else
{
sectorsPerCluster = 1;
totalClusters = sectorCount;
}
u32 fatSizeInBytes = ((totalClusters + 1) >> 1) * 3; // 2 sectors -> 3 byte
auto fatHeader = std::make_unique<fat_header_t>();
fatHeader->jumpInstruction[0] = 0xE9;
fatHeader->jumpInstruction[1] = 0;
fatHeader->jumpInstruction[2] = 0;
memcpy(fatHeader->oemName, "MSWIN4.1", 8);
fatHeader->bytesPerSector = 512;
fatHeader->sectorsPerCluster = sectorsPerCluster;
fatHeader->reservedSectors = 1;
fatHeader->numberOfFats = 2;
fatHeader->rootEntries = saveSize < 0x8C000 ? 32 : 512;
fatHeader->totalSectorsSmall = sectorCount;
fatHeader->mediaType = 0xF8; // "hard drive"
fatHeader->fatSectorCount = (fatSizeInBytes + 511) >> 9;
fatHeader->sectorsPerTrack = secPerTrk;
fatHeader->numberOfHeads = numHeads;
fatHeader->hiddenSectors = 0;
fatHeader->totalSectorsLarge = 0;
fatHeader->diskNumber = 5;
fatHeader->BS_Reserved1 = 0;
fatHeader->bootSignature = 0x29;
fatHeader->volumeSerialNumber = 0x12345678;
memcpy(fatHeader->volumeLabel, "VOLUMELABEL", 11);
memcpy(fatHeader->fileSystemType, "FAT12 ", 8);
memset(fatHeader->bootCode, 0, sizeof(fatHeader->bootCode));
fatHeader->endOfSectorMarker = 0xAA55;
return fatHeader;
}
bool DsiWareSaveArranger::CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const
{
auto fileInfo = std::make_unique<FILINFO>();
strcpy(deviceListPath, "nand:/");
char* shortPath = deviceListPath + 6;
char* currentPathSegment = strchr(savePath, '/');
do
{
currentPathSegment = strchr(currentPathSegment + 1, '/');
if (currentPathSegment)
{
*currentPathSegment = 0;
}
if (f_stat(savePath, fileInfo.get()) != FR_OK)
{
LOG_DEBUG("Failed to create short path\n");
return false;
}
LOG_DEBUG("%s\n", savePath);
// Use fname when altname is empty to handle short filenames.
const char* nameToUse = fileInfo->altname[0]
? fileInfo->altname
: fileInfo->fname;
u32 length = strlen(nameToUse);
if (shortPath + length - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
strcpy(shortPath, nameToUse);
shortPath += length;
if (currentPathSegment)
{
if (shortPath + 1 - deviceListPath >= 64)
{
LOG_DEBUG("Path too long\n");
return false;
}
*shortPath++ = '/';
*currentPathSegment = '/';
}
} while (currentPathSegment);
LOG_DEBUG("%s\n", deviceListPath);
return true;
}

View File

@@ -0,0 +1,65 @@
#pragma once
#include <memory>
#include "fat/ff.h"
#include "ndsHeader.h"
/// @brief Struct holding the result of setting up DSiWare save data.
struct DsiWareSaveResult
{
/// @brief The short filename path of the private save file.
char privateSavePath[64];
/// @brief The short filename path of the public save file.
char publicSavePath[64];
/// @brief The short filename path of rom file.
char romFilePath[64];
};
/// @brief Class for setting up the save files for DSiWare roms.
class DsiWareSaveArranger
{
public:
/// @brief Sets up the save files for a DSiWare rom with the given \p romPath and \p romHeader.
/// @param romPath The path to the DSiWare rom to setup the save files for.
/// @param romHeader The header of the DSiWare rom.
/// @param result Struct in which the resulting paths are returned.
/// @return \c true when setting up the save was successful, or \c false otherwise.
bool SetupDsiWareSave(const TCHAR* romPath, const nds_header_twl_t& romHeader, DsiWareSaveResult& result) const;
private:
#pragma pack(push, 1)
/// @brief Struct representing a FAT header.
struct fat_header_t
{
u8 jumpInstruction[3];
u8 oemName[8];
u16 bytesPerSector;
u8 sectorsPerCluster;
u16 reservedSectors;
u8 numberOfFats;
u16 rootEntries;
u16 totalSectorsSmall;
u8 mediaType;
u16 fatSectorCount;
u16 sectorsPerTrack;
u16 numberOfHeads;
u32 hiddenSectors;
u32 totalSectorsLarge;
u8 diskNumber;
u8 BS_Reserved1;
u8 bootSignature;
u32 volumeSerialNumber;
u8 volumeLabel[11];
u8 fileSystemType[8];
u8 bootCode[448];
u16 endOfSectorMarker;
};
#pragma pack(pop)
static_assert(sizeof(fat_header_t) == 512, "Invalid size of fat_header_t.");
bool SetupDsiWareSaveFile(const TCHAR* savePath, u32 saveSize) const;
std::unique_ptr<fat_header_t> CreateFatHeader(u32 saveSize) const;
bool CreateDeviceListPath(TCHAR* savePath, char* deviceListPath) const;
};

View File

@@ -0,0 +1,39 @@
#pragma once
#include "common.h"
template <typename T, u32 PatternLength>
class InverseKmpMatcher
{
const T* _pattern;
u8 _prefixFunc[PatternLength];
public:
consteval InverseKmpMatcher(const T(&pattern)[PatternLength])
: _pattern(pattern)
{
_prefixFunc[0] = 0;
int k = 0;
for (u32 q = 1; q < PatternLength; q++)
{
while (k > 0 && pattern[k] != pattern[q])
k = _prefixFunc[k - 1];
if (pattern[k] == pattern[q])
k++;
_prefixFunc[q] = k;
}
}
int FindFirstOccurance(const T* data, u32 length) const
{
int q = 0;
for (u32 i = 0; i < length; i++)
{
while (q > 0 && ~_pattern[q] != data[i])
q = _prefixFunc[q - 1];
if (~_pattern[q] == data[i])
q++;
if (q == PatternLength)
return i - q + 1;
}
return -1;
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,75 @@
#pragma once
#include "common.h"
#include "ndsHeader.h"
#include "DsiWareSaveArranger.h"
#include "BootMode.h"
struct dsi_devicelist_entry_t;
/// @brief Class for loading DS(i) roms.
class NdsLoader
{
public:
/// @brief Sets the \p romPath.
/// @param romPath The rom path.
void SetRomPath(const TCHAR* romPath)
{
_romPath = romPath;
}
/// @brief Sets the \p savePath to use.
/// @param savePath The save path to use.
void SetSavePath(const TCHAR* savePath)
{
_savePath = savePath;
}
/// @brief Sets the argv arguments to pass to the rom.
/// @param arguments The argv arguments.
/// @param argumentsLength The length of the argv arguments.
void SetArguments(const char* arguments, u32 argumentsLength)
{
_arguments = arguments;
_argumentsLength = argumentsLength;
}
/// @brief Loads the rom according to the specified \p bootMode.
/// @param bootMode The boot mode.
void Load(BootMode bootMode);
private:
FIL _romFile;
const TCHAR* _romPath;
const TCHAR* _savePath;
u32 _argumentsLength = 0;
const char* _arguments = nullptr;
nds_header_twl_t _romHeader;
DsiWareSaveResult _dsiwareSaveResult;
bool IsCloneBootRom(u32 romOffset);
void ApplyArm7Patches();
void SetupSharedMemory(u32 cardId, u32 agbMem, u32 resetParam, u32 romOffset, u32 bootType);
void LoadFirmwareUserSettings();
bool ShouldAttemptDldiPatch();
void ClearMainMemory();
void CreateRomClusterTable();
bool TryLoadRomHeader(u32 romOffset);
void HandleCardSave();
void HandleAntiPiracy();
void RemapWram();
bool TryLoadArm9();
bool TryLoadArm9i();
bool TryDecryptArm9i();
bool TryDecryptArm7i();
bool TryLoadArm7();
bool TryLoadArm7i();
void HandleDldiPatching();
void StartRom(BootMode bootMode);
void SetupTwlConfig();
void SetDeviceListEntry(dsi_devicelist_entry_t& deviceListEntry,
char driveLetter, const char* deviceName, const char* path, u8 flags, u8 accessRights);
void SetupDsiDeviceList();
void InsertArgv();
bool TrySetupDsiWareSave();
bool TryDecryptSecureArea();
};

View File

@@ -0,0 +1,60 @@
#include "common.h"
#include <memory>
#include "PicoLoaderArranger.h"
#define PICO_LOADER_9_PATH "/_pico/picoLoader9.bin"
#define PICO_LOADER_7_PATH "/_pico/picoLoader7.bin"
bool PicoLoaderArranger::SetupPicoLoaderInfo(loader_info_t* info) const
{
auto file = std::make_unique<FIL>();
if (f_open(file.get(), PICO_LOADER_9_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader9.bin\n");
return false;
}
DWORD* clusterTab = (DWORD*)info->clusterMap9;
clusterTab[0] = sizeof(info->clusterMap9) / sizeof(u32);
file->cltbl = clusterTab;
FRESULT seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader9 cluster table. Result: %d\n", seekResult);
return false;
}
info->clusterShift = __builtin_ctz(file->obj.fs->csize);
info->picoLoaderBootDrive = gLoaderHeader.bootDrive;
info->database = file->obj.fs->database;
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader9 file\n");
return false;
}
if (f_open(file.get(), PICO_LOADER_7_PATH, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open picoLoader7.bin\n");
return false;
}
clusterTab = (DWORD*)info->clusterMap7;
clusterTab[0] = sizeof(info->clusterMap7) / sizeof(u32);
file->cltbl = clusterTab;
seekResult = f_lseek(file.get(), CREATE_LINKMAP);
if (seekResult != FR_OK)
{
LOG_FATAL("Failed to make picoLoader7 cluster table. Result: %d\n", seekResult);
return false;
}
if (f_close(file.get()) != FR_OK)
{
LOG_FATAL("Failed to close picoLoader7 file\n");
return false;
}
return true;
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "LoaderInfo.h"
/// @brief Class for setting up \see loader_info_t.
class PicoLoaderArranger
{
public:
/// @brief Sets up the given \p info for being able to reload Pico Loader at a later time.
/// @param info The \see loader_info_t struct to fill.
/// @return \c true when setting up was successful, or \c false otherwise.
bool SetupPicoLoaderInfo(loader_info_t* info) const;
};

View File

@@ -0,0 +1,22 @@
#include "common.h"
#include <algorithm>
#include "SaveList.h"
const SaveListEntry* SaveList::FindEntry(u32 gameCode)
{
if (_count != 0)
{
const auto gameEntry = std::lower_bound(_entries.get(), _entries.get() + _count, gameCode,
[] (const SaveListEntry& entry, u32 value)
{
return entry.GetGameCode() < value;
});
if (gameEntry != _entries.get() + _count && gameEntry->GetGameCode() == gameCode)
{
return gameEntry;
}
}
return nullptr;
}

View File

@@ -0,0 +1,74 @@
#pragma once
#include <memory>
#include "common.h"
#include "CardSaveType.h"
/// @brief Class representing a save list entry.
class SaveListEntry
{
u32 gameCode;
u8 saveType; // see CardSaveType
u8 saveSize; // 0 or 1 << x
u8 reserved[2]; // for possible future use
public:
u32 GetGameCode() const { return gameCode; }
CardSaveType GetSaveType() const { return static_cast<CardSaveType>(saveType); }
u32 GetSaveSize() const { return saveSize == 0 ? 0 : (1u << saveSize); }
void Dump() const
{
const char* saveType;
switch (GetSaveType())
{
case CardSaveType::None:
{
saveType = "none";
break;
}
case CardSaveType::Eeprom:
{
saveType = "eeprom";
break;
}
case CardSaveType::Flash:
{
saveType = "flash";
break;
}
case CardSaveType::Nand:
{
saveType = "nand";
break;
}
default:
{
saveType = "unknown";
break;
}
}
LOG_DEBUG("%c%c%c%c - %s - 0x%X\n",
gameCode & 0xFF, (gameCode >> 8) & 0xFF, (gameCode >> 16) & 0xFF, gameCode >> 24,
saveType,
GetSaveSize());
}
};
static_assert(sizeof(SaveListEntry) == 8, "Invalid sizeof(SaveListEntry)");
/// @brief Class representing a save list.
class SaveList
{
public:
SaveList(std::unique_ptr<const SaveListEntry[]> entries, u32 count)
: _entries(std::move(entries)), _count(count) { }
/// @brief Attempts to find the save list entry for the given \p gameCode.
/// @param gameCode The game code to search for.
/// @return A pointer to the \see SaveListEntry when found, or \c nullptr otherwise.
const SaveListEntry* FindEntry(u32 gameCode);
private:
std::unique_ptr<const SaveListEntry[]> _entries;
u32 _count;
};

View File

@@ -0,0 +1,24 @@
#include "common.h"
#include "SaveListFactory.h"
SaveList* SaveListFactory::CreateFromFile(const TCHAR *path)
{
FIL file;
if (f_open(&file, path, FA_OPEN_EXISTING | FA_READ) != FR_OK)
{
LOG_FATAL("Failed to open save list file\n");
return nullptr;
}
const u32 entryCount = f_size(&file) / sizeof(SaveListEntry);
auto entries = std::make_unique_for_overwrite<SaveListEntry[]>(entryCount);
UINT bytesRead = 0;
FRESULT result = f_read(&file, entries.get(), entryCount * sizeof(SaveListEntry), &bytesRead);
if (result != FR_OK || bytesRead != entryCount * sizeof(SaveListEntry))
{
LOG_FATAL("Failed to read save list file\n");
return nullptr;
}
f_close(&file);
return new SaveList(std::move(entries), entryCount);
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include "SaveList.h"
/// @brief Factory for creating \see SaveList instances.
class SaveListFactory
{
public:
/// @brief Creates a \see SaveList instance from the file at the given \p path.
/// @param path The save list file path.
/// @return A pointer to the constructed \see SaveList instance, or \c nullptr if construction failed.
SaveList* CreateFromFile(const TCHAR* path);
};

View File

@@ -0,0 +1,104 @@
#pragma once
#include <nds/ndstypes.h>
#define DATA32_SUPPORT
#define SDMMC_BASE 0x04004800
#define REG_SDCMD 0x00
#define REG_SDPORTSEL 0x02
#define REG_SDCMDARG 0x04
#define REG_SDCMDARG0 0x04
#define REG_SDCMDARG1 0x06
#define REG_SDSTOP 0x08
#define REG_SDRESP 0x0c
#define REG_SDBLKCOUNT 0x0a
#define REG_SDRESP0 0x0c
#define REG_SDRESP1 0x0e
#define REG_SDRESP2 0x10
#define REG_SDRESP3 0x12
#define REG_SDRESP4 0x14
#define REG_SDRESP5 0x16
#define REG_SDRESP6 0x18
#define REG_SDRESP7 0x1a
#define REG_SDSTATUS0 0x1c
#define REG_SDSTATUS1 0x1e
#define REG_SDIRMASK0 0x20
#define REG_SDIRMASK1 0x22
#define REG_SDCLKCTL 0x24
#define REG_SDBLKLEN 0x26
#define REG_SDOPT 0x28
#define REG_SDFIFO 0x30
#define REG_SDDATACTL 0xd8
#define REG_SDRESET 0xe0
#define REG_SDPROTECTED 0xf6 //bit 0 determines if sd is protected or not?
#define REG_SDDATACTL32 0x100
#define REG_SDBLKLEN32 0x104
#define REG_SDBLKCOUNT32 0x108
#define REG_SDFIFO32 0x10C
#define REG_CLK_AND_WAIT_CTL 0x138
#define REG_RESET_SDIO 0x1e0
//The below defines are from linux kernel drivers/mmc tmio_mmc.h.
/* Definitions for values the CTRL_STATUS register can take. */
#define TMIO_STAT0_CMDRESPEND 0x0001
#define TMIO_STAT0_DATAEND 0x0004
#define TMIO_STAT0_CARD_REMOVE 0x0008
#define TMIO_STAT0_CARD_INSERT 0x0010
#define TMIO_STAT0_SIGSTATE 0x0020
#define TMIO_STAT0_WRPROTECT 0x0080
#define TMIO_STAT0_CARD_REMOVE_A 0x0100
#define TMIO_STAT0_CARD_INSERT_A 0x0200
#define TMIO_STAT0_SIGSTATE_A 0x0400
#define TMIO_STAT1_CMD_IDX_ERR 0x0001
#define TMIO_STAT1_CRCFAIL 0x0002
#define TMIO_STAT1_STOPBIT_ERR 0x0004
#define TMIO_STAT1_DATATIMEOUT 0x0008
#define TMIO_STAT1_RXOVERFLOW 0x0010
#define TMIO_STAT1_TXUNDERRUN 0x0020
#define TMIO_STAT1_CMDTIMEOUT 0x0040
#define TMIO_STAT1_RXRDY 0x0100
#define TMIO_STAT1_TXRQ 0x0200
#define TMIO_STAT1_ILL_FUNC 0x2000
#define TMIO_STAT1_CMD_BUSY 0x4000
#define TMIO_STAT1_ILL_ACCESS 0x8000
#define SDMC_NORMAL 0x00000000
#define SDMC_ERR_COMMAND 0x00000001
#define SDMC_ERR_CRC 0x00000002
#define SDMC_ERR_END 0x00000004
#define SDMC_ERR_TIMEOUT 0x00000008
#define SDMC_ERR_FIFO_OVF 0x00000010
#define SDMC_ERR_FIFO_UDF 0x00000020
#define SDMC_ERR_WP 0x00000040
#define SDMC_ERR_ABORT 0x00000080
#define SDMC_ERR_FPGA_TIMEOUT 0x00000100
#define SDMC_ERR_PARAM 0x00000200
#define SDMC_ERR_R1_STATUS 0x00000800
#define SDMC_ERR_NUM_WR_SECTORS 0x00001000
#define SDMC_ERR_RESET 0x00002000
#define SDMC_ERR_ILA 0x00004000
#define SDMC_ERR_INFO_DETECT 0x00008000
#define SDMC_STAT_ERR_UNKNOWN 0x00080000
#define SDMC_STAT_ERR_CC 0x00100000
#define SDMC_STAT_ERR_ECC_FAILED 0x00200000
#define SDMC_STAT_ERR_CRC 0x00800000
#define SDMC_STAT_ERR_OTHER 0xf9c70008
#define TMIO_MASK_ALL 0x837f031d
#define TMIO_MASK_GW (TMIO_STAT1_ILL_ACCESS | TMIO_STAT1_CMDTIMEOUT | TMIO_STAT1_TXUNDERRUN | TMIO_STAT1_RXOVERFLOW | \
TMIO_STAT1_DATATIMEOUT | TMIO_STAT1_STOPBIT_ERR | TMIO_STAT1_CRCFAIL | TMIO_STAT1_CMD_IDX_ERR)
#define TMIO_MASK_READOP (TMIO_STAT1_RXRDY | TMIO_STAT1_DATAEND)
#define TMIO_MASK_WRITEOP (TMIO_STAT1_TXRQ | TMIO_STAT1_DATAEND)

View File

@@ -0,0 +1,153 @@
#include "common.h"
#include <libtwl/sys/twlScfg.h>
#include <libtwl/sys/twlFuse.h>
#include <libtwl/dma/dmaTwl.h>
#include "TwlAes.h"
#define KEY_SLOT_MODULE 0
#define KEY_SLOT_NAND 3
#define MODULE_KEYX_NINT 0x746E694E // Nint
#define MODULE_KEYX_ENDO 0x6F646E65 // endo
#define NAND_KEYX_DSI_XOR_LO 0x24EE6906
#define NAND_KEYX_DSI_XOR_HI 0xE65B601D
#define NAND_KEYX_3DS_NINT 0x544E494E // NINT
#define NAND_KEYX_3DS_ENDO 0x4F444E45 // ENDO
#define NAND_KEYY_WORD_3 0xE1A00005 // mov r0, r5
#define DMA_CHANNEL_AES_OUT 0
#define DMA_CHANNEL_AES_IN 1
void TwlAes::SetupAes(const nds_header_twl_t* romHeader) const
{
// ensure AES is enabled
REG_SCFG_EXT |= SCFG_EXT_AES;
REG_SCFG_CLK |= SCFG_CLK_AES;
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
SetupModuleKeyXY(romHeader);
SetupNandKeyX();
aes_waitKeyBusy();
(&REG_AES_SEED0)[KEY_SLOT_NAND * 3].words[3] = NAND_KEYY_WORD_3;
}
void TwlAes::DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const
{
REG_AES_CNT = 0;
aes_reset();
aes_reset();
aes_waitKeyBusy();
aes_setKeySlot(0);
aes_waitKeyBusy();
u32 offset = 0;
while (length > 0)
{
REG_AES_CNT = 0;
aes_reset();
u32 blockLength = std::min<u32>(length, 0xFFFF0u);
auto ctr = *iv;
((u64*)&ctr)[1] += __builtin_add_overflow(((u64*)&ctr)[0], offset >> 4, &((u64*)&ctr)[0]);
aes_setInitializationVector(&ctr);
aes_setPayloadBlockCount(blockLength >> 4);
LOG_DEBUG("%p\n", (u8*)data + offset);
dma_twl_config_t inputDmaConfig
{
.src = (u8*)data + offset,
.dst = (void*)&REG_AES_IFIFO,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_DST_MODE_FIXED | NDMACNT_SRC_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_IN | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_IN, &inputDmaConfig);
dma_twl_config_t outputDmaConfig
{
.src = (const void*)&REG_AES_OFIFO,
.dst = (u8*)data + offset,
.totalWordCount = blockLength >> 2,
.wordCount = 4,
.blockInterval = NDMABCNT_INTERVAL(8),
.fillData = 0,
.control = NDMACNT_SRC_MODE_FIXED | NDMACNT_DST_MODE_INCREMENT |
NDMACNT_PHYSICAL_COUNT_4 | NDMACNT_MODE_AES_OUT | NDMACNT_ENABLE
};
dma_twlSetParams(DMA_CHANNEL_AES_OUT, &outputDmaConfig);
aes_start(
AES_CNT_INPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_OUTPUT_FIFO_DMA_SIZE_4_BYTES |
AES_CNT_MODE_CTR);
dma_twlWait(DMA_CHANNEL_AES_OUT);
aes_waitBusy();
offset += blockLength;
length -= blockLength;
}
}
void TwlAes::SetupModuleKeyXY(const nds_header_twl_t* romHeader) const
{
if ((romHeader->ntrHeader.twlFlags & (1 << 2)) || (romHeader->twlFlags2 & (1 << 7)))
{
// debug
aes_setKey(KEY_SLOT_MODULE, (const aes_u128_t*)romHeader);
}
else
{
// retail
aes_u128_t keyX { .words =
{
MODULE_KEYX_NINT,
MODULE_KEYX_ENDO,
romHeader->ntrHeader.gameCode,
__builtin_bswap32(romHeader->ntrHeader.gameCode)
}};
aes_setKeyXY(KEY_SLOT_MODULE, &keyX, (const aes_u128_t*)romHeader->arm9iSha1Hmac);
}
}
void TwlAes::SetupNandKeyX() const
{
if ((REG_SCFG_A7ROM & SCFG_A7ROM_DISABLE_FUSE) || (REG_FUSE_VERIFY & FUSE_VERIFY_ERROR))
{
// No access to the console id register. We'll assume nand key x is already setup.
return;
}
u32 consoleIdLo = REG_FUSE_ID0;
u32 consoleIdHi = REG_FUSE_ID1;
aes_u128_t keyX;
keyX.words[0] = consoleIdLo;
if (consoleIdLo & 0x80000000)
{
// 3DS
keyX.words[1] = NAND_KEYX_3DS_NINT;
keyX.words[2] = NAND_KEYX_3DS_ENDO;
}
else
{
// DSi
keyX.words[1] = consoleIdLo ^ NAND_KEYX_DSI_XOR_LO;
keyX.words[2] = consoleIdHi ^ NAND_KEYX_DSI_XOR_HI;
}
keyX.words[3] = consoleIdHi;
aes_setKeyX(KEY_SLOT_NAND, &keyX);
}

View File

@@ -0,0 +1,22 @@
#pragma once
#include <libtwl/aes/aes.h>
#include "ndsHeader.h"
/// @brief Class handling AES crypto for DSi roms.
class TwlAes
{
public:
/// @brief Sets up AES for the DSi rom with the given \p romHeader.
/// @param romHeader The header of the DSi rom to setup AES for.
void SetupAes(const nds_header_twl_t* romHeader) const;
/// @brief Performs modcrypt decryption.
/// @param data The data to decrypt.
/// @param length The length of the data to decrypt.
/// @param iv The AES initialization vector.
void DecryptModuleAes(void* data, u32 length, const aes_u128_t* iv) const;
private:
void SetupModuleKeyXY(const nds_header_twl_t* romHeader) const;
void SetupNandKeyX() const;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#include "common.h"
#define DLDI_MAGIC 0xBF8DA5ED
#define DLDI_DRIVER_MAGIC_NONE 0x49444C44
#define DLDI_FIX_ALL (1 << 0)
#define DLDI_FIX_GLUE (1 << 1)
#define DLDI_FIX_GOT (1 << 2)
#define DLDI_FIX_BSS (1 << 3)
#define DLDI_FEATURE_CANREAD (1 << 0)
#define DLDI_FEATURE_CANWRITE (1 << 1)
#define DLDI_FEATURE_SLOT_GBA (1 << 4)
#define DLDI_FEATURE_SLOT_NDS (1 << 5)
typedef bool (*dldi_startup_func_t)(void);
typedef bool (*dldi_inserted_func_t)(void);
typedef bool (*dldi_read_func_t)(u32 sector, u32 count, void* dst);
typedef bool (*dldi_write_func_t)(u32 sector, u32 count, const void* src);
typedef bool (*dldi_clearstatus_func_t)(void);
typedef bool (*dldi_shutdown_func_t)(void);
/// @brief Struct representing a DLDI header.
struct dldi_header_t
{
u32 dldiMagic;
u8 dldiString[8];
u8 dldiVersion;
u8 driverSize;
u8 fixFlags;
u8 stubSize;
u8 driverName[0x30];
u32 driverStartAddress;
u32 driverEndAddress;
u32 glueStartAddress;
u32 glueEndAddress;
u32 gotStartAddress;
u32 gotEndAddress;
u32 bssStartAddress;
u32 bssEndAddress;
u32 driverMagic;
u32 featureFlags;
u32 startupFuncAddress;
u32 isInsertedFuncAddress;
u32 readSectorsFuncAddress;
u32 writeSectorsFuncAddress;
u32 clearStatusFuncAddress;
u32 shutdownFuncAddress;
};
static_assert(sizeof(dldi_header_t) == 0x80, "Size of dldi_header_t incorrect");

View File

@@ -0,0 +1,43 @@
#include <nds.h>
#include "NitroEmulatorOutputStream.h"
#define ISND_DBGINFO_ADDRESS 0x027FFF60
#define ISND_DBGINFO_AGB_ADDR_OFFSET 0x1C
#define ISND_DBGINFO_AGB_ADDR (*(u32*)(ISND_DBGINFO_ADDRESS + ISND_DBGINFO_AGB_ADDR_OFFSET))
#define ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET 0x90
#define ISND_AGB_PRINT_ARM7_WRITE_PTR_OFFSET 0x92
#define ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET 0x94
#define ISND_AGB_PRINT_ARM7_READ_PTR_OFFSET 0x96
#define ISND_AGB_SOMETHING_OFFSET 0xFE
#define ISND_AGB_PRINT_ARM9_RING_OFFSET 0x8000
#define ISND_AGB_PRINT_ARM7_RING_OFFSET 0xC000
#define ISND_AGB_PRINT_RING_LENGTH 0x4000
NitroEmulatorOutputStream::NitroEmulatorOutputStream()
{
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_SOMETHING_OFFSET) = 0x202;
}
void NitroEmulatorOutputStream::Write(const char* str)
{
char c;
vu16* ring = (vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_RING_OFFSET);
u32 writePtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET);
u32 readPtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET);
while ((c = *str++) != 0)
{
u32 newWritePtr = (writePtr + 1) & (ISND_AGB_PRINT_RING_LENGTH - 1);
while (newWritePtr == readPtr)
{
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET) = writePtr;
readPtr = *(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_READ_PTR_OFFSET);
}
if (writePtr & 1)
ring[writePtr >> 1] = (ring[writePtr >> 1] & 0xFF) | (c << 8);
else
ring[writePtr >> 1] = (ring[writePtr >> 1] & 0xFF00) | c;
writePtr = newWritePtr;
}
*(vu16*)(ISND_DBGINFO_AGB_ADDR + ISND_AGB_PRINT_ARM9_WRITE_PTR_OFFSET) = writePtr;
}

View File

@@ -0,0 +1,11 @@
#pragma once
#include "logger/IOutputStream.h"
class NitroEmulatorOutputStream : public IOutputStream
{
public:
NitroEmulatorOutputStream();
void Write(const char* str) override;
void Flush() override { }
};

View File

@@ -0,0 +1,16 @@
#pragma once
#include "logger/IOutputStream.h"
#define REG_NOCASH_STRING_OUT (*(vu32*)0x04FFFA10)
#define REG_NOCASH_CHAR_OUT (*(vu32*)0x04FFFA1C)
class NocashOutputStream : public IOutputStream
{
public:
void Write(const char* str) override
{
REG_NOCASH_STRING_OUT = (u32)str;
}
void Flush() override { }
};

View File

@@ -0,0 +1,26 @@
#pragma once
#include "core/mini-printf.h"
#include <memory>
#include "logger/ILogger.h"
#include "logger/IOutputStream.h"
class PlainLogger : public ILogger
{
LogLevel _maxLogLevel;
std::unique_ptr<IOutputStream> _outputStream;
char _logBuffer[512];
public:
PlainLogger(LogLevel maxLogLevel, std::unique_ptr<IOutputStream> outputStream)
: _maxLogLevel(maxLogLevel), _outputStream(std::move(outputStream))
{ }
void LogV(LogLevel level, const char* fmt, va_list vlist) override
{
if (level > _maxLogLevel)
return;
mini_vsnprintf(_logBuffer, sizeof(_logBuffer), fmt, vlist);
_outputStream->Write(_logBuffer);
}
};

229
arm7/source/main.cpp Normal file
View File

@@ -0,0 +1,229 @@
#include "common.h"
#include <memory>
#include <string.h>
#include <libtwl/ipc/ipcFifo.h>
#include <libtwl/ipc/ipcSync.h>
#include <libtwl/i2c/i2cMcu.h>
#include <libtwl/sio/sioRtc.h>
#include <libtwl/sound/sound.h>
#include <libtwl/sound/soundChannel.h>
#include <libtwl/sound/soundCapture.h>
#include "core/Environment.h"
#include "logger/NitroEmulatorOutputStream.h"
#include "logger/PicoAgbAdapterOutputStream.h"
#include "logger/NocashOutputStream.h"
#include "logger/NullLogger.h"
#include "logger/PlainLogger.h"
#include "fat/dldi.h"
#include "loader/NdsLoader.h"
#include "sharedMemory.h"
#include "ndsHeader.h"
#include "globalHeap.h"
#include "mmc/tmio.h"
#define HANDSHAKE_PART0 0xA
#define HANDSHAKE_PART1 0xB
#define HANDSHAKE_PART2 0xC
#define HANDSHAKE_PART3 0xD
ILogger* gLogger;
FATFS gFatFs;
static NdsLoader sLoader;
static void initLogger()
{
std::unique_ptr<IOutputStream> outputStream;
if (Environment::IsIsNitroEmulator() && Environment::SupportsAgbSemihosting())
{
outputStream = std::make_unique<NitroEmulatorOutputStream>();
}
// else if (Environment::HasPicoAgbAdapter())
// {
// outputStream = std::make_unique<PicoAgbAdapterOutputStream>();
// }
else if (Environment::SupportsNocashPrint())
{
outputStream = std::make_unique<NocashOutputStream>();
}
else
{
gLogger = new NullLogger();
return;
}
gLogger = new PlainLogger(LogLevel::All, std::move(outputStream));
}
static bool mountDldi()
{
FRESULT res = f_mount(&gFatFs, "fat:", 1);
if (res != FR_OK)
{
LOG_ERROR("dldi mount failed: %d\n", res);
return false;
}
f_chdrive("fat:");
return true;
}
static bool mountDsiSd()
{
FRESULT res = f_mount(&gFatFs, "sd:", 1);
if (res != FR_OK)
{
LOG_ERROR("dsi sd mount failed: %d\n", res);
return false;
}
f_chdrive("sd:");
return true;
}
static bool mountAgbSemihosting()
{
FRESULT res = f_mount(&gFatFs, "pc2:", 1);
if (res != FR_OK)
{
LOG_ERROR("pc2 sd mount failed: %d\n", res);
return false;
}
f_chdrive("pc2:");
return true;
}
extern "C" void __libc_init_array();
static void handleSavePath()
{
if (gLoaderHeader.loadParams.savePath[0] == 0)
{
char* savePath = (char*)gLoaderHeader.loadParams.savePath;
strcpy(savePath, gLoaderHeader.loadParams.romPath);
char* extension = strrchr(savePath, '.');
if (!extension)
extension = &savePath[strlen(savePath)];
extension[0] = '.';
extension[1] = 's';
extension[2] = 'a';
extension[3] = 'v';
extension[4] = 0;
}
sLoader.SetSavePath(gLoaderHeader.loadParams.savePath);
}
static void clearSoundRegisters()
{
REG_SOUNDCNT = 0;
REG_SNDCAP0CNT = 0;
REG_SNDCAP1CNT = 0;
for (int i = 0; i < 16; i++)
{
REG_SOUNDxCNT(i) = 0;
REG_SOUNDxSAD(i) = 0;
REG_SOUNDxTMR(i) = 0;
REG_SOUNDxPNT(i) = 0;
REG_SOUNDxLEN(i) = 0;
}
}
static void initIpc()
{
ipc_clearSendFifo();
ipc_ackFifoError();
ipc_disableRecvFifoNotEmptyIrq();
ipc_enableFifo();
while (!ipc_isRecvFifoEmpty())
{
ipc_recvWordDirect();
}
ipc_setArm7SyncBits(HANDSHAKE_PART0);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART0);
ipc_setArm7SyncBits(HANDSHAKE_PART1);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART1);
ipc_setArm7SyncBits(HANDSHAKE_PART2);
while (ipc_getArm9SyncBits() != HANDSHAKE_PART2);
ipc_setArm7SyncBits(HANDSHAKE_PART3);
while (ipc_getArm9SyncBits() == HANDSHAKE_PART2);
}
extern "C" void loaderMain()
{
__libc_init_array();
clearSoundRegisters();
rtos_initIrq();
rtos_startMainThread();
initIpc();
bool dsiMode = ipc_getArm9SyncBits() == 1;
Environment::Initialize(dsiMode);
heap_init();
initLogger();
rtc_init(); // ensure rtc irqs are disabled
LOG_DEBUG("Pico Loader ARM7 started\n");
if (Environment::IsDsiMode())
{
// Let the mcu handle the power button
mcu_writeReg(MCU_REG_MODE, 0);
TMIO_init();
}
memset(&gFatFs, 0, sizeof(gFatFs));
bool multiboot = (gLoaderHeader.bootDrive & PLOAD_BOOT_DRIVE_MULTIBOOT_FLAG) != 0;
gLoaderHeader.bootDrive &= ~PLOAD_BOOT_DRIVE_MULTIBOOT_FLAG;
switch (gLoaderHeader.bootDrive)
{
case PLOAD_BOOT_DRIVE_DLDI:
{
if (dldi_init())
{
mountDldi();
}
break;
}
case PLOAD_BOOT_DRIVE_DSI_SD:
{
if (Environment::IsDsiMode())
{
mountDsiSd();
}
break;
}
case PLOAD_BOOT_DRIVE_AGB_SEMIHOSTING:
{
if (Environment::SupportsAgbSemihosting())
{
mountAgbSemihosting();
}
break;
}
}
if (multiboot)
{
LOG_DEBUG("Multiboot\n");
sLoader.Load(BootMode::Multiboot);
}
else if (((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader)->arm7EntryAddress == (u32)gLoaderHeader.entryPoint)
{
LOG_DEBUG("Retail soft reset detected\n");
u32 originalArm7EntryAddress = ((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.cardRomHeader)->arm7EntryAddress;
((nds_header_ntr_t*)TWL_SHARED_MEMORY->ntrSharedMem.romHeader)->arm7EntryAddress = originalArm7EntryAddress;
sLoader.Load(BootMode::SdkResetSystem);
}
else
{
sLoader.SetRomPath(gLoaderHeader.loadParams.romPath);
handleSavePath();
sLoader.SetArguments(gLoaderHeader.loadParams.arguments, gLoaderHeader.loadParams.argumentsLength);
sLoader.Load(BootMode::Normal);
}
while (true);
}

235
arm7/source/mmc/mmc_spec.h Normal file
View File

@@ -0,0 +1,235 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Based on JEDEC eMMC Card Product Standard V4.41.
#include "tmio.h"
// Controller specific macros. Add controller specific bits here.
// MMC_CMD_[response type]_[transfer type]
// Transfer type: R = read, W = write.
#define MMC_CMD_NONE(id) (CMD_RESP_NONE | (id))
#define MMC_CMD_R1(id) (CMD_RESP_R1 | (id))
#define MMC_CMD_R1b(id) (CMD_RESP_R1b | (id))
#define MMC_CMD_R2(id) (CMD_RESP_R2 | (id))
#define MMC_CMD_R3(id) (CMD_RESP_R3 | (id))
#define MMC_CMD_R4(id) (CMD_RESP_R4 | (id))
#define MMC_CMD_R5(id) (CMD_RESP_R5 | (id))
#define MMC_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define MMC_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
// Basic commands and read-stream command (class 0 and class 1).
#define MMC_GO_IDLE_STATE MMC_CMD_NONE(0u) // -, [31:0] 0x00000000 GO_IDLE_STATE, 0xF0F0F0F0 GO_PRE_IDLE_STATE, 0xFFFFFFFA BOOT_INITIATION.
#define MMC_SEND_OP_COND MMC_CMD_R3(1u) // R3, [31:0] OCR with-out busy.
#define MMC_ALL_SEND_CID MMC_CMD_R2(2u) // R2, [31:0] stuff bits.
#define MMC_SET_RELATIVE_ADDR MMC_CMD_R1(3u) // R1, [31:16] RCA [15:0] stuff bits.
#define MMC_SET_DSR MMC_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
#define MMC_SLEEP_AWAKE MMC_CMD_R1b(5u) // R1b, [31:16] RCA [15] Sleep/Awake [14:0] stuff bits.
#define MMC_SWITCH MMC_CMD_R1b(6u) // R1b, [31:26] Set to 0 [25:24] Access [23:16] Index [15:8] Value [7:3] Set to 0 [2:0] Cmd Set.
#define MMC_SELECT_CARD MMC_CMD_R1b(7u) // R1/R1b, [31:16] RCA [15:0] stuff bits. Note: "R1b while selecting from Disconnected State to Programming State."
#define MMC_DESELECT_CARD MMC_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
#define MMC_SEND_EXT_CSD MMC_CMD_R1_R(8u) // R1, [31:0] stuff bits.
#define MMC_SEND_CSD MMC_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
#define MMC_SEND_CID MMC_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
#define MMC_READ_DAT_UNTIL_STOP MMC_CMD_R1_R(11u) // R1, [31:0] data address.
#define MMC_STOP_TRANSMISSION MMC_CMD_R1b(12u) // R1/R1b, [31:16] RCA [15:1] stuff bits [0] HPI. Note: "RCA in CMD12 is used only if HPI bit is set." Note 2: "R1 for read cases and R1b for write cases."
#define MMC_SEND_STATUS MMC_CMD_R1(13u) // R1, [31:16] RCA [15:1] stuff bits [0] HPI.
#define MMC_BUSTEST_R MMC_CMD_R1_R(14u) // R1, [31:0] stuff bits.
#define MMC_GO_INACTIVE_STATE MMC_CMD_NONE(15u) // -, [31:16] RCA [15:0] stuff bits.
#define MMC_BUSTEST_W MMC_CMD_R1_W(19u) // R1, [31:0] stuff bits.
// Block-oriented read commands (class 2).
#define MMC_SET_BLOCKLEN MMC_CMD_R1(16u) // R1, [31:0] block length.
#define MMC_READ_SINGLE_BLOCK MMC_CMD_R1_R(17u) // R1, [31:0] data address.
#define MMC_READ_MULTIPLE_BLOCK MMC_CMD_R1_R(18u) // R1, [31:0] data address.
// Stream write commands (class 3).
#define MMC_WRITE_DAT_UNTIL_STOP MMC_CMD_R1_W(20u) // R1, [31:0] data address.
// Block-oriented write commands (class 4).
#define MMC_SET_BLOCK_COUNT MMC_CMD_R1(23u) // R1, [31] Reliable Write Request [30:16] set to 0 [15:0] number of blocks.
#define MMC_WRITE_BLOCK MMC_CMD_R1_W(24u) // R1, [31:0] data address.
#define MMC_WRITE_MULTIPLE_BLOCK MMC_CMD_R1_W(25u) // R1, [31:0] data address.
#define MMC_PROGRAM_CID MMC_CMD_R1_W(26u) // R1, [31:0] stuff bits.
#define MMC_PROGRAM_CSD MMC_CMD_R1_W(27u) // R1, [31:0] stuff bits.
// Block-oriented write protection commands (class 6).
#define MMC_SET_WRITE_PROT MMC_CMD_R1b(28u) // R1b, [31:0] data address.
#define MMC_CLR_WRITE_PROT MMC_CMD_R1b(29u) // R1b, [31:0] data address.
#define MMC_SEND_WRITE_PROT MMC_CMD_R1_R(30u) // R1, [31:0] write protect data address.
#define MMC_SEND_WRITE_PROT_TYPE MMC_CMD_R1_R(31u) // R1, [31:0] write protect data address.
// Erase commands (class 5).
#define MMC_ERASE_GROUP_START MMC_CMD_R1(35u) // R1, [31:0] data address.
#define MMC_ERASE_GROUP_END MMC_CMD_R1(36u) // R1, [31:0] data address.
#define MMC_ERASE MMC_CMD_R1b(38u) // R1b, [31] Secure request [30:16] set to 0 [15] Force Garbage Collect request [14:1] set to 0 [0] Identify Write block for Erase.
// I/O mode commands (class 9).
#define MMC_FAST_IO MMC_CMD_R4(39u) // R4, [31:16] RCA [15:15] register write flag [14:8] register address [7:0] register data.
#define MMC_GO_IRQ_STATE MMC_CMD_R5(40u) // R5, [31:0] stuff bits.
// Lock card commands (class 7).
#define MMC_LOCK_UNLOCK MMC_CMD_R1_W(42u) // R1, [31:0] stuff bits.
// Application-specific commands (class 8).
#define MMC_APP_CMD MMC_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
#define MMC_GEN_CMD_R MMC_CMD_R1_R(56u) // R1, [31:1] stuff bits [0] RD/WR = 1.
#define MMC_GEN_CMD_W MMC_CMD_R1_W(56u) // R1, [31:1] stuff bits [0] RD/WR = 0.
// 7.13 Card status.
// Type:
// E: Error bit.
// S: Status bit.
// R: Detected and set for the actual command response.
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
//
// Clear Condition:
// A: These bits are persistent, they are set and cleared in accordance with the card status.
// B: These bits are cleared as soon as the response (reporting the error) is sent out.
#define MMC_R1_APP_CMD (1u<<5) // S R A, The card will expect ACMD, or indication that the command has been interpreted as ACMD.
#define MMC_R1_URGENT_BKOPS (1u<<6) // S R A, If set, device needs to perform backgroundoperations urgently. Host can check EXT_CSD field BKOPS_STATUS for the detailed level.
#define MMC_R1_SWITCH_ERROR (1u<<7) // E X B, If set, the card did not switch to the expected mode as requested by the SWITCH command.
#define MMC_R1_READY_FOR_DATA (1u<<8) // S R A, Corresponds to buffer empty signalling on the bus.
#define MMC_R1_STATE_IDLE (0u<<9) // S R A
#define MMC_R1_STATE_READY (1u<<9) // S R A
#define MMC_R1_STATE_IDENT (2u<<9) // S R A
#define MMC_R1_STATE_STBY (3u<<9) // S R A
#define MMC_R1_STATE_TRAN (4u<<9) // S R A
#define MMC_R1_STATE_DATA (5u<<9) // S R A
#define MMC_R1_STATE_RCV (6u<<9) // S R A
#define MMC_R1_STATE_PRG (7u<<9) // S R A
#define MMC_R1_STATE_DIS (8u<<9) // S R A
#define MMC_R1_STATE_BTST (9u<<9) // S R A
#define MMC_R1_STATE_SLP (10u<<9) // S R A
#define MMC_R1_ERASE_RESET (1u<<13) // E R B, An erase sequence was cleared before executing because an out of erase sequence command was received (commands other than CMD35, CMD36, CMD38 or CMD13.
#define MMC_R1_WP_ERASE_SKIP (1u<<15) // E X B, Only partial address space was erased due to existing write protected blocks.
#define MMC_R1_CXD_OVERWRITE (1u<<16) // E X B, Can be either one of the following errors: - The CID register has been already written and can not be overwritten - The read only section of the CSD does not match the card content. - An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
#define MMC_R1_OVERRUN (1u<<17) // E X B, The card could not sustain data programming in stream write mode.
#define MMC_R1_UNDERRUN (1u<<18) // E X B, The card could not sustain data transfer in stream read mode.
#define MMC_R1_ERROR (1u<<19) // E X B, (Undefined by the standard) A generic card error related to the (and detected during) execution of the last host command (e.g. read or write failures).
#define MMC_R1_CC_ERROR (1u<<20) // E R B, (Undefined by the standard) A card error occurred, which is not related to the host command.
#define MMC_R1_CARD_ECC_FAILED (1u<<21) // E X B, Card internal ECC was applied but failed to correct the data.
#define MMC_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
#define MMC_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
#define MMC_R1_LOCK_UNLOCK_FAILED (1u<<24) // E X B, Set when a sequence or password error has been detected in lock/unlock card command.
#define MMC_R1_CARD_IS_LOCKED (1u<<25) // S R A, When set, signals that the card is locked by the host.
#define MMC_R1_WP_VIOLATION (1u<<26) // E X B, Attempt to program a write protected block.
#define MMC_R1_ERASE_PARAM (1u<<27) // E X B, An invalid selection of erase groups for erase occurred.
#define MMC_R1_ERASE_SEQ_ERROR (1u<<28) // E R B, An error in the sequence of erase commands occurred.
#define MMC_R1_BLOCK_LEN_ERROR (1u<<29) // E R B, Either the argument of a SET_BLOCKLEN command exceeds the maximum value allowed for the card, or the previously defined block length is illegal for the current command (e.g. the host issues a write command, the current block length is smaller than the cards maximum and write partial blocks is not allowed).
#define MMC_R1_ADDRESS_MISALIGN (1u<<30) // E R/X B, The command s address argument (in accordance with the currently set block length) positions the first data block misaligned to the card physical blocks. A multiple block read/write operation (although started with a valid address/blocklength combination) is attempting to read or write a data block which does not align with the physical blocks of the card.
#define MMC_R1_ADDRESS_OUT_OF_RANGE (1u<<31) // E R/X B, The commands address argument was out of the allowed range for this card. A multiple block or stream read/write operation is (although started in a valid address) attempting to read or write beyond the card capacity.
#define MMC_R1_ERR_ALL (MMC_R1_ADDRESS_OUT_OF_RANGE | MMC_R1_ADDRESS_MISALIGN | \
MMC_R1_BLOCK_LEN_ERROR | MMC_R1_ERASE_SEQ_ERROR | \
MMC_R1_ERASE_PARAM | MMC_R1_WP_VIOLATION | MMC_R1_LOCK_UNLOCK_FAILED | \
MMC_R1_COM_CRC_ERROR | MMC_R1_ILLEGAL_COMMAND | MMC_R1_CARD_ECC_FAILED | \
MMC_R1_CC_ERROR | MMC_R1_ERROR | MMC_R1_UNDERRUN | MMC_R1_OVERRUN | \
MMC_R1_CXD_OVERWRITE | MMC_R1_WP_ERASE_SKIP | MMC_R1_ERASE_RESET | \
MMC_R1_SWITCH_ERROR)
// 8.1 OCR register.
// Same bits for CMD1 argument.
#define MMC_OCR_1_7_1_95V (1u<<7) // 1.701.95V.
#define MMC_OCR_2_0_2_1V (1u<<8) // 2.0-2.1V.
#define MMC_OCR_2_1_2_2V (1u<<9) // 2.1-2.2V.
#define MMC_OCR_2_2_2_3V (1u<<10) // 2.2-2.3V.
#define MMC_OCR_2_3_2_4V (1u<<11) // 2.3-2.4V.
#define MMC_OCR_2_4_2_5V (1u<<12) // 2.4-2.5V.
#define MMC_OCR_2_5_2_6V (1u<<13) // 2.5-2.6V.
#define MMC_OCR_2_6_2_7V (1u<<14) // 2.6-2.7V.
#define MMC_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
#define MMC_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
#define MMC_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
#define MMC_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
#define MMC_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
#define MMC_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
#define MMC_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
#define MMC_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
#define MMC_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
#define MMC_OCR_BYTE_MODE (0u<<29) // Access mode = byte mode.
#define MMC_OCR_SECT_MODE (2u<<29) // Access mode = sector mode.
#define MMC_OCR_READY (1u<<31) // Card power up status bit (busy). 0 = busy.
// 7.6.1 Command sets and extended settings.
#define MMC_SWITCH_ACC_CMD_SET (0u)
#define MMC_SWITCH_ACC_SET_BITS (1u)
#define MMC_SWITCH_ACC_CLR_BITS (2u)
#define MMC_SWITCH_ACC_WR_BYTE (3u)
#define MMC_SWITCH_ARG(acc, idx, val, cmdSet) (((acc)&3u)<<24 | ((idx)&0xFFu)<<16 | ((val)&0xFFu)<<8 | ((cmdSet)&7u))
// 8.4 Extended CSD register.
// size in bytes, access, description.
#define EXT_CSD_SEC_BAD_BLK_MGMNT (134u) // 1, R/W, Bad Block Management mode.
#define EXT_CSD_ENH_START_ADDR (136u) // 4, R/W, Enhanced User Data Start Address.
#define EXT_CSD_ENH_SIZE_MULT (140u) // 3, R/W, Enhanced User Data Area Size.
#define EXT_CSD_GP_SIZE_MULT (143u) // 12, R/W, General Purpose Partition Size.
#define EXT_CSD_PARTITION_SETTING_COMPLETED (155u) // 1, R/W, Paritioning Setting.
#define EXT_CSD_PARTITIONS_ATTRIBUTE (156u) // 1, R/W, Partitions attribute.
#define EXT_CSD_MAX_ENH_SIZE_MULT (157u) // 3, R, Max Enhanced Area Size.
#define EXT_CSD_PARTITIONING_SUPPORT (160u) // 1, R, Partitioning Support.
#define EXT_CSD_HPI_MGMT (161u) // 1, R/W/E_P, HPI management.
#define EXT_CSD_RST_n_FUNCTION (162u) // 1, R/W, H/W reset function.
#define EXT_CSD_BKOPS_EN (163u) // 1, R/W, Enable background operations handshake.
#define EXT_CSD_BKOPS_START (164u) // 1, W/E_P, Manually start background operations.
#define EXT_CSD_WR_REL_PARAM (166u) // 1, R, Write reliability parameter register.
#define EXT_CSD_WR_REL_SET (167u) // 1, R/W, Write reliability setting register.
#define EXT_CSD_RPMB_SIZE_MULT (168u) // 1, R, RPMB Size.
#define EXT_CSD_FW_CONFIG (169u) // 1, R/W, FW configuration.
#define EXT_CSD_USER_WP (171u) // 1, R/W, R/W/C_P & R/W/E_P, User area write protection register.
#define EXT_CSD_BOOT_WP (173u) // 1, R/W & R/W/C_P, Boot area write protection register.
#define EXT_CSD_ERASE_GROUP_DEF (175u) // 1, R/W/E_P, High-density erase group definition.
#define EXT_CSD_BOOT_BUS_WIDTH (177u) // 1, R/W/E, Boot bus width1.
#define EXT_CSD_BOOT_CONFIG_PROT (178u) // 1, R/W & R/W/C_P, Boot config protection.
#define EXT_CSD_PARTITION_CONFIG (179u) // 1, R/W/E & R/W/E_P, Partition configuration.
#define EXT_CSD_ERASED_MEM_CONT (181u) // 1, R, Erased memory content.
#define EXT_CSD_BUS_WIDTH (183u) // 1, W/E_P, Bus width mode.
#define EXT_CSD_HS_TIMING (185u) // 1, R/W/E_P, High-speed interface timing.
#define EXT_CSD_POWER_CLASS (187u) // 1, R/W/E_P, Power class.
#define EXT_CSD_CMD_SET_REV (189u) // 1, R, Command set revision.
#define EXT_CSD_CMD_SET (191u) // 1, R/W/E_P, Command set.
#define EXT_CSD_EXT_CSD_REV (192u) // 1, R, Extended CSD revision.
#define EXT_CSD_CSD_STRUCTURE (194u) // 1, R, CSD structure version.
#define EXT_CSD_CARD_TYPE (196u) // 1, R, Card type.
#define EXT_CSD_OUT_OF_INTERRUPT_TIME (198u) // 1, R, Out-of-interrupt busy timing.
#define EXT_CSD_PARTITION_SWITCH_TIME (199u) // 1, R, Partition switching timing.
#define EXT_CSD_PWR_CL_52_195 (200u) // 1, R, Power class for 52MHz at 1.95V.
#define EXT_CSD_PWR_CL_26_195 (201u) // 1, R, Power class for 26MHz at 1.95V.
#define EXT_CSD_PWR_CL_52_360 (202u) // 1, R, Power class for 52MHz at 3.6V.
#define EXT_CSD_PWR_CL_26_360 (203u) // 1, R, Power class for 26MHz at 3.6V.
#define EXT_CSD_MIN_PERF_R_4_26 (205u) // 1, R, Minimum Read Performance for 4bit at 26MHz.
#define EXT_CSD_MIN_PERF_W_4_26 (206u) // 1, R, Minimum Write Performance for 4bit at 26MHz.
#define EXT_CSD_MIN_PERF_R_8_26_4_52 (207u) // 1, R, Minimum Read Performance for 8bit at 26MHz, for 4bit at 52MHz.
#define EXT_CSD_MIN_PERF_W_8_26_4_52 (208u) // 1, R, Minimum Write Performance for 8bit at 26MHz, for 4bit at 52MHz.
#define EXT_CSD_MIN_PERF_R_8_52 (209u) // 1, R, Minimum Read Performance for 8bit at 52MHz.
#define EXT_CSD_MIN_PERF_W_8_52 (210u) // 1, R, Minimum Write Performance for 8bit at 52MHz.
#define EXT_CSD_SEC_COUNT (212u) // 4, R, Sector Count.
#define EXT_CSD_S_A_TIMEOUT (217u) // 1, R, Sleep/awake timeout.
#define EXT_CSD_S_C_VCCQ (219u) // 1, R, Sleep current (VCCQ).
#define EXT_CSD_S_C_VCC (220u) // 1, R, Sleep current (VCC).
#define EXT_CSD_HC_WP_GRP_SIZE (221u) // 1, R, High-capacity write protect group size.
#define EXT_CSD_REL_WR_SEC_C (222u) // 1, R, Reliable write sector count.
#define EXT_CSD_ERASE_TIMEOUT_MULT (223u) // 1, R, High-capacity erase timeout.
#define EXT_CSD_HC_ERASE_GRP_SIZE (224u) // 1, R, High-capacity erase unit size.
#define EXT_CSD_ACC_SIZE (225u) // 1, R, Access size.
#define EXT_CSD_BOOT_SIZE_MULTI (226u) // 1, R, Boot partition size.
#define EXT_CSD_BOOT_INFO (228u) // 1, R, Boot information.
#define EXT_CSD_SEC_TRIM_MULT (229u) // 1, R, Secure TRIM Multiplier.
#define EXT_CSD_SEC_ERASE_MULT (230u) // 1, R, Secure Erase Multiplier.
#define EXT_CSD_SEC_FEATURE_SUPPORT (231u) // 1, R, Secure Feature support.
#define EXT_CSD_TRIM_MULT (232u) // 1, R, TRIM Multiplier.
#define EXT_CSD_MIN_PERF_DDR_R_8_52 (234u) // 1, R, Minimum Read Performance for 8bit at 52MHz in DDR mode.
#define EXT_CSD_MIN_PERF_DDR_W_8_52 (235u) // 1, R, Minimum Write Performance for 8bit at 52MHz in DDR mode.
#define EXT_CSD_PWR_CL_DDR_52_195 (238u) // 1, R, Power class for 52MHz, DDR at 1.95V.
#define EXT_CSD_PWR_CL_DDR_52_360 (239u) // 1, R, Power class for 52MHz, DDR at 3.6V.
#define EXT_CSD_INI_TIMEOUT_AP (241u) // 1, R, 1st initialization time after partitioning.
#define EXT_CSD_CORRECTLY_PRG_SECTORS_NUM (242u) // 4, R, Number of correctly programmed sectors.
#define EXT_CSD_BKOPS_STATUS (246u) // 1, R, Background operations status.
#define EXT_CSD_BKOPS_SUPPORT (502u) // 1, R, Background operations support.
#define EXT_CSD_HPI_FEATURES (503u) // 1, R, HPI features.
#define EXT_CSD_S_CMD_SET (504u) // 1, R, Supported Command Sets.

192
arm7/source/mmc/sd_spec.h Normal file
View File

@@ -0,0 +1,192 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Based on SD specification version 8.00.
#include "tmio.h"
// Controller specific macros. Add controller specific bits here.
// SD_[command type]_[response type]_[transfer type]
// Command type: CMD = regular command, ACMD = Application-Specific Command.
// Transfer type: R = read, W = write.
#define SD_CMD_NONE(id) (CMD_RESP_NONE | (id))
#define SD_CMD_R1(id) (CMD_RESP_R1 | (id))
#define SD_CMD_R1b(id) (CMD_RESP_R1b | (id))
#define SD_CMD_R2(id) (CMD_RESP_R2 | (id))
#define SD_CMD_R6(id) (CMD_RESP_R6 | (id))
#define SD_CMD_R7(id) (CMD_RESP_R7 | (id))
#define SD_CMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define SD_CMD_R1_W(id) (CMD_DATA_W | CMD_DATA_EN | CMD_RESP_R1 | (id))
#define SD_ACMD_R1(id) (CMD_RESP_R1 | CMD_ACMD | (id))
#define SD_ACMD_R3(id) (CMD_RESP_R3 | CMD_ACMD | (id))
#define SD_ACMD_R1_R(id) (CMD_DATA_R | CMD_DATA_EN | CMD_RESP_R1 | CMD_ACMD | (id))
// Basic Commands (class 0).
#define SD_GO_IDLE_STATE SD_CMD_NONE(0u) // -, [31:0] stuff bits.
#define SD_ALL_SEND_CID SD_CMD_R2(2u) // R2, [31:0] stuff bits.
#define SD_SEND_RELATIVE_ADDR SD_CMD_R6(3u) // R6, [31:0] stuff bits.
#define SD_SET_DSR SD_CMD_NONE(4u) // -, [31:16] DSR [15:0] stuff bits.
#define SD_SELECT_CARD SD_CMD_R1b(7u) // R1b, [31:16] RCA [15:0] stuff bits.
#define SD_DESELECT_CARD SD_CMD_NONE(7u) // -, [31:16] RCA [15:0] stuff bits.
#define SD_SEND_IF_COND SD_CMD_R7(8u) // R7, [31:12] reserved bits [11:8] supply voltage (VHS) [7:0] check pattern.
#define SD_SEND_CSD SD_CMD_R2(9u) // R2, [31:16] RCA [15:0] stuff bits.
#define SD_SEND_CID SD_CMD_R2(10u) // R2, [31:16] RCA [15:0] stuff bits.
#define SD_VOLTAGE_SWITCH SD_CMD_R1(11u) // R1, [31:0] reserved bits (all 0).
#define SD_STOP_TRANSMISSION SD_CMD_R1b(12u) // R1b, [31:0] stuff bits.
#define SD_SEND_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
#define SD_SEND_TASK_STATUS SD_CMD_R1(13u) // R1, [31:16] RCA [15] Send Task Status Register [14:0] stuff bits.
#define SD_GO_INACTIVE_STATE SD_CMD_NONE(15u) // -, [31:16] RCA [15:0] reserved bits.
// Block-Oriented Read Commands (class 2).
#define SD_SET_BLOCKLEN SD_CMD_R1(16u) // R1, [31:0] block length.
#define SD_READ_SINGLE_BLOCK SD_CMD_R1_R(17u) // R1, [31:0] data address.
#define SD_READ_MULTIPLE_BLOCK SD_CMD_R1_R(18u) // R1, [31:0] data address.
#define SD_SEND_TUNING_BLOCK SD_CMD_R1_R(19u) // R1, [31:0] reserved bits (all 0).
#define SD_SPEED_CLASS_CONTROL SD_CMD_R1b(20u) // R1b, [31:28] Speed Class Control [27:0] See command description.
#define SD_ADDRESS_EXTENSION SD_CMD_R1(22u) // R1, [31:6] reserved bits (all 0) [5:0] extended address.
#define SD_SET_BLOCK_COUNT SD_CMD_R1(23u) // R1, [31:0] Block Count.
// Block-Oriented Write Commands (class 4).
// SET_BLOCKLEN
// SPEED_CLASS_CONTROL
// ADDRESS_EXTENSION
// SET_BLOCK_COUNT
#define SD_WRITE_BLOCK SD_CMD_R1_W(24u) // R1, [31:0] data address.
#define SD_WRITE_MULTIPLE_BLOCK SD_CMD_R1_W(25u) // R1, [31:0] data address.
#define SD_PROGRAM_CSD SD_CMD_R1_W(27u) // R1, [31:0] stuff bits.
// Block Oriented Write Protection Commands (class 6).
#define SD_SET_WRITE_PROT SD_CMD_R1b(28u) // R1b, [31:0] data address.
#define SD_CLR_WRITE_PROT SD_CMD_R1b(29u) // R1b, [31:0] data address.
#define SD_SEND_WRITE_PROT SD_CMD_R1_R(30u) // R1, [31:0] write protect data address.
// Erase Commands (class 5).
#define SD_ERASE_WR_BLK_START SD_CMD_R1(32u) // R1, [31:0] data address.
#define SD_ERASE_WR_BLK_END SD_CMD_R1(33u) // R1, [31:0] data address.
#define SD_ERASE SD_CMD_R1b(38u) // R1b, [31:0] Erase Function.
// Lock Card (class 7).
// SET_BLOCKLEN
// Command 40 "Defined by DPS Spec.".
#define SD_LOCK_UNLOCK SD_CMD_R1_W(42u) // R1, [31:0] Reserved bits (Set all 0).
// Application-Specific Commands (class 8).
#define SD_APP_CMD SD_CMD_R1(55u) // R1, [31:16] RCA [15:0] stuff bits.
#define SD_GEN_CMD_R SD_CMD_R1_R(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 1.
#define SD_GEN_CMD_W SD_CMD_R1_W(56u) // R1, [31:1] stuff bits. [0]: RD/WR = 0.
// Application Specific Commands used/reserved by SD Memory Card.
#define SD_APP_SET_BUS_WIDTH SD_ACMD_R1(6u) // R1, [31:2] stuff bits [1:0] bus width.
#define SD_APP_SD_STATUS SD_ACMD_R1_R(13u) // R1, [31:0] stuff bits.
#define SD_APP_SEND_NUM_WR_BLOCKS SD_ACMD_R1_R(22u) // R1, [31:0] stuff bits.
#define SD_APP_SET_WR_BLK_ERASE_COUNT SD_ACMD_R1(23u) // R1, [31:23] stuff bits [22:0] Number of blocks.
#define SD_APP_SD_SEND_OP_COND SD_ACMD_R3(41u) // R3, [31] reserved bit [30] HCS (OCR[30]) [29] reserved for eSD [28] XPC [27:25] reserved bits [24] S18R [23:0] VDD Voltage Window (OCR[23:0]).
#define SD_APP_SET_CLR_CARD_DETECT SD_ACMD_R1(42u) // R1, [31:1] stuff bits [0] set_cd.
#define SD_APP_SEND_SCR SD_ACMD_R1_R(51u) // R1, [31:0] stuff bits.
// Switch Function Commands (class 10).
#define SD_SWITCH_FUNC SD_CMD_R1_R(6u) // R1, [31] Mode 0: Check function 1: Switch function [30:24] reserved (All '0') [23:20] reserved for function group 6 (0h or Fh) [19:16] reserved for function group 5 (0h or Fh) [15:12] function group 4 for PowerLimit [11:8] function group 3 for Drive Strength [7:4] function group 2 for Command System [3:0] function group 1 for Access Mode.
// Function Extension Commands (class 11).
#define SD_READ_EXTR_SINGLE SD_CMD_R1_R(48u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO[26] Reserved (=0) [25:9] ADDR [8:0] LEN.
#define SD_WRITE_EXTR_SINGLE SD_CMD_R1_W(49u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] MW [25:9] ADDR [8:0] LEN/MASK.
#define SD_READ_EXTR_MULTI SD_CMD_R1_R(58u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
#define SD_WRITE_EXTR_MULTI SD_CMD_R1_W(59u) // R1, [31] MIO0: Memory, 1: I/O [30:27] FNO [26] BUS0: 512B, 1: 32KB [25:9] ADDR [8:0] BUC.
// Command Queue Function Commands (class 1).
#define SD_Q_MANAGEMENT SD_CMD_R1b(43u) // R1b, [31:21] Reserved [20:16]: Task ID [3:0]: Operation Code (Abort tasks etc.).
#define SD_Q_TASK_INFO_A SD_CMD_R1(44u) // R1, [31] Reserved [30] Direction [29:24] Extended Address [23] Priority [22:21] Reserved [20:16] Task ID [15:0] Number of Blocks.
#define SD_Q_TASK_INFO_B SD_CMD_R1(45u) // R1, [31:0] Start block address.
#define SD_Q_RD_TASK SD_CMD_R1_R(46u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
#define SD_Q_WR_TASK SD_CMD_R1_W(47u) // R1, [31:21] Reserved [20:16] Task ID [15:0] Reserved.
// 4.10.1 Card Status.
// Type:
// E: Error bit.
// S: Status bit.
// R: Detected and set for the actual command response.
// X: Detected and set during command execution. The host can get the status by issuing a command with R1 response.
//
// Clear Condition:
// A: According to the card current status.
// B: Always related to the previous command. Reception of a valid command will clear it (with a delay of one command).
// C: Clear by read.
#define SD_R1_AKE_SEQ_ERROR (1u<<3) // E R C, Error in the sequence of the authentication process.
#define SD_R1_APP_CMD (1u<<5) // S R C, The card will expect ACMD, or an indication that the command has been interpreted as ACMD.
#define SD_R1_FX_EVENT (1u<<6) // S X A, ExtensionFunctions may set this bit to get host to deal with events.
#define SD_R1_READY_FOR_DATA (1u<<8) // S X A, Corresponds to buffer empty signaling on the bus.
#define SD_R1_STATE_IDLE (0u<<9) // S X B
#define SD_R1_STATE_READY (1u<<9) // S X B
#define SD_R1_STATE_IDENT (2u<<9) // S X B
#define SD_R1_STATE_STBY (3u<<9) // S X B
#define SD_R1_STATE_TRAN (4u<<9) // S X B
#define SD_R1_STATE_DATA (5u<<9) // S X B
#define SD_R1_STATE_RCV (6u<<9) // S X B
#define SD_R1_STATE_PRG (7u<<9) // S X B
#define SD_R1_STATE_DIS (8u<<9) // S X B
#define SD_R1_ERASE_RESET (1u<<13) // S R C, An erase sequence was cleared before executing because an out of erase sequence command was received.
#define SD_R1_CARD_ECC_DISABLED (1u<<14) // S X A, The command has been executed without using the internal ECC.
#define SD_R1_WP_ERASE_SKIP (1u<<15) // E R X C, Set when only partial address space was erased due to existing write protected blocks or the temporary or permanent write protected cardwas erased.
#define SD_R1_CSD_OVERWRITE (1u<<16) // E R X C, Can be either one of the following errors: -The read only section of the CSD does not match the card content. -An attempt to reverse the copy (set as original) or permanent WP (unprotected) bits was made.
// 17 reserved for DEFERRED_RESPONSE (Refer to eSD Addendum)
#define SD_R1_ERROR (1u<<19) // E R X C, A general or an unknown error occurred during the operation.
#define SD_R1_CC_ERROR (1u<<20) // E R X C, Internal card controller error:
#define SD_R1_CARD_ECC_FAILED (1u<<21) // E R X C, Card internal ECC was applied but failed to correct the data.
#define SD_R1_ILLEGAL_COMMAND (1u<<22) // E R B, Command not legal for the card state.
#define SD_R1_COM_CRC_ERROR (1u<<23) // E R B, The CRC check of the previous command failed.
#define SD_R1_LOCK_UNLOCK_FAILED (1u<<24) // E R X C, Set when a sequence or password error has been detected in lock/unlock card command.
#define SD_R1_CARD_IS_LOCKED (1u<<25) // S X A, When set, signals that the card is locked by the host.
#define SD_R1_WP_VIOLATION (1u<<26) // E R X C, Set when the host attempts to write to a protected block or to thetemporary or permanent write protected card.
#define SD_R1_ERASE_PARAM (1u<<27) // E R X C, An invalid selection of write-blocks for erase occurred.
#define SD_R1_ERASE_SEQ_ERROR (1u<<28) // E R C, An error in the sequence of erase commands occurred.
#define SD_R1_BLOCK_LEN_ERROR (1u<<29) // E R X C, The transferred block length is not allowed for this card, or the number of transferred bytes does not match the block length.
#define SD_R1_ADDRESS_ERROR (1u<<30) // E R X C, A misaligned address which did not match the block length was used in the command.
#define SD_R1_OUT_OF_RANGE (1u<<31) // E R X C, The command's argument was out of the allowed range for this card.
#define SD_R1_ERR_ALL (SD_R1_OUT_OF_RANGE | SD_R1_ADDRESS_ERROR | SD_R1_BLOCK_LEN_ERROR | \
SD_R1_ERASE_SEQ_ERROR | SD_R1_ERASE_PARAM | SD_R1_WP_VIOLATION | \
SD_R1_LOCK_UNLOCK_FAILED | SD_R1_COM_CRC_ERROR | SD_R1_ILLEGAL_COMMAND | \
SD_R1_CARD_ECC_FAILED | SD_R1_CC_ERROR | SD_R1_ERROR | \
SD_R1_CSD_OVERWRITE | SD_R1_WP_ERASE_SKIP | SD_R1_AKE_SEQ_ERROR)
// Argument bits for SEND_IF_COND (CMD8).
#define SD_CMD8_CHK_PATT (0xAAu) // Check pattern.
#define SD_CMD8_VHS_2_7_3_6V (1u<<8) // Voltage supplied (VHS) 2.7-3.6V.
#define SD_CMD8_PCIe (1u<<12) // PCIe Avail-ability.
#define SD_CMD8_PCIe_1_2V (1u<<13) // PCIe 1.2V Support.
// 5.1 OCR register.
#define SD_OCR_2_7_2_8V (1u<<15) // 2.7-2.8V.
#define SD_OCR_2_8_2_9V (1u<<16) // 2.8-2.9V.
#define SD_OCR_2_9_3_0V (1u<<17) // 2.9-3.0V.
#define SD_OCR_3_0_3_1V (1u<<18) // 3.0-3.1V.
#define SD_OCR_3_1_3_2V (1u<<19) // 3.1-3.2V.
#define SD_OCR_3_2_3_3V (1u<<20) // 3.2-3.3V.
#define SD_OCR_3_3_3_4V (1u<<21) // 3.3-3.4V.
#define SD_OCR_3_4_3_5V (1u<<22) // 3.4-3.5V.
#define SD_OCR_3_5_3_6V (1u<<23) // 3.5-3.6V.
#define SD_OCR_S18A (1u<<24) // S18A: Switching to 1.8V Accepted. 0b: Continues current voltage signaling, 1b: Ready for switching signal voltage.
#define SD_OCR_CO2T (1u<<27) // Over 2TB Card. CCS must also be 1 if this is 1.
#define SD_OCR_UHS_II (1u<<29) // UHS-II Card Status. 0b: Non UHS-II Card, 1b: UHS-II Card.
#define SD_OCR_CCS (1u<<30) // Card Capacity Status. 0b: SDSC, 1b: SDHC or SDXC.
#define SD_OCR_READY (1u<<31) // Busy Status. 0b: On Initialization, 1b: Initialization Complete.
// Argument bits for SEND_OP_COND (ACMD41).
// For voltage bits see OCR register above.
#define SD_ACMD41_S18R (1u<<24) // S18R: Switching to 1.8V Request. 0b: Use current signal voltage, 1b: Switch to 1.8V signal voltage.
#define SD_ACMD41_HO2T (1u<<27) // Over 2TB Supported Host. HCS must also be 1 if this is 1.
#define SD_ACMD41_XPC (1u<<28) // SDXC Power Control. 0b: Power Saving, 1b: Maximum Performance.
#define SD_ACMD41_HCS (1u<<30) // Host Capacity Support. 0b: SDSC Only Host, 1b: SDHC or SDXC Supported.
// 4.3.10 Switch Function Command.
// mode: 0 = check function, 1 = set function
// pwr: Function group 4 Power Limit.
// driver: Function group 3 Driver Strength.
// cmd: Function group 2 Command system.
// acc: Function group 1 Access mode.
#define SD_SWITCH_FUNC_ARG(mode, pwr, driver, cmd, acc) ((mode)<<31 | 0xFFu<<16 | ((pwr)&0xFu)<<12 | ((driver)&0xFu)<<8 | ((cmd)&0xFu)<<4 | ((acc)&0xFu))

751
arm7/source/mmc/sdmmc.c Normal file
View File

@@ -0,0 +1,751 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <stdalign.h>
#include <string.h>
#include <nds/ndstypes.h>
#include "mmc/sdmmc.h" // Includes types.h.
#include "tmio.h"
#include "mmc/mmc_spec.h"
#include "mmc/sd_spec.h"
// Note on INIT_CLOCK:
// 400 kHz is allowed by the specs. 523 kHz has been proven to work reliably
// for SD cards and eMMC but very early MMCs can fail at init.
// We lose about 5 ms of time on init by using 261 kHz.
#define INIT_CLOCK (400000u) // Maximum 400 kHz.
#define DEFAULT_CLOCK (20000000u) // Maximum 20 MHz.
#define HS_CLOCK (50000000u) // Maximum 50 MHz.
// ARM7 timer clock = controller clock = CPU clock.
// swiDelay() doesn't seem to be cycle accurate meaning
// one cycle is 4 (?) CPU cycles.
#define SLEEP_MS_FUNC(ms) swi_waitByLoop(8378 * (ms))
#define MMC_OCR_VOLT_MASK (MMC_OCR_3_2_3_3V) // We support 3.3V only.
#define SD_OCR_VOLT_MASK (SD_OCR_3_2_3_3V) // We support 3.3V only.
#define SD_IF_COND_ARG (SD_CMD8_VHS_2_7_3_6V | SD_CMD8_CHK_PATT)
#define SD_OP_COND_ARG (SD_OCR_VOLT_MASK) // We support 100 mA and 3.3V. Without HCS bit.
#define MMC_OP_COND_ARG (MMC_OCR_SECT_MODE | MMC_OCR_VOLT_MASK) // We support sector addressing and 3.3V.
// Note: DEV_TYPE_NONE must be zero.
enum
{
// Device types.
DEV_TYPE_NONE = 0u, // Unitialized/no device.
DEV_TYPE_MMC = 1u, // (e)MMC.
DEV_TYPE_MMCHC = 2u, // High capacity (e)MMC (>2 GB).
DEV_TYPE_SDSC = 3u, // SDSC.
DEV_TYPE_SDHC = 4u, // SDHC, SDXC.
DEV_TYPE_SDUC = 5u // SDUC.
};
#define IS_DEV_MMC(dev) ((dev) < DEV_TYPE_SDSC)
typedef struct
{
TmioPort port;
u8 type; // Device type. 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC,
// 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
u8 prot; // Protection bits. Each bit 1 = protected.
// Bit 0 SD card slider, bit 1 temporary write protection (CSD),
// bit 2 permanent write protection (CSD) and bit 3 password protection.
u16 rca; // Relative Card Address (RCA).
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
u32 sectors; // Size in 512 byte units.
u32 status; // R1 card status on error. Only updated on errors.
// Cached card infos.
u32 cid[4]; // Raw CID without the CRC.
} SdmmcDev;
static SdmmcDev g_devs[2] = {0};
static u32 sendAppCmd(TmioPort *const port, const u16 cmd, const u32 arg, const u32 rca)
{
// Send app CMD. Same CMD for (e)MMC/SD.
// TODO: Check the APP_CMD bit in the response?
// Linux does it but is it really necessary? SD spec 4.3.9.1.
u32 res = TMIO_sendCommand(port, MMC_APP_CMD, rca);
if(res == 0)
{
res = TMIO_sendCommand(port, cmd, arg);
}
return res;
}
static u32 goIdleState(TmioPort *const port)
{
// Enter idle state before we start the init procedure.
// Works from all but inactive state. CMD is the same for (e)MMC/SD.
// For (e)MMC there are optional init paths:
// arg = 0x00000000 -> GO_IDLE_STATE.
// arg = 0xF0F0F0F0 -> GO_PRE_IDLE_STATE.
// arg = 0xFFFFFFFA -> BOOT_INITIATION.
u32 res = TMIO_sendCommand(port, MMC_GO_IDLE_STATE, 0);
if(res != 0) return SDMMC_ERR_GO_IDLE_STATE;
return SDMMC_ERR_NONE;
}
static u32 initIdleState(TmioPort *const port, u8 *const devTypeOut)
{
// Tell the card what interfaces and voltages we support.
// Only SD v2 and up will respond. (e)MMC won't respond.
u32 res = TMIO_sendCommand(port, SD_SEND_IF_COND, SD_IF_COND_ARG);
if(res == 0)
{
// If the card supports the interfaces and voltages
// it should echo back the check pattern and set the
// support bits.
// Since we don't support anything but the
// standard SD interface at 3.3V we can check
// the whole response at once.
if(port->resp[0] != SD_IF_COND_ARG) return SDMMC_ERR_IF_COND_RESP;
}
else if(res != STATUS_ERR_CMD_TIMEOUT) return SDMMC_ERR_SEND_IF_COND; // Card responded but an error occured.
// Send the first app CMD. If this times out it's (e)MMC.
// If SEND_IF_COND timed out tell the SD card we are a v1 host.
const u32 opCondArg = SD_OP_COND_ARG | (res<<8 ^ SD_ACMD41_HCS); // Caution! Controller specific hack.
u8 devType = DEV_TYPE_SDSC;
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
if(res == STATUS_ERR_CMD_TIMEOUT) devType = DEV_TYPE_MMC; // Continue with (e)MMC init.
else if(res != 0) return SDMMC_ERR_SEND_OP_COND; // Unknown error.
if(devType == DEV_TYPE_MMC) // (e)MMC.
{
// Loop until a timeout of 1 second or the card is ready.
u32 tries = 200;
u32 ocr;
while(1)
{
res = TMIO_sendCommand(port, MMC_SEND_OP_COND, MMC_OP_COND_ARG);
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
ocr = port->resp[0];
if(!--tries || (ocr & MMC_OCR_READY)) break;
// Linux uses 10 ms but the card doesn't become ready faster
// when polling with delay. Use 5 ms as compromise so not much
// time is wasted when the card becomes ready in the middle of the delay.
SLEEP_MS_FUNC(5);
}
// (e)MMC didn't finish init within 1 second.
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
// Check if the (e)MMC supports the voltage and if it's high capacity.
if(!(ocr & MMC_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
if(ocr & MMC_OCR_SECT_MODE) devType = DEV_TYPE_MMCHC; // 7.4.3.
}
else // SD card.
{
// Loop until a timeout of 1 second or the card is ready.
u32 tries = 200;
u32 ocr;
while(1)
{
ocr = port->resp[0];
if(!--tries || (ocr & SD_OCR_READY)) break;
// Linux uses 10 ms but the card doesn't become ready faster
// when polling with delay. Use 5 ms as compromise so not much
// time is wasted when the card becomes ready in the middle of the delay.
SLEEP_MS_FUNC(5);
res = sendAppCmd(port, SD_APP_SD_SEND_OP_COND, opCondArg, 0);
if(res != 0) return SDMMC_ERR_SEND_OP_COND;
}
// SD card didn't finish init within 1 second.
if(tries == 0) return SDMMC_ERR_OP_COND_TMOUT;
if(!(ocr & SD_OCR_VOLT_MASK)) return SDMMC_ERR_VOLT_SUPPORT; // Voltage not supported.
if(ocr & SD_OCR_CCS) devType = DEV_TYPE_SDHC;
}
*devTypeOut = devType;
return SDMMC_ERR_NONE;
}
static u32 initReadyState(SdmmcDev *const dev)
{
TmioPort *const port = &dev->port;
// SD card voltage switch sequence goes here if supported.
// Get the CID. CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_ALL_SEND_CID, 0);
if(res != 0) return SDMMC_ERR_ALL_SEND_CID;
memcpy(dev->cid, port->resp, 16);
return SDMMC_ERR_NONE;
}
static u32 initIdentState(SdmmcDev *const dev, const u8 devType, u32 *const rcaOut)
{
TmioPort *const port = &dev->port;
u32 rca;
if(IS_DEV_MMC(devType)) // (e)MMC.
{
// Set the RCA of the (e)MMC to 1. 0 is reserved.
// The RCA is in the upper 16 bits of the argument.
rca = 1;
u32 res = TMIO_sendCommand(port, MMC_SET_RELATIVE_ADDR, rca<<16);
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
}
else // SD card.
{
// Ask the SD card to send its RCA.
u32 res = TMIO_sendCommand(port, SD_SEND_RELATIVE_ADDR, 0);
if(res != 0) return SDMMC_ERR_SET_SEND_RCA;
// RCA in upper 16 bits. Discards lower status bits of R6 response.
rca = port->resp[0]>>16;
}
dev->rca = rca;
*rcaOut = rca<<16;
return SDMMC_ERR_NONE;
}
// Based on UNSTUFF_BITS from linux/drivers/mmc/core/sd.c.
// Extracts up to 32 bits from a u32[4] array.
static inline u32 extractBits(const u32 resp[4], const u32 start, const u32 size)
{
const u32 mask = (size < 32 ? 1u<<size : 0u) - 1;
const u32 off = 3 - (start / 32);
const u32 shift = start & 31u;
u32 res = resp[off]>>shift;
if(size + shift > 32)
res |= resp[off - 1]<<((32u - shift) & 31u);
return res & mask;
}
static void parseCsd(SdmmcDev *const dev, const u8 devType, u8 *const spec_vers_out)
{
// Note: The MSBs are in csd[0].
const u32 *const csd = dev->port.resp;
const u8 structure = extractBits(csd, 126, 2); // [127:126]
*spec_vers_out = extractBits(csd, 122, 4); // [125:122] All 0 for SD cards.
dev->ccc = extractBits(csd, 84, 12); // [95:84]
u32 sectors = 0;
if(structure == 0 || devType == DEV_TYPE_MMC) // structure = 0 is CSD version 1.0.
{
const u32 read_bl_len = extractBits(csd, 80, 4); // [83:80]
const u32 c_size = extractBits(csd, 62, 12); // [73:62]
const u32 c_size_mult = extractBits(csd, 47, 3); // [49:47]
// For SD cards with CSD 1.0 and <=2 GB (e)MMC this calculation is used.
// Note: READ_BL_LEN is at least 9.
// Modified/simplified to calculate sectors instead of bytes.
sectors = (c_size + 1)<<(c_size_mult + 2 + read_bl_len - 9);
}
else if(devType != DEV_TYPE_MMCHC)
{
// SD CSD version 3.0 format.
// For version 2.0 this is 22 bits however the upper bits
// are reserved and zero filled so this is fine.
const u32 c_size = extractBits(csd, 48, 28); // [75:48]
// Calculation for SD cards with CSD >1.0.
sectors = (c_size + 1)<<10;
}
// Else for high capacity (e)MMC the sectors will be read later from EXT_CSD.
dev->sectors = sectors;
// Parse temporary and permanent write protection bits.
u8 prot = extractBits(csd, 12, 1)<<1; // [12:12] Not checked by Linux.
prot |= extractBits(csd, 13, 1)<<2; // [13:13]
dev->prot |= prot;
}
static u32 initStandbyState(SdmmcDev *const dev, const u8 devType, const u32 rca, u8 *const spec_vers_out)
{
TmioPort *const port = &dev->port;
// Get the CSD. CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_SEND_CSD, rca);
if(res != 0) return SDMMC_ERR_SEND_CSD;
parseCsd(dev, devType, spec_vers_out);
// CMD is the same for (e)MMC/SD however both R1 and R1b responses are used.
// We assume R1b and hope it doesn't time out.
res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
// The SD card spec mentions that we should check the lock bit in the
// response to CMD7 to identify cards requiring a password to unlock.
// Same seems to apply for (e)MMC.
// Same bit for (e)MMC/SD R1 card status.
dev->prot |= (port->resp[0] & MMC_R1_CARD_IS_LOCKED)>>22; // Bit 3.
return SDMMC_ERR_NONE;
}
// TODO: Set the timeout based on clock speed (Tmio uses SDCLK for timeouts).
// The tmio driver sets a sane default but we should calculate it anyway.
static u32 initTranState(SdmmcDev *const dev, const u8 devType, const u32 rca, const u8 spec_vers)
{
TmioPort *const port = &dev->port;
if(IS_DEV_MMC(devType)) // (e)MMC.
{
// EXT_CSD, non-1 bit bus width and HS timing are only
// supported by (e)MMC SPEC_VERS 4.1 and higher.
if(spec_vers > 3) // Version 4.14.24.3 or higher.
{
// The (e)MMC spec says to check the card status after a SWITCH CMD (7.6.1).
// I think we can get away without checking this because support for HS timing
// and 4 bit bus width is mandatory for this spec version. If the card is
// non-standard we will encounter errors on the next CMD anyway.
// Switch to 4 bit bus mode.
const u32 busWidthArg = MMC_SWITCH_ARG(MMC_SWITCH_ACC_WR_BYTE, EXT_CSD_BUS_WIDTH, 1, 0);
u32 res = TMIO_sendCommand(port, MMC_SWITCH, busWidthArg);
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
TMIO_setBusWidth(port, 4);
// We should also check in the EXT_CSD the power budget for the card.
// Nintendo seems to leave it on default (no change).
if(devType == DEV_TYPE_MMCHC)
{
// Note: The EXT_CSD is normally read before touching HS timing and bus width.
// We can take advantage of the faster data transfer with this order.
alignas(4) u8 ext_csd[512];
TMIO_setBuffer(port, (u32*)ext_csd, 1);
res = TMIO_sendCommand(port, MMC_SEND_EXT_CSD, 0);
if(res != 0) return SDMMC_ERR_SEND_EXT_CSD;
// Get sector count from EXT_CSD only if sector addressing is used because
// byte addressed (e)MMC may set sector count to 0.
dev->sectors = ext_csd[EXT_CSD_SEC_COUNT + 3]<<24 | ext_csd[EXT_CSD_SEC_COUNT + 2]<<16 |
ext_csd[EXT_CSD_SEC_COUNT + 1]<<8 | ext_csd[EXT_CSD_SEC_COUNT + 0];
}
}
}
else // SD card.
{
// Remove DAT3 pull-up. Linux doesn't do it but the SD spec recommends it.
u32 res = sendAppCmd(port, SD_APP_SET_CLR_CARD_DETECT, 0, rca); // arg = 0 removes the pull-up.
if(res != 0) return SDMMC_ERR_SET_CLR_CD;
// Switch to 4 bit bus mode.
res = sendAppCmd(port, SD_APP_SET_BUS_WIDTH, 2, rca); // arg = 2 is 4 bit bus width.
if(res != 0) return SDMMC_ERR_SET_BUS_WIDTH;
TMIO_setBusWidth(port, 4);
}
// SD: The description for CMD SET_BLOCKLEN says 512 bytes is the default.
// (e)MMC: The description for READ_BL_LEN (CSD) says 512 bytes is the default.
// So it's not required to set the block length.
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_init(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
// Check SD card write protection slider.
if(devNum == SDMMC_DEV_CARD)
dev->prot = !TMIO_cardWritable();
// Init port, enable clock output and wait 74 clocks.
TmioPort *const port = &dev->port;
TMIO_initPort(port, devNum);
TMIO_powerupSequence(port); // Setup continuous clock and wait 74 clocks.
u32 res = goIdleState(port);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in idle state (idle).
u8 devType;
res = initIdleState(port, &devType);
if(res != SDMMC_ERR_NONE) return res;
// Stop clock at idle, init clock.
TMIO_setClock(port, INIT_CLOCK);
// (e)MMC/SD now in ready state (ready).
res = initReadyState(dev);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in identification state (ident).
u32 rca;
res = initIdentState(dev, devType, &rca);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in stand-by state (stby).
// Maximum at this point would be 20 MHz for (e)MMC and 25 for SD.
// SD: We can increase the clock after end of identification state.
// TODO: eMMC spec section 7.6
// "Until the contents of the CSD register is known by the host,
// the fPP clock rate must remain at fOD. (See Section 12.7 on page 176.)"
// Since the absolute minimum clock rate is 20 MHz and we are in push-pull
// mode already can we cheat and switch to <=20 MHz before getting the CSD?
// Note: This seems to be working just fine in all tests.
TMIO_setClock(port, DEFAULT_CLOCK);
u8 spec_vers;
res = initStandbyState(dev, devType, rca, &spec_vers);
if(res != SDMMC_ERR_NONE) return res;
// (e)MMC/SD now in transfer state (tran).
res = initTranState(dev, devType, rca, spec_vers);
if(res != SDMMC_ERR_NONE) return res;
// Only set dev type on successful init.
dev->type = devType;
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
const u32 rca = (u32)dev->rca<<16;
const u8 devType = dev->type;
if(enabled)
{
// Deselect card to go back to stand-by state.
// CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_DESELECT_CARD, 0);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
// Only (e)MMC can go into true sleep mode.
if(IS_DEV_MMC(devType))
{
// Switch (e)MMC into sleep mode.
res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca | 1u<<15);
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
// TODO: Power down eMMC. This is confirmed working on 3DS.
}
}
else
{
if(IS_DEV_MMC(devType))
{
// TODO: Power up eMMC. This is confirmed working on 3DS.
// Wake (e)MMC up from sleep mode.
u32 res = TMIO_sendCommand(port, MMC_SLEEP_AWAKE, rca);
if(res != 0) return SDMMC_ERR_SLEEP_AWAKE;
}
// Select card to go back to transfer state.
// CMD is the same for (e)MMC/SD.
u32 res = TMIO_sendCommand(port, MMC_SELECT_CARD, rca);
if(res != 0) return SDMMC_ERR_SELECT_CARD;
}
return SDMMC_ERR_NONE;
}
// TODO: Is there any "best practice" way of deinitializing cards?
// Kick the card back into idle state maybe?
// Linux seems to deselect cards on "suspend".
u32 pico_SDMMC_deinit(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
memset(&g_devs[devNum], 0, sizeof(SdmmcDev));
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen)
{
// Password length is maximum 16 bytes except when replacing a password.
if(devNum > SDMMC_MAX_DEV_NUM || pwdLen > 32) return SDMMC_ERR_INVAL_PARAM;
// Set block length on (e)MMC/SD side and host.
// Same CMD for (e)MMC/SD.
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
const u32 blockLen = (mode != SDMMC_LK_ERASE ? 2 + pwdLen : 1);
u32 res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, blockLen);
if(res != 0) return SDMMC_ERR_SET_BLOCKLEN;
TMIO_setBlockLen(port, blockLen);
do
{
// Prepare lock/unlock data block.
alignas(4) u8 buf[36] = {0}; // Size multiple of 4 (TMIO driver limitation).
buf[0] = mode;
buf[1] = pwdLen;
memcpy(&buf[2], pwd, pwdLen);
// Dirty hack to extend the data timeout to a bit over 4 minutes with TMIO controller.
// We need 3 minutes minimum for erase.
const u16 clk_ctrl_backup = port->sd_clk_ctrl;
TMIO_setClock(port, 130913);
// Note: Command class 7 support is mandatory for (e)MMC. Not for SD cards until 2.00.
// Same CMD for (e)MMC/SD.
TMIO_setBuffer(port, (u32*)buf, 1);
res = TMIO_sendCommand(port, MMC_LOCK_UNLOCK, 0);
port->sd_clk_ctrl = clk_ctrl_backup; // Undo the data timeout hack.
if(res != 0)
{
res = SDMMC_ERR_LOCK_UNLOCK;
break;
}
// Restore default block length and get the R1 status.
// Same CMD for (e)MMC/SD.
res = TMIO_sendCommand(port, MMC_SET_BLOCKLEN, 512);
if(res != 0)
{
res = SDMMC_ERR_SET_BLOCKLEN;
break;
}
TMIO_setBlockLen(port, 512);
// Check if lock/unlock worked.
// Same bit for (e)MMC/SD R1 card status.
const u32 status = port->resp[0];
if(status & MMC_R1_LOCK_UNLOCK_FAILED)
res = SDMMC_ERR_LOCK_UNLOCK_FAIL;
// Update lock status.
const u8 prot = dev->prot & ~(1u<<3);
dev->prot = prot | (status>>22 & 1u<<3);
} while(0);
return res;
}
// People should not mess with the state which is the reason
// why the struct is not exposed directly.
static_assert(sizeof(SdmmcDev) == 64, "Wrong SDMMC dev export/import size.");
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
const SdmmcDev *const dev = &g_devs[devNum];
if(dev->type == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
memcpy(devOut, dev, 64);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
// Make sure there is a card inserted.
if(devNum == SDMMC_DEV_CARD && !TMIO_cardDetected()) return SDMMC_ERR_NO_CARD;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
if(dev->type != DEV_TYPE_NONE) return SDMMC_ERR_INITIALIZED;
memcpy(dev, devIn, 64);
// Update write protection slider state just in case.
dev->prot |= !TMIO_cardWritable();
return SDMMC_ERR_NONE;
}
// TODO: Less controller dependent code.
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
const SdmmcDev *const dev = &g_devs[devNum];
const TmioPort *const port = &dev->port;
infoOut->type = dev->type;
infoOut->prot = dev->prot;
infoOut->rca = dev->rca;
infoOut->sectors = dev->sectors;
const u32 clkSetting = port->sd_clk_ctrl & 0xFFu;
infoOut->clock = TMIO_HCLK / (clkSetting ? clkSetting<<2 : 2);
memcpy(infoOut->cid, dev->cid, 16);
infoOut->ccc = dev->ccc;
infoOut->busWidth = (port->sd_option & OPTION_BUS_WIDTH1 ? 1 : 4);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4])
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
if(cidOut != NULL) memcpy(cidOut, g_devs[devNum].cid, 16);
return SDMMC_ERR_NONE;
}
/*#include "fatfs/ff.h" // Needed for the "byte" type used in diskio.h.
#include "fatfs/diskio.h"
u8 SDMMC_getDiskStatus(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return STA_NODISK | STA_NOINIT;
u8 status = 0;
if(devNum == SDMMC_DEV_CARD)
status = (TMIO_cardDetected() == true ? 0 : STA_NODISK | STA_NOINIT);
const SdmmcDev *const dev = &g_devs[devNum];
status |= (dev->prot != 0 ? STA_PROTECT : 0);
if(dev->type == DEV_TYPE_NONE)
status |= STA_NOINIT;*/
// "Not valid if STA_NODISK is set."
/*if(status & STA_NODISK)
status &= ~STA_PROTECT;*/
// return status;
//}
u32 pico_SDMMC_getSectors(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
return g_devs[devNum].sectors;
}
static u32 updateStatus(SdmmcDev *const dev, const bool stopTransmission)
{
TmioPort *const port = &dev->port;
// MMC_STOP_TRANSMISSION: Same CMD for (e)MMC/SD. Relies on the driver returning a proper response.
// MMC_SEND_STATUS: Same CMD for (e)MMC/SD but the argument format differs slightly.
u32 res;
if(stopTransmission) res = TMIO_sendCommand(port, MMC_STOP_TRANSMISSION, 0);
else res = TMIO_sendCommand(port, MMC_SEND_STATUS, (u32)dev->rca<<16);
dev->status = (res == 0 ? port->resp[0] : 0); // Don't update the status with stale data.
return res;
}
// Note: On multi-block read from the last 2 sectors there are no errors reported by the controller
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
// This error is normal for (e)MMC and can be ignored.
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count)
{
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
const u8 devType = dev->type;
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
// Set destination buffer and sector count.
TmioPort *const port = &dev->port;
TMIO_setBuffer(port, buf, count);
// Read a single 512 bytes block. Same CMD for (e)MMC/SD.
// Read multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
const u16 readCmd = (count == 1 ? MMC_READ_SINGLE_BLOCK : MMC_READ_MULTIPLE_BLOCK);
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
u32 res = TMIO_sendCommand(port, readCmd, sect);
if(res != 0)
{
// On error in the middle of multi-block reads the card will be stuck
// in data state and we need to send STOP_TRANSMISSION to bring it
// back to tran state.
// Otherwise for single-block reads just update the status.
updateStatus(dev, count > 1);
return SDMMC_ERR_SECT_RW;
}
return SDMMC_ERR_NONE;
}
// Note: On multi-block write to the last 2 sectors there are no errors reported by the controller
// however the R1 card status may report ADDRESS_OUT_OF_RANGE on next(?) status read.
// This error is normal for (e)MMC and can be ignored.
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count)
{
if(devNum > SDMMC_MAX_DEV_NUM || count == 0) return SDMMC_ERR_INVAL_PARAM;
// Check if the device is initialized.
SdmmcDev *const dev = &g_devs[devNum];
const u8 devType = dev->type;
if(devType == DEV_TYPE_NONE) return SDMMC_ERR_NO_CARD;
// Check if the device is write protected.
if(dev->prot != 0) return SDMMC_ERR_WRITE_PROT;
// Set source buffer and sector count.
TmioPort *const port = &dev->port;
TMIO_setBuffer(port, (void*)buf, count);
// Write a single 512 bytes block. Same CMD for (e)MMC/SD.
// Write multiple 512 bytes blocks. Same CMD for (e)MMC/SD.
const u16 writeCmd = (count == 1 ? MMC_WRITE_BLOCK : MMC_WRITE_MULTIPLE_BLOCK);
if(devType == DEV_TYPE_MMC || devType == DEV_TYPE_SDSC) sect *= 512; // Byte addressing.
const u32 res = TMIO_sendCommand(port, writeCmd, sect);
if(res != 0)
{
// On error in the middle of multi-block writes the card will be stuck
// in data state and we need to send STOP_TRANSMISSION to bring it
// back to tran state.
// Otherwise for single-block writes just update the status.
updateStatus(dev, count > 1);
return SDMMC_ERR_SECT_RW;
}
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd)
{
if(devNum > SDMMC_MAX_DEV_NUM) return SDMMC_ERR_INVAL_PARAM;
SdmmcDev *const dev = &g_devs[devNum];
TmioPort *const port = &dev->port;
TMIO_setBlockLen(port, mmcCmd->blkLen);
TMIO_setBuffer(port, mmcCmd->buf, mmcCmd->count);
const u32 res = TMIO_sendCommand(port, mmcCmd->cmd, mmcCmd->arg);
TMIO_setBlockLen(port, 512); // Restore default block length.
if(res != 0)
{
updateStatus(dev, false);
return SDMMC_ERR_SEND_CMD;
}
memcpy(mmcCmd->resp, port->resp, 16);
return SDMMC_ERR_NONE;
}
u32 pico_SDMMC_getLastR1error(const u8 devNum)
{
if(devNum > SDMMC_MAX_DEV_NUM) return 0;
SdmmcDev *const dev = &g_devs[devNum];
const u32 status = dev->status;
dev->status = 0;
return status;
}

240
arm7/source/mmc/sdmmc.h Normal file
View File

@@ -0,0 +1,240 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
// Possible error codes for most of the functions below.
enum
{
SDMMC_ERR_NONE = 0u, // No error.
SDMMC_ERR_INVAL_PARAM = 1u, // Invalid parameter.
SDMMC_ERR_INITIALIZED = 2u, // The device is already initialized.
SDMMC_ERR_GO_IDLE_STATE = 3u, // GO_IDLE_STATE CMD error.
SDMMC_ERR_SEND_IF_COND = 4u, // SEND_IF_COND CMD error.
SDMMC_ERR_IF_COND_RESP = 5u, // IF_COND response pattern mismatch or unsupported voltage.
SDMMC_ERR_SEND_OP_COND = 6u, // SEND_OP_COND CMD error.
SDMMC_ERR_OP_COND_TMOUT = 7u, // Card initialization timeout.
SDMMC_ERR_VOLT_SUPPORT = 8u, // Voltage not supported.
SDMMC_ERR_ALL_SEND_CID = 9u, // ALL_SEND_CID CMD error.
SDMMC_ERR_SET_SEND_RCA = 10u, // SET/SEND_RELATIVE_ADDR CMD error.
SDMMC_ERR_SEND_CSD = 11u, // SEND_CSD CMD error.
SDMMC_ERR_SELECT_CARD = 12u, // SELECT_CARD CMD error.
SDMMC_ERR_LOCKED = 13u, // Card is locked with a password.
SDMMC_ERR_SEND_EXT_CSD = 14u, // SEND_EXT_CSD CMD error.
SDMMC_ERR_SWITCH_HS = 15u, // Error on switching to high speed mode.
SDMMC_ERR_SET_CLR_CD = 16u, // SET_CLR_CARD_DETECT CMD error.
SDMMC_ERR_SET_BUS_WIDTH = 17u, // Error on switching to a different bus width.
SDMMC_ERR_SEND_STATUS = 18u, // SEND_STATUS CMD error.
SDMMC_ERR_CARD_STATUS = 19u, // The card returned an error via its status.
SDMMC_ERR_NO_CARD = 20u, // Card unitialized or not inserted.
SDMMC_ERR_SECT_RW = 21u, // Sector read/write error.
SDMMC_ERR_WRITE_PROT = 22u, // The card is write protected.
SDMMC_ERR_SEND_CMD = 23u, // An error occured while sending a custom CMD via SDMMC_sendCommand().
SDMMC_ERR_SET_BLOCKLEN = 24u, // SET_BLOCKLEN CMD error.
SDMMC_ERR_LOCK_UNLOCK = 25u, // LOCK_UNLOCK CMD error.
SDMMC_ERR_LOCK_UNLOCK_FAIL = 26u, // Lock/unlock operation failed (R1 status).
SDMMC_ERR_SLEEP_AWAKE = 27u // (e)MMC SLEEP_AWAKE CMD error.
};
// (e)MMC/SD device numbers.
enum
{
SDMMC_DEV_CARD = 0u, // SD card/MMC.
SDMMC_DEV_eMMC = 1u, // Builtin eMMC.
// Alias for internal use only.
SDMMC_MAX_DEV_NUM = SDMMC_DEV_eMMC
};
// Bit definition for SdmmcInfo.prot.
// Each bit 1 = protected.
#define SDMMC_PROT_SLIDER (1u) // SD card write protection slider.
#define SDMMC_PROT_TEMP (1u<<1) // Temporary write protection (CSD).
#define SDMMC_PROT_PERM (1u<<2) // Permanent write protection (CSD).
#define SDMMC_PROT_PASSWORD (1u<<3) // (e)MMC/SD card is password protected.
typedef struct
{
u8 type; // 0 = none, 1 = (e)MMC, 2 = High capacity (e)MMC, 3 = SDSC, 4 = SDHC/SDXC, 5 = SDUC.
u8 prot; // See SDMMC_PROT_... defines above for details.
u16 rca; // Relative Card Address (RCA).
u32 sectors; // Size in 512 byte units.
u32 clock; // The current clock frequency in Hz.
u32 cid[4]; // Raw CID without the CRC.
u16 ccc; // (e)MMC/SD command class support from CSD. One per bit starting at 0.
u8 busWidth; // The current bus width used to talk to the card.
} SdmmcInfo;
typedef struct
{
u16 cmd; // Command. T̲h̲e̲ ̲f̲o̲r̲m̲a̲t̲ ̲i̲s̲ ̲c̲o̲n̲t̲r̲o̲l̲l̲e̲r̲ ̲s̲p̲e̲c̲i̲f̲i̲c̲!̲
u32 arg; // Command argument.
u32 resp[4]; // Card response. Length depends on command.
u32 *buf; // In/out data buffer.
u16 blkLen; // Block length. Usually 512.
u16 count; // Number of blkSize blocks to transfer.
} MmcCommand;
// Mode bits for SDMMC_lockUnlock().
#define SDMMC_LK_CLR_PWD (1u<<1) // Clear password.
#define SDMMC_LK_UNLOCK (0u) // Unlock.
#define SDMMC_LK_LOCK (1u<<2) // Lock.
#define SDMMC_LK_ERASE (1u<<3) // Force erase a locked (e)MMC/SD card.
#define SDMMC_LK_COP (1u<<4) // SD cards only. Card Ownership Protection operation.
#ifdef __cplusplus
extern "C"{
#endif
/**
* @brief Initializes a (e)MMC/SD card device.
*
* @param[in] devNum The device to initialize.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_init(const u8 devNum);
/**
* @brief Switches a (e)MMC/SD card device between sleep/awake mode.
* Note that SD cards don't have a true sleep mode.
*
* @param[in] devNum The device.
* @param[in] enabled The mode. true to enable sleep and false to wake up.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_setSleepMode(const u8 devNum, const bool enabled);
/**
* @brief Deinitializes a (e)MMC/SD card device.
*
* @param[in] devNum The device to deinitialize.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_deinit(const u8 devNum);
/**
* @brief Manage password protection for a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] mode The mode of operation. See defines above.
* @param[in] pwd The password buffer pointer.
* @param[in] pwdLen The password length. Maximum 32 for password replace. Otherwise 16.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_lockUnlock(const u8 devNum, const u8 mode, const u8 *const pwd, const u8 pwdLen);
/**
* @brief Exports the internal device state for fast init (bootloaders ect.).
*
* @param[in] devNum The device state to export.
* @param devOut A pointer to a u8[60] array.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD on failure.
*/
u32 pico_SDMMC_exportDevState(const u8 devNum, u8 devOut[64]);
/**
* @brief Imports a device state for fast init (bootloaders ect.).
* The state should be validated for example with a checksum.
*
* @param[in] devNum The device state to import.
* @param[in] devIn A pointer to a u8[60] array.
*
* @return Returns SDMMC_ERR_NONE on success or
* SDMMC_ERR_INVAL_PARAM/SDMMC_ERR_NO_CARD/SDMMC_ERR_INITIALIZED on failure.
*/
u32 pico_SDMMC_importDevState(const u8 devNum, const u8 devIn[64]);
/**
* @brief Outputs infos about a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param infoOut A pointer to a SdmmcInfo struct.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_getDevInfo(const u8 devNum, SdmmcInfo *const infoOut);
/**
* @brief Outputs the CID of a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param cidOut A u32[4] pointer for storing the CID.
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_INVAL_PARAM on failure.
*/
u32 pico_SDMMC_getCid(const u8 devNum, u32 cidOut[4]);
/**
* @brief Returns the DSTATUS bits of a (e)MMC/SD card device. See FatFs diskio.h.
*
* @param[in] devNum The device.
*
* @return Returns the DSTATUS bits or STA_NODISK | STA_NOINIT on failure.
*/
//u8 SDMMC_getDiskStatus(const u8 devNum);
/**
* @brief Outputs the number of sectors for a (e)MMC/SD card device.
*
* @param[in] devNum The device.
*
* @return Returns the number of sectors or 0 on failure.
*/
u32 pico_SDMMC_getSectors(const u8 devNum);
/**
* @brief Reads one or more sectors from a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] sect The start sector.
* @param buf The output buffer pointer. NULL for DMA.
* @param[in] count The number of sectors to read.
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_readSectors(const u8 devNum, u32 sect, void *const buf, const u16 count);
/**
* @brief Writes one or more sectors to a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param[in] sect The start sector.
* @param[in] buf The input buffer pointer. NULL for DMA.
* @param[in] count The count
*
* @return Returns SDMMC_ERR_NONE on success or
* one of the errors listed above on failure.
*/
u32 pico_SDMMC_writeSectors(const u8 devNum, u32 sect, const void *const buf, const u16 count);
/**
* @brief Sends a custom command to a (e)MMC/SD card device.
*
* @param[in] devNum The device.
* @param cmd MMC command struct pointer (see above).
*
* @return Returns SDMMC_ERR_NONE on success or SDMMC_ERR_SEND_CMD on failure.
*/
u32 pico_SDMMC_sendCommand(const u8 devNum, MmcCommand *const mmcCmd);
/**
* @brief Returns the R1 card status for a previously failed read/write/custom command.
*
* @param[in] devNum The device.
*
* @return Returns the R1 card status or 0 if there was either no command error or invalid devNum.
*/
u32 pico_SDMMC_getLastR1error(const u8 devNum);
// TODO: TRIM/erase support.
#ifdef __cplusplus
}
#endif

279
arm7/source/mmc/tmio.c Normal file
View File

@@ -0,0 +1,279 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <stdatomic.h>
#include "tmio.h"
// Using atomic load/store produces better code than volatile
// but still ensures that the status is always read from memory.
#define GET_STATUS(ptr) atomic_load_explicit((ptr), memory_order_relaxed)
#define SET_STATUS(ptr, val) atomic_store_explicit((ptr), (val), memory_order_relaxed)
// ARM7 timer clock = controller clock = CPU clock.
// swiDelay() doesn't seem to be cycle accurate meaning
// one cycle is 4 (?) CPU cycles.
#define INIT_DELAY_FUNC() swi_waitByLoop(TMIO_CLK2DIV(400000u) * 74 / 4)
static u32 g_status[2] = {0};
static rtos_event_t sSdEvent;
__attribute__((always_inline)) static inline u8 port2Controller(const u8 portNum)
{
return portNum / 2;
}
static void tmio1Isr(u32 irqMask) // SD/eMMC.
{
Tmio *const regs = getTmioRegs(0);
g_status[0] |= regs->sd_status;
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
rtos_signalEvent(&sSdEvent);
// TODO: Some kind of event to notify the main loop for remove/insert.
}
static void tmio2Isr(u32 irqMask) // WiFi SDIO.
{
Tmio *const regs = getTmioRegs(1);
g_status[1] |= regs->sd_status;
regs->sd_status = STATUS_CMD_BUSY; // Never acknowledge STATUS_CMD_BUSY.
}
void TMIO_init(void)
{
rtos_createEvent(&sSdEvent);
// Register ISR and enable IRQs.
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, tmio1Isr);
rtos_setIrq2Func(RTOS_IRQ2_SDIO, tmio2Isr);
rtos_enableIrq2Mask(RTOS_IRQ2_SDMMC);
rtos_enableIrq2Mask(RTOS_IRQ2_SDIO);
// Reset all controllers.
for(u32 i = 0; i < 2; i++)
{
// Setup 32 bit FIFO.
Tmio *const regs = getTmioRegs(i);
regs->sd_fifo32_cnt = FIFO32_CLEAR | FIFO32_EN;
regs->sd_blocklen32 = 512;
regs->sd_blockcount32 = 1;
regs->dma_ext_mode = DMA_EXT_DMA_MODE;
// Reset. Unlike similar controllers no delay is needed.
// Resets the following regs:
// REG_SD_STOP, REG_SD_RESP0-7, REG_SD_STATUS1-2, REG_SD_ERR_STATUS1-2,
// REG_SD_CLK_CTRL, REG_SD_OPTION, REG_SDIO_STATUS.
regs->soft_rst = SOFT_RST_RST;
regs->soft_rst = SOFT_RST_NORST;
regs->sd_portsel = PORTSEL_P0;
regs->sd_blockcount = 1;
regs->sd_status_mask = STATUS_MASK_DEFAULT;
regs->sd_clk_ctrl = SD_CLK_DEFAULT;
regs->sd_blocklen = 512;
regs->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
regs->ext_cdet_mask = EXT_CDET_MASK_ALL;
regs->ext_cdet_dat3_mask = EXT_CDET_DAT3_MASK_ALL;
// Disable SDIO.
regs->sdio_mode = 0;
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
regs->ext_sdio_irq = EXT_SDIO_IRQ_MASK_ALL;
}
}
void TMIO_deinit(void)
{
rtos_disableIrq2Mask(RTOS_IRQ2_SDMMC);
rtos_setIrq2Func(RTOS_IRQ2_SDMMC, NULL);
rtos_disableIrq2Mask(RTOS_IRQ2_SDIO);
rtos_setIrq2Func(RTOS_IRQ2_SDIO, NULL);
// Mask all IRQs.
for(u32 i = 0; i < 2; i++)
{
// 32 bit FIFO IRQs.
Tmio *const regs = getTmioRegs(i);
regs->sd_fifo32_cnt = 0; // FIFO and all IRQs disabled/masked.
// Regular IRQs.
regs->sd_status_mask = STATUS_MASK_ALL;
// SDIO IRQs.
regs->sdio_status_mask = SDIO_STATUS_MASK_ALL;
}
}
void TMIO_initPort(TmioPort *const port, const u8 portNum)
{
// Reset port state.
port->portNum = portNum;
port->sd_clk_ctrl = SD_CLK_DEFAULT;
port->sd_blocklen = 512;
port->sd_option = OPTION_BUS_WIDTH1 | OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
}
// TODO: What if we get rid of setPort() and only use one port per controller?
static void setPort(Tmio *const regs, const TmioPort *const port)
{
// TODO: Can we somehow prevent all these reg writes each time?
// Maybe some kind of dirty flag + active port check?
regs->sd_portsel = port->portNum % 2u;
regs->sd_clk_ctrl = port->sd_clk_ctrl;
const u16 blocklen = port->sd_blocklen;
regs->sd_blocklen = blocklen;
regs->sd_option = port->sd_option;
regs->sd_blocklen32 = blocklen;
}
bool TMIO_cardDetected(void)
{
return getTmioRegs(0)->sd_status & STATUS_DETECT;
}
bool TMIO_cardWritable(void)
{
return getTmioRegs(0)->sd_status & STATUS_NO_WRPROT;
}
void TMIO_powerupSequence(TmioPort *const port)
{
port->sd_clk_ctrl = SD_CLK_EN | SD_CLK_DEFAULT;
setPort(getTmioRegs(port2Controller(port->portNum)), port);
INIT_DELAY_FUNC();
}
static void getResponse(const Tmio *const regs, TmioPort *const port, const u16 cmd)
{
// We could check for response type none as well but it's not worth it.
if((cmd & CMD_RESP_MASK) != CMD_RESP_R2)
{
port->resp[0] = regs->sd_resp[0];
}
else // 136 bit R2 responses need special treatment...
{
u32 resp[4];
for(u32 i = 0; i < 4; i++) resp[i] = regs->sd_resp[i];
port->resp[0] = resp[3]<<8 | resp[2]>>24;
port->resp[1] = resp[2]<<8 | resp[1]>>24;
port->resp[2] = resp[1]<<8 | resp[0]>>24;
port->resp[3] = resp[0]<<8; // TODO: Add the missing CRC7 and bit 0?
}
}
// Note: Using STATUS_DATA_END to detect transfer end doesn't work reliably
// because STATUS_DATA_END fires before we even read anything from FIFO
// on single block read transfer.
static void doCpuTransfer(Tmio *const regs, const u16 cmd, u8 *buf, const u32 *const statusPtr)
{
const u32 blockLen = regs->sd_blocklen;
u32 blockCount = regs->sd_blockcount;
vu32 *const fifo = getTmioFifo(regs);
if(cmd & CMD_DATA_R)
{
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
{
if(regs->sd_fifo32_cnt & FIFO32_FULL) // RX ready.
{
const u8 *const blockEnd = buf + blockLen;
do
{
if((uintptr_t)buf % 4 == 0)
{
*((u32*)buf) = *fifo;
}
else
{
const u32 tmp = *fifo;
buf[0] = tmp;
buf[1] = tmp>>8;
buf[2] = tmp>>16;
buf[3] = tmp>>24;
}
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
else rtos_waitEvent(&sSdEvent, false, true);
}
}
else
{
// TODO: Write first block ahead of time?
// gbatek Command/Param/Response/Data at bottom of page.
while((GET_STATUS(statusPtr) & STATUS_MASK_ERR) == 0 && blockCount > 0)
{
if(!(regs->sd_fifo32_cnt & FIFO32_NOT_EMPTY)) // TX request.
{
const u8 *const blockEnd = buf + blockLen;
do
{
if((uintptr_t)buf % 4 == 0)
{
*fifo = *((u32*)buf);
}
else
{
u32 tmp = buf[0];
tmp |= (u32)buf[1]<<8;
tmp |= (u32)buf[2]<<16;
tmp |= (u32)buf[3]<<24;
*fifo = tmp;
}
buf += 4;
} while(buf < blockEnd);
blockCount--;
}
else rtos_waitEvent(&sSdEvent, false, true);
}
}
}
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg)
{
const u8 controller = port2Controller(port->portNum);
Tmio *const regs = getTmioRegs(controller);
// Clear status before sending another command.
u32 *const statusPtr = &g_status[controller];
SET_STATUS(statusPtr, 0);
setPort(regs, port);
const u16 blocks = port->blocks;
regs->sd_blockcount = blocks; // sd_blockcount32 doesn't need to be set.
regs->sd_stop = STOP_AUTO_STOP; // Auto STOP_TRANSMISSION (CMD12) on multi-block transfer.
regs->sd_arg = arg;
// We don't need FIFO IRQs when using DMA. buf = NULL means DMA.
u8 *buf = port->buf;
u16 f32Cnt = FIFO32_CLEAR | FIFO32_EN;
if(buf != NULL) f32Cnt |= (cmd & CMD_DATA_R ? FIFO32_FULL_IE : FIFO32_NOT_EMPTY_IE);
regs->sd_fifo32_cnt = f32Cnt;
regs->sd_cmd = (blocks > 1 ? CMD_MULTI_DATA | cmd : cmd); // Start.
// TODO: Benchmark if this order is ideal?
// Response end comes immediately after the
// command so we need to check before __wfi().
// On error response end still fires.
while((GET_STATUS(statusPtr) & STATUS_RESP_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
getResponse(regs, port, cmd);
if((cmd & CMD_DATA_EN) != 0)
{
// If we have to transfer data do so now.
if(buf != NULL) doCpuTransfer(regs, cmd, buf, statusPtr);
// Wait for data end if needed.
// On error data end still fires.
while((GET_STATUS(statusPtr) & STATUS_DATA_END) == 0) rtos_waitEvent(&sSdEvent, false, true);
}
// STATUS_CMD_BUSY is no longer set at this point.
return GET_STATUS(statusPtr) & STATUS_MASK_ERR;
}

414
arm7/source/mmc/tmio.h Normal file
View File

@@ -0,0 +1,414 @@
#pragma once
// SPDX-License-Identifier: MIT
// Copyright (c) 2023 profi200
#include <assert.h>
#include <stddef.h>
#include <nds/ndstypes.h>
#include <libtwl/sys/swi.h>
#include <libtwl/rtos/rtosIrq.h>
#include <libtwl/rtos/rtosEvent.h>
// For simplicity we will name the accessible 2 controllers 1 and 2.
// The real controller number is in the comment.
#define TMIO1_REGS_BASE (0x04004800u) // Controller 1.
#define TMIO2_REGS_BASE (0x04004A00u) // Controller 2.
#define TMIO_HCLK (33513982u) // In Hz.
typedef struct
{
vu16 sd_cmd; // 0x000
vu16 sd_portsel; // 0x002
vu32 sd_arg; // 0x004 SD_ARG0 and SD_ARG1 combined.
vu16 sd_stop; // 0x008
vu16 sd_blockcount; // 0x00A
const vu32 sd_resp[4]; // 0x00C SD_RESP0-7 16 bit reg pairs combined.
vu32 sd_status; // 0x01C SD_STATUS1 and SD_STATUS2 combined.
vu32 sd_status_mask; // 0x020 SD_STATUS1_MASK and SD_STATUS2_MASK combined.
vu16 sd_clk_ctrl; // 0x024
vu16 sd_blocklen; // 0x026
vu16 sd_option; // 0x028 Card detect timer, data timeout and bus width.
u8 _0x2a[2];
const vu32 sd_err_status; // 0x02C SD_ERR_STATUS1 and SD_ERR_STATUS2 combined.
vu16 sd_fifo; // 0x030
u8 _0x32[2];
vu16 sdio_mode; // 0x034
vu16 sdio_status; // 0x036
vu16 sdio_status_mask; // 0x038
u8 _0x3a[0x9e];
vu16 dma_ext_mode; // 0x0D8
u8 _0xda[6];
vu16 soft_rst; // 0x0E0
const vu16 revision; // 0x0E2 Controller version/revision?
u8 _0xe4[0xe];
vu16 unkF2; // 0x0F2 Power related? Default 0. Other values do nothing?
vu16 ext_sdio_irq; // 0x0F4 Port 1/2/3 SDIO IRQ control.
const vu16 ext_wrprot; // 0x0F6 Apparently for eMMC.
vu16 ext_cdet; // 0x0F8 Card detect status.
vu16 ext_cdet_dat3; // 0x0FA DAT3 card detect status.
vu16 ext_cdet_mask; // 0x0FC Card detect mask (IRQ).
vu16 ext_cdet_dat3_mask; // 0x0FE DAT3 card detect mask (IRQ).
vu16 sd_fifo32_cnt; // 0x100
u8 _0x102[2];
vu16 sd_blocklen32; // 0x104
u8 _0x106[2];
vu16 sd_blockcount32; // 0x108
u8 _0x10a[2];
vu32 sd_fifo32; // 0x10C Note: This is in the FIFO region on ARM11 (3DS).
} Tmio;
#ifdef __cplusplus
extern "C"
{
#endif
static_assert(offsetof(Tmio, sd_fifo32) == 0x10C, "Error: Member sd_fifo32 of Tmio is not at offset 0x10C!");
__attribute__((always_inline)) static inline Tmio* getTmioRegs(const u8 controller)
{
return (controller == 0 ? (Tmio*)TMIO1_REGS_BASE : (Tmio*)TMIO2_REGS_BASE);
}
__attribute__((always_inline)) static inline vu32* getTmioFifo(Tmio *const regs)
{
return &regs->sd_fifo32;
}
#ifdef __cplusplus
}
#endif
// REG_SD_CMD
// Auto response supported commands:
// CMD0, CMD2, CMD3 (only SD?), CMD7 (only select?), CMD9, CMD10, CMD12, CMD13,
// CMD16, CMD17, CMD18, CMD25, CMD28, CMD55, ACMD6, ACMD23, ACMD42, ACMD51.
//
// When using auto response leave bits 11-13 unset (zero).
// Bit 0-5 command index.
#define CMD_ACMD (1u<<6) // Application command.
#define CMD_RESP_AUTO (0u) // Response type auto. Only works with certain commands.
#define CMD_RESP_NONE (3u<<8) // Response type none.
#define CMD_RESP_R1 (4u<<8) // Response type R1 48 bit.
#define CMD_RESP_R5 (CMD_RESP_R1) // Response type R5 48 bit.
#define CMD_RESP_R6 (CMD_RESP_R1) // Response type R6 48 bit.
#define CMD_RESP_R7 (CMD_RESP_R1) // Response type R7 48 bit.
#define CMD_RESP_R1b (5u<<8) // Response type R1b 48 bit + busy.
#define CMD_RESP_R5b (CMD_RESP_R1b) // Response type R5b 48 bit + busy.
#define CMD_RESP_R2 (6u<<8) // Response type R2 136 bit.
#define CMD_RESP_R3 (7u<<8) // Response type R3 48 bit OCR without CRC.
#define CMD_RESP_R4 (CMD_RESP_R3) // Response type R4 48 bit OCR without CRC.
#define CMD_RESP_MASK (CMD_RESP_R3)
#define CMD_DATA_EN (1u<<11) // Data transfer enable.
#define CMD_DATA_R (1u<<12) // Data transfer direction read.
#define CMD_DATA_W (0u) // Data transfer direction write.
#define CMD_MULTI_DATA (1u<<13) // Multi block transfer (auto STOP_TRANSMISSION).
#define CMD_SEC_SDIO (1u<<14) // Security/SDIO command.
// REG_SD_PORTSEL
#define PORTSEL_P0 (0u) // Controller port 0.
#define PORTSEL_P1 (1u) // Controller port 1.
#define PORTSEL_P2 (2u) // Controller port 2.
#define PORTSEL_P3 (3u) // Controller port 3.
#define PORTSEL_MASK (PORTSEL_P3)
// Bit 8-9 number of supported ports?
#define PORTSEL_UNK10 (1u<<10) // Unknown writable bit 10?
// REG_SD_STOP
#define STOP_STOP (1u) // Abort data transfer and send STOP_TRANSMISSION CMD.
#define STOP_AUTO_STOP (1u<<8) // Automatically send STOP_TRANSMISSION on multi-block transfer end.
// REG_SD_STATUS1/2 Write 0 to acknowledge a bit.
// REG_SD_STATUS1/2_MASK (M) = Maskable bit. 1 = disabled.
// Unmaskable bits act as status only, don't trigger IRQs and can't be acknowledged.
#define STATUS_RESP_END (1u) // (M) Response end.
#define STATUS_DATA_END (1u<<2) // (M) Data transfer end (triggers after last block).
#define STATUS_REMOVE (1u<<3) // (M) Card got removed.
#define STATUS_INSERT (1u<<4) // (M) Card got inserted. Set at the same time as DETECT.
#define STATUS_DETECT (1u<<5) // Card detect status (SD_OPTION detection timer). 1 = inserted.
#define STATUS_NO_WRPROT (1u<<7) // Write protection slider unlocked (low).
#define STATUS_DAT3_REMOVE (1u<<8) // (M) Card DAT3 got removed (low).
#define STATUS_DAT3_INSERT (1u<<9) // (M) Card DAT3 got inserted (high).
#define STATUS_DAT3_DETECT (1u<<10) // Card DAT3 status. 1 = inserted.
#define STATUS_ERR_CMD_IDX (1u<<16) // (M) Bad CMD index in response.
#define STATUS_ERR_CRC (1u<<17) // (M) Bad CRC in response.
#define STATUS_ERR_STOP_BIT (1u<<18) // (M) Stop bit error. Failed to recognize response frame end?
#define STATUS_ERR_DATA_TIMEOUT (1u<<19) // (M) Response data timeout.
#define STATUS_ERR_RX_OVERF (1u<<20) // (M) Receive FIFO overflow.
#define STATUS_ERR_TX_UNDERF (1u<<21) // (M) Send FIFO underflow.
#define STATUS_ERR_CMD_TIMEOUT (1u<<22) // (M) Response start bit timeout.
#define STATUS_SD_BUSY (1u<<23) // SD card signals busy if this bit is 0 (DAT0 held low).
#define STATUS_RX_RDY (1u<<24) // (M) FIFO ready for read.
#define STATUS_TX_REQ (1u<<25) // (M) FIFO write request.
// Bit 27 is maskable. Purpose unknown.
// Bit 29 exists (not maskable). Signals when clock divider changes are allowed?
#define STATUS_CMD_BUSY (1u<<30) // Command register busy.
#define STATUS_ERR_ILL_ACC (1u<<31) // (M) Illegal access error. TODO: What does that mean?
#define STATUS_MASK_ALL (0xFFFFFFFFu)
#define STATUS_MASK_DEFAULT ((1u<<27) | STATUS_TX_REQ | STATUS_RX_RDY | \
STATUS_DAT3_INSERT | STATUS_DAT3_REMOVE)
#define STATUS_MASK_ERR (STATUS_ERR_ILL_ACC | STATUS_ERR_CMD_TIMEOUT | STATUS_ERR_TX_UNDERF | \
STATUS_ERR_RX_OVERF | STATUS_ERR_DATA_TIMEOUT | STATUS_ERR_STOP_BIT | \
STATUS_ERR_CRC | STATUS_ERR_CMD_IDX)
// REG_SD_CLK_CTRL
#define SD_CLK_DIV_2 (0u) // Clock divider 2.
#define SD_CLK_DIV_4 (1u) // Clock divider 4.
#define SD_CLK_DIV_8 (1u<<1) // Clock divider 8.
#define SD_CLK_DIV_16 (1u<<2) // Clock divider 16.
#define SD_CLK_DIV_32 (1u<<3) // Clock divider 32.
#define SD_CLK_DIV_64 (1u<<4) // Clock divider 64.
#define SD_CLK_DIV_128 (1u<<5) // Clock divider 128.
#define SD_CLK_DIV_256 (1u<<6) // Clock divider 256.
#define SD_CLK_DIV_512 (1u<<7) // Clock divider 512.
#define SD_CLK_EN (1u<<8) // Clock enable.
#define SD_CLK_PWR_SAVE (1u<<9) // Disables clock on idle.
// Bit 10 is writable... at least according to gbatek (can't confirm). Purpose unknown.
// Outputs the matching divider for clk.
// Shift the output right by 2 to get the value for REG_SD_CLK_CTRL.
#define TMIO_CLK2DIV(clk) \
({ \
u32 __shift = 1; \
while((clk) < TMIO_HCLK>>__shift) ++__shift; \
1u<<__shift; \
})
// Clock off by default.
// Nearest possible for 400 kHz is 261.827984375 kHz.
#define SD_CLK_DEFAULT (TMIO_CLK2DIV(400000)>>2)
// REG_SD_OPTION
// Note on card detection time:
// The card detection timer starts only on inserting cards (including cold boot with inserted card)
// and when mapping ports between controllers. Card power doesn't have any effect on the timer.
//
// Bit 0-3 card detect timer 0x400<<x HCLKs. 0xF timer test (0x100 HCLKs).
// Bit 4-7 data timeout 0x2000<<x SDCLKs. 0xF timeout test (0x100 SDCLKs).
#define OPTION_UNK14 (1u<<14) // "no C2 module" What the fuck is a C2 module?
#define OPTION_BUS_WIDTH4 (0u) // 4 bit bus width.
#define OPTION_BUS_WIDTH1 (1u<<15) // 1 bit bus width.
// Card detect time: 0x400<<8 / 33513982 = 0.007821929 seconds.
// Data timeout: 0x2000<<11 / (33513982 / 2) = 1.001206959 seconds.
#define OPTION_DEFAULT_TIMINGS (11u<<4 | 8u)
// REG_SD_ERR_STATUS1/2 Write 0 to acknowledge a bit.
// TODO: Are all of these actually supported on this controller?
#define ERR_RESP_CMD_IDX (1u) // Manual command index error in response.
#define ERR_RESP_CMD12_IDX (1u<<1) // Auto command index error in response.
#define ERR_RESP_STOP_BIT (1u<<2) // Manual command response stop bit error.
#define ERR_RESP_STOP_BIT_CMD12 (1u<<3) // Auto command response stop bit error.
#define ERR_STOP_BIT_DATA_READ (1u<<4) // Stop bit error in read data.
#define ERR_STOP_BIT_WR_CRC (1u<<5) // Stop bit error for write CRC status. What the hell does that mean?
#define ERR_CMD_RESP_CRC (1u<<8) // Manual command response CRC error.
#define ERR_CMD12_RESP_CRC (1u<<9) // Auto command response CRC error.
#define ERR_DATA_READ_CRC (1u<<10) // CRC error for read data.
#define ERR_WR_CRC_STAT (1u<<11) // "CRC error for Write CRC status for a write command". What the hell does that mean?
// Bit 13 always 1.
#define ERR_CMD_RESP_TMOUT (1u<<16) // Manual command response timeout.
#define ERR_CMD12_RESP_TMOUT (1u<<17) // Auto command response timeout.
// TODO: Add the correct remaining ones.
// REG_SDIO_MODE
#define SDIO_MODE_SDIO_IRQ_EN (1u) // SDIO IRQ enable (DAT1 low).
#define SDIO_MODE_UNK2_EN (1u<<2) // IRQ on "read wait" requests?
#define SDIO_MODE_UNK8 (1u<<8) // Aborts command and data transfer?
#define SDIO_MODE_UNK9 (1u<<9) // Aborts command but not data transfer? CMD52 related.
// REG_SDIO_STATUS Write 0 to acknowledge a bit.
// REG_SDIO_STATUS_MASK (M) = Maskable bit. 1 = disabled.
#define SDIO_STATUS_SDIO_IRQ (1u) // (M) SDIO IRQ (DAT1 low).
#define SDIO_STATUS_UNK1_IRQ (1u<<1) // (M) IRQ once CMD52 can be used after abort?
#define SDIO_STATUS_UNK2_IRQ (1u<<2) // (M) Related to SDIO_MODE_UNK2_EN?
#define SDIO_STATUS_UNK14_IRQ (1u<<14) // (M) Related to SDIO_MODE_UNK9?
#define SDIO_STATUS_UNK15_IRQ (1u<<15) // (M) Related to SDIO_MODE_UNK2_EN?
#define SDIO_STATUS_MASK_ALL (0xFFFFu)
// REG_DMA_EXT_MODE
#define DMA_EXT_CPU_MODE (0u) // Disables DMA requests. Actually also turns off the 32 bit FIFO.
#define DMA_EXT_DMA_MODE (1u<<1) // Enables DMA requests.
#define DMA_EXT_UNK5 (1u<<5) // "Buffer status mode"?
// REG_SOFT_RST
#define SOFT_RST_RST (0u) // Reset.
#define SOFT_RST_NORST (1u) // No reset.
// REG_EXT_SDIO_IRQ
#define EXT_SDIO_IRQ_P1 (1u) // Port 1 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P2 (1u<<1) // Port 2 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P3 (1u<<2) // Port 3 SDIO IRQ (DAT1 low). Write 0 to acknowledge.
#define EXT_SDIO_IRQ_P1_EN (1u<<4) // Port 1 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P2_EN (1u<<5) // Port 2 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P3_EN (1u<<6) // Port 3 SDIO IRQ enable (controller).
#define EXT_SDIO_IRQ_P1_MASK (1u<<8) // Port 1 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_P2_MASK (1u<<9) // Port 2 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_P3_MASK (1u<<10) // Port 3 SDIO IRQ mask. 1 = disable IRQ (CPU).
#define EXT_SDIO_IRQ_MASK_ALL (EXT_SDIO_IRQ_P3_MASK | EXT_SDIO_IRQ_P2_MASK | EXT_SDIO_IRQ_P1_MASK)
// REG_EXT_WRPROT Each bit 1 = write protected unlike SD_STATUS.
#define EXT_WRPROT_P1 (1u)
#define EXT_WRPROT_P2 (1u<<1)
#define EXT_WRPROT_P3 (1u<<2)
// REG_EXT_CDET Acknowledgeable?
// REG_EXT_CDET_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
#define EXT_CDET_P1_REMOVE (1u) // (M) Port 1 card got removed.
#define EXT_CDET_P1_INSERT (1u<<1) // (M) Port 1 card got inserted. TODO: With detection timer?
#define EXT_CDET_P1_DETECT (1u<<2) // Port 1 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_P2_REMOVE (1u<<3) // (M) Port 2 card got removed.
#define EXT_CDET_P2_INSERT (1u<<4) // (M) Port 2 card got inserted. TODO: With detection timer?
#define EXT_CDET_P2_DETECT (1u<<5) // Port 2 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_P3_REMOVE (1u<<6) // (M) Port 3 card got removed.
#define EXT_CDET_P3_INSERT (1u<<7) // (M) Port 3 card got inserted. TODO: With detection timer?
#define EXT_CDET_P3_DETECT (1u<<8) // Port 3 card detect status. 1 = inserted. TODO: With detection timer?
#define EXT_CDET_MASK_ALL (0xFFFFu)
// REG_EXT_CDET_DAT3 Acknowledgeable?
// REG_EXT_CDET_DAT3_MASK (M) = Maskable bit. 1 = disabled (no IRQ).
#define EXT_CDET_DAT3_P1_REMOVE (1u) // (M) Port 1 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P1_INSERT (1u<<1) // (M) Port 1 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P1_DETECT (1u<<2) // Port 1 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_P2_REMOVE (1u<<3) // (M) Port 2 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P2_INSERT (1u<<4) // (M) Port 2 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P2_DETECT (1u<<5) // Port 2 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_P3_REMOVE (1u<<6) // (M) Port 3 card DAT3 got removed (low).
#define EXT_CDET_DAT3_P3_INSERT (1u<<7) // (M) Port 3 card DAT3 got inserted (high).
#define EXT_CDET_DAT3_P3_DETECT (1u<<8) // Port 3 card DAT3 status. 1 = inserted.
#define EXT_CDET_DAT3_MASK_ALL (0xFFFFu)
// REG_SD_FIFO32_CNT
// Bit 0 unknown, non-writable.
#define FIFO32_EN (1u<<1) // Enables the 32 bit FIFO.
#define FIFO32_FULL (1u<<8) // FIFO is full.
#define FIFO32_NOT_EMPTY (1u<<9) // FIFO is not empty. Inverted bit. 0 means empty.
#define FIFO32_CLEAR (1u<<10) // Clears the FIFO.
#define FIFO32_FULL_IE (1u<<11) // FIFO full IRQ enable.
#define FIFO32_NOT_EMPTY_IE (1u<<12) // FIFO not empty IRQ enable.
typedef struct
{
u8 portNum;
u16 sd_clk_ctrl;
u16 sd_blocklen; // Also sd_blocklen32.
u16 sd_option;
void *buf;
u16 blocks;
u32 resp[4]; // Little endian, MSB first.
} TmioPort;
#ifdef __cplusplus
extern "C"
{
#endif
/**
* @brief Initializes the tmio driver.
*/
void TMIO_init(void);
/**
* @brief Deinitializes the tmio driver.
*/
void TMIO_deinit(void);
/**
* @brief Initializes a tmio port to defaults.
*
* @param port A pointer to the port struct.
* @param[in] portNum The port number.
*/
void TMIO_initPort(TmioPort *const port, const u8 portNum);
/**
* @brief Checks if a MMC/SD card is inserted.
*
* @return Returns true if a card is inserted.
*/
bool TMIO_cardDetected(void);
/**
* @brief Checks if the write protect slider is set to locked.
*
* @return Returns true if the card is unlocked.
*/
bool TMIO_cardWritable(void);
/**
* @brief Handles the device specific powerup sequence including the 74 clocks.
*
* @param port A pointer to the port struct.
*/
void TMIO_powerupSequence(TmioPort *const port);
/**
* @brief Sends a command.
*
* @param port A pointer to the port struct.
* @param[in] cmd The command.
* @param[in] arg The argument for the command.
*
* @return Returns 0 on success otherwise see REG_SD_STATUS1/2 bits.
*/
u32 TMIO_sendCommand(TmioPort *const port, const u16 cmd, const u32 arg);
/**
* @brief Sets the clock for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] clk The target clock in Hz.
*/
__attribute__((always_inline)) static inline void TMIO_setClock(TmioPort *const port, const u32 clk)
{
port->sd_clk_ctrl = SD_CLK_PWR_SAVE | SD_CLK_EN | TMIO_CLK2DIV(clk)>>2;
}
/**
* @brief Sets the transfer block length for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] blockLen The block length. Caution: Provide a buffer with multiple of 4 size regardless of block length.
*/
__attribute__((always_inline)) static inline void TMIO_setBlockLen(TmioPort *const port, u16 blockLen)
{
if(blockLen > 512) blockLen = 512;
port->sd_blocklen = blockLen;
}
/**
* @brief Sets the bus width for a tmio port.
*
* @param port A pointer to the port struct.
* @param[in] width The bus width.
*/
__attribute__((always_inline)) static inline void TMIO_setBusWidth(TmioPort *const port, const u8 width)
{
port->sd_option = (width == 4 ? OPTION_BUS_WIDTH4 : OPTION_BUS_WIDTH1) |
OPTION_UNK14 | OPTION_DEFAULT_TIMINGS;
}
/**
* @brief Sets a transfer buffer for a tmio port.
*
* @param port A pointer to the port struct.
* @param buf The buffer pointer.
* @param[in] blocks The number of blocks to transfer.
*/
__attribute__((always_inline)) static inline void TMIO_setBuffer(TmioPort *const port, void *buf, const u16 blocks)
{
port->buf = buf;
port->blocks = blocks;
}
#ifdef __cplusplus
}
#endif