NVRAM Write capability (#946)

* Writable NVRAM

Also refactors the spiflash module to detect chips at runtime
This commit is contained in:
Wolfvak 2026-02-21 20:51:42 -03:00 committed by GitHub
parent 8754d32fe0
commit 7eb559cf21
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 214 additions and 44 deletions

View File

@ -19,6 +19,7 @@
#include <types.h> #include <types.h>
#include <spi.h> #include <spi.h>
#include "arm/timer.h"
#include "hw/nvram.h" #include "hw/nvram.h"
// returns manuf id, memory type and size // returns manuf id, memory type and size
@ -29,12 +30,20 @@
#define CMD_READ 0x03 #define CMD_READ 0x03
#define CMD_WREN 0x06 #define CMD_WREN 0x06
#define CMD_WRDI 0x04 #define CMD_WRDI 0x04
#define CMD_WRITE 0x0A
#define CMD_RDSR 0x05 #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_DPD 0xB9 // deep power down
#define CMD_RDP 0xAB // release from 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) static u32 NVRAM_SendStatusCommand(u32 cmd, u32 width)
{ {
u32 ret; u32 ret;
@ -53,16 +62,21 @@ static u32 NVRAM_SendStatusCommand(u32 cmd, u32 width)
return ret; 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) u32 NVRAM_Status(void)
{ {
return NVRAM_SendStatusCommand(CMD_RDSR, 1); return NVRAM_SendStatusCommand(CMD_RDSR, 1);
} }
u32 NVRAM_ReadID(void)
{
return NVRAM_SendStatusCommand(CMD_RDID, 3);
}
void NVRAM_DeepStandby(void) void NVRAM_DeepStandby(void)
{ {
NVRAM_SendStatusCommand(CMD_DPD, 0); NVRAM_SendStatusCommand(CMD_DPD, 0);
@ -73,12 +87,19 @@ void NVRAM_Wakeup(void)
NVRAM_SendStatusCommand(CMD_RDP, 0); 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]; SPI_XferInfo xfer[2];
u32 cmd; 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; cmd = __builtin_bswap32(address) | CMD_READ;
xfer[0].buf = &cmd; xfer[0].buf = &cmd;
@ -90,4 +111,63 @@ void NVRAM_Read(u32 address, u32 *buffer, u32 len)
xfer[1].read = true; xfer[1].read = true;
SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, 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;
} }

View File

@ -20,15 +20,11 @@
#include <types.h> #include <types.h>
#include <spi.h>
#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_Status(void);
u32 NVRAM_ReadID(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_DeepStandby(void);
void NVRAM_Wakeup(void); void NVRAM_Wakeup(void);

View File

@ -167,15 +167,19 @@ void __attribute__((noreturn)) MainLoop(void)
break; break;
} }
// checks whether the NVRAM chip is online (not doing any work) // gets the NVRAM chip ID
case PXICMD_NVRAM_ONLINE: case PXICMD_NVRAM_ID:
pxiReply = (NVRAM_Status() & NVRAM_SR_WIP) == 0; pxiReply = NVRAM_ReadID();
break; break;
// reads data from the NVRAM chip // reads data from the NVRAM chip
case PXICMD_NVRAM_READ: case PXICMD_NVRAM_READ:
NVRAM_Read(args[0], sharedMem.dataBuffer.w, args[1]); pxiReply = NVRAM_Read(args[0], sharedMem.dataBuffer.w, args[1]);
pxiReply = 0; break;
// writes data to the NVRAM chip
case PXICMD_NVRAM_WRITE:
pxiReply = NVRAM_Write(args[0], sharedMem.dataBuffer.w, args[1]);
break; break;
// sets the notification LED with the given color and period // sets the notification LED with the given color and period

View File

