YAFFS2移植指南




YAFFS2移植指南

版本V2.0

翻译自YAFFS官方版文档,译者做了少许修改。

原文地址: http://www.yaffs.net/yaffs-porting-guide

  1. –  prife 2011/07/02 17:27
  2. –  prife 2011/07/03 13:53

背景知识和NAND Flash特性

(暂略部分)

对NAND Flash编程时,只能将1编程为0,因此要将0x00变为0xFF,需要先将NAND擦除,这同时会将该Block其余部分也擦除。然而,后续的程序在没有0变为1的情况下,就不需要擦除,这在一些后续数据有规律的情况下可能很有用。

刚出厂的NAND Flash就包含坏块,坏块是不可靠的,其中存储的数据可能会轻易崩溃。因此需要记录这些坏块,OOB数据区用于标记坏块。

NAND通常产生比特位错误,因此,OOB数据区也用于存储错误校验码(Error Correction Code, ECC),这样就可以甄别一些错误。

YAFFS NAND 模型

这里,最好先搞明白YAFFS期望的NAND FLASH的行为,这样你就知道要将YAFFS运行起来,需要在NAND的驱动程序中提供哪些东西。 Yaffs使用了一个相当抽象的NAND FLASH模型。它以高度灵活的方式使用YAFFS。YAFFS是为NAND FLASH设计的,它作了以下的假设或定义。

  1. NAND Flash是基于块(block)的,每一个Block大小相同,由整数个chunk组成。每一个Block可单独擦除。
  2. 一页(page,或chunk)为Flash的分配单元。
  3. 所有的访问(读或者写)都是基于页(或chunk)的。(或者译为:页对齐的)
  4. 当对NAND Flash编程时,只有二进制中的0被编程,而1则“不关心”。比如,一个字节包含的二进制数为1010,那么当编程1001,会导致这两个数的位与操作。结果为1000.这和NOR FLASH不同。

YAFFS 分别用块号和 chunk id 标示块和 chunk 。它将空块(填满0xFF)当作空闲块或者已擦除块。这样,格式化一个YAFFS分区等价于擦除所有未损坏的块。

因此YAFFS最少需要一些函数能够擦除块,读一个页,写一个页。如果能够仅读取OOB数据,而仅仅为了读取/更新OOB数据就读写整个页的方式,耗费大量总线串行读写周期。this is an overhead that can be avoided using the random access read and write features of some nand chips

编写NAND FLASH芯片驱动程序

编写Balloon开发板(V3.01)上的三星628 K9F2G08U0M PCB0的驱动程序

坏块检测

三星芯片每一个坏块的第一个或第二个页的第一个字节不是0xFF 这些坏块的标记并被记录下来,并且当又发现一个坏块时,按同样的方式添加坏块记录。Yaffs希望你能提供一个函数,在它认为必要的情况下,可以标记一个块为坏块。

YAFFS自己记录坏块,因此驱动程序不要担心mask out 坏块。

擦除一个块

读一个块

写一个块

YAFFS

如何使用YAFFS

从CVS中提取代码 第一步就是从获取YAFFS的代码。 译者注:这里的说明已经过时了,因此不再翻译。

对源码稍作修改

对最新的代码,你需要做一些小的改动,才能在编译时没有错误和警告。在本文档中有一个补丁文件(译者注:这是针对2009年的版本来说的)

你需要提供一个loff_t的定义(在yaffs2/direct/ydirectenv.h)中。这个类型是存储文件字节大小的。因此为了支持你希望支持的最大的文件大小,你得提供一个足够大的数据类型,unsigned long是一个不错的选择。 编辑 yaffsfs.c,将#if修改为#ifdef。然后你会得到一些相关的警告和错误,你需要修正一些printf格式化字符串使之正确(将几个 '%d' 改变为 '%ld')

  change yaffs/ydirectenv.h
  + typedef unsigned long loff_t;
  change yaffs2/direct/yaffsfs.c ~line 1117
  - #if CONFIG_YAFFS_WINCE
  + #ifdef CONFIG_YAFFS_WINCE

直接接口(Direct Interface)的相关文件

仅需要提取少量文件。使用yaffs的直接接口,你不需要所有的文件。你实际需要的文件列在下面,其余文件不需要编译。

direct/yaffsfs.c

yaffs_guts.c

direct/yaffscfg.c

