diff --git a/arm11/source/hw/nvram.c b/arm11/source/hw/nvram.c index 6792dce..cfbec03 100755 --- a/arm11/source/hw/nvram.c +++ b/arm11/source/hw/nvram.c @@ -19,6 +19,7 @@ #include #include +#include "arm/timer.h" #include "hw/nvram.h" // returns manuf id, memory type and size @@ -29,12 +30,20 @@ #define CMD_READ 0x03 #define CMD_WREN 0x06 #define CMD_WRDI 0x04 +#define CMD_WRITE 0x0A #define CMD_RDSR 0x05 +#define NVRAM_SR_WIP BIT(0) // work in progress / busy +#define NVRAM_SR_WEL BIT(1) // write enable latch + #define CMD_DPD 0xB9 // deep power down #define CMD_RDP 0xAB // release from deep power down +#define NVRAM_PAGE_SIZE 0x100 // 256 byte pages +#define NVRAM_ADDR_MASK 0xFFFFFF // 24bit address +#define NVRAM_ADDR_MAX (NVRAM_ADDR_MASK + 1) + static u32 NVRAM_SendStatusCommand(u32 cmd, u32 width) { u32 ret; @@ -53,16 +62,21 @@ static u32 NVRAM_SendStatusCommand(u32 cmd, u32 width) return ret; } +static void NVRAM_SetWriteEnable(bool write_en) +{ + u8 cmd = write_en ? CMD_WREN : CMD_WRDI; + SPI_XferInfo xfer; + xfer.buf = &cmd; + xfer.len = 1; + xfer.read = false; + SPI_DoXfer(SPI_DEV_NVRAM, &xfer, 1, true); +} + u32 NVRAM_Status(void) { return NVRAM_SendStatusCommand(CMD_RDSR, 1); } -u32 NVRAM_ReadID(void) -{ - return NVRAM_SendStatusCommand(CMD_RDID, 3); -} - void NVRAM_DeepStandby(void) { NVRAM_SendStatusCommand(CMD_DPD, 0); @@ -73,12 +87,19 @@ void NVRAM_Wakeup(void) NVRAM_SendStatusCommand(CMD_RDP, 0); } -void NVRAM_Read(u32 address, u32 *buffer, u32 len) +u32 NVRAM_ReadID(void) +{ + return NVRAM_SendStatusCommand(CMD_RDID, 3); +} + +int NVRAM_Read(u32 address, u32 *buffer, u32 len) { SPI_XferInfo xfer[2]; u32 cmd; - address &= BIT(24) - 1; + if (address >= NVRAM_ADDR_MAX || len > NVRAM_ADDR_MAX || (address + len) > NVRAM_ADDR_MAX) + return -1; + cmd = __builtin_bswap32(address) | CMD_READ; xfer[0].buf = &cmd; @@ -90,4 +111,63 @@ void NVRAM_Read(u32 address, u32 *buffer, u32 len) xfer[1].read = true; SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, true); + return 0; +} + +static int NVRAM_WritePage(u32 address, const u32 *buffer, u32 len) +{ + SPI_XferInfo xfer[2]; + u32 cmd, i; + + if (address >= NVRAM_ADDR_MAX || len > NVRAM_PAGE_SIZE || (address + len) > NVRAM_ADDR_MAX) + return -1; + + cmd = __builtin_bswap32(address) | CMD_WRITE; + + xfer[0].buf = &cmd; + xfer[0].len = 4; + xfer[0].read = false; + + xfer[1].buf = (void*)buffer; + xfer[1].len = len; + xfer[1].read = false; + + // enable the write latch + NVRAM_SetWriteEnable(true); + // make sure it's enabled + for (i = 0; i <= 1000; i++) { + if (i == 1000) return -2; + if (NVRAM_Status() & NVRAM_SR_WEL) break; + TIMER_WaitMS(1); + } + + // do the write transfer + SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, true); + + // wait until it's done, disable the write latch + for (i = 0; i <= 1000; i++) { + if (i == 1000) return -3; + if (!(NVRAM_Status() & NVRAM_SR_WIP)) break; + TIMER_WaitMS(1); + } + NVRAM_SetWriteEnable(false); + + return 0; +} + +int NVRAM_Write(u32 address, const u32 *buffer, u32 len) +{ + while(len > 0) { + u32 blksz = len < NVRAM_PAGE_SIZE ? len : NVRAM_PAGE_SIZE; + + int result = NVRAM_WritePage(address, buffer, blksz); + if (result != 0) + return result; + + address += blksz; + buffer += (blksz / 4); + len -= blksz; + } + + return 0; } diff --git a/arm11/source/hw/nvram.h b/arm11/source/hw/nvram.h index 3f01c3e..ead5cd7 100755 --- a/arm11/source/hw/nvram.h +++ b/arm11/source/hw/nvram.h @@ -20,15 +20,11 @@ #include -#include - -#define NVRAM_SR_WIP BIT(0) // work in progress / busy -#define NVRAM_SR_WEL BIT(1) // write enable latch - u32 NVRAM_Status(void); u32 NVRAM_ReadID(void); -void NVRAM_Read(u32 offset, u32 *buffer, u32 len); +int NVRAM_Read(u32 offset, u32 *buffer, u32 len); +int NVRAM_Write(u32 address, const u32 *buffer, u32 len); void NVRAM_DeepStandby(void); void NVRAM_Wakeup(void); diff --git a/arm11/source/main.c b/arm11/source/main.c index 60a23f7..6174d52 100644 --- a/arm11/source/main.c +++ b/arm11/source/main.c @@ -167,15 +167,19 @@ void __attribute__((noreturn)) MainLoop(void) break; } - // checks whether the NVRAM chip is online (not doing any work) - case PXICMD_NVRAM_ONLINE: - pxiReply = (NVRAM_Status() & NVRAM_SR_WIP) == 0; + // gets the NVRAM chip ID + case PXICMD_NVRAM_ID: + pxiReply = NVRAM_ReadID(); break; // reads data from the NVRAM chip case PXICMD_NVRAM_READ: - NVRAM_Read(args[0], sharedMem.dataBuffer.w, args[1]); - pxiReply = 0; + pxiReply = NVRAM_Read(args[0], sharedMem.dataBuffer.w, args[1]); + break; + + // writes data to the NVRAM chip + case PXICMD_NVRAM_WRITE: + pxiReply = NVRAM_Write(args[0], sharedMem.dataBuffer.w, args[1]); break; // sets the notification LED with the given color and period diff --git a/arm9/source/common/touchcal.c b/arm9/source/common/touchcal.c index 76412dc..ae67a42 100644 --- a/arm9/source/common/touchcal.c +++ b/arm9/source/common/touchcal.c @@ -101,10 +101,6 @@ bool CalibrateTouchFromFlash(void) { // set calibration defaults SetCalibrationDefaults(); - // check SPIflash status - if (!spiflash_get_status()) - return false; - // check NVRAM console ID u32 console_id = 0; if (!spiflash_read(0x001C, 4, (u8*)&console_id)) diff --git a/arm9/source/filesys/fsperm.c b/arm9/source/filesys/fsperm.c index ae4eb60..fb1b460 100644 --- a/arm9/source/filesys/fsperm.c +++ b/arm9/source/filesys/fsperm.c @@ -14,7 +14,7 @@ "1:/private/movable.sed", "1:/ro/sys/HWCAL0.dat", "1:/ro/sys/HWCAL1.dat", \ "S:/ctrnand_fat.bin", "S:/ctrnand_full.bin", "S:/" ESSENTIAL_NAME #define PATH_SYS_LVL3 "S:/firm0.bin", "S:/firm1.bin", "S:/nand.bin", "S:/nand_minsize.bin", "S:/nand_hdr.bin", \ - "S:/sector0x96.bin", "S:/twlmbr.bin" + "S:/sector0x96.bin", "S:/twlmbr.bin", "M:/nvram.mem" #define PATH_EMU_LVL1 "E:/ctrnand_fat.bin", "E:/ctrnand_full.bin", "E:/nand.bin", "E:/nand_minsize.bin", "E:/nand_hdr.bin" // write permissions - careful with this diff --git a/arm9/source/system/spiflash.c b/arm9/source/system/spiflash.c index c1b3bcd..473ce67 100755 --- a/arm9/source/system/spiflash.c +++ b/arm9/source/system/spiflash.c @@ -3,13 +3,64 @@ #include "pxi.h" #include "shmem.h" -bool spiflash_get_status(void) +#include "ui.h" + +typedef struct { + u32 id; + u32 size; + const char *name; +} spiflash_chip_t; + +/** + * List of known SPI flash chips available in the NDS/3DS family of systems. + * If it doesn't match any of these, it should be safe to assume we have a 128k flash chip. + * Obtained from gbatek (https://problemkaputt.de/gbatek-ds-firmware-serial-flash-memory.htm) as of 1/21/26. + */ +static const spiflash_chip_t spiflash_known_chips[] = { + { 0x124020, 256u << 10, "ST M45PE20" }, + { 0x125020, 256u << 10, "ST M35PE20" }, + { 0x138020, 512u << 10, "ST M25PE40" }, + { 0x114020, 128u << 10, "ST 45PE10V6" }, + { 0x0C5820, 128u << 10, "5A32 (4k)" }, + { 0x0C6262, 128u << 10, "32B/3XH (4k)" }, +}; + +static const spiflash_chip_t *spiflash_initialize(void) { + static spiflash_chip_t spiflash_chip = { + .id = 0xFFFFFFFF, /* filled at runtime */ + .size = 128u << 10, + .name = "Unknown" + }; + if (spiflash_chip.id != 0xFFFFFFFF) + return &spiflash_chip; + + u32 id = PXI_DoCMD(PXICMD_NVRAM_ID, NULL, 0); + + spiflash_chip.id = id; + + for (unsigned i = 0; i < countof(spiflash_known_chips); i++) { + if (id == spiflash_known_chips[i].id) { + spiflash_chip = spiflash_known_chips[i]; + break; + } + } + + return &spiflash_chip; +} + +u32 spiflash_size(void) { - return PXI_DoCMD(PXICMD_NVRAM_ONLINE, NULL, 0); + return spiflash_initialize()->size; } bool spiflash_read(u32 offset, u32 size, u8 *buf) { + u32 total_size = spiflash_size(); + if ((offset >= total_size) || + (size > total_size) || + (offset + size > total_size)) + return false; + u32 *const dataBuffer = ARM_GetSHMEM()->dataBuffer.w; u32 args[2]; @@ -19,7 +70,12 @@ bool spiflash_read(u32 offset, u32 size, u8 *buf) args[0] = offset; args[1] = blksz; - PXI_DoCMD(PXICMD_NVRAM_READ, args, 2); + int result = PXI_DoCMD(PXICMD_NVRAM_READ, args, 2); + if (result != 0) { + ShowPrompt(false, "SPI flash read failed at offset\n0x%08lx size 0x%08lx (%d)", offset, blksz, result); + return false; + } + ARM_InvDC_Range(dataBuffer, blksz); ARM_DSB(); @@ -32,3 +88,38 @@ bool spiflash_read(u32 offset, u32 size, u8 *buf) return true; } + +bool spiflash_write(u32 offset, u32 size, const u8 *buf) +{ + u32 total_size = spiflash_size(); + if ((offset >= total_size) || + (size > total_size) || + (offset + size > total_size)) + return false; + + u32 *const dataBuffer = ARM_GetSHMEM()->dataBuffer.w; + u32 args[2]; + + while(size > 0) { + u32 blksz = min(size, SHMEM_BUFFER_SIZE); + + args[0] = offset; + args[1] = blksz; + + memcpy(dataBuffer, buf, blksz); + ARM_WbInvDC_Range(dataBuffer, blksz); + ARM_DSB(); + + int result = PXI_DoCMD(PXICMD_NVRAM_WRITE, args, 2); + if (result != 0) { + ShowPrompt(false, "SPI flash write failed at offset\n0x%08lx size 0x%08lx (%d)", offset, blksz, result); + return false; + } + + buf += blksz; + size -= blksz; + offset += blksz; + } + + return true; +} diff --git a/arm9/source/system/spiflash.h b/arm9/source/system/spiflash.h index 9cea180..142d058 100644 --- a/arm9/source/system/spiflash.h +++ b/arm9/source/system/spiflash.h @@ -22,9 +22,6 @@ #include "arm.h" #include "pxi.h" -#define NVRAM_SIZE 0x20000 // 1 Mbit (128kiB) - -// true if spiflash is installed, false otherwise -bool spiflash_get_status(void); - +u32 spiflash_size(void); bool spiflash_read(u32 offset, u32 size, u8 *buf); +bool spiflash_write(u32 offset, u32 size, const u8 *buf); diff --git a/arm9/source/virtual/vmem.c b/arm9/source/virtual/vmem.c index f07978e..10bc9db 100644 --- a/arm9/source/virtual/vmem.c +++ b/arm9/source/virtual/vmem.c @@ -82,7 +82,7 @@ static const VirtualFile vMemFileTemplates[] = { { "mcu_dsi_regs.mem" , VMEM_CALLBACK_MCU_REGISTERS, 0x00000100, I2C_DEV_POWER, VFLAG_CALLBACK | VFLAG_READONLY }, { "sd_cid.mem" , VMEM_CALLBACK_FLASH_CID , 0x00000010, 0x00, VFLAG_CALLBACK | VFLAG_READONLY }, { "nand_cid.mem" , VMEM_CALLBACK_FLASH_CID , 0x00000010, 0x01, VFLAG_CALLBACK | VFLAG_READONLY }, - { "nvram.mem" , VMEM_CALLBACK_NVRAM , NVRAM_SIZE, 0x00, VFLAG_CALLBACK | VFLAG_READONLY } + { "nvram.mem" , VMEM_CALLBACK_NVRAM , 0x00000000, 0x00, VFLAG_CALLBACK } }; bool ReadVMemDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir object generated in virtual.c @@ -93,6 +93,11 @@ bool ReadVMemDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir // copy current template to vfile memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); + // special case for the NVRAM as it can have a variable size + if (vfile->offset == VMEM_CALLBACK_NVRAM) { + vfile->size = spiflash_size(); + } + // process special flags if (((vfile->flags & VFLAG_N3DS_EXT) && (IS_O3DS)) || // this is not on O3DS consoles and locked by sighax ((vfile->flags & VFLAG_OTP) && !(IS_UNLOCKED)) || // OTP still locked @@ -158,17 +163,13 @@ int ReadVMemFlashCID(const VirtualFile* vfile, void* buffer, u64 offset, u64 cou // Read NVRAM. int ReadVMemNVRAM(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) { - static bool wififlash_initialized = false; (void) vfile; + return spiflash_read((u32) offset, (u32) count, buffer) ? 0 : 1; +} - if (!wififlash_initialized) { - wififlash_initialized = spiflash_get_status(); - if (!wififlash_initialized) return 1; - } - - if (!spiflash_read((u32) offset, (u32) count, buffer)) - return 1; - return 0; +int WriteVMemNVRAM(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) { + (void) vfile; + return spiflash_write((u32) offset, (u32) count, buffer) ? 0 : 1; } int ReadVMemFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) { @@ -184,8 +185,11 @@ int ReadVMemFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) } int WriteVMemFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) { - if (vfile->flags & (VFLAG_READONLY|VFLAG_CALLBACK)) { - return 1; // not writable / writes blocked + if (vfile->flags & VFLAG_READONLY) { + return 1; // read-only file, don't even try + } else if (vfile->flags & VFLAG_CALLBACK) { + // assume the only callback-capable writable file is NVRAM for now + return WriteVMemNVRAM(vfile, buffer, offset, count); } else { u32 foffset = vfile->offset + offset; memcpy((u8*) foffset, buffer, count); diff --git a/common/pxi.h b/common/pxi.h index 54e52cd..d8b4333 100644 --- a/common/pxi.h +++ b/common/pxi.h @@ -32,8 +32,10 @@ enum { PXICMD_GET_SHMEM_ADDRESS, PXICMD_I2C_OP, - PXICMD_NVRAM_ONLINE, + + PXICMD_NVRAM_ID, PXICMD_NVRAM_READ, + PXICMD_NVRAM_WRITE, PXICMD_SET_NOTIFY_LED, PXICMD_SET_BRIGHTNESS,