Dump data from NAND flash with Arduino

 Posted by:   Posted on:   Updated on:  2021-01-01T15:47:59Z

An Arduino sketch that reads pages and blocks from NAND Flash memory and prints data to serial port

A while ago I decided to see if it is possible to read data from a NAND flash memory chip using an Arduino. Although I found out it is possible, it is not quite practical. The ATmega328 Arduino is way too slow to read and transfer large amounts of data. Nevertheless, dumping data is possible. But for common usage, such a slow and limited microcontroller shall not be used for this purpose.

In the previous posts I wired the NAND to a 3.3V Arduino and wrote a basic sketch to communicate with the flash chip and read its ID register. Now I will attempt to read data from the memory and transfer it to PC over serial port. I must say I have no prior experience with NAND memory chips and this is the first time I’m ever attempting this.

Dump data from NAND flash with Arduino

The memory array

Before writing the code, we must have a basic understanding of the memory organization in a NAND flash. The smallest unit of the flash is a cell, a floating gate transistor (FGT), which is the element which holds a bit of information (0 or 1). These are connected in a two-dimensional grid, where columns are word lines and rows are bit lines.

Without getting into too much detail regarding the physical structure of the NAND flash, it’s important to know that multiple cells make a page. You will find small page devices (512+16 bytes/page) and large pages devices (2048+64 bytes/page). The page is divided into one or more main areas and a spare area (i.e., the page of a small page device holds 512 bytes in the main area and 16 bytes in the spare area). The main area of the array is used to store data whereas the spare area is typically used to store Error correction Codes, software flags or Bad Block identification.

The chip has a corresponding page register (buffer). When reading, data is transfer from NAND cells of a page to this register, while when programming, data is transferred to cells from this register. Thus, the page is the elementary unit of operation for a NAND flash (the smallest unit that you read or write).

Multiple pages make a block. The block is important too, because it is the smallest unit that can be erased. Any cells that have been set to 0 by programming can only be reset to 1 by erasing the entire block. This means that before new data can be programmed into a page that already contains data, the current contents of the page must be either stored at the host (read and kept in RAM of the controller) or moved to a new, erased page of the flash. It is doable with Arduino for small page devices.

An auto-incrementing address pointer is used to read large amounts of data without sending a new address to flash chip before any read operation. The pointer is usually limited to a block, but for some chips it may be able to address the whole chip.

ST NAND512W3A

Memory array organization of NAND512W3A

Memory array organization of NAND512W3A

The above illustration is specific to my NAND512W3A chip. It is a small page device with 528 bytes/page. Note that the main area of the page is divided in two halves. A block is a collection of 32 pages and there are a total of 4096 blocks in this flash.

This flash chip has some additional features when compared to the standard model:

  • random read – you can start reading from any column of the page. I don’t know if, internally, the full page is copied to buffer or only the requested columns;
  • partial page programming – up to three consecutive program operations are allowed in a page without needing to erase the block and program all pages of that block;
  • copy back program – copy the data stored in one page and reprogram it in another page, without using an additional memory device. The operation is particularly useful when a portion of a block is updated and the rest of the block needs to be copied to the newly assigned block.

Address insertion

The address must contain the column, the page and the block. For NAND512W3A, there is a different read command for each of the areas (0x00 for first half - A, 0x01 for second half - B and 0x50 for spare - C). Then the column is an 8-bit number (0 to 255 for A, B and 0 to 15 for C). Then a 5-bit number represents the page address in block (0 to 31) and a 13-bit number is block address (0 to 4095 for this flash).

Still, I must refer to the datasheet for precise address insertion. It is similar to sending a command, with the difference that AL (address latch) control signal must be held high during address transmission. Obviously, CL (command latch) is low. You are writing to the chip, so data is pulsed with W signal.

Address insertion for NAND512W3A (adapted from datasheet)

Address insertion for NAND512W3A (adapted from datasheet)

Putting this into code shouldn’t be quite difficult. Some knowledge of bit shifting operations is required.

void nandSendAddress(uint8_t column, uint8_t page, uint16_t block) {
  digitalWrite(NAND_AL, HIGH);

  digitalWrite(NAND_W, LOW);
  portWrite(column);
  digitalWrite(NAND_W, HIGH);

  digitalWrite(NAND_W, LOW);
  portWrite((page & 0x1F) | ((block & 0x07) << 5));
  digitalWrite(NAND_W, HIGH);

  digitalWrite(NAND_W, LOW);
  portWrite((block >> 3) & 0xFF);
  digitalWrite(NAND_W, HIGH);

  digitalWrite(NAND_W, LOW);
  portWrite((block >> 11) & 0x03);
  digitalWrite(NAND_W, HIGH);

  digitalWrite(NAND_AL, LOW);
}

The first thing to do is raise AL and keep it raised during the transmission. Then, by pulsing W, four bytes will be written. The first is the column address, the second is 3 bytes from block address and page address. The remaining two bytes are part of block address.

Read a page area

The page of this flash is split in three areas. The two main areas are 256 bytes each and the spare area is 16 bytes. The function that reads one area only handles R and prints temporarily stored data to serial port.