@ -101,10 +101,6 @@ bool CalibrateTouchFromFlash(void) {
// set calibration defaults // set calibration defaults
SetCalibrationDefaults(); SetCalibrationDefaults();
// check SPIflash status
if (!spiflash_get_status())
return false;
// check NVRAM console ID // check NVRAM console ID
u32 console_id = 0; u32 console_id = 0;
if (!spiflash_read(0x001C, 4, (u8*)&console_id)) if (!spiflash_read(0x001C, 4, (u8*)&console_id))

View File

@ -14,7 +14,7 @@
"1:/private/movable.sed", "1:/ro/sys/HWCAL0.dat", "1:/ro/sys/HWCAL1.dat", \ "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 "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", \ #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" #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 // write permissions - careful with this

View File

@ -3,13 +3,64 @@
#include "pxi.h" #include "pxi.h"
#include "shmem.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) 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 *const dataBuffer = ARM_GetSHMEM()->dataBuffer.w;
u32 args[2]; u32 args[2];
@ -19,7 +70,12 @@ bool spiflash_read(u32 offset, u32 size, u8 *buf)
args[0] = offset; args[0] = offset;
args[1] = blksz; 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_InvDC_Range(dataBuffer, blksz);
ARM_DSB(); ARM_DSB();
@ -32,3 +88,38 @@ bool spiflash_read(u32 offset, u32 size, u8 *buf)
return true; 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;
}

View File

@ -22,9 +22,6 @@
#include "arm.h" #include "arm.h"
#include "pxi.h" #include "pxi.h"
#define NVRAM_SIZE 0x20000 // 1 Mbit (128kiB) u32 spiflash_size(void);
// true if spiflash is installed, false otherwise
bool spiflash_get_status(void);
bool spiflash_read(u32 offset, u32 size, u8 *buf); bool spiflash_read(u32 offset, u32 size, u8 *buf);
bool spiflash_write(u32 offset, u32 size, const u8 *buf);

View File

@ -82,7 +82,7 @@ static const VirtualFile vMemFileTemplates[] = {
{ "mcu_dsi_regs.mem" , VMEM_CALLBACK_MCU_REGISTERS, 0x00000100, I2C_DEV_POWER, VFLAG_CALLBACK | VFLAG_READONLY }, { "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 }, { "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 }, { "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 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 // copy current template to vfile
memcpy(vfile, templates + vdir->index, sizeof(VirtualFile)); 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 // process special flags
if (((vfile->flags & VFLAG_N3DS_EXT) && (IS_O3DS)) || // this is not on O3DS consoles and locked by sighax 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 ((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. // Read NVRAM.
int ReadVMemNVRAM(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) { int ReadVMemNVRAM(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) {
static bool wififlash_initialized = false;
(void) vfile; (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)) int WriteVMemNVRAM(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) {
return 1; (void) vfile;
return 0; return spiflash_write((u32) offset, (u32) count, buffer) ? 0 : 1;
} }
int ReadVMemFile(const VirtualFile* vfile, void* buffer, u64 offset, u64 count) { 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) { int WriteVMemFile(const VirtualFile* vfile, const void* buffer, u64 offset, u64 count) {
if (vfile->flags & (VFLAG_READONLY|VFLAG_CALLBACK)) { if (vfile->flags & VFLAG_READONLY) {
return 1; // not writable / writes blocked 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 { } else {
u32 foffset = vfile->offset + offset; u32 foffset = vfile->offset + offset;
memcpy((u8*) foffset, buffer, count); memcpy((u8*) foffset, buffer, count);

View File

@ -32,8 +32,10 @@ enum {
PXICMD_GET_SHMEM_ADDRESS, PXICMD_GET_SHMEM_ADDRESS,
PXICMD_I2C_OP, PXICMD_I2C_OP,
PXICMD_NVRAM_ONLINE,
PXICMD_NVRAM_ID,
PXICMD_NVRAM_READ, PXICMD_NVRAM_READ,
PXICMD_NVRAM_WRITE,
PXICMD_SET_NOTIFY_LED, PXICMD_SET_NOTIFY_LED,
PXICMD_SET_BRIGHTNESS, PXICMD_SET_BRIGHTNESS,