Yaffs Porting Guide

version 2.0

Last changed: rrw@kynesim.co.uk, 2009-03-20


The purpose of this document is to describe how to bring up Yaffs2 on a board previously unsupported using the Yaffs Direct Interface, with an example using eCos on a Balloon board.

Before we start you probably want to have the data sheet for the NAND chips you are using in front you so that you can refer to it as we go along.

Background and NAND Flash Properties

NAND flash is generally arranged in pages and blocks. A page contains a section of data bytes and then a section of out-of-band (OOB) data bytes. Blocks contain a number of pages .
YAFFS porting guide diagram

The OOB section of each page is used keep track of various things like file system state, error correction data and bad block marking.

NAND flash is programmed (written to) and read on a largely page basis (though with some level of random access) and is erased on a block basis. That is, to erase a page, you must erase the rest of the containing block along with it.

When programming NAND flash only 1's can be programmed to 0's, so to change a byte containing 0x00 to 0xFF it must be erased, along with the rest of the block. However subsequent programs may still be made without an erase if no 0s are to be changed to 1s, this might be useful in a scenario where subsequent data might regularly have this property.

When manufactured NAND flash contains bad blocks, these are blocks that are not reliable and any data in them will commonly corrupt easily. These must be kept track of and the OOB data section is used to mark a bad block.

NAND in general is prone to bit errors and for that reason the OOB data is also used to write error correction codes (ECC) so that some errors can be corrected.

Yaffs NAND Model

It is probably a good thing to detail at this point how Yaffs2 expects the NAND to behave so that you are aware of what you have to provide in the NAND driver to get YAFF running. Yaffs uses some different terminology in that it refers to a page as a chunk.

Yaffs2 uses a fairly abstract model for NAND flash, this allows a lot of flexibility in the way Yaffs can be used. Yaffs is designed for NAND flash and makes the following assumptions and definitions:

  • NAND flash is arranged in blocks. Each block is the same size and comprises an integer number of chunks. Each block is treated as a single erasable item.
  • A page (chunk) equates to the allocation units of flash.
  • All accesses (reads and writes) are page (chunk) aligned.
  • When programming a NAND flash, only the zero bits in the pattern being programmed are relevant, and one bits are "don't care". For example, if a byte already contains the binary pattern 1010, then programming 1001 will result in the pattern which is the logical AND of these two patterns i.e. 1000. This is different to NOR flash.

Yaffs identifies blocks by their block number and chunks by their chunk Id. Yaffs treats a blank (0xFF filled) block as being free or erased. Thus, the equivalent of formatting for a Yaffs partition is to erase all the blocks that are not bad.

Therefore Yaffs minimally needs access to functions that enable it to erase blocks and to read and write on a page basis. It is also helpful to be able to read only OOB data as reading/writing a full page to only view/update OOB data will cost many serial read cycles over the bus. This is an overhead that can be avoided using the random access read and write features of some nand chips.


Writing a Driver for NAND flash Chips

Writing a Driver for the Samsung 628 K9F2G08U0M PCB0 on the Balloon Board (v3.01)

For the Balloon board the NAND chips hang off the CPLD which is in turn hooked up to the system bus. This is quite board specific and in this case reading the VHDL code reveals how to change the values on the pins of the NAND chips. You may look at the code by downloading the balloon source code (Appendix A) and looking at balloon3.vhd and Balloon_nand.vhd in balloon/vhdl/fpga/.

It will be likely that this is significantly different on your platform and you should consult the relevant data sheets and documentation to determine how to access the NAND hardware on your board. I will present an example of the balloon board in order to give an overview of the kind of thing that is required.

On the Balloon the CPLD routes data on the bus with certain address to the logic on the CPLD that actually controls the flash chips. They are as follows:

NAND Data Bus : 0x10e00000 NAND Control 2: 0x10e00010 NAND Control : 0x10e00014

Sending data to the 'NAND Data Bus' address will put that data onto the I/O pins of the NAND chips. All of the nand chips are hooked up to the same pins on the CPLD so writing to the 'NAND Data Bus' address will put the value written on the I/O pins for all of the available flash chips. To isolate the chip you want to work with using the chip enable pin on each flash chip.

