nand
- NAND Flash Device Simulator
In order to test the filesystems that work with NAND flash devices in a simulator, this exists to provide a virtual NAND flash device, along with its driver, to allow manual (or scripted) testing, as well as to provide an option to log the various actions performed under-the-hood along with the state of the device, which includes the read, write and erase counts of each page in the device.
Structure of NAND Flash
Most NAND flashes share a common interface, specified by the Open NAND Flash Interface (ONFI).
The important part from it, required in this context, is that a NAND Flash is divided into a lot of blocks. And each blocks are divided into a lot of pages.
Here’s the peculiar bit. If you want to erase a page, you need to erase the entire block that it is part of, ie. blocks are the smallest erasable units in a NAND flash. However, a page is the smallest unit to which you can write data, or read data from.
Why would you need erase operation? The Program/ Erase (P/E) cycle states that a page (and thus its block) need to be erased first before data can be written to it (Erase-Before-Write).
Each page has a data area, and a spare area. Depending on the data area’s
size, the spare area may have different structures (schemes). All the
required schemes are defined in /drivers/mtd/mtd_nandscheme.c
(in
the g_nand_sparescheme*
structures).
Due to the nature of NAND flash, upon testing, a manufaturer may decide that a certain block fails some test(s), and mark it as a bad block by writing a certain value in a certain position in the spare area (depends on data area’s size, and thus, the spare area’s scheme) of every page in it.
Note
While certain blocks may still work even if they are marked as bad, it’s inadvisable to store any data in it.
The spare data is the only record of a block being bad or not. Please do not erase it.
Certain blocks may become bad after continuous usage, and would need to be marked as such by either the filesystem or the driver.
Currently, this simulator supports only 512 B sized pages, which means it
will follow the g_nand_sparescheme512
scheme for its spare area, and
thus have a bad block marker at index 5
.
If a block is not bad, it contains a value of 0xff
in the place of a
bad block marker. Any other value denote it’s a bad block.
RAM to Device (Lower Half)
Since this is an emulation, RAM of the host running the simulator is used to create the device. While the speed of operations won’t be even close to the original, this being in the RAM, which works multitudes faster than actual device, the functionality on the other hand has been kept consistent to the utmost.
First, /include/nuttx/mtd/nand.h
has a structure struct nand_dev_s
defining a raw NAND MTD device (lowest level). Its field nand_dev_s->raw
is of type struct nand_raw_s *
(defined in
include/nuttx/mtd/nand_raw.h
), and this is what will hold the methods
for the raw device. There are primarily 3 methods that need to be looked
into:
eraseblock
rawread
rawwrite
Conforming to the functionality of NAND flashes, these three were implemented
as nand_ram_*
in /drivers/mtd/nand_ram.c
to emulate RAM into a
virtual NAND flash.
While in real devices, the spare area follows the data area (in most schemes)
, since this is virtual, we can get away with keeping the two into two
separate arrays, namely nand_ram_flash_data
and nand_ram_flash_spare
for data and spare respectively. Each array has as many elements as number
of pages in the device.
As the spare areas has some spare bytes we can use, some space is used as counters for the reads/writes/erases each page faces, thus giving a clear picture of wear of the virtual device to the tester.
Note
ECC extension has not been implemented yet, so ECC bits in spare are yet to be used or initialized properly.
The method nand_ram_initialize()
takes in a preallocated space for a
raw device (of type struct nand_raw_s
as defined in
include/nuttx/mtd/nand_raw.h
) and attaches these 3 custom methods as well
as device information like page size, block size, etc. to it. These form
the lower half of the driver.
Upper Half
The method nand_ram_initialize()
also initializes a
struct mtd_dev_s *
(defined in include/nuttx/mtd/mtd.h
), which it
returns. This structure contains a reference to our custom lower half in
mtd_dev_s->raw
, as well as an upper half containing methods nand_*
(defined in drivers/mtd/mtd_nand.c
).
Wrapper Over Upper Half
The upper half, along with the lower half attached to it, received from
nand_ram_initialize()
contains these 5 methods for the upper half:
erase
bread
bwrite
ioctl
isbad
markbad
Each driver’s upper half needs to be registered with NuttX before it can
appear in the list of devices (in /dev
). Instead of the previously
acquired upper-half, we’ll be registering a wrapper over it, to improve
logging. The wrapper methods are defined as nand_wrapper_*
in
drivers/mtd/mtd_nandwrapper.c
.
Here’s a complicated bit. The actual upper half is an MTD device, but
more specifically, it is a NAND MTD device, which is represented by
struct nand_dev_s
. Due to how it is defined, struct mtd_dev_s
forms
the very start of struct nand_dev_s
, and hence they can be type-casted
to each other (provided required memory is accessible). Our wrapper, being a
wrapper over an MTD device, is an MTD device itself as well. MTD device
methods take in a struct mtd_dev_s *dev
which describe the device
itself (which is the actual device that is registered using
register_mtddriver
), which includes its methods. Our wrapper methods
receive such a device as well, which contains the wrapper device including
the wrapper functions. But, this way, there is no way of accessing the
methods of the actual upper half itself. Thus, instead of dev
being
of type struct nand_dev_s
, it is actually of type
struct nand_wrapper_dev_s
which is a superset of struct nand_dev_s
,
who itself is a superset of struct mtd_dev_s
. Similar to previous case,
struct mtd_dev_s
forms the very start of struct nand_wrapper_dev_s
,
and thus the types are inter-changeable.
The methods nand_wrapper_*
in drivers/mtd/mtd_nandwrapper.c
all
pass the parameters to corresponding method of the actual upper half
after logging it. But, the device passed on to the actual upper half
is still the wrapper, not the actual upper half, as the upper half methods
may call the other methods as well internally and we might want to log
them as well.
Registering Device & Daemon
This wrapper is then registered using register_mtddriver
, and this
whole thing is converted to be a daemon, so that the device can keep running
in the background.
Making it a daemon is achieved by using fork()
, killing the parent, and
using daemon()
in child.
Known Issues
ECC is not implemented yet.
MLC NAND Flash is not implemented yet.
Due to the fixed name of the device, there can’t be more than one instance of this virtual device.