yaffs_nand.c

yaffs_tagsvalidity.c

yaffs_checkptrw.c

yaffs_qsort.c

yaffs_tagscompat.c

yaffs_ecc.c

yaffs_packedtags2.c

一些相关的预编译标志

yaffs有很多不同的配置选项,这里给出一些你可能会用于Yaffs直接接口的选项。

  1. CONFIG_YAFFS_DIRECT - 使用YAFFS直接接口
  2. CONFIG_YAFFS_YAFFS2 - 使用YAFFS2
  3. CONFIG_YAFFS_PROVIDE_DEFS - 提供一些文件类型的定义以及 #includes linux/types.h, linux/fs.h, linux/stat.h (参见 yaffs2/devextras.h).
  4. CONFIG_YAFFSFS_PROVIDE_VALUES - 可能使用此选项,这样你可以自定义错误值,以及一些常数,比如O_RDONLY和系统数值匹配, 如果没有定义这个选项,那么yaffs会包含errno.h,sys/stat.h and fcntl.h (参见yaffs2/direct/yaffsfs.h).

在编译YAFFS的时候,指明这些选项,比如-DCONFIG_YAFFS_DIRECT

配置

你需要向YAFFS提供配置信息,包括,你所使用的YAFFS芯片的详细信息,以及它读/写/擦除flash时需要调用的函数。这些函数构成了yaffs和底层硬件驱动程序的桥梁。这些自定义配置在yaffscfg.h中完成。在yaffscfg.h中,你需要提供一些类似操作系统的函数,以及一个初始化yaffs的函数。比如,需要提供malloc和free函数。这里列出了需要实现的函数。

操作系统函数

 void yaffsfs_SetError(int err);

报告系统错误,例如,若存在errno,则使用它。

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

作为的标准的malloc和free,如果您的系统之已经包含了这两个函数,那么仅仅调用就成了。

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

使用yaffsfs_LocalInitalisation初始化实时操作系统状态,如果该系统是多线程的,那么应该初始化信号量/互斥锁,用于加锁或解锁。在 LocalInitialisation 中应该使用信号量来加锁或解锁,在多线程访问的时候锁住YAFFS。

 u32 yaffsfs_CurrentTime(void);

这个函数可以用于系统中任何时间的增加。如果不需要,将该函数直接返回0即可。

Yaffs 启动函数

 int yaffs_StartUp(void);

返回值: YAFFS_OK 或者 YAFFS_FAIL

这个函数向yaffs提供很多底层nand设备的特性信息。在此函数中,你需要建立'yaffs_Device'(yaffs_guts.h), 'yaffsfs_DeviceConfiguration'(direct/yaffscfg.h),如何建立一个yaffs设备将在下一节描述。

一旦你建立了需要的yaffs_Device结构,你需要调用yaffs_initialise(yaffs_guts.h),通过一个以null指针结尾的 yaffs_DeviceConfiguration结构。一个yaffs_DeviceConfiguration结构包含跟该设备相关的,一个指向yaffs_Device结构的指针和一个“挂载点”字符串(prefix)。

yaffs 设备

一个yaffs设备是一个逻辑设备,它代表了一个物理设备的部分或整体。你可以认为它是一个nand上的一个“分区”。比如,该分区可能覆盖整个NAND,也许只是一半,而另外一半就是另一个yaffs_Device.它也可以用于你使用一个非flash设备(比如RAM)来测试的情况下。

一个yaffs_Device记录了起始和结束块。通过改变它的起始和结束块,你就可以在同一个物理设备上使用不止一个的yaffs_Device。

这里将需要你自己建立的yaffs_Device结构的数据域列出,其他数据域由yaffs自动创建。

 int nDataBytesPerChunk

如其名,这是每一个chunk的字节数,还记得吧,在yaffs术语中,一个页就是一个chunk,因而它也是一页的字节数。它是数据字节数,即不包含OOB的数据。比如一页时2048字节+64字节的OOB,那么数值nDataBytesPerChunk为2048

 int nChunksPerBlock

物理nand设备中每页包含的chunk(就是nand上的页)的数目,最少是2.

 int spareBytesPerChunk

空闲域(spare area)大小,比如:每个chunk(页)的OOB字节数

 int startBlock