This is done by writing to the 'NAND Control' (0x10e00014) address. When writing to 'NAND control', the CPLD takes the data value on the bus and routes each bit to a different wire. This bit field is structured as follows:


Pin Attached To Bit in NAND Control value
CLE (Control Latch Enable) 0
ALE (Address Latch Enable) 1
nCE0 (not Chip Enable for chip 0) 2
nCE1 (for chip 1) 3
nCE2 (for chip 2) 4
nCE3 (for chip 3) 5
nSE (not Chip Enable for chip 0) 6
nWP (not Write Protect) 7


So for example bit 1 on the data lines is put onto the ALE pin on the physical chip. Bits prefixed with 'n' are active low, for example chip 0 is selected when nCE0 is low and nCE1-3 are high.


So writing to 'NAND Control' we can change the values of the pins on the flash. Again, CLE, ALE, nSE and nWP change the value of the corresponding pin on all the flash chips available, nCE[0-3] will select the chip you are using.

Finally, NAND flash typically comes in couple of flavours, 8 bit wide (x8) and 16 bit wide (x16). This means that x8 chips have 8 I/O pins and x16 have 16. The logic on the CPLD on the Balloon can handle both if you tell it which you are currently dealing with. Writing to 'NAND Control 2' a value of 1 puts the CPLD logic for the NAND chips into x8 mode and 0 into x16 mode. I will assume for the rest of this document that there are only x8 chips connected to the balloon but it should be fairly trivial to extend it to support both.

Using the Flash Chips

The flash chips we're using have a number of commands, each triggered by a sequence of one or two command codes. You will want to refer to the data sheets for precise details of these functions, but I will briefly describe some of them in this document. The command set that we have for the particular chip we're using is table 1 on page 7 of the data sheet.

To input a command there is generally one or two command input cycles. For two cycle commands there is generally an initial command followed by some data needed to complete the command then another command cycle to notify that the relevant data has been input and to start execution. For example for read operations the command 0x00 is latched in followed by some address input cycles followed by the command 0x30 to start the operation. With this in mind I will now describe how to input commands, the 'Command Latch Cycle'.

Note that for this section I have assumed that the bus is sufficiently slow that the set-up and hold times are not an issue, if you have a fast bus then you may have to wait for a small amount of time (refer to the data sheet) after changing values on the pins (by writing to the correct address) so that set-up and hold times are not violated.

Command Latch Cycle

For the command latch cycle you need to first select a chip by setting the relevant nCE low and then you need to set the CLE bit high, that means writing to the 'NAND Control' address. ALE must also be low and should be set at the same time as CLE. It is then a case of putting the command value onto the I/O pins by writing to the 'NAND Data Bus' address the value of the command you want. This will then latch in the command, afterwards you will need to clear the CLE bit again.

You will also notice from looking at the data sheet that something needs to be done with the nWE (not write enable) pin (specifically it should be low while latching in the command), but that I have yet to describe a mechanism for changing its value. This is because it is dealt with by the logic in the CPLD and whenever data is sent to the data address the CPLD will toggle nWE appropriately. (This is also true of nRE (not read enable) when reading). This process is implemented in the nand_chip_select and the send_command functions (flash.c).

Address Latch Cycle and Addressing

The process for the Address Latch Cycle is similar to that for command latching (with the sense of ALE and CLE reversed) and can be looked at in the data sheet and is implemented in send_address in flash.c. The addressing is done by row and column. There are 5 address cycles on the flash chips we're looking at. The first cycle contains bits 0-7 of the column address, the second bits 8-11 with the remaining bits of the byte to be sent low. The row address is over 3 more cycles in a similar fashion.

You may wish to chose to address the individual bytes of the chip with the OOB data not directly addressable. This has enabled me to lump several chips into the same address space and select the correct chip based on the address. Addresses then need to be converted to row and column. You should also check that they are page/block aligned in the relevant places if that's what your functions expect. It needn't be done this way any you could use page numbers instead of bytes just as easily.

YAFFS porting guide diagram 2


The 'row' is essentially the page index and the 'column' is the index into to that page, so if you wanted to read from the start of page 213 you would send 0 for the column and 213 for the row. Conversely if you wanted just the OOB bytes from the same page you would send a column value of 2048 (given a page size of 2048+64 bytes).