void nandReadPageArea(uint16_t areaSize = 256) {
  uint8_t d[areaSize];
  uint8_t cd = 0;

  for (uint16_t i = 0; i < areaSize; i++) {
    digitalWrite(NAND_R, LOW);
    d[i] = portRead();
    digitalWrite(NAND_R, HIGH);
  }

  for (uint16_t i = 0; i < areaSize; i++) {
    if (i % 16 == 0) Serial.println();

    if (d[i] < 0x10) Serial.print('0');
    Serial.print(d[i], HEX);
    Serial.print(' ');
  }
}

Read a block

Since the useful data should be in main area, I will not read spare memory area. The function handles port, sends commands and address and reads the full page.

void nandReadBlock(uint16_t block) {
  for (uint8_t i = 0; i < 32; i++) {
    // read first half
    portModeOutput();
    nandSendCommand(0x00);
    nandSendAddress(0x00, i, block);
    portModeInput();

    while (digitalRead(NAND_RB) == LOW) ;
    nandReadPageArea();

    // read second half
    portModeOutput();
    nandSendCommand(0x01);
    nandSendAddress(0x00, i, block);
    portModeInput();

    while (digitalRead(NAND_RB) == LOW) ;
    nandReadPageArea();
  }
}

I already know there are 32 pages in a block, so I have to read each of the halves 32 times. Although the datasheet mentions a sequential reading mode, where the address pointer jumps automatically to the next page, I did not have success reading this NAND until I decided to send the read command and addresses for each half. Inside the for loop, there are a couple of blocking conditions which depend on the state of RB signal. There should be no problems though as long as NAND is functioning properly.

Data sample

Reading a single block of 16 kilobytes and printing it to serial port takes 8.7 seconds. I had to decrease baudrate of serial port, because with 115200, I got garbage even though I replaced the jumper wires with a connector. I had to switch to 57600. Another solution to speed up data transfer is to print raw bytes. Now, I output data in hexadecimal format to serial port. For a single read byte, Arduino has to write 3 bytes to serial port (i.e., for 0x25, Arduino prints 2, 5 and a space). This is how a sample output looks like (truncated):

NAND Test
NAND manufacturer: 20
NAND device ID:    76
NAND status:       60
NAND Block Dump:

10 00 02 7A 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
..
..
..
00 00 00 00 00 00 00 00 00 00 08 C0 7F 00 00 00 
00 00 00 00 11 00 00 00 10 73 45 72 43 6F 4D 6D 

Read time: 8700 milliseconds
Speed: 1.84 kilobytes/second

All the hexadecimal data may be selected, copied and pasted into a new file in HxD editor. Have I got it right? Well, the flash comes from a router (modem) with Broadcom chipset. These devices use a CFE bootloader and this specific BCM6361 device comes with NVRAM version greater than 5. Comparing the hex dump screenshot from OpenWrt Wiki with the data I got from NAND, I am able to identify expected sections of bootloader.

Screenshot of data dump from NAND

Screenshot of data dump from NAND

Conclusion

You won't be building a full featured NAND dumper and programmer using Arduino ATmega328p. That's for sure. I will not continue this series of posts on the same platform. I did though a full dump of the NAND I have (64 megabytes). Although it should take nearly 10 hours at 1.84 kilobytes/second, I did some modifications and finished in less than 4 hours. The Serial Monitor of Arduino IDE will block if you print more than 512 kilobytes in hexadecimal format. So, I decided to print raw data from NAND and capture it with a small C++ application I wrote for this purpose.

After about 4 hours I had a full dump. But is this data useful? Well, I'm not so sure. The NAND driver of the router may use ECC and block relocation algorithms not known to me. The bootloader starts at offset 0 in block 0, probably with a jump instruction. As I found out subsequent blocks start with 0x1985. This is a known header for JFFS2 filesystem. For example, at offset 0x20000 I had a surprise:

JFFS2 header splits data

JFFS2 header splits data

The string Invalid MAC addresses number is split by an array of 68 bytes. And that string is actually a CFE error message. So, I'm still at the bootloader. The continuation proves I switched properly to a new block of data (at that offset I am between 8th and 9th block).

Interactive sketch for dumping NAND contents to serial output

Interactive sketch for dumping NAND contents to serial output

I came up with a simple interactive sketch. Disable line ending characters in Serial Monitor and you will be able to read blocks from NAND. Sending an "m" selects main area, sending an "s" selects spare area. Numeric values select the block to be dumped. Other non-numeric characters will result in printing contents of block 0. The sketch is available for download.

References

  1. Wikipedia contributors, "Flash memory," Wikipedia, The Free Encyclopedia, https://en.wikipedia.org/w/index.php?title=Flash_memory&oldid=996658711 (accessed December 31, 2020).
  2. Novotný R, Kadlec J, Kuchta R (2015) NAND Flash Memory Organization and Operations. J Inform Tech Softw Eng 5: 139. doi:10.4172/2165-7866.1000139.
  3. Mohan V, Gurumurthi S, Stan M (2010) FlashPower: A detailed power model for NAND flash memory. 502-507. 10.1109/DATE.2010.5457154.
  4. One Transistor: Attempts at reading parallel NAND Flash with Arduino (previous post 1)
  5. One Transistor: Read NAND Flash device signature with Arduino (previous post 2)
  6. James Tate (2017). Arduino based NAND chip reader on HWREBLOG (February 28, 2017).
  7. Arduino Mega: Direct R/W of a Nand Flash memory chip on Reverse Engineering (hardcoreforensics.com, January 2, 2012)

No comments :

Post a Comment

Please read the comments policy before publishing your comment.