mirror of
https://github.com/d0k3/GodMode9.git
synced 2026-02-25 08:24:39 +00:00
NVRAM Write capability (#946)
* Writable NVRAM Also refactors the spiflash module to detect chips at runtime
This commit is contained in:
parent
8754d32fe0
commit
7eb559cf21
@ -19,6 +19,7 @@
|
||||
#include <types.h>
|
||||
|
||||
#include <spi.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
@ -20,15 +20,11 @@
|
||||
|
||||
#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_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);
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user