YAFFS porting guide diagram 3


Finding Chip information

You will then need to find out which chips are active (e.g. on the balloon there are 4 possible chips, not all of which will exist) and what type of chips they are. For this there is the 'Read ID' operation. This operation is implemented in the read_ids function in flash.c. The result of the operation gives the manufacturer and device code of the chip and information about the chip (page size, block size, oob size etc). To find which chips are available, loop through the possible chips and start a Read ID command on each of them, if the expected data is returned then there is a chip at that location.

The operation is started by sending the command 0x90 and then one address cycle of 0x00. There are then 5 read cycles which give you data about the device. A description of how to unpack this information from the returned bytes is located on page 32 of the data sheet for the chip described here.

This then gives you how many chips you have and what layout the data in them has.

Bad Block Checking

Contained within a nand chip are blocks that the manufacturer will not guarantee the reliability of (this is to increase yield in manufacturing) and so bad blocks must be kept track of. The Samsung chips come with the first byte (or word for x16) of the first and second page of each bad block not programmed as 0xFF.

This bad block marking should be maintained and should be added to in the same way when a bad block is is discovered. Yaffs will expect you to provide a function it can call to mark a particular block as bad when yaffs decides it is necessary.

Yaffs keeps track of bad blocks itself so that the driver doesn't need to worry about masking out bad blocks.

Erasing a Block

An example of this operation is given in nand_erase_block in flash.c. If you are addressing the nand by bytes then you must first check that the address is aligned to the start of a block and then convert the address into a block number. If there is more than one chip this will involve working out which chip the address refers to and then selecting the correct chip. This can be seen in the select_chip function in flash.c. You must then send the first command cycle for the erase operation before sending 3 cycles of address and then finally the last command cycle.

For the erase operation the 3 address cycles are different than for the read/write operations. Only address bits A18 to A29 is valid (for x8) while A12 to A17 is ignored. In terms of the ordinary 5 cycles address input you only need to deal with the last 3 cycles and of those only the highest 2 bits of the 3rd cycle are used and the full 4th and 5th cycle are used.

Once the operation has been started the program can check the progress of the operation by sending a command of 70h. Each subsequent read will contain the read status. The driver should wait until the read is complete (by the read status operation), the read status operation also contains information about whether the erase failed and this information should be returned from the function doing the erase.

Reading a Page

Reading a page is performed by first selecting the correct chip and then converting the address to a row and column number. You can then sending the first command cycle for the read operation followed by 5 address cycles as described in the previous sections. The second command cycle then starts the read operation. The driver must then wait for the data to be retrieved into the data registers on the NAND before they can be read out over the bus. For the example chip this is 25us, consult the data sheet as this may differ. The data can then be read out sequentially byte by byte until all the data needed is obtained.

Writing a Page

Page program operations are performed by first selecting the correct chip converting the address to row and column numbers. The page program operation is initiated by the relevant fist command latch cycle (command 0x80) followed by an address latch cycle (as for read), serial data loading and then the second command latch cycle (command 0x10).

The serial data loading is done by serially writing data to the I/O pins. Up to 2112 bytes (x8) can be loaded into the data registers. Once the second command cycle is sent the program is initiated and there is then a programming period where the loaded data is programmed into the appropriate cell. The driver should wait for this to complete by issuing a read status command (0x70) and monitoring the R/nB (ready / not busy) bit. The R/nB flag is on I/O pin 6 and can be checked by repeatedly reading from the 'NAND Data Bus' address. Once the operation is complete the Write Status Bit (I/O 0 ) may be checked to confirm that the operation was successful.


How To Use Yaffs

Checking Out From Git

The first thing to do is to obtain the source code for Yaffs. This can be done from CVS, details of the process can be found at http://www.yaffs.net/download-yaffs-using-git.

The following command will checkout the latest Git version using public read-only access, if you are using bash.

git clone git://www.aleph1.co.uk/yaffs2 

Making Some Tweaks to the Source Code

As of the latest version from CVS in January 2009, you will need to make a small number of changes before you can compile the source code without errors and warnings. There is a patch file included with this document.

