按:这篇文章主要根据tornado2.0 for arm的在线帮助写成,前面大部分是翻译过来的,后面将详细介绍mtd驱动的编写,并会给出实际的例子。trueffs对于作嵌入式系统来说非常有用,我希望这儿的每一个开发者都能掌握它。我对trueffs的研究还很浅薄,所以如果有信口开河的地方还请大方之家斧正。如果能对大家有所帮助,也就无枉此笔了。 一. 概述 1.简介 Tornado的TrueFFS是和vxworks兼容的一种M-Systems Flite实现方式,版本为2.0。它为种类繁多的flash存储设备提供了统一的块设备接口,并且具有可重入、线程安全的特点,支持大多数流行的CPU构架。 有了Tornado的TrueFFS,应用程序对flash存储设备的读写就好象它们对拥有MS-DOS文件系统的磁碟设备的操作一样。这样 TrueFFS就屏蔽了下层存储介质的差异,为开发者提供了统一的接口方式。然而,当你设计一个嵌入式系统的时候,了解flash存储器和磁碟设备的不同之处还是很重要的。尽管flash存储器不可能适用于所有的嵌入式系统,但由于flash存储器具有体积小,耗电省,非易失存储的特性,在许多的环境下尤其在移动设备和手持设备领域里它成为了理想的选择。 Flash存储器在拥有众多优点的同时也有一些自身的缺点。首先,它在写之前必须要进行擦除操作,而且不能一个字节一个字节的擦,它只能以一个扇区、一个块或者整片的方式进行擦操作。它的写和擦操作都需要较复杂的步骤才能完成,这就降低了它的易使用性。另外,Flash存储器最大的一个缺点就是寿命有限,可擦除的次数因不同厂商而有所不同,一般都在1万到10万次左右。 一个运行在flash存储器上性能良好的块设备应该能针对可移动的媒体(如flash卡)处理各种复杂的情况。比如,完全有这样的可能:当驱动程序正在进行写操作时用户却把flash卡抽出来了。这有时会造成灾难性的后果。幸运的是,TrueFFS在设计时已经仔细考虑了flash存储器的各种特性,以及掉电和用户由于不耐烦或好奇的原因而猛地拔出flash卡想看看发生了什么事的情况。
2.TrueFFS和块数据 Flash存储器不能无限次重复使用。它的每个扇区的擦除次数虽然很大,但却有限。因此,随着使用次数的加长,它最终会变成只读状态。所以我们应该尽最大可能延长它的寿命。行之有效的方法就是平衡使用所有的存储单元,而不让某一单元过度使用。这种技术被称之为wear leveling。TrueFFS使用一种基于一张动态维护表的block-to-flash(块对应于flash)传输系统来实现wear leveling技术。当块数据被修改、移动,或碎片回收后,这张维护表会自动调整。 就象块设备驱动需要的那样,TrueFFS把flash存储空间映射到一个特殊的连续存储块队列中,以便文件系统可以对它进行数据的读写。这些块被从0开始连续编号。 从一个数据块中读取数据的操作非常直接。文件系统请求指定块的内容,TrueFFS响应后将块号传送到flash存储匹配器(flash memory coordinates)中,找到之上的数据,然后把数据返回给文件系统。向块写数据也一样的直接,只要这个块还没有被写过。TrueFFS要作的就是传送块号到flash存储匹配器(flash memory coordinates)中,并写数据到所指定的地址。 但如果写请求试图修改一个已经写过了的块设备内容,情况就复杂了。TrueFFS先是找到flash空间中一个已经可写的区域,然后把修改后的块数据写到那里。当数据安全地写完后,TrueFFS更新它的block-to-flash映射结构,以使块现在映射的区域为放置修改后数据的区域。这就保证了一定程度上的wear leveling。然而,如果存储在flash上的一些数据本质上是静态的,只有在修改时wear leveling才会产生一种被称为静态文件锁定的问题。 存储这些静态数据的区域根本不会被轮循使用,这将降低flash期望的生命值。TrueFFS通过强制转移静态区域的方法成功克服了静态文件锁定的问题。因为block-to-flash映射表是动态的,TrueFFS能够以对文件系统不可见的方式管理这些wear-leveling转移。由于绝对强制 wear-leveling方式会对性能产生一些负面的影响,所以TrueFFS采取了一种非绝对wear-leveling的算法。它保证了所有空间的使用近似平等。 最后,TrueFFS的wear-leveling算法更增强了突破被称为死锁这种模式的性能。 所谓死锁就是一些简单的wear-leveling算法只是两个或多个单元长时间循环而忽略了其余的单元的现象。
3.碎片回收 块数据的修改使得flash的一些块区域不再包含合法的数据,并且这些区域在擦除之前变得不可写。如果没有机制来回收这些区域,flash很快就会变成只读的状态了。不幸的是由于这些块不可以单独擦除,回收这些块就有些复杂了。单次擦除被限制在一个叫作擦除单元(erase unit)的较大范围内。如对于Intel的flash器件来说是64k字节。 TrueFFS使用一种被称为碎片回收(garbage collection)的机制来回收那些不再包含有效数据的块。该机制从一个预擦除单元(source erase unit)内拷贝所有的有效数据块到另一个新的被称为转移单元(transfer unit)的擦除单元。TrueFFS然后更新block-to-flash映射表再擦除这个废旧的预擦除单元。 这样,原来的块出现在外界时仍然包含了原来的数据,虽然这些数据现在已经存放在flash存储器的其它空间。 怎样触发碎片回收功能呢?如果太频繁,则会降低wear-leveling的效果,并影响整个flash的性能。因此在TrueFFS内部,碎片回收只是在块分配算法需要的时候才会触发。块分配算法尽量保持在同一个擦除单元(erase unit)内有一段连续的自由块池。如果块池变得太小,块分配算法就会马上采用碎片回收算法进行碎片回收。然后碎片回收算法就会找到并回收和下面标准最吻合的擦除单元(erase unit): 另外,碎片回收算法也会采用随机选择的处理方法。这样可以保证回收处理能够均匀地覆盖整修存储空间,而不会由于应用程序使用数据的方式而偏差。
4.块分配和关联数据集结的方法 为了提高数据的读取效率,TrueFFS使用一种空间分配策略:将关联的数据(如由同一个文件的内容组成的多个块)集结到同一个单独擦除单元(erase unit)内的一段连续的区域中。为此,TrueFFS尽量在同一个擦除单元(erase unit)内维持一个由多个物理上连续自由的块组成的存储池。如果这样连续的存储池无法实现,TrueFFS会尽量保证池中的所有块是在同一个擦除单元(erase unit)内。如果连这样的情况也不可能的话,TrueFFS会尽量把块池分配到一个拥有最多可用空间的擦除单元内。 这种集结关联数据的途径有几个好处。首先,如果TrueFFS必须从一个小的存储窗口(memory window)来访问flash,那么这样集结了的关联数据可以减少调用映射物理块(physical blocks)到该窗口的次数。这样可以加快文件继续访问速度(通常是这样)。其次,这种策略可以减少碎片的产生。这是因为删除一个文件可以释放掉更容易回收的完整块。这意味着碎片回收会变得更快。另外,它可以使属于静态文件的多个块存放在同一地方,这样当wear leveling算法决定移动静态区域时,转移这些块就变得更加容易了。
5.TrueFFS中的错误恢复 向flash写数据有时可能会出错,比如在响应文件系统写请求时、碎片回收期间甚至在TrueFFS格式化或擦除flash时。在这些情况下, TrueFFS能够从错误中恢复过来。但在新数据第一次写入flash时,如果出错就会丢掉这些数据。然而,TrueFFS非常仔细地保证所有已经存放在 flash上的数据是可恢复的。 TrueFFS健壮的关键是它使用了一种“先写后擦”(erase afer write)的策略。当更新flash一个扇区的数据时,只有在更新操作完成并且新存储的数据校验成功后,先前的数据才会被允许擦掉。这样的结果是数据扇区不能处于部分写状态。操作成功的话新扇区的数据有效,否则老扇区的数据有效。很明显,这样有利于用户已经写到flash媒体上的数据的稳定性。“先写后擦”对flash存储映射表也有积极的影响。比如,尽管TrueFFS采用了用一张RAM-resident的映射表来记录flash存储器内容的方式,但TrueFFS还会非常小心,决不将临界映射信息只放在RAM中。因此,如果更新映射信息时遇到掉电或者flash媒体被移除的情况,原来的映射信息依然是可用的。当电源恢复(或者flash重新连接),TrueFFS能够使用存放在flash上的信息来重建或校验flash映射表的RAM- resident版本。这样,映射信息能够存在于媒体的任何地方,这在开始看来是一项不可能的工作。 幸运的是,flash中的每一个擦除单元(erase unit)都在一个可以预知的位置放置了头信息(一般在该扇区的0偏移地址处)。仔细核对在每一个擦除单元(erase unit)中的头信息,TrueFFS可以重建或者校验flash映射表的RAM拷贝。所以掉电后唯一后果是如果这时正在回收碎片则碎片回收需要重新开始。 如果写操作第一次尝试失败,TrueFFS通常不会报告给用户。相反,它会使用它的动态映射能力在flash的另一个不同地方来重新进行一次写操作。 TrueFFS通过这种自动修复错误的方法确保了数据的完整性。当flash媒体进入它生命极限的时候,这种写错误修复机制变得显著有用。这时, flash的写/擦失败变得更加频繁,但用户看到的结果只是性能的缓慢降低(因为写操作重试的需要)。 TrueTFFS回收碎片的方式是通过将数据从一个旧的单元移到另一个新的单元(置换单元),然后擦除这个旧单元的方式来完成的。这样在进行写数据或者擦除旧单元的过程中碎片回收可能失败。为了最小化这种失败的发生,TrueTFFS将flash媒体格式成包含多个置换单元的方式。这样,如果写到一个置换单元的操作失败,TrueTFFS就会尝试写到另一个置换单元上去。对用户数据没有直接影响,但原来的置换单元不再接受新的数据,变成了只读状态。 有时,flash第一次格式化时就被发现有些空间不可用。倘若坏单元的数目没有超过置换单元的数目,格式化能够成功。对用户的影响也只是flash容量减少了。
6.引导映象和TrueFFS共享flash存储空间 缺省情况下,TrueFFS格式化工具会把整个flash空间用于TrueFFS。虽然TrueFFS的转译服务结合一个文件系统可以为数据的管理提供很多好处,但这也同样增加了flash作为一个引导设备时的复杂性。唯一可行的解决方案就是把TrueFFS排斥在flash的引导区外。 幸运地是,TrueFFS自带的格式化工具可以允许你从一个偏移地址开始格式化flash。这样就创建了一个TrueFFS不能触及的区域,你可以使用 TrueFFS的一些工具来管理这个区域。从而你能把一个引导映象写到这个区域或者从上面擦除。不过,你得十分小心地使用这些工具,因为它们不仅可能过度使用flash,也可能写操作时超过这个区域,从而破坏掉TrueFFS。
7.TrueFFS的层结构简介 如图1-1所示,TrueFFS包含三层:翻译层(translation layer),MTD层(MTD layer),socket层(socket layer)。 图1-1 TrueFFS的层结构 翻译层主要实现TrueFFS和dosFs之间的高级交互功能。它也包含了控制flash映射到块、wear-leveling、碎片回收和数据完整性所需的智能化处理功能。目前有三种不同的翻译层模块可供选择。选择哪一种层要看你所用的flash介质是采用NOR-based, 还是NAND-based, 或者SSFDC-based技术而定。 Socket层则是提供TrueFFS和板卡硬件(如flash卡)的接口服务。其名字来源于用户可以插入flash卡的物理插槽。用来向系统注册socket设备,检测设备拔插,硬件写保护等。后面将详细讲解它的功能。 MTD层(Memory Technology Drivers)功能主要是实现对具体的flash进行读、写、擦、ID识别等驱动,并设置与flash密切相关的一些参数。TrueFFS已经包含了支持Intel,AMD以及samsung部分flash芯片的MTD层驱动。新的芯片需要新的MTD支持,你可以使用一个标准的接口来加入这些驱动。 以上三部分,我们关心的将会是后两层。 当在vxworks下配置TrueFFS时,你必须为每一层至少包含一个软件模块。后面我们将详细讨论。
8.TrueFFS配置显示工具 主要有两个:tffsShow()和tffsShowAll().前者为一个指定的socket接口打印设备信息。它的主要用来确定当要写一个引导映象时所需要的擦除单元数。后者则是提供在vxworks中已注册的所有socket接口的信息。 二. 起步 1.介绍 本章将告诉你在Tornado下怎样去配置TrueFFS并包含它。为了在一个vxworks映象中包含TrueFFS,你必须编辑config.h文件并定义INCLUDE_TFFS。这使得vxworks的初始化代码调用tffsDrv()来创建管理TrueFFS所需的结构和全局变量,并为所有挂接了的flash设备注册socket组件驱动。在链接的时候,通过解析与tffsDrv()相关联的符号(symbols)可以将TrueFFS所必需的软件模块链接到vxworks映象中。 为了支持TrueFFS,每一个bsp必须包含一个sysTffs.c文件。它将TrueFFS所有的层(翻译层,socket层和MTD层)链接到一起并和vxworks绑定。因此,你必须编辑这个文件并决定哪一种MTD和翻译层模块应该包含到TrueFFS中。另外,如果你的目标系统包含了一个MMU 单元,你还得编辑sysLib.c中的sysPhysMemDesc[ ]数组。 在重新编译vxworks映象并重启目标系统后,你应该可以使用诸如格式化flash、创建TrueFFS块设备、绑定此块设备到dosFs所必要的功能。
2.配置和使用TrueFFS总述 配置vxworks使其包含TrueFFS需要编辑如下: - Makefile 在bsp的.o列表中加入sysTffs.o
- config.h 包含TrueFFS
- sysLib.c 调整ROM区的描述
- sysTffs.c 确定包含在TrueFFS中的功能特点
对于一个支持TrueFFS的BSP来说,以上所有文件必须放在target/config下你的BSP配置目录里。但是, sysTffs.c不会自动捆绑到这个目录。相反,几个sysTffs.c不同的版本被捆绑到src/drv/tffs/sockets下,如 ads860-sysTffs.c, mv177-sysTffs.c, hkbaha47-sysTffs.c等等。阅读一下src/drv/tffs/sockets/README这个文件,你就可以确定哪一个 bspname-sysTffs.c适合你的BSP。这个README也描述了要支持TrueFFS所有BSP需要修改的特定地方。 当你启动该映象后,它会自动运行tffsDrv( )。这个函数自动为每一个flash设备注册一个socket组件。这时flash设备还没有挂上块设备驱动。但socket组件驱动已经为调用 tffsDevFormat( )函数提供了充足的条件。为了使用TrueFFS,必须用这个函数来格式化flash媒体。为了在socket组件的顶部创建一个TrueFFS块设备并 mount dos文件系统到这个块设备上,你还得调用usrTffsConfig()函数。下面就具体讲讲这几个文件的修改。 修改Makefile: 为了加入sysTffs.o的编译,你应该在其中加入如下的宏定义: MACH_EXTRA = sysTffs.o 修改config.h: 对于大多数的BSP来说,包含TrueFFS也就是要在config.h中加入如下的两个宏定义: #ifndef INCLUDE_TFFS #define INCLUDE_TFFS #endif #ifndef INCLUDE_DOSFS #define INCLUDE_DOSFS #endif 当然如果你想使用tffsShow()和tffsShowAll()来查看socket信息,你还要加上#define INCLUDE_SHOW_ROUTINES这样一条宏定义。 修改sysLib.c: 如果你的目标系统包含了MMU模块,那么它的BSP在sysLib.c文件里面就定义了一个sysPhysMemDesc[ ]表。典型地,这个表告诉MMU包含启动映象(boot image)的存储区域是WRITABLE_NOT(不可写)的,或者说是ROM型的。ROM曾经是唯一能可靠存储启动映象的技术,所以vxworks一直默认包含一个启动映象的存储区域为ROM型的。然而随着flash技术的到来,这种可能性已经得到了扩展。因为它即可写也可以可靠地存储启动映象。所以你必须编辑sysPhysMemDesc[ ],重新设置启动映象所在的存储区域为WRITABLE(可写)型的。 修改sysTffs.c: 这个文件的主要功能就是定义一些BSP特殊的socket代码,作为连接flash硬件和vxworks的桥梁。缺省地,一个WRS支持的 sysTffs.c包含了所有的翻译层模块,所有的MTD层模块,以及tffsBootImagePut( ), tffsShow( ), tffsShowAll( )这样的工具函数。为了减小映象的大小,你可以通过编辑sysTffs.c来去掉一些你知道对你的应用不必要的模块。 首先,我们应该选择翻译层模块。根据不同的flash技术有三种翻译层供你选择,如下表所示: ---------------------- 宏定义符号 | 相应的flash技术 ---------------------- INCLUDE_TL_NFTL | NAND-based flash INCLUDE_TL_FTL | NOR-based flash INCLUDE_TL_SSFDC | SSFDC flash ---------------------- 我们一般的flash芯片如sst39vf040,sst39vf160,am29lv160等都是NOR-based flash型,所以可以只“#define INCLUDE_TL_FTL”。 接下来,我们选择MTD层模块。Vxworks自带了支持一些flash型号的MTD层驱动模块。如下表所示: ____________________________________ 宏定义符号 相应的flash设备 INCLUDE_MTD_I28F016 Intel 28f016 INCLUDE_MTD_I28F008 Intel 28f008 INCLUDE_MTD_I28F008_BAJA Intel 28f008 on the Heurikon Baja 4000 INCLUDE_MTD_AMD AMD, Fujitsu: 29F0{40,80,16} 8-bit devices INCLUDE_MTD_CDSN Toshiba, Samsung: NAND CDSN devices INCLUDE_MTD_DOC2 Toshiba, Samsung: NAND DiskOnChip2000 INCLUDE_MTD_CFISCS CFI/SCS device INCLUDE_MTD_WAMD AMD, Fujitsu 29F0{40,80,16} 16-bit devices ______________________________________ 同翻译层一样你也可以去掉一些不必要的模块宏定义。比如,如果你使用的是8bit数据宽度的AMD29F040,那么你完全可以undefine掉除了INCLUDE_MTD_AMD以外的其它宏定义。如果你所用的flash型号很不幸不在这里面,那么你只有自己写MTD层驱动模块了。这时你可以在 src/drv/tffs下找一个比较类似的驱动来修改然后把它放到你的BSP目录下,并修改Makefile加入其.o文件到MACH_EXTRA的. o文件列表中。当然你也可以把它放到src/drv/tffs下,同样地修改该目录下的Makefile文件。 另外,缺省情况下sysTffs.c定义了INCLUDE_TFFS_BOOT_IMAGE。这将在sysTffs.o中自动包含了 tffsBootImagePut( )函数。通过使用tffsBootImagePut( )函数,你可以绕过TrueFFS(和它的翻译层)而直接向flash存储空间写数据。用tffsDevFormat( )函数可以将TrueFFS管理区域的起始地址定位到一个偏移地址上,从而在TrueFFS之外留下一个自由区域,可以用来放置启动映象(boot image)。tffsBootImagePut( )可以将启动映象写入这个自由区域。 再下来是选择socket层特征。尽管在sysTffs.c文件中设置一些宏定义可以加入翻译层模块和MTD层模块以及其它的一些相关工具模块,但文件的大部分内容还是专注于socket组件驱动程序的定义。这些驱动程序是一个标准的API,作为连接设备硬件和vxworks的桥梁。它们在很大程度上由你使用的flash硬件来决定相应的功能定义。Tornado的TrueFFS支持三种通常种类的flash硬件: - PC flash卡(可移动的flash媒介)
- DiskOnChip 2000 设备
- 板上固定的flash片组
后面两者都是不可移动的媒介,它们的socket接口相对来说要简单一些。所以对于大多数标准API函数来说,可以简单地“作一点或根本不作”。然而 PC flash卡则是可移动的设备,所以接口处理就要复杂得多。大多数BSP提供的接口程序都很精干,但过于简单。它们都假定socket包含了一张 flash卡,但不支持热拔插。目前,这种简单的PCIC驱动有big-endian 和little-endian两种版本。big-endian版本是由ads860的BSP带有的,little-endian版本则是由PC386和 PC486的BSP提供的。相对于PCIC驱动有两种宏定义:INCLUDE_SOCKET_PCIC0和 INCLUDE_SOCKET_PCIC1。前者是为slot 0创建一个socket接口,后者是为slot 1创建一个socket接口。 对于PC386和PC486来说,你可选择一个比简单的PCIC更加完善的socket接口。对此,后面我们有时间的话还会细讲。
3.格式化Flash 为了使用TrueFFS,要先调用tffsDevFormat( )函数来格式式flash。在格式化的过程中,该函数先擦除flash然后将TrueFFS数据管理结构(data-management structures)写到位于每个擦除单元(如扇区)起始处的头里。tffsDevFormat( )需要两个输入参数:设备号(socket组件号)和一个指向格式化参数(FormatParams)的指针。设备号由socket组件在系统中注册的先后顺序决定。而FormatParams结构则是传递如何格式化此flash的值。这个结构定义如下: typedef struct { /* FTL formatting section */ long int bootImageLen; /*bootImage需要从flash媒体开始处预留的长度*/ unsigned percentUse; /*flash媒体被格式化的百分率,为了提高TrueFFS的性能,不要设为100%,以便任何时候都有空余 空间。默认值为99%*/ unsigned noOfSpareUnits; /*空余擦除单元数目,目的在于flash出现坏块时可以用它来替代,默认为1*/ unsigned long vmAddressingLimit; /* FTL 在RAM中映射的大小,默认为61Kbytes*/ FLStatus (*progressCallback)(int totalUnitsToFormat, int totalUnitsFormattedSoFar); /* 回调函数,用来监测flash擦除过程,如果返回值为OK,则继续,否则停止擦除。*/ /* DOS formatting section */ char volumeId[4]; /*Dos卷标号*/ char FAR1 * volumeLabel; /*Dos卷标字符串,如果为NULL,则没有卷标*/ unsigned noOfFATcopies; /* 文件分配表(FAT)的拷贝数,正常情况下只使用一个FAT,而另一个只有在使用的FAT被破坏的情况下用来恢复分配表,默认为2*/ unsigned embeddedCISlength; /* CIS 嵌在单元头部(unit header)之后的字节长度*/ char FAR1 * embeddedCIS; /* 单元头部被结构化用来作为一个PCMCIA ''tuple'' 链(a CIS)的起始,它包含了一个数据组织tuple,通常用16进制的0xFF来标示上一个单元头部结束的位置(''end-of-tuple- chain'')。*/ } FormatParams; /* 默认的FormatParams 结构*/ #define STD_FORMAT_PARAMS {0, 99, 1, 0x10000l, NULL, {0,0,0,0}, NULL, 2, 0, NULL}。 一般来说,你可以把这个指针赋为NULL(0),这样就告诉tffsDevFormat( )使用在dosformt.h中定义的默认FormatParams结构。在这个默认的结构中定义的值对于绝大多数的应用已经足够了。然而,如果你想在该 flash媒体上共用TrueFFS和引导映象(boot image),这个缺省的结构就不太合适了。你必须修改bootImageLen的值,以适应boot Image的大小,tffsDevFormat( )在格式化的时候会保留这一部分空间。 另外,我们还应该了解另一个格式化函数sysTffsFormat( )。它在内部其实还是调用了tffsDevFormat( )函数。它没有入口参数,它使用自己的FormatParams参数来格式化flash。要了解更详细的信息你可以参阅bsp中sysTffs.c文件中的sysTffsFormat( )函数。
4.设置簇的大小 在为TrueFFS格式化一个flash设备时,扇区都被分配到簇里面,一个簇包含的扇区数由flMinClusterSize来决定,这是一个在 dosFormat.c中定义的int型全局变量。合法的值是2的非负数n次幂(1,2,4,8,……)。默认值为4。你可以减小这个值以获得更好的存贮密度,但由于FAT的入口数是有限的,flash媒体上的可寻址空间也会相应减小。例如,如果把flMinClusterSize设为1,则最大的可寻址空间为16M字节。
5.与bootImage区域相关的问题 正如上面所说的,当tffsDevFormat( )在一个偏移地址上格式化flash时,它不会碰偏移地址以下的的空间。正常情况下,这是好事。然而,如果先前使用了TrueFFS格式化了偏移地址以下的空间,那么这些空间的擦除单元就会包含tffsDevFormat( )写入的TrueFFS格式化头部信息。当TrueFFS挂接(mount)一个flash设备时,它会扫描整个flash的TrueFFS格式化头部信息,用来建立一个它控制的flash memory的区域表。如果在偏移地址之下的区域碰巧包含这样的头,那么TrueFFS挂接(mount)程序还可以看到这些头,结果当然会失败。 怎样解决这个问题呢?你可以调用tffsRawio( )来对这些有问题的单元作一个物理擦除。这非常有效,但也非常危险。如果误用,有可能永久地损坏flash。在你的嵌入式系统开发初期,你可以小心地使用它。如果已经是产品了,你就必须保证你的应用程序和它的用户不被伤及。下图就是调用tffsRawio( )的情况。
6.创建TrueFFS块设备 在你能创建一个逻辑TrueFFS块设备之前,你需要运行tffsDrv( )。如果你正确配置了VxWorks,它会在启动的时候自动加载。tffsDrv( )为Tornado初始化TrueFFS,包括建立互斥信号量、全局变量和用来管理TrueFFS的数据结构。也包括为目标机上所有的flash设备注册 socket组件的驱动程序。 注册socket组件的驱动程序从获取FLSocket中预先分配的5元素(5-element)TrueFFS内部数组开始。下一步是更新FLSocket结构以包含那些控制flash设备基本硬件接口的数据和函数指针。 当TrueFFS需要和具体的socket硬件打交道时,它使用设备号(0-4)作为索引来查找它的FLSocket结构,然后用相应结构中的函数来控制它的硬件接口需求。虽然这些socket接口函数并没有提供完整的块设备接口,但它们的确提供了一个足够好的使用tffsDevFormat( )的接口。这对于一个从未格式化的flash媒体来说是非常重要的,因为这种能力使得在此阶段创建一个TrueFFS块设备成为可能。 注册完一个socket组件驱动后,就可以调用tffsDevCreate( )在此之上创建一个TrueFFS块设备。作为一个输入参数,你必须为它指定一个设备号(0-4)。也就进入FLSocket结构数组的索引。作为设备号,它稍后对于dosFs是可见的。 在创建了TrueFFS块设备后,你必须调用dosFsDevInit( )函数将dos文件系统挂接(mount)到它上面。之后你就可以象从一个标准disk设备上读写flash了。为了方便,函数 usrTffsConfig( )将tffsDevCreate( )和dosFsDevInit( )合成了,并包含了一些创建TrueFFS块设备和挂接dosFS必要的函数,你只需调用它就可以了。以下是bootCofig.c中关于TrueFFS 块设备的代码,当采用tffs方式引导vxworks内核时,该函数被调用。 LOCAL STATUS tffsLoad ( int drive, /* TFFS drive number (0 - (noOfDrives-1)) */ int removable, /* 0 - nonremovable flash media */ char * fileName, /* file name to download */ FUNCPTR * pEntry ) { int fd; if (tffsDrv () != OK) { printErr ("Could not initialize./n"); return (ERROR); } printf ("Attaching to TFFS... "); dosFsInit (NUM_DOSFS_FILES); /* initialize DOS-FS */ if (usrTffsConfig (drive, removable, fileName) == ERROR) { printErr ("usrTffsConfig failed./n"); return (ERROR); } printErr ("done./n"); /* load the boot file */ printErr ("Loading %s...", fileName); if ((fd = open (fileName, O_RDONLY, 0)) == ERROR) { printErr ("/nCannot open /"%s/"./n", fileName); return (ERROR); } if (bootLoadModule (fd, pEntry) != OK) goto tffsLoadErr; close (fd); return (OK); } Tornado之TrueFFS编程者指南(六)-By George (续) 按:看了seasoblue兄如此辛苦地翻译了TrueFFS编程者指南1-5,心想不能老是伸手要吃要穿,自己应当做点什么,就心血来潮也翻译起来。seasoblue兄谦虚说自己对TrueFFS造诣不深,其实大家心里明白他是大牛,因为字里行间已现山露水。我需要说明的是我对TrueFFS确实是啥屁不懂,仅仅是想学学,顺便照着字面就翻译了,字里行间肯定是破绽百出。之所以还厚着脸贴出来,是希望大方之家(如seasoblue、xiaohua等)能多多斧正,俺也好早点入门。
第三章:如何写socket驱动与MTD 1,简介 这一章将为你提供FLASH存储器、tffs与VXWORKS的接口。它将阐述所有对socket驱动和MTD至关重要的函数和结构体。其中我们最关心的两个结构体是FLFlash和FLSocket。 tffs内部分配了一个包含5个FLFlash结构体的阵列(array),每个都对应一个可能的flash设备。tffs使用这些FLFlash结构体来存储数据和函数指针(这些函数是用于管理FLASH设备的)。比如,tffs使用MTD函数来处理对FLASH煤质基本的读写操作,而FLFlash结构体就包含这些MTD函数指针。当运行一个MTD识别程序时,系统就安装了这些函数指针。 FLFlash结构体还包含一个指向FLSocket结构体的指针。tffs使用这些FLSocket结构体来存储数据和函数指针(所不同的是,这些函数是用于处理与FLASH设备的硬件接口的,也就是socket接口)。从我们的sysTffsInit()到xxxRegister()程序调用时,则安装这些函数。 用TFFS注册我们的socket驱动: 在VXWORKS中包含TFFS将会使usrRoot()调用tffsDrv()。而这将发起一个函数调用链:如下图
这些函数调用的目的之一就是用TFFS注册我们的socket驱动函数。 多数情况下,注册工作都发生在xxxRegister()中(这个函数的定义在sysTffs.c中)。该函数能更新FLSocket结构体。而此时,TFFS已经对应socket驱动中的服务程序给FLSocket结构体赋予了一个设备号(也即卷标)。TFFS调用FLSocket结构体中引用的函数来处理与Flash设备的硬件接口。
给FLASH技术确定一个MTD; 要创建一个TFFS块设备,我们必须调用tffsDevCreate()。这一调用也将发起一个函数调用链。如图3-2
这些函数调用的目的之一是确认合适的MTD。该确认过程在flIdentifyFlash()中。flIdentifyFlash()通过逐个执行xxxIdentify()表中的程序来确定合适的MTD。(相同的MTD是可以在多个不同的FLASH卷标中同时有效的)。 一旦找到合适的MTD,确认程序就会更新FLFlash结构体中的数据以及指向用于读、写、擦除、映射等操作的MTD程序指针。此外,确认过程还将完成在当前FLFlash结构体中涉及的FLSocket结构体的初始化。
2,关于FLFlash结构体和FLSocket结构体 tffs最多可以处理5个TFFS块设备。它给每个FLFlash结构体和FLSocket结构体都分配一个可能存在的FLASH设备。当使用TFFS注册我们的socket驱动时,系统也同时对这些结构体进行初始化。 多数情况下,注册也将更新FLFlash中socket成员涉及的FLSocket结构体。而FLFlash结构体的初始化使通过运行一个MTD确认程序来完成的。因为,确认程序依赖于FLSocket结构体中所涉及的函数,所以我们必须在运行MTD确认程序之前安装号socket驱动。
2.1 FLFlash之庐山真面目 几乎所有的FLFlash结构体成员都是通过MTD确认程序设定的,这样它就可以通过检测flash硬件获取它所需要的绝大多数数据。唯一例外的是socket成员,它是通过TFFS内部函数设定的。 FLFlash结构体在h/tffs/flflash.h中有定义: typedef struct tFlash FLFlash; /* forward definition */ struct tFlash { FlashType type; /* flash device type (JEDEC id) */ long int erasableBlockSize; /* smallest erasable area */ long int chipSize; /* chip size */ int noOfChips; /* no. of chips in array */ int interleaving; /* chip interleaving */ unsigned flags; /* special options */ void * mtdVars; /* MTD private area for socket */ FLSocket * socket; /* FLSocket for this drive */ /* MTD-supplied flash map function */ void FAR0 * (*map)(FLFlash *, CardAddress, int); /* MTD-supplied flash read function */ FLStatus (*read)(FLFlash *, CardAddress, void FAR1 *, int, int); /* MTD-supplied flash write function */ FLStatus (*write)(FLFlash *,CardAddress,const void FAR1 *,int,int) /* MTD-supplied flash erase function */ FLStatus (*erase)(FLFlash *, int, int); /* callback to execute after power up */ void (*setPowerOnCallback)(FLFlash *); }; 说明: (1) type: JEDEC ID用于标识FLASH存储器的硬件。该成员的值在MTD确认程序中设定; (2) erasableBlockSize:大小,单位为字节,为flash存储器一个可擦除块的大小。该值通过交叉计算而得,因此当在MTD中设定该值时,通常用如下格式: vol.erasableBlockSize=aValue*vol.interleaving; 其中,aValue就是未与其它flash芯片交叉存取的可擦除块的大小。 (3) chipsize: 单片flash得存储容量,单位字节。通过MTD中的外部函数flFitInSocketWindow()设定。 (4)noOfChips: 构成FLASH存储阵列的flash存储器的片数; (5)interleaving: flash存储阵列的交叉存取参数,必须为2的整数幂(如1,2,4等)。用于定义在一个存储芯片上,两个字节连续媒体的地址差异。 (6)flags: bits0-7 保留用于TFFS;bits8-15 保留用于MTD; (7)mtdVars: 该区域如果被MTD占用,mtd的确认程序则将它初始化为一个指针指向一个特定的存储区。比如,16位AMD设备的MTD使用该成员存储一个指针,该指向存有AMD特有的FLASH参数。 (8)socket: 是个指向FLSocket结构体的指针。而这个FLSocket结构体含有指向socket层函数的指针和数据。当我们注册socket驱动时,该FLSocket结构体中涉及的函数将被安装。而且,因为TFFS要使用这些socket驱动函数来寻址FLASH存储器,所以我们必须在运行MTD确认程序之前注册我们的socket驱动。 (9)map: 一个指向flash存储器映射(MAP)函数的指针,该函数将flash映射到存储器的一个区域。TFFS初始化时默认将这个指针成员指向一个适合所有NOR flash存储器类型的映射函数。NAND或其他类型FLASH必须用另外一个指向使用拷贝映射机制的程序指针来替换默认的。 (10)read: 一个指向flash读函数的指针。在MTD确认程序之前,系统就将该成员指针初始化指向一个适合所有NORflash存储器类型的读函数。其实,读flash的过程是通过从一个映射窗口拷贝来实现的。如果默认的read指针不适合我们的flash设备,MTD确认程序需要更新这个指针成员以便让她指向正确的函数;如果正好适合,那当然不需要改。 (11)write: 一个指向flash写函数的指针。该成员默认指向的程序在发现FLASH的写函数不合适时将返回一个写保护错误信息。同read指针类似,write指针一样需要MTD确认程序将它指向一个合适的函数。 (12)erase: 一个指向flash擦除函数的指针。在发现擦除函数与flash设备不匹配时,该指针默认指向的函数也将返回一个写操作错误信息。erase指针一样需要MTD确认程序将它指向一个合适的函数。 (13) setPowerOnCallback: TFFS在flash设备上电后应当执行一个函数以便挂接该设备,setPowerOnCallback成员就是指向这个函数的指针。(需要注意,不要混淆了setPowerOnCallback成员和FLSocket结构体中的PowerOnCallback成员)。对许多flash存储设备而言,这个函数并不必须。
2.2 FLSocket之庐山真面目 作为socket驱动的开发人员,我们最需要关心的就是FLSocket结构体的初始化。这个结构体为TFFS提供了指向处理flash硬件接口的函数指针。一般情况下,怎样实现这些flash接口函数相对软件来说更需要硬件细节。 相应地,FLSocket结构体的成员描述指出了风河提供的BSP,这样我们就可以用来作例子。但是,在使用这些BSP时,我们需要对特定的硬件特别谙熟。 FLSocket结构体在h/tffs/flsocket.h中有定义: typedef struct tSocket FLSocket; /* forward definition */ struct tSocket { unsigned volNo; /* volume no. of socket */ unsigned serialNo; /* serial no. of socket on controller */ FLBoolean cardChanged; /* need media change notification */ int VccUsers; /* no. of current VCC users */ int VppUsers; /* No. of current VPP users */ PowerState VccState; /* actual VCC state */ PowerState VppState; /* actual VPP state */ FLBoolean remapped; /* set to TRUE if the socket window is moved */ void (*powerOnCallback)(void *flash); /* notification routine for Vcc on */ void * flash; /* flash object for callback */ struct { /* window state */ unsigned int baseAddress; /* physical base as a 4K page */ unsigned int currentPage; /* our current window page mapping */ void FAR0 * base; /* pointer to window base */ long int size; /* window size (must by power of 2) */ unsigned speed; /* in nsec. */ unsigned busWidth; /* 8 or 16 bits */ } window; FLBoolean (*cardDetected) (FLSocket vol); void (*VccOn) (FLSocket vol); void (*VccOff) (FLSocket vol); #ifdef SOCKET_12_VOLTS FLStatus (*VppOn)(FLSocket vol); void (*VppOff)(FLSocket vol); #endif /* SOCKET_12_VOLTS */ FLStatus (*initSocket) (FLSocket vol); void (*setWindow) (FLSocket vol); void (*setMappingContext) (FLSocket vol, unsigned page); FLBoolean (*getAndClearCardChangeIndicator) (FLSocket vol); FLBoolean (*writeProtected) (FLSocket vol); #ifdef EXIT void (*freeSocket) (FLSocket vol); #endif }; (1)volNo: 卷标号,除非通过MTD确认程序,否则不要改其值; (2)serialNo: 可自使用的结构体成员,典型值设为0; (3)cardChanged: 该成员的作用是追踪监测是否有flash卡改变,监测周期为100ms,监测函数为cardDetected成员中的函数。如果cardDetected返回值FALSE,则cardChanged被设为TRUE。如果FLASH媒体不可移动,则该成员应设为FALSE。如果为TRUE,在TFFS寻址FLASH时,它将重新挂接FLASH设备。 (4)VccUsers: 内部使用,无需改变。 (5)VppUsers: 内部使用,无需改变。 (6)VccState: 内部使用,无需改变。 (7)VppState: 内部使用,无需改变。 (8) remapped: 内部使用。TFFS使用该成员追踪检测window是否被重映射过。如果我们写自己的MTD映射函数,应在返回值之前设定该成员。 (9)powerOnCallback:通过我们的xxxRegister()函数设定。tffs在调用VccOn成员中的xxxRegister()函数后调用此处的函数。如果没有必要使用该成员,可将其设为空指针。 (10)flash: 内部使用,无需改变其值。该成员用于支持可移动的flash媒体,且通过TFFS动态设置。 (11)window.baseAddress: 通过我们的xxxRegister()函数设定。在这个上下文中,一个窗口就是主机系统MEM的一部分,通过这个窗口,一部分媒体空间可直接寻址并可进行地址设定。window.baseAddress成员按4000页保存着这个主机系统的内存地址(也即窗口基地址除以4K)。之所以这样做是为了防止重建时基地址的页结盟(此处不懂)。 (12)window.currentPage:内部使用。用于存储当前映射的window页。 (13)window.base: 通过在setWindow成员中提供的函数设定。TFFS使用window.base来存放FLASH存储器上存储窗口的基地址。 (14)window.size:通过在setWindow成员中提供的函数设定。 TFFS使用window.size来存放FLASH存储器上存储窗口的大小。 (15)window.speed:内部使用。不要通过socket驱动改变此值。TFFS主要用它来存放完成flash设备互操作所需要的时 间。初始化后一般的默认值为250ns,但风河提供的MTD将它复位为120ns。如果我们自己写MTD,一定要在映射函数中正确地复位该成员值。 (16)window.busWidth:内部使用。用于存储flash的位宽是8位还是16位。最初的复位值是16,但风河提供的MTD都设位8。如果自己写MTD,也需要在映射函数中正确设置。 (17)cardDetected:通过我们的xxxRegister()函数初始化。它指向一个用于标识PCMICIA槽上有无flash存储卡的函数。对不可移动的媒体,这个函数通常返回TRUE。如果这个函数返回FALSE,则cardChanged为TRUE。函数的巡检周期为100ms。 (18)VccOn:通过xxxRegister()函数设定。该成员指向一个用于打开flash操作电源的函数。当媒体空闲时,系统会关掉电源以省电,而在再次访问flash媒体前,系统会调用该函数来再次打开电源。 在打开电源时,只有等到操作电压稳定了,VccOn函数才会返回值,如果必要,还需要调用flDelayMsec()或空闲语句来等待VCC稳定。 (19)VccOff:通过xxxRegister()函数设定。该成员指向一个用于关闭flash操作电源的函数。通常,在系统相当空闲的情况下,电源才会关闭。 (20)VppOn:通过xxxRegister()函数设定。系统通常调用该程序来打开编程电源(Vpp通常为12V,而vcc通常为3.3或5V)。由于并不是所有的flash芯片都需要这个电压,因而,只有当定义了SOCKET_12_VOLTS后这个成员才会被包含。 VppOn的函数返回状况与VccOn类似。 (21)VppOff:通过xxxRegister()函数设定。系统通常调用该程序来关闭编程电源。同样地,只有当定义了SOCKET_12_VOLTS后这个成员才会被包含。 (22)initSocket:通过xxxRegister()函数设定。系统在试图访问套接字之前调用与该成员相关的函数。TFFS用这个函数来处理在访问套接字之前的一些必要的初始化,尤其是当这些初始化在套接字注册时不能完成时。比方说,如果你在套接字注册时没有做硬件检测,或者flash存储器是可移动的,这个函数应当检测flash媒体,并在它移动后作出正确的回应。 (22)setWindow:通过xxxRegister()函数设定。系统调用该成员指向的函数来更新window结构体中的关键成员。为大多数硬件写一个setWindow函数,通常需要做如下几步: * 按4000页设定window.baseAddress为基地址。页就是window的基地址除以4000。 * 调用flSetWindowSize()并以4K单位指定window大小。 TFFS假定访问窗口总是独占式的。也就是说,在设定这些窗口特征之一后,系统就不再允许你的应用程序直接改变他们了,否则将破坏它。唯一的例外就是映射寄存器。因为,TFFS在它访问flash后总是重新建立寄存器,你的应用程序也许会为了别的目的映射这些窗口而不是TFFS。但是,我们千万不能在中断函数中这么做。 系统如果带有多个socket驱动,请确认窗口的基地址各不相同。此外,我们还必须计算窗口大小并确认他们没有重叠。 (23)setMappingContext:通过xxxRegister()函数设定。该成员指向一个用于设定window映射寄存器的函数。因为,焊在板子上的flash阵列通常将全部的flash都映射到RAM,所以他们不需要这个函数。但众所周知,ss5 BSP相关的flash阵列是个例外。还有,使用PCMCIA槽的flash卡使用这个函数来寻址映射寄存器,以便将有效的flash地址转移到主机的内存窗口。 (24)getAndClearCardChangeIndicator:通过xxxRegister()函数设定。这个函数读取硬件卡改变标志并清除。如果你没有这种硬件,可将它设为空指针。 (25)writeProtected:通过xxxRegister()函数设定。它指向一个可以获取当前媒体写保护开关状态的函数。当然,前提是假设这个写保护开关存在。 (26)freeSocket:通过xxxRegister()函数设定。它指向的函数可以释放被socket驱动内部保留的资源。 Tornado之TrueFFS编程者指南(七_Done) 狗尾再续:Done By George On 2004-06-01 3.3 组件驱动 系统启动时,TrueFFS将为BSP中所支持的每个flash设备创建一个组件驱动。正如图3-1所示(我上次翻译中的内容), tffsDrv()调用的最终目的时调用sysTffsInit()函数。为系统中所带的每个flash设备注册socket驱动所必须的工作都将由该函数完成。 大多数情况下,这意味着给socket驱动指定一个合适FLSocket结构体并安装函数指针。严格地讲,sysTffsInit()内部是不存在任何强加的组织的。但,为了可量测性,每个flash设备的细节是与xxxRegister()相关的,该函数因flash设备不同而不同。 最后,你的socket驱动必须提供两个全局函数:flFitInSocketWindow()和flDelayLoop()。(不能声明成LOCAL和static-这是当然的)。flFitInSocketWindow()函数返回flash阵列中一个独立的片子的容量(字节)。TFFS使用这个值来决定socket窗是否足够大。flDelayLoop()函数为TFFS提供一个适于硬件的等待方法(单位ms)。 3.3.1 如何写一个sysTffsInit() sysTffsInit()函数是一个无参数也无返回值的函数。它至少要为每个flash设备调用一个注册程序。举一个最简单的例子,mv177BSP中sysTffs.c中定义的sysTffsInit(): LOCAL void sysTffsInit (void) { **Register (); } 这个BSP只支持一个flash设备,这样,它的sysTffsInit()也就只有一个**Register()的调用。其他的BSP定义的sysTffsInit()则要复杂一些,比如:pc486中的sysTffsInit()有三个xxxRegister()调用: #ifdef INCLUDE_SOCKET_DOC (void) docRegister (); /* Disk On Chip */ #endif /* INCLUDE_SOCKET_DOC */ #ifdef INCLUDE_SOCKET_PCIC0 (void) pcRegister (0, PC_BASE_ADRS_0); /* flash card on socket 0 */ #endif /* INCLUDE_SOCKET_PCIC0 */ #ifdef INCLUDE_SOCKET_PCIC1 (void) pcRegister (1, PC_BASE_ADRS_1); /* flash card on socket 1 */ #endif /* INCLUDE_SOCKET_PCIC1 */ 注意,每个xxxRegister()调用都有预编译信息。这些预编译信息中的宏在BSP的config.h中都有定义。使用这些宏,你可以有选择地控制哪些调用在编译时要包含在sysTffsInit()中。 3.3.2 如何写一个xxxRegister() TFFS内部分配了一个FLSocket结构体数组,且TFFS使用这些结构体来找到需要与硬件打交道的SOCKET驱动函数(也有其他信息)。在你的xxxRegister()函数里,你必须在这些FLSocket结构体中找到一个合适的并初始化它的成员。可以调用flSocketOf( )来从TFFS中找到一个合适的FLSocket结构体: FLSocket *flSocketOf(unsigned volNo) 作为输入,这个函数需要一个设备号(0-4)。完成调用后,该函数返回一个指向FLSocket结构体的指针。可参口如下程序段: FLSocket vol = flSocketOf (noOfDrives); if (noOfDrives >= DRIVES) return (flTooManyComponents); tffsSocket[noOfDrives] = "RFA"; noOfDrives++; 变量noOfDrives和tffsSocket[]都是全局的。DRIVES是一个设为最大驱动器号(5,请勿改变)的符号常量。在你调用flSocketOf( )时,你必须使用全局变量noOfDrives作为volNo参数,而且在选择合适的FLSocket结构体成功后必须更新它。TFFS用这个全局变量计算已经创建了的本地flash设备的个数。同样地,你还需要更新tffsSocket[]数组的noOfDrives成员以便为刚才创建的驱动器包含一个lable。这个lable在tffsShow()和tffsShowAll()的输出时用到。 在返回FLSocket结构体指针后,你的xxxRegister()函数必须设置如下成员: window.baseAddress cardDetected VccOn VccOff VppOn VppOff initSocket setWindow setMappingContext getAndClearCardChangeIndicator writeProtected freeSocket 这些成员在前面都有详述。 3.3.3如何写一个flDelayLoop() TFFS使用flDelayLoop( )函数来处理与flash硬件打交道所需要的时间。该函数的作用很简单,就是等待所需要的时钟周期数。其格式如下: void flDelayLoop(int) mv177BSP中的flDelayLoop()是如下定义的: void flDelayLoop ( int cycles /* 所需要的周期数 */ ) { while (--cycles); } 3.3.4 如何写一个flFitInSocketWindow( )函数 TFFS用flFitInSocketWindow( )来确认chipSize不会比windowSize大。其格式应如下: long int flFitInSocketWindow ( long int chipSize, /* size of single physical chip in bytes */ int interleaving, /* flash chip interleaving (1,2,4 etc) */ long int windowSize /* socket window size in bytes */ ) 如果你的BSP使用自适应的窗口或者它把所有的flash都映射到窗口中,你定义的flFitInSocketWindow()可以简单地只返回chipSize作为函数值。 如果你的BSP使用固定的窗口,而且窗口并不足以将所有的flash媒体映射到主机的存储器,你的flFitInSocketWindow()函数就必须比较chipSize和windowSize的大小了,且返回一个适应windowSize的值。MTD映射函数使用flFitInSocketWindow()函数的返回值来调整FLSocket.chipSize的值。
|