该逻辑yaffs_Device设备第一个块的块号(而字节地址),注意,yaffs需要第一个块是空闲的,因此你不可以设置该变量为0,如果设置为0,yaffs会给它加1,并且会在结束块号上也加1,在你设置设备从块0开始,到最后一个块结束,这意味着yaffs试图写一个不存在的块,从而出现错误。

 int endBlock

该逻辑yaffs_Device设备的最后一个块号。如果startBlock为0,那么yaffs会使用endBlock+1,至少使startBlock+nReservedBlocks+2

 int nReservedBlocks

这是YAFFS必须保留,用于垃圾回收和块错误恢复的可擦除块的数目。至少是2,但是5更好。如果你使用一个不会损坏的介质,比如RAM或者RAM盘,或者主机文件系统模拟,那么可以是2。

 int nShortOpCaches

配置当前设备YAFFS Cache项的数目。0值表示不使用cache。对于大多数系统,推荐使用10到20之间的一个数值。不能大于YAFFS_MAX_SHORT_OP_CACHES定义的数值。

 int useNANDECC

这是一个标志,用于指示是由yaffs执行ECC计算,还是由NAND驱动程序来执行ECC计算。(译者注:此数值取0,则使用yaffs来执行ECC计算,软件ECC计算。如果想要使用硬件ECC校验时,应该设置为1,并且在NAND驱动程序中加入硬件ECC校验的代码。)

 void *genericDevice

这是一个指针,它应该指向任何数据,底层NAND驱动程序需要知道以从物理设备读,写。

 int isYaffs2

我们使用的是YAFFS2么? 本文档中,我们仅讨论yaffs2

 int inbandTags

是否为OOB区,如果不是,那么它为真,仅用于yaffs2

 u32 totalBytesPerChunk

这个名字可能有点误导人,它应该等于nDataBytesPerChunk ,而非其名字暗示的nDataBytesPerChunk + spareBytesPerChunk。如果inbandTags为真,那么yaffs设置nDataBytesPerChunk,因此有足够的空闲空间存储数据,yaffs会在空闲域中正常存储。

 write/readChunkWithTagsFromNAND, 
 markNANDBlockBad
 queryNANDBlock

这些都是函数指针,你需要提供这些函数来给yaffs,读写nandf。

NAND 访问函数

如果使用yaffs2,你需要向yaffs提供函数指针供yaffs调用,来读写nand flash。这些函数实现的样例在yaffs_mtdif.c.这些函数为linux的mtd层提供相同作用,读写功能。下面的函数执行成功时返回YAFFS_OK,失败时返回YAFFS_FAIL

  int (*writeChunkWithTagsToNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, const u8 * data, const yaffs_ExtendedTags * tags); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  chunkInNAND: 将chunk写入的页 
  Data: 指向需要写入的数据的指针
  tags: 未压缩(打包)的OOB数据 
  Return: YAFFS_OK / YAFFS_FAIL

该函数将页(chunk)写入nand中。在该函数中,你可能想先检索你放入dev→genericDevice(在yaffs_StartUp函数中)的所有有用信息并且通过它来调用你先前所写的函数,向nand中写入数据。yaffs2的数据和标签(tags)永远不应为 NULL. chunkInNand 是将要写入的页的页号,而不是需要转换的地址,如果那是你的底层函数要写入到nand期望的东西。

然后你将需要压缩标签(tags)成一个yaffs_PackedTags2结构,这会利用所有跟 yaffs_ExtendedTags 标签结构相关的信息,并且会在将他们置于packedtags2结构之前,计算 ECC 数据。该结构就是你要写入到nand中空闲区域(spare area)的OOB数据。

如何写这些数据到OOB数据取决于你使用的硬件的体系结构、nand芯片,以及坏块是如何标记的。如,第一个字节用于坏块标记,那么就可以在第一个字节可被获取之后,简单地写入标签。压缩后标签中的ECC数据仅包含对OOB数据中标签的错误校正和检测,如果你想要对数据(译者注:这是的数据是指数据区的数据)做ECC,那你必须自己做。