You will need to provide a definition of loff_t (in yaffs2/direct/ydirectenv.h) this is for holding the file size in bytes, you should make it big enough to hold the largest file size you wish to support. Unsigned long is probably a good place to start. You will need to edit yaffsfs.c to change a #if to a #ifdef. Then if you have the relevant warnings and -Werror switched on you will need to correct a couple of printf format strings to get the type right (changing a few '%d' to '%ld').

change yaffs/ydirectenv.h

+ typedef unsigned long loff_t;

change yaffs2/direct/yaffsfs.c ~line 1117



Relevant Files to the Direct Interface

There are quite a few files that get checked out, not all of which you will need to get going with the yaffs direct interface. The ones you will actually need are listed bellow, you do not need to compile the rest in.











Some Relevant Preprocessor Flags

There are many different configuration options for yaffs, here are a few that you should be aware of for using the yaffs direct interface.

  • CONFIG_YAFFS_DIRECT - Tell yaffs to use the direct interface.
  • CONFIG_YAFFS_YAFFS2 - Tell yaffs we want to use yaffs2
  • CONFIG_YAFFS_PROVIDE_DEFS - provides some definitions for file types and #includes linux/types.h, linux/fs.h, linux/stat.h (see yaffs2/devextras.h).
  • CONFIG_YAFFSFS_PROVIDE_VALUES - This may be used so that you can customise error values and constants like O_RDONLY to match the underlying system values, if itis not defined then it includes errno.h,sys/stat.h and fcntl.h (see yaffs2/direct/yaffsfs.h).

Specify these options when compiling yaffs, i.e. -DCONFIG_YAFFS_DIRECT etc.


You will then need to provide yaffs with configuration information. This includes details about the nand flash you are using and also tells yaffs which functions to call when when it wants to read/write/erase parts of the flash, these functions will form the bridge between yaffs and the underlying hardware driver. This customisation is done in yaffscfg.h. Within yaffscfg.h you will need to provide a number of operating-system-like functions and a function to initialise yaffs. For example providing functions for malloc and free. Here is a breakdown of those functions that need to be implemented.

Operating System Functions

void yaffsfs_SetError(int err);

Report system errors, for example using errno if it exists.

void *yaffs_malloc(size_t size); void yaffs_free(void *ptr);

As for the standard malloc and free functions, if they already exist on your system then you can just shim them.

void yaffsfs_LocalInitialisation(void); void yaffsfs_Lock(void); void yaffsfs_Unlock(void);

Use yaffsfs_LocalInitalisation to initialise "Real Time Operating System" state, if the system is multi-threaded then it should initialise a semaphore/mutex for use by lock and unlock. Lock/unlock should use the semaphore set up in LocalInitialisation to lock yaffs from multi-threaded access.

__u32 yaffsfs_CurrentTime(void);

This can be any time increment of use to the system. If this is not required, then it is fine to just use a function that returns zero.

Yaffs Startup Function

int yaffs_StartUp(void);

Return value: YAFFS_OK or YAFFS_FAIL

This functions should be used to provide yaffs with many details about the properties of the underlying nand device. Within this function you will need to set up a 'yaffs_Device' (yaffs_guts.h) and a 'yaffsfs_DeviceConfiguration' (direct/yaffscfg.h). How to set up a yaffs device will follow in the next section. Once you have set up the required yaffs_Device structures you should call yaffs_initialise (yaffs_guts.h) with a pointer to a null terminated array of yaffs_DeviceConfiguration structures. A yaffs_DeviceConfiguration structure contains a pointer to a yaffs_Device and the 'mount point' string (prefix) that that device corresponds to.

Yaffs Devices

A yaffs device is a logical device that represents part of or a whole physical device. You might think of it as a 'partition' on the nand. For example, it might cover the whole of the nand device or perhaps only claim half, with another yaffs_Device claiming the other half. It may also be used so that you can use a device other than a flash device (e.g. RAM) for testing purposes.

A yaffs_Device keeps track of the start and end block, so you may have more than one yaffs_Device corresponding to the same physical device by altering the start and end block of the yaffs device.

The fields on the yaffs_Device struct that you need to set up yourself are listed below, all other fields are setup automatically by yaffs.

int nDataBytesPerChunk

