NAND erase block

Problem

Bad Blocks

NAND memory apparently gets shipped with blocks that are already bad. The vendor just marks those blocks as bad, thus resulting in higher yield and lower per-unit cost. Thus, the flash will in the end contain three kinds of blocks:

Factory-Default bad blocks

Worn-out bad blocks

Good blocks

The only block that is guaranteed to be good, is the first block (first 16kBytes).

We are also guaranteed that a minimum of 4016 blocks (out of the total 4096) are good. This means up to 80 blocks (1.3MBytes) can be dead, resulting in a total guaranteed amount of working NAND storage of 65798144 bytes.

Solution

Boot loader

first-stage

This code (mostly written in ARM assembly) was altered to detect and skip bad blocks. This means, the first stage bootloader can itself extend over bad blocks.

This also menas that the flashing routine needs to detect and skip bad blocks, resulting in a u-boot image that can have gaping holes ;) The existing "traditional" sjf2410-linux JTAG flashing program is not detecting bad blocks (Note: this might be changed through a compile option, see below)

Environment

The u-boot environment is traditionally stored at a fixed location within the NAND flash. This is not acceptable, since it could be a factory-set bad block.

The solution that was implemented for OpenMoko/Neo1973 was to put the in-flash address of the environment into the out-of-band (OOB) area of the first block (the one which is guaranteed to be good). Since the environment address is unlikely to change often, the 1000 erase cycles guaranteed for the first block are good enough.

The exact location is byte 8..15 of the 16byte OOB area, starting with the four ASCII bytes ENVO, followed by the little-endian 32bit unsigned integer of the NAND address where the environment is located.

The u-boot "dynenv get" command can be used to read out a pre-programmed Environment offset from NAND, and the "dynenv set" can be used to write the offset (if the last eight bytes of OOB area are erased (0xff)).

Partition Table

Since those up to 80 factory-bad blocks can be located about anywhere in NAND, we have to accomodate for this worst case. However, we cannot just make every partition 1.3MB larger than it needs to be, since this would waste a lot of otherwise good flash.

The only solution to this (that Harald could think of) is to dynamically calculate a partition table for each device. Every NAND flash has different factory-bad blocks at different locations, thus the partition table on every NAND flash will look different.

So as an example, lets' assume we have a 0x30000 (196k) bytes sized partition for u-boot, starting ad address 0 in NAND. If there were no bad blocks, it would extend from 0x00000 to 0x30000. From 0x30000 to 0x230000 (2MB) we have the kernel partition.

Let's now assume that blocks 0x20000 and 0x28000 (each 0x4000 in size) are marked as factory-bad. Thus, in order to have 0x30000 bytes of usable storage, the uboot partition actually extends from 0x00000 to 0x38000. This shifts the start address of the kernel partition to 0x38000.

If the kernel partition contains more bad blocks, the start address of the rootfs partition (following the e kernel partition) is further shifted down to the end.

Those calculations have been implemented as u-boot "dynpart" command. Once you issue "dynpart", the partition configuration is put in the "mtdparts" environment variable. If you "saveenv" the environment, it is saved into the non-volatile environment partition.

Bad Block Table (BBT)

Since the usual bad block marker in the OOB area does not allow us to distinguish between factory-bad and worn-out-bad blocks, we need to store this information elsewhere. This places is called bad-block table (BBT), and it is placed as a bitmap into the last two working/good blocks at the end of NAND. To increase security, a backup of those two blocks is kept in the next two working/good blocks just before.

The BBT location itself is identified by special markers (BBT0/BBT1) in the OOB area of the first page of the respective erase blocks.

The BBT consists of two bits per block, which distinguish the three conditions (factory-bad/worn-out/good).

Both u-boot and Linux implement the same BBT layout and thus interoperate quite well.

BBT creation

The BBT is created once a BBT-implementing u-boot is started for the first time. The BBT scanning code assumes that the NAND is completely erased, i.e. only contains 0xff as content. Any block that contains bytes != 0xff in the OOB is marked as "factory bad" block.

Flashing kernel/rootfs from u-boot

The kernel is contained in its own partiton QT2410#NAND. We have to flash it using the

As opposed to using fixed NAND flash addresses, we can use the mtd partition names. Thus, the magic of device-specific dynamic partition layout is all hidden neatly from the user. A flash command using a partiton name looks like:

nand write.e 0x32000000 kernel

Where 0x32000000 is the source address in RAM, and 'kernel' is the name of the kernel partition.

Kernel

Bad block table

In order to maintain the BBT created by u-boot, the kernel needs to have BBT support enabled. Unfortunately the mainline kernel doesn't have a CONFIG option for it, so if you're not using the -moko kernel tree, you have to manually patch the s3c2410 nand driver to enable the BBT option.

Flash Tool

sjf2410 (during development)

The sjf2410-linux tool has a compile-time option to check (and skip) bad blocks. If we use this for flashing u-boot, we will preserve the bad block info, once u-boot steppingstone code has been enhanced to skip bad blocks.

However, even if the bad-block skipping works, sjf2410-linux only supports the extremely slow parallel-port based JTAG adaptors. Also, the overall flashing process is getting quite complicated. sjf2410-linux could thus only be used for flashing the bootloader. Everything else has to be done by some more intelligent software anyway.

JTAG / OpenOCD / u-boot RAM based

The idea is to use JTAG to download and execute a special version of u-boot. This u-boot then automatically scans the whole NAND and creates the BBT. The process can be monitored and controlled by the developer (or some script). The chronological steps are