对数据字节执行ECC校验,你可以调用yaffs_ECCCalculate函数,它计算256字节大小的数据段的ECC数据。这个函数每256字得到3字节。所以对每页中2048个字节的数据,你需要额外的(2048/256) * 3 = 8 * 3 = 24个ECC数据。这些数据可以写在坏块数据和压缩标签数据之后。

  int (*readChunkWithTagsFromNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, u8 * data, yaffs_ExtendedTags * tags);
  
  dev: 要写入的yaffs_Device逻辑设备. 
  chunkInNAND: 将chunk读入的页 
  Data: 指向需要读入的数据的缓冲区指针
  tags: 指向未压缩(打包)的OOB数据的缓冲区指针 
  Return: YAFFS_OK / YAFFS_FAIL

该函数执行上一个函数的相反的功能,首先,读取数据和OOB字节,接着将这些输入放在一个由参数data指向的缓冲区中,然后使用yaffs_UnpackTags2把未压缩的OOB字节放在参数tags指向的缓冲区中

一旦标签被解压缩,你应该对刚读回数据重新计算ECC,就像你在写操作的时候做的那样。然后使用新计算的ECC和你读出的ECC,利用yaffs_ECCCorrect来校验数据。

根据yaffs_ECCCorrect返回值,无论有任何被校正的数据还是校正失败,你都应该设置tags→eccResult来反映这种变化。如果数据校验的结果比已经在那里的糟糕,那yaffs_UnpackTags2也得设置tags→eccResult,这样你只需要更新它。例如,假设标签被解压缩以后,eccResult是YAFFS_ECC_RESULT_FIXED,对读回的数据做ECC校验发现没有没有错误,那么你应该离开unpackedtags2和YAFFS_ECC_RESULT_FIXED,并且不要设置为YAFFS_ECC_RESULT_NO_ERROR。

  int (*markNANDBlockBad) (struct yaffs_DeviceStruct * dev, int blockNo); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  blockNo: 要标记的块. 
  Return: YAFFS_OK / YAFFS_FAIL 

它应标记坏块。意味着,读OOB数据、改变相关的标记坏块的字节,以及写回OOB数据

  int (*queryNANDBlock) (struct yaffs_DeviceStruct * dev, int blockNo, yaffs_BlockState * state, u32 *sequenceNumber); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  blockNo: 要标记的块. 
  state: Upon returning this should be set to the relevant state of this particular block. 
  Sequance number: 该块的顺序号(The Sequence number),为0表示此块未使用
  Return: YAFFS_OK / YAFFS_FAIL

它应检查一个块是否是有效的。

如果在OOB中设置了坏块标记,那么*state应该被赋值为YAFFS_BLOCK_STATE_DEAD,*sequenceNumber赋值为0,然后返回YAFFS_FAIL。

如果该块没坏,那么应解压缩标签。标签解压缩后,若发现chunk已使用(查看tags.chunkUsed),则*sequenceNumber应赋值为tags.sequenceNumber,*state赋值为YAFFS_BLOCK_STATE_NEEDS_SCANNING,否则该块未使用,则*sequenceNumber赋值为0,*state赋值为YAFFS_BLOCK_STATE_EMPTY

应用层接口

yaffs为连接的应用程序提供了一组函数。大部分跟标准C库函数,如open/close一致,只是增加了yaffs_前缀,如 yaffs_open. 这些函数定义在direct/yaffsfs.h中。

初始化yaffs来完成读写,你必须在每个你要使用的yaffs设备上调用yaffs_mount。比如yaffs_mount(”/boot”)。这可以在系统启动的时候执行,如果存在一个操作系统,那么程序需要考虑这点。在你完成使用的时候,你也需要调用yaffs_umount函数,这样yaffs就会将它需要状态写入磁盘。

如果读写文件的应用层接口已经存在,你可以根据相关的操作系统调用来封装相关的函数调用。

比如简单的编写

 #define open(path, oflag, mode) yaffs_open(path,oflag,mode)

当不止一个文件系统存在时,则使用更复杂的方法提供间接的函数调用。 例如:

 int open(const char *path,...)
 {
      //判断正在使用那个文件系统
      if(it_is_a_yaffs_path(path)) { 
           return yaffs_open(...);
      } else {
      //做其他工作
      }
 }

附录

附录A-获取Ballon源代码

ballon开发板的最新源代码可以从SVN中提取。不同的ballon版本有不同的代码。获取代码,创建工具链的方法在下面的网页中有详细的描述 http://www.balloonboard.org/software.html

如果你仅想看看最新的主干代码,那么下面会提供你想要的。

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

YAFFS2移植指南

版本V2.0

翻译自YAFFS官方版文档,译者做了少许修改。