As the name suggests this is the number of bytes per chunk, remembering that in yaffs terminology a page is referred to as a chunk, this is then the number of bytes per page. This is the number of data bytes, that is the number of bytes excluding the OOB byte data. E.g. if a page is 2048byte + 64bytes of OOB then set nDataBytesPerChunk to 2048.

int nChunksPerBlock

Straightforwardly the number of chunks (pages on the nand) in each block on the physical nand device. Must be at least 2.

int spareBytesPerChunk
This is the size of the spare area, i.e. the number of OOB bytes per chunk (page).

int startBlock

The block number (not the address in bytes) on the chip of the first block of this logical yaffs_Device. Beware, yaffs needs the first block to be free so you should not set this to 0, if it is then yaffs will add on one to it itself and also add on one to the end block which could be an issue if you wanted your device to start at block 0 and finish at the last block which might mean yaffs trying to write to a non-existent block.

int endBlock

The block number on the chip of the last block for this logical yaffs_Device. If startBlock == 0 then yaffs will use endBlock+1. Must be at least startBlock + nReservedBlocks + 2.

int nReservedBlocks

This is then number of erasable blocks that Yaffs must keep in reserve for garbage collection and to cover for block failures. This must be a minimum of 2, but 5 or so is better. If you are using a medium that is not expected to fail (eg. RAM for a RAM disk, or a host file system emulation then 2 is OK).

int nShortOpCaches

This configures the number of entries in the Yaffs cache for this device. A value of zero defeats the cache. A value of 10 to 20 is recommended for most systems. Cannon be any bigger than YAFFS_MAX_SHORT_OP_CACHES.

int useNANDECC

This is a flag that tells yaffs whether it should be doing ECC calculations or if it should be left to the driver.

void *genericDevice

This is a pointer which should point to any data that the underlying nand driver will need to know to write and read from the physical device.

int isYaffs2

Are we using Yaffs2? We only deal with Yaffs2 in this document.

int inbandTags

Is there an OOB section? If not then this should be true. Only relevant to Yaffs2.

_u32 totalBytesPerChunk

Possibly a misleading name, it should be equal to nDataBytesPerChunk not nDataBytesPerChunk + spareBytesPerChunk as the name might suggest. If inbandTags is true then yaffs sets nDataBytesPerChunk such that there is enough space for the data yaffs would normally store in the spare area.

write/readChunkWithTagsFromNAND, markNANDBlockBad, queryNANDBlock

These are all pointers to functions that you shall provide to provide Yaffs with a method of writing/reading etc from the NAND.

NAND access functions

If you are using Yaffs2 you need to provide pointers to functions that Yaffs can call to write and read from the nand. Example implementations of these function can be seen in yaffs_mtdif.c, these are the functions written to do the same job for the linux mtd layer and provides the write and read functions. The following functions return YAFFS_OK on success and YAFFS_FAIL on failure.

int (*writeChunkWithTagsToNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, const __u8 * data, const yaffs_ExtendedTags * tags); dev: yaffs_Device to write to. chunkInNAND: Which page to write the chunk to. Data: Pointer to the data to write tags: Unpacked OOB data. Return: YAFFS_OK / YAFFS_FAIL

This function is to write pages (chunks) to the nand. In this function you will probably want to first retrive any useful data that you put in dev->genericDevice (in the yaffs_StartUp function) and use it to call the function you wrote previously to write data to the nand. For yaffs2 data and tags should never be NULL. chunkInNand is the page number of the page to write to, not an address so it will need converting if that's what your underling functions to write to the nand expect.

You will then need to pack the tags into a yaffs_PackedTags2 structure, this takes out the all relevant details from the yaffs_ExtendedTags tags structure and calculates ECC data on it before placing them in the packedtags2 structure. This structure is the OOB data you will want to write out to the nands spare area.

How you write these out to the OOB data depends on your architecture, nand chips and therefore how bad block marking is done. If for example the first byte is used for bad block marking then simply writing the tags out after the first byte is acceptable. The ECC data in packed tags only covers error correction and detection for the tags in the OOB data, if you want ECC on the data then you must do that yourself.

