Choosing the Correct MTD Routine for the Flash Filesystem

This technote explains how to choose the correct Memory Technology Driver (MTD) routine for your flash filesystem. Before you begin, you should first review the descriptions of all available MTD callouts for a flash driver. See "Building your flash filesystem driver" in the Customizing the Flash Filesystem chapter of Building Embedded Systems.

The MTD callouts are as follows:

Callout Description
ident() Identify the flash chip(s)
reset() Return the flash to the read state after an error
read() Read data from flash (if NULL, an internal memcpy() is used)
write() Write data to flash
erase() Erase a flash block (also known as a "unit")
sync() Poll for the completion of an erasure
suspend() Suspend an erasure for a read/write operation (if supported)
resume() Resume a suspended erasure after a read/write operation (if supported)
islock() Determine whether a block/unit is write protected
lock() Write-protect a block
unlock() Disable write protection
unlockall() Invoke a single command to unlock the whole chip (if supported)
Note: Although we refer to the callouts in this technote using the general notation above, the source code for the MTD libraries contains different API versions. The exact callouts available for the two versions of the library (MTDv1 and MTDv2) are:
Generic callout MTDv1 MTDv2
ident() f3s_ident() f3s_ident()
reset() f3s_reset() f3s_reset()
read() f3s_read() f3s_v2read()
write() f3s_write() f3s_v2write()
erase() f3s_erase() f3s_v2erase()
sync() f3s_sync() f3s_v2sync()
suspend() f3s_suspend() f3s_v2suspend()
resume() f3s_resume() f3s_v2resume()
islock() f3s_v2islock()
lock() f3s_v2lock()
unlock() f3s_v2unlock()
unlockall() f3s_v2unlockall()

Ideally, there would be a table that describes every combination of board and flash in existence and tells you which callout to use. Since this is unrealistic, this technote focuses mainly on describing a general method for choosing the right combination of MTD callouts. You will need to:

Unusual flash configurations

As a first step, you should determine whether your board has an unusual flash configuration. In a standard flash configuration, the flash should be located at a contiguous physical memory address that can be directly mapped, read, and written to by software. Usually, the chips are allowed to be interleaved for performance/bus-width reasons.

Here are some examples of nonstandard flash configurations:

Special alignment restrictions
Some CPUs and/or boards require that the memory be read or written to at certain sizes and alignments. These boards cause unpredictable behavior (corruption, SIGBUS, or SIGSEGV) if the default memcpy()-based MTD read function is used.
Miswired endian
Some boards have improperly wired flash that cause bytes to be in the wrong endian. This requires significant custom modifications to many MTD callouts in order to write commands and read CFI data in the right endian.
Under rare situations, some NOR flash-based PC Card storage devices are supported. These require special interactions with the PCMCIA driver.
SDRAM controller based
Most Micron flash chips use an interface based on SDRAM bus commands. While they can be made to work, special code must be written to access the board's memory controller.
NAND flash
CompactFlash, SD Card, SmartMedia, Sony MemoryStick, etc. These devices use NAND flash and are not supported by the devf-* flash drivers. NAND flash is totally different from NOR flash, but is often confused with the NOR flash.
The devf-ram driver may work with some battery-backed SRAM. They are to be assessed on a case-by-case basis. Keep reading if you want to give it a try, but we can't guarantee the driver's resilience to power failure. This is because SRAM has a completely different "failure mode" from that of NOR flash.

If your board has any such unusual configuration, don't hesitate to consult your QNX Software Systems representative.

MTD source code

You can find the complete source to the MTD driver library under bsp_root/libs/src/hardware/flash/mtd-flash.

The callouts are grouped by manufacturer (e.g., intel/iCFI_write.c). Each of these files contains enough comments to detail the type of flash they work with. These comment-blocks provide the most up-to-date information on selecting the right MTD callout. Don't be afraid to look at these files.


Since the MTD source code is organized by manufacturer, your next step is to determine the manufacturer. Look at the board itself or read the flash's datasheet. Flash components made by different manufacturers are usually not compatible with each other.

Note: To date, we support flash from these manufacturers: Intel and FASL LLC (Spansion/AMD/Fujitsu).

There are also groups for SRAM and ROM; in this case there's only one choice for each callout, so we won't discuss them here.

Choosing the callouts

In most cases, setting the callout pointer to NULL is sufficient. This causes the MTD to use memcpy() to read directly from flash. You need to write a custom read callout if your board has special read restrictions.

Here's an example:

#include <sys/f3s_mtd.h>

int32_t f3s_mtd_read(f3s_dbase_t *dbase,
                     f3s_access_t *access,
                     uint32_t flags,
                     uint32_t offset,
                     int32_t size,
                     uint8_t *buffer)
   uint8_t *memory;
   /* Set proper page on socket */
   memory = (uint8_t *)access->service->page(&access->socket,
   if (memory == NULL)
               "%s: %d page() returns NULL\n",
       return (-1);
   /* Replace this memcpy with your special handling code */
   memcpy(buffer, memory, size);
   return (size);
There are currently two main choices available for ident() callouts: one for CFI and another for non-CFI. Most modern flash chips support the common flash interface (CFI) specification, a common method of identifying a flash chip and its capabilities. If your flash chip supports CFI, then you should always use the CFI-specific ident() callout. The alternative is to use a hard-coded table of recognized flash IDs. These non-CFI-specific ident() callouts usually require some customization and should be used as a last resort.
Flash can be written to either a word (8 or 16 bits) or a buffer (several words at a time). All chips support single-word writes, so it is always a conservative choice. More sophisticated chips such as Intel StrataFlash and AMD MirrorBit support buffered writes, which are significantly faster.

When consulting AMD's datasheets, don't confuse their "Unlock Bypass" write mode with their buffered write mode. The unlock bypass mode eliminates only the extra handshake between each word write. Real buffered write modes collect several words into an internal buffer and programs them in parallel.

Choosing an erase() callout is very straightforward; there's usually just one to choose from.
For MTDv1, there are usually two main types of f3s_sync() callouts: one for boot-block flash and one for regular flash. The boot-block flash have two different sizes for erase blocks. The boot-block f3s_sync() callout needs to know the size of the block it wants to erase. The regular f3s_sync() callout doesn't need this extra logic. If you aren't sure, pick the f3s_sync() for boot-block flash; it's slightly slower, but works for any kind of flash.

If you need to use a f3s_v2sync() callout, there's usually only one to choose from.

The f3s_v2sync() callout doesn't need to know the erase block size. The size is determined by the filesystem via a different API.

suspend() and resume()
Flash chips either support suspend() and resume() callouts or they don't. This is evident from the datasheet. Like the erase() callout, there's usually only one choice.
islock(), lock(), unlock(), and unlockall()
Locking is new to the MTD version 2 library (MTDv2). Support for locking is evident from the datasheet. For chips that do support block-level write protection, there are two different implementations: persistent and volatile.
  • Persistent write protection uses flash cells to remember which block is locked. Just like normal flash cells, these cells can be arbitrarily locked, but must be unlocked at the same time. Persistent implementations use the islock(), lock(), and unlockall() callouts.
  • Volatile implementations always default to being completely locked after every reset. This implementation has the advantage that blocks can be locked and unlocked at will. These flash chips use the islock(), lock(), and unlock() callouts.