原文地址: http://www.yaffs.net/yaffs-porting-guide

  1. –  prife 2011/07/02 17:27
  2. –  prife 2011/07/03 13:53

背景知识和NAND Flash特性

(暂略部分)

对NAND Flash编程时,只能将1编程为0,因此要将0x00变为0xFF,需要先将NAND擦除,这同时会将该Block其余部分也擦除。然而,后续的程序在没有0变为1的情况下,就不需要擦除,这在一些后续数据有规律的情况下可能很有用。

刚出厂的NAND Flash就包含坏块,坏块是不可靠的,其中存储的数据可能会轻易崩溃。因此需要记录这些坏块,OOB数据区用于标记坏块。

NAND通常产生比特位错误,因此,OOB数据区也用于存储错误校验码(Error Correction Code, ECC),这样就可以甄别一些错误。

YAFFS NAND 模型

这里,最好先搞明白YAFFS期望的NAND FLASH的行为,这样你就知道要将YAFFS运行起来,需要在NAND的驱动程序中提供哪些东西。 Yaffs使用了一个相当抽象的NAND FLASH模型。它以高度灵活的方式使用YAFFS。YAFFS是为NAND FLASH设计的,它作了以下的假设或定义。

  1. NAND Flash是基于块(block)的,每一个Block大小相同,由整数个chunk组成。每一个Block可单独擦除。
  2. 一页(page,或chunk)为Flash的分配单元。
  3. 所有的访问(读或者写)都是基于页(或chunk)的。(或者译为:页对齐的)
  4. 当对NAND Flash编程时,只有二进制中的0被编程,而1则“不关心”。比如,一个字节包含的二进制数为1010,那么当编程1001,会导致这两个数的位与操作。结果为1000.这和NOR FLASH不同。

YAFFS 分别用块号和 chunk id 标示块和 chunk 。它将空块(填满0xFF)当作空闲块或者已擦除块。这样,格式化一个YAFFS分区等价于擦除所有未损坏的块。

因此YAFFS最少需要一些函数能够擦除块,读一个页,写一个页。如果能够仅读取OOB数据,而仅仅为了读取/更新OOB数据就读写整个页的方式,耗费大量总线串行读写周期。this is an overhead that can be avoided using the random access read and write features of some nand chips

编写NAND FLASH芯片驱动程序

编写Balloon开发板(V3.01)上的三星628 K9F2G08U0M PCB0的驱动程序

坏块检测

三星芯片每一个坏块的第一个或第二个页的第一个字节不是0xFF 这些坏块的标记并被记录下来,并且当又发现一个坏块时,按同样的方式添加坏块记录。Yaffs希望你能提供一个函数,在它认为必要的情况下,可以标记一个块为坏块。

YAFFS自己记录坏块,因此驱动程序不要担心mask out 坏块。

擦除一个块

读一个块

写一个块

YAFFS

如何使用YAFFS

从CVS中提取代码 第一步就是从获取YAFFS的代码。 译者注:这里的说明已经过时了,因此不再翻译。

对源码稍作修改

对最新的代码,你需要做一些小的改动,才能在编译时没有错误和警告。在本文档中有一个补丁文件(译者注:这是针对2009年的版本来说的)

你需要提供一个loff_t的定义(在yaffs2/direct/ydirectenv.h)中。这个类型是存储文件字节大小的。因此为了支持你希望支持的最大的文件大小,你得提供一个足够大的数据类型,unsigned long是一个不错的选择。 编辑 yaffsfs.c,将#if修改为#ifdef。然后你会得到一些相关的警告和错误,你需要修正一些printf格式化字符串使之正确(将几个 '%d' 改变为 '%ld')

  change yaffs/ydirectenv.h
  + typedef unsigned long loff_t;
  change yaffs2/direct/yaffsfs.c ~line 1117
  - #if CONFIG_YAFFS_WINCE
  + #ifdef CONFIG_YAFFS_WINCE

直接接口(Direct Interface)的相关文件

仅需要提取少量文件。使用yaffs的直接接口,你不需要所有的文件。你实际需要的文件列在下面,其余文件不需要编译。

direct/yaffsfs.c

yaffs_guts.c

direct/yaffscfg.c

yaffs_nand.c

yaffs_tagsvalidity.c

yaffs_checkptrw.c

yaffs_qsort.c

