NAND Flash Memory

From PSP Developer wiki
Revision as of 23:46, 29 January 2023 by Artart78 (talk | contribs) (Created page with "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 P...")
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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)