NAND Flash Memory
The PSP Flash Memory, also called 'NAND', contains all the PSP firmware including the IPL, the IDStorage, and all the settings of the user.
Physical layout
The PSP MCP uses a 32MB NAND with the following layout:
- 512+16 bytes per page (512 bytes of main data + 16 bytes of spare data)
- 32 pages per block (16K+512)
- 2048 blocks per device (32MB+1MB)
A block is the smallest erasable unit, a page the smallest writable (programmable). A block holds 32 pages (for the latest small page NAND devices, including the MCP used for the PSP).
User Area (Main Data)
The IPL area (including IDStorage) is not part of any filesystem. Everything else (above 1MiB phys) is FAT12 with a SmartMedia style Block mapping but with a custom mapping area (i.e. different layout from what is/was mandated for SM). Only FAT organized area of on-board flash chip, system file volume and configuration file volume, can be accessed via FAT Filesystem. The bootstrap area is unreachable by the flash and lflash drivers. (lflash returns all 0x00)
Physical Layout (unmapped)
Start | End | Size | Description |
---|---|---|---|
0x00000000 | 0x000FFFFF | 1MB | Bootstrap (IPL) area |
0x00100000 | 0x01FFFFFF | 31MB | Mapped (filesystem) area |
Logical Layout (mapped)
When the Flashdriver starts up it reads all the extra data sections (usually from the first page of each block). From this data it extracts the logical block number which in turn is used to build up a table (index is LBN, value is PBN). Reading from logical blocks works by simple address translation (LBN->PBN). Writing is usually done using a write before erase strategy, i.e. an emtpy block is filled with the data (new/replacement), then the LBN entry is remapped to the new PBN and the old physical block is erased (and goes either back to the free pool are becomes a bad block).
Start offset | Start block | Size | Description |
---|---|---|---|
0x00000000 | 0x000 | Master Boot Record (MBR) | |
0x00008000 | 0x002 | Partition Boot Record (PBR) | |
0x0000C000 | 0x003 | 24MiB | FAT12 Partition #1 (flash0) |
0x01808000 | 0x602 | ||
0x0180C000 | 0x603 | 4MiB | FAT12 Partition #2 (flash1) |
0x01C08000 | 0x702 | ||
0x01C0C000 | 0x703 | FAT12 Partition #3 (empty) | |
0x01D08000 | 0x742 | ||
0x01D0C000 | 0x743 | FAT12 Partition #4 (empty) | |
0x01DF8000 | 0x77e | ||
0x01DFC000 | 0x77f | Last Block |
Bootstrap (IPL Area)
The IPL, region and serial number are located within the NAND non-FAT area (in an encrypted form).
Start offset | Start block | Size | Description |
---|---|---|---|
0x00000000 | 0 | 64k | Empty (all 0xff including spare data) |
0x00010000 | 4 | 16k | List of physical block numbers of the IPL (16bits per value, stored in little endian) |
0x00014000 | 5 | Duplicate of the previous block | |
0x00018000 | 6 | Duplicate of the previous block | |
0x0001C000 | 7 | Duplicate of the previous block | |
0x00020000 | 8 | Duplicate of the previous block | |
0x00024000 | 9 | Duplicate of the previous block | |
0x00028000 | 10 | Duplicate of the previous block | |
0x0002C000 | 11 | Duplicate of the previous block | |
0x00030000 | 12 | Empty | |
0x00040000 | 16 | Encrypted IPL (by chunks of 0x1000 bytes each, ie 8 pages) | |
... | ... | Empty (position depending on the size of the IPL) | |
0x000C0000 | 48 | ID Storage area | |
... | ... | Empty (position depending on the size of the IDStorage) |
IPL Block Mapping
Physical blocks 4-11 hold mapping information. Each block contains the same information, for redundancy presumably. If one of these blocks becomes invalid, the next one is used etc. If all these blocks are bad the PSP won't be able to find the IPL and will crash.
ID Storage Area
See IDStorage for information about the ID Storage.
FAT Area
FAT12 with a cluster size of 16K which conveniently matches the erase block size.
Spare data
For each 512-byte long NAND page, an additional 16 bytes are available to store various metadata.
Start | Size | Description |
---|---|---|
0x00 | 4 | The 24-bit ECC of the user data of the page. See below for the ECC algorithm. |
0x04 | 1 | 0xFF for the IPL, 0x00 for FAT |
0x05 | 1 | 0xFF if the block is valid |
0x06 | 2 | Logical block number for FAT, 0xFFFF for IPL, 0x73 0x01 (= IDStorage version 1) for the IDStorage index |
0x08 | 4 | 0xFFFFFFFF for empty pages, 0x6DC64A38 for the IPL (including the block table), 0xFFFF0101 for the IDStorage index (byte is either 1 or 0 depending on whether the IDStorage has been formatted or not, and byte 9 indicates if the IDStorage is read-only or not) |
0x0C | 2 | The 12-bit of the ECC of bytes 0x04-0x0B of this area (high nibble is always 0xF) |
0x0E | 2 | Always 0xFF |
ECC algorithms
Both ECC algorithms rely on the parity function (1 if there is an odd number of bits to 1, 0 otherwise):
def get_parity(b):
return ((0x6996 >> (b & 0xF)) ^ (0x6996 >> (b >> 4))) & 1
ECC algorithm for the page data
def compute_ecc(page):
output = [0]*24
for (i, byte) in enumerate(page):
par = get_parity(byte)
for j in range(9):
if i & (1 << j):
output[1 + 2*j] ^= par
else:
output[2*j] ^= par
output[18] ^= ((byte >> 6) ^ (byte >> 4) ^ (byte >> 2) ^ (byte >> 0)) & 1
output[19] ^= ((byte >> 7) ^ (byte >> 5) ^ (byte >> 3) ^ (byte >> 1)) & 1
output[20] ^= ((byte >> 5) ^ (byte >> 4) ^ (byte >> 1) ^ (byte >> 0)) & 1
output[21] ^= ((byte >> 7) ^ (byte >> 6) ^ (byte >> 3) ^ (byte >> 2)) & 1
output[22] ^= ((byte >> 3) ^ (byte >> 2) ^ (byte >> 1) ^ (byte >> 0)) & 1
output[23] ^= ((byte >> 7) ^ (byte >> 6) ^ (byte >> 5) ^ (byte >> 4)) & 1
result = 0
for i in range(24):
result |= output[i] << i
return result
ECC algorithm for the spare data
def compute_ecc_spare(buf):
xall = buf[7] ^ buf[6] ^ buf[5] ^ buf[4] ^ buf[3] ^ buf[2] ^ buf[1] ^ buf[0]
x7654 = buf[7] ^ buf[6] ^ buf[5] ^ buf[4]
x3210 = buf[3] ^ buf[2] ^ buf[1] ^ buf[0]
x7632 = buf[7] ^ buf[6] ^ buf[3] ^ buf[2]
x5410 = buf[5] ^ buf[4] ^ buf[1] ^ buf[0]
x7531 = buf[7] ^ buf[5] ^ buf[3] ^ buf[1]
x6420 = buf[6] ^ buf[4] ^ buf[2] ^ buf[0]
return \
(get_parity(xall & 0x55) << 0) \
| (get_parity(xall & 0x33) << 1) \
| (get_parity(xall & 0x0F) << 2) \
| (get_parity(x6420) << 3) \
| (get_parity(x5410) << 4) \
| (get_parity(x3210) << 5) \
| (get_parity(xall & 0xAA) << 6) \
| (get_parity(xall & 0xCC) << 7) \
| (get_parity(xall & 0xF0) << 8) \
| (get_parity(x7531) << 9) \
| (get_parity(x7632) << 10) \
| (get_parity(x7654) << 11)