yaffs_tagscompat.c

yaffs_ecc.c

yaffs_packedtags2.c

一些相关的预编译标志

yaffs有很多不同的配置选项,这里给出一些你可能会用于Yaffs直接接口的选项。

  1. CONFIG_YAFFS_DIRECT - 使用YAFFS直接接口
  2. CONFIG_YAFFS_YAFFS2 - 使用YAFFS2
  3. CONFIG_YAFFS_PROVIDE_DEFS - 提供一些文件类型的定义以及 #includes linux/types.h, linux/fs.h, linux/stat.h (参见 yaffs2/devextras.h).
  4. CONFIG_YAFFSFS_PROVIDE_VALUES - 可能使用此选项,这样你可以自定义错误值,以及一些常数,比如O_RDONLY和系统数值匹配, 如果没有定义这个选项,那么yaffs会包含errno.h,sys/stat.h and fcntl.h (参见yaffs2/direct/yaffsfs.h).

在编译YAFFS的时候,指明这些选项,比如-DCONFIG_YAFFS_DIRECT

配置

你需要向YAFFS提供配置信息,包括,你所使用的YAFFS芯片的详细信息,以及它读/写/擦除flash时需要调用的函数。这些函数构成了yaffs和底层硬件驱动程序的桥梁。这些自定义配置在yaffscfg.h中完成。在yaffscfg.h中,你需要提供一些类似操作系统的函数,以及一个初始化yaffs的函数。比如,需要提供malloc和free函数。这里列出了需要实现的函数。

操作系统函数

 void yaffsfs_SetError(int err);

报告系统错误,例如,若存在errno,则使用它。

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

作为的标准的malloc和free,如果您的系统之已经包含了这两个函数,那么仅仅调用就成了。

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

使用yaffsfs_LocalInitalisation初始化实时操作系统状态,如果该系统是多线程的,那么应该初始化信号量/互斥锁,用于加锁或解锁。在 LocalInitialisation 中应该使用信号量来加锁或解锁,在多线程访问的时候锁住YAFFS。

 u32 yaffsfs_CurrentTime(void);

这个函数可以用于系统中任何时间的增加。如果不需要,将该函数直接返回0即可。

Yaffs 启动函数

 int yaffs_StartUp(void);

返回值: YAFFS_OK 或者 YAFFS_FAIL

这个函数向yaffs提供很多底层nand设备的特性信息。在此函数中,你需要建立'yaffs_Device'(yaffs_guts.h), 'yaffsfs_DeviceConfiguration'(direct/yaffscfg.h),如何建立一个yaffs设备将在下一节描述。

一旦你建立了需要的yaffs_Device结构,你需要调用yaffs_initialise(yaffs_guts.h),通过一个以null指针结尾的 yaffs_DeviceConfiguration结构。一个yaffs_DeviceConfiguration结构包含跟该设备相关的,一个指向yaffs_Device结构的指针和一个“挂载点”字符串(prefix)。

yaffs 设备

一个yaffs设备是一个逻辑设备,它代表了一个物理设备的部分或整体。你可以认为它是一个nand上的一个“分区”。比如,该分区可能覆盖整个NAND,也许只是一半,而另外一半就是另一个yaffs_Device.它也可以用于你使用一个非flash设备(比如RAM)来测试的情况下。

一个yaffs_Device记录了起始和结束块。通过改变它的起始和结束块,你就可以在同一个物理设备上使用不止一个的yaffs_Device。

这里将需要你自己建立的yaffs_Device结构的数据域列出,其他数据域由yaffs自动创建。

 int nDataBytesPerChunk

如其名,这是每一个chunk的字节数,还记得吧,在yaffs术语中,一个页就是一个chunk,因而它也是一页的字节数。它是数据字节数,即不包含OOB的数据。比如一页时2048字节+64字节的OOB,那么数值nDataBytesPerChunk为2048

 int nChunksPerBlock

物理nand设备中每页包含的chunk(就是nand上的页)的数目,最少是2.

 int spareBytesPerChunk

空闲域(spare area)大小,比如:每个chunk(页)的OOB字节数

 int startBlock

该逻辑yaffs_Device设备第一个块的块号(而字节地址),注意,yaffs需要第一个块是空闲的,因此你不可以设置该变量为0,如果设置为0,yaffs会给它加1,并且会在结束块号上也加1,在你设置设备从块0开始,到最后一个块结束,这意味着yaffs试图写一个不存在的块,从而出现错误。

 int endBlock