To perform ECC checks to the data bytes you may use the yaffs_ECCCalculate function which calculates ECC data for 256 byte sections of data. This function produces 3 bytes of ECC data per 256 bytes. Therefore for 2048 bytes of data in each page you would need (2048/256) * 3 = 8 * 3 = 24 extra bytes of ECC data. This can be written out after the bad block byte and packed tags data.

int (*readChunkWithTagsFromNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, __u8 * data, yaffs_ExtendedTags * tags); dev: The yaffs_Device to read from. chunkInNAND: Which page to read from. Data: Pointer to a buffer to read data to. tags: Pointer to a buffer to unpacked OOB data into. Return: YAFFS_OK / YAFFS_FAIL

This function should do the opposite to the previous function. Firstly, read in the data and OOB bytes. Secondly place the data bytes in the area pointed to by data, then unpack the OOB bytes into into the pointer to tags using yaffs_UnpackTags2.

Once the tags have been unpacked you should then recalculate the ECC data on the data bytes you just read back, just as you did for the write operation. Then use the recalculated ECC and the ECC data read out of the OOB bytes with yaffs_ECCCorrect to correct the data.

Depending on the return value from yaffs_ECCCorrect you should set tags->eccResult to reflect whether anything was corrected or the correction failed. yaffs_UnpackTags2 also sets tags->eccResult so you should only update it if the result of the data correction is worse than is already there. i.e. if after the tags are unpacked and eccResult is YAFFS_ECC_RESULT_FIXED and ECC correction on the data read back resulted in no errors then you should leave unpackedtags2 and YAFFS_ECC_RESULT_FIXED and not set it to YAFFS_ECC_RESULT_NO_ERROR.

int (*markNANDBlockBad) (struct yaffs_DeviceStruct * dev, int blockNo); dev: yaffs_Device to write to. blockNo: Which block to mark. Return: YAFFS_OK / YAFFS_FAIL

This should mark the block bad. This probably means reading the OOB data and changing the relevant byte that marks the bad block, and writing the OOB data back.

int (*queryNANDBlock) (struct yaffs_DeviceStruct * dev, int blockNo, yaffs_BlockState * state, __u32 *sequenceNumber); dev: The yaffs_Device to write to. blockNo: Which block to mark. state: Upon returning this should be set to the relevant state of this particular block. Sequence number: The Sequence number of this block. 0 if the block is unused. Return: YAFFS_OK / YAFFS_FAIL

This should check whether or not the block is valid.

If the bad block marker is set in the OOB then *state should be set to YAFFS_BLOCK_STATE_DEAD *sequenceNumber set to 0, and return YAFFS_FAIL.

If the block is not bad then the tags should be unpacked. After the tags are unpacked, and it is found that chunk is used (see tags.chunkUsed) *sequenceNumber should be set to tags.sequenceNumber and *state to YAFFS_BLOCK_STATE_NEEDS_SCANNING, otherwise if the block is not used then *sequenceNumber should be 0 and *state should be set to YAFFS_BLOCK_STATE_EMPTY.

Application Interface

The yaffs provides a number of functions for applications to interface with it. These are mostly the same as the standard clib open/close etc prefixed with yaffs_ eg yaffs_open. These functions are defined in direct/yaffsfs.h.

To initialise yaffs so that reading and writing can be done, you must call yaffs mount for each yaffs device you would like to use, e.g. yaffs_mount("/boot"). This could be done at system boot if there is an operating system present so that the application need to worry about this. You will also need to call yaffs_unmount when you have finished so that yaffs may write any state it needs to to disk.

If there is already an application interface for reading and writing files you may shim the relevant function calls with the related operating system calls.

E.g. simply doing #define open(path, oflag, mode) yaffs_open(path, oflag, mode)

Or having something more complex to provide redirection functions if there is more than one file system present.

For example: int open(const char *path,...)
//Determine which file system is being used
if(it_is_a_yaffs_path(path)) { return yaffs_open(...);
} else {
// Do something else


Appendix A - Accessing the Balloon Source Code

The latest source code for the balloon board can be checked out from SVN. There is different code for different balloon versions. Details of getting the source and setting up tool chains is decribed in the following page http://www.balloonboard.org/software.html. If you just want to take a look at the latest mainline code then the following will give you what you want.

svn checkout svn://balloonboard.org/balloon/trunk/