NAND erase block

Problem

Bad Blocks

NAND memory apparently gets shipped with blocks that are already bad. The vendor just marks those blocks as bad, thus resulting in higher yield and lower per-unit cost. Thus, the flash will in the end contain three kinds of blocks:

Factory-Default bad blocks

Worn-out bad blocks

Good blocks

The only block that is guaranteed to be good, is the first block (first 16kBytes).

We are also guaranteed that a minimum of 4016 blocks (out of the total 4096) are good. This means up to 80 blocks (1.3MBytes) can be dead, resulting in a total guaranteed amount of working NAND storage of 65798144 bytes.

Solution

Boot loader

first-stage

This code (mostly written in ARM assembly) was altered to detect and skip bad blocks. This means, the first stage bootloader can itself extend over bad blocks.

This also menas that the flashing routine needs to detect and skip bad blocks, resulting in a u-boot image that can have gaping holes ;) The existing "traditional" sjf2410-linux JTAG flashing program is not detecting bad blocks (Note: this might be changed through a compile option, see below)

Environment

The u-boot environment is traditionally stored at a fixed location within the NAND flash. This is not acceptable, since it could be a factory-set bad block.

The solution that was implemented for OpenMoko/Neo1973 was to put the in-flash address of the environment into the out-of-band (OOB) area of the first block (the one which is guaranteed to be good). Since the environment address is unlikely to change often, the 1000 erase cycles guaranteed for the first block are good enough.

The exact location is byte 8..15 of the 16byte OOB area, starting with the four ASCII bytes ENVO, followed by the little-endian 32bit unsigned integer of the NAND address where the environment is located.

The u-boot "dynenv get" command can be used to read out a pre-programmed Environment offset from NAND, and the "dynenv set" can be used to write the offset (if the last eight bytes of OOB area are erased (0xff)).

Partition Table

Since those up to 80 factory-bad blocks can be located about anywhere in NAND, we have to accomodate for this worst case. However, we cannot just make every partition 1.3MB larger than it needs to be, since this would waste a lot of otherwise good flash.

The only solution to this (that Harald could think of) is to dynamically calculate a partition table for each device. Every NAND flash has different factory-bad blocks at different locations, thus the partition table on every NAND flash will look different.

So as an example, lets' assume we have a 0x30000 (196k) bytes sized partition for u-boot, starting ad address 0 in NAND. If there were no bad blocks, it would extend from 0x00000 to 0x30000. From 0x30000 to 0x230000 (2MB) we have the kernel partition.

Let's now assume that blocks 0x20000 and 0x28000 (each 0x4000 in size) are marked as factory-bad. Thus, in order to have 0x30000 bytes of usable storage, the uboot partition actually extends from 0x00000 to 0x38000. This shifts the start address of the kernel partition to 0x38000.

If the kernel partition contains more bad blocks, the start address of the rootfs partition (following the e kernel partition) is further shifted down to the end.

Those calculations have been implemented as u-boot "dynpart" command. Once you issue "dynpart", the partition configuration is put in the "mtdparts" environment variable. If you "saveenv" the environment, it is saved into the non-volatile environment partition.

Bad Block Table (BBT)

Since the usual bad block marker in the OOB area does not allow us to distinguish between factory-bad and worn-out-bad blocks, we need to store this information elsewhere. This places is called bad-block table (BBT), and it is placed as a bitmap into the last two working/good blocks at the end of NAND. To increase security, a backup of those two blocks is kept in the next two working/good blocks just before.

The BBT location itself is identified by special markers (BBT0/BBT1) in the OOB area of the first page of the respective erase blocks.

The BBT consists of two bits per block, which distinguish the three conditions (factory-bad/worn-out/good).

Both u-boot and Linux implement the same BBT layout and thus interoperate quite well.

BBT creation

The BBT is created once a BBT-implementing u-boot is started for the first time. The BBT scanning code assumes that the NAND is completely erased, i.e. only contains 0xff as content. Any block that contains bytes != 0xff in the OOB is marked as "factory bad" block.

Flashing kernel/rootfs from u-boot

The kernel is contained in its own partiton QT2410#NAND. We have to flash it using the

As opposed to using fixed NAND flash addresses, we can use the mtd partition names. Thus, the magic of device-specific dynamic partition layout is all hidden neatly from the user. A flash command using a partiton name looks like:

nand write.e 0x32000000 kernel

Where 0x32000000 is the source address in RAM, and 'kernel' is the name of the kernel partition.

Kernel

Bad block table

In order to maintain the BBT created by u-boot, the kernel needs to have BBT support enabled. Unfortunately the mainline kernel doesn't have a CONFIG option for it, so if you're not using the -moko kernel tree, you have to manually patch the s3c2410 nand driver to enable the BBT option.

Flash Tool

sjf2410 (during development)

The sjf2410-linux tool has a compile-time option to check (and skip) bad blocks. If we use this for flashing u-boot, we will preserve the bad block info, once u-boot steppingstone code has been enhanced to skip bad blocks.

However, even if the bad-block skipping works, sjf2410-linux only supports the extremely slow parallel-port based JTAG adaptors. Also, the overall flashing process is getting quite complicated. sjf2410-linux could thus only be used for flashing the bootloader. Everything else has to be done by some more intelligent software anyway.

JTAG / OpenOCD / u-boot RAM based

The idea is to use JTAG to download and execute a special version of u-boot. This u-boot then automatically scans the whole NAND and creates the BBT. The process can be monitored and controlled by the developer (or some script). The chronological steps are