该逻辑yaffs_Device设备的最后一个块号。如果startBlock为0,那么yaffs会使用endBlock+1,至少使startBlock+nReservedBlocks+2

 int nReservedBlocks

这是YAFFS必须保留,用于垃圾回收和块错误恢复的可擦除块的数目。至少是2,但是5更好。如果你使用一个不会损坏的介质,比如RAM或者RAM盘,或者主机文件系统模拟,那么可以是2。

 int nShortOpCaches

配置当前设备YAFFS Cache项的数目。0值表示不使用cache。对于大多数系统,推荐使用10到20之间的一个数值。不能大于YAFFS_MAX_SHORT_OP_CACHES定义的数值。

 int useNANDECC

这是一个标志,用于指示是由yaffs执行ECC计算,还是由NAND驱动程序来执行ECC计算。(译者注:此数值取0,则使用yaffs来执行ECC计算,软件ECC计算。如果想要使用硬件ECC校验时,应该设置为1,并且在NAND驱动程序中加入硬件ECC校验的代码。)

 void *genericDevice

这是一个指针,它应该指向任何数据,底层NAND驱动程序需要知道以从物理设备读,写。

 int isYaffs2

我们使用的是YAFFS2么? 本文档中,我们仅讨论yaffs2

 int inbandTags

是否为OOB区,如果不是,那么它为真,仅用于yaffs2

 u32 totalBytesPerChunk

这个名字可能有点误导人,它应该等于nDataBytesPerChunk ,而非其名字暗示的nDataBytesPerChunk + spareBytesPerChunk。如果inbandTags为真,那么yaffs设置nDataBytesPerChunk,因此有足够的空闲空间存储数据,yaffs会在空闲域中正常存储。

 write/readChunkWithTagsFromNAND, 
 markNANDBlockBad
 queryNANDBlock

这些都是函数指针,你需要提供这些函数来给yaffs,读写nandf。

NAND 访问函数

如果使用yaffs2,你需要向yaffs提供函数指针供yaffs调用,来读写nand flash。这些函数实现的样例在yaffs_mtdif.c.这些函数为linux的mtd层提供相同作用,读写功能。下面的函数执行成功时返回YAFFS_OK,失败时返回YAFFS_FAIL

  int (*writeChunkWithTagsToNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, const u8 * data, const yaffs_ExtendedTags * tags); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  chunkInNAND: 将chunk写入的页 
  Data: 指向需要写入的数据的指针
  tags: 未压缩(打包)的OOB数据 
  Return: YAFFS_OK / YAFFS_FAIL

该函数将页(chunk)写入nand中。在该函数中,你可能想先检索你放入dev→genericDevice(在yaffs_StartUp函数中)的所有有用信息并且通过它来调用你先前所写的函数,向nand中写入数据。yaffs2的数据和标签(tags)永远不应为 NULL. chunkInNand 是将要写入的页的页号,而不是需要转换的地址,如果那是你的底层函数要写入到nand期望的东西。

然后你将需要压缩标签(tags)成一个yaffs_PackedTags2结构,这会利用所有跟 yaffs_ExtendedTags 标签结构相关的信息,并且会在将他们置于packedtags2结构之前,计算 ECC 数据。该结构就是你要写入到nand中空闲区域(spare area)的OOB数据。

如何写这些数据到OOB数据取决于你使用的硬件的体系结构、nand芯片,以及坏块是如何标记的。如,第一个字节用于坏块标记,那么就可以在第一个字节可被获取之后,简单地写入标签。压缩后标签中的ECC数据仅包含对OOB数据中标签的错误校正和检测,如果你想要对数据(译者注:这是的数据是指数据区的数据)做ECC,那你必须自己做。

对数据字节执行ECC校验,你可以调用yaffs_ECCCalculate函数,它计算256字节大小的数据段的ECC数据。这个函数每256字得到3字节。所以对每页中2048个字节的数据,你需要额外的(2048/256) * 3 = 8 * 3 = 24个ECC数据。这些数据可以写在坏块数据和压缩标签数据之后。

  int (*readChunkWithTagsFromNAND) (struct yaffs_DeviceStruct * dev, int chunkInNAND, u8 * data, yaffs_ExtendedTags * tags);
  
  dev: 要写入的yaffs_Device逻辑设备. 
  chunkInNAND: 将chunk读入的页 
  Data: 指向需要读入的数据的缓冲区指针
  tags: 指向未压缩(打包)的OOB数据的缓冲区指针 
  Return: YAFFS_OK / YAFFS_FAIL

该函数执行上一个函数的相反的功能,首先,读取数据和OOB字节,接着将这些输入放在一个由参数data指向的缓冲区中,然后使用yaffs_UnpackTags2把未压缩的OOB字节放在参数tags指向的缓冲区中

一旦标签被解压缩,你应该对刚读回数据重新计算ECC,就像你在写操作的时候做的那样。然后使用新计算的ECC和你读出的ECC,利用yaffs_ECCCorrect来校验数据。

根据yaffs_ECCCorrect返回值,无论有任何被校正的数据还是校正失败,你都应该设置tags→eccResult来反映这种变化。如果数据校验的结果比已经在那里的糟糕,那yaffs_UnpackTags2也得设置tags→eccResult,这样你只需要更新它。例如,假设标签被解压缩以后,eccResult是YAFFS_ECC_RESULT_FIXED,对读回的数据做ECC校验发现没有没有错误,那么你应该离开unpackedtags2和YAFFS_ECC_RESULT_FIXED,并且不要设置为YAFFS_ECC_RESULT_NO_ERROR。

  int (*markNANDBlockBad) (struct yaffs_DeviceStruct * dev, int blockNo); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  blockNo: 要标记的块. 
  Return: YAFFS_OK / YAFFS_FAIL 

它应标记坏块。意味着,读OOB数据、改变相关的标记坏块的字节,以及写回OOB数据

  int (*queryNANDBlock) (struct yaffs_DeviceStruct * dev, int blockNo, yaffs_BlockState * state, u32 *sequenceNumber); 
  
  dev: 要写入的yaffs_Device逻辑设备. 
  blockNo: 要标记的块. 
  state: Upon returning this should be set to the relevant state of this particular block. 
  Sequance number: 该块的顺序号(The Sequence number),为0表示此块未使用
  Return: YAFFS_OK / YAFFS_FAIL

它应检查一个块是否是有效的。

如果在OOB中设置了坏块标记,那么*state应该被赋值为YAFFS_BLOCK_STATE_DEAD,*sequenceNumber赋值为0,然后返回YAFFS_FAIL。

如果该块没坏,那么应解压缩标签。标签解压缩后,若发现chunk已使用(查看tags.chunkUsed),则*sequenceNumber应赋值为tags.sequenceNumber,*state赋值为YAFFS_BLOCK_STATE_NEEDS_SCANNING,否则该块未使用,则*sequenceNumber赋值为0,*state赋值为YAFFS_BLOCK_STATE_EMPTY

应用层接口

yaffs为连接的应用程序提供了一组函数。大部分跟标准C库函数,如open/close一致,只是增加了yaffs_前缀,如 yaffs_open. 这些函数定义在direct/yaffsfs.h中。

初始化yaffs来完成读写,你必须在每个你要使用的yaffs设备上调用yaffs_mount。比如yaffs_mount(”/boot”)。这可以在系统启动的时候执行,如果存在一个操作系统,那么程序需要考虑这点。在你完成使用的时候,你也需要调用yaffs_umount函数,这样yaffs就会将它需要状态写入磁盘。

如果读写文件的应用层接口已经存在,你可以根据相关的操作系统调用来封装相关的函数调用。

比如简单的编写

 #define open(path, oflag, mode) yaffs_open(path,oflag,mode)

当不止一个文件系统存在时,则使用更复杂的方法提供间接的函数调用。 例如:

 int open(const char *path,...)
 {
      //判断正在使用那个文件系统
      if(it_is_a_yaffs_path(path)) { 
           return yaffs_open(...);
      } else {
      //做其他工作
      }
 }

附录

附录A-获取Ballon源代码

ballon开发板的最新源代码可以从SVN中提取。不同的ballon版本有不同的代码。获取代码,创建工具链的方法在下面的网页中有详细的描述 http://www.balloonboard.org/software.html

如果你仅想看看最新的主干代码,那么下面会提供你想要的。

svn checkout svn://balloonboard.org/balloon/trunk/
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页