linux NAND驱动之一:内核中的NAND代码布局
在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips 、devices 、maps 、nand 、onenand 和ubi 六个子目录。其中只有nand 和onenand 目录中的代码才与NAND 驱动相关,不过nand 目录中的代码比较通用,而onenand 目录中的代码相对于nand 中的代码而言则简化了很多,它是针对三星公司开发的另一类Flash芯片
在Linux 内核中,MTD 源代码放在/driver/mtd 目录中,该目录中包含chips 、devices 、maps 、nand 、onenand 和ubi 六个子目录。其中只有nand 和onenand 目录中的代码才与NAND 驱动相关,不过nand 目录中的代码比较通用,而onenand 目录中的代码相对于nand 中的代码而言则简化了很多,它是针对三星公司开发的另一类Flash芯片,即OneNAND Flash,是一种较常用NAND先进的FLASH吧,只是目前似乎普及率并不高,本文也将不做讨论。
因此,若只是开发基于MTD 的NAND 驱动程序,那么我们需要关注的代码就基本上全在drivers/mtd/nand 目录中了,而该目录中也不是所有的代码文件都与我们将要开发的NAND 驱动有关,除了Makefile 和Kconfig 之外,其中真正与NAND 驱动有关的代码文件只有6 个,即:
1)nand_base.c :
定义了NAND 驱动中对NAND 芯片最基本的操作函数和操作流程,如擦除、读写page 、读写oob 等。当然这些函数都只是进行一些default 的操作,若你的系统在对NAND 操作时有一些特殊的动作,则需要在你自己的驱动代码中进行定义,然后Replace 这些default 的函数。
2)nand_bbt.c :
定义了NAND 驱动中与坏块管理有关的函数和结构体。
3)nand_ids.c :
定义了两个全局类型的结构体:struct nand_flash_dev nand_flash_ids[ ] 和struct nand_manufacturers nand_manuf_ids[ ] 。其中前者定义了一些NAND 芯片的类型,后者定义了NAND 芯片的几个厂商。NAND 芯片的ID 至少包含两项内容:厂商ID 和厂商为自己的NAND 芯片定义的芯片ID 。当NAND 驱动被加载的时候,它会去读取具体NAND 芯片的ID ,然后根据读取的内容到上述定义的nand_manuf_ids[ ] 和nand_flash_ids[ ] 两个结构体中去查找,以此判断该NAND 芯片是那个厂商的产品,以及该NAND 芯片的类型。若查找不到,则NAND 驱动就会加载失败,因此在开发NAND 驱动前必须事先将你的NAND 芯片添加到这两个结构体中去(其实这两个结构体中已经定义了市场上绝大多数的NAND 芯片,所以除非你的NAND 芯片实在比较特殊,否则一般不需要额外添加)。
值得一提的是,nand_flash_ids[ ] 中有三项属性比较重要,即pagesize 、chipsize 和erasesize ,驱动就是依据这三项属性来决定对NAND 芯片进行擦除,读写等操作时的大小的。其中pagesize 即NAND 芯片的页大小,一般为256 、512 或2048 ;chipsize 即NAND 芯片的容量;erasesize 即每次擦除操作的大小,通常就是NAND 芯片的block 大小。
4)nand_ecc.c :
定义了NAND 驱动中与softeware ECC 有关的函数和结构体,若你的系统支持hardware ECC ,且不需要software ECC ,则该文件也不需理会。
5)nandsim.c :
定义了Nokia 开发的模拟NAND 设备,默认是Toshiba NAND 8MiB 1,8V 8-bit (根据ManufactureID ),开发普通NAND 驱动时不用理会。
6)diskonchip.c :
定义了片上磁盘(DOC) 相关的一些函数,开发普通NAND 驱动时不用理会。
除了上述六个文件之外,nand 目录中其他文件基本都是特定系统的NAND 驱动程序例子,但看来真正有参考价值的还有cafe_nand.c 和s3c2410.c 两个,而其中又尤以cafe_nand.c 更为详细,另外,nand 目录中也似乎只有cafe_nand.c 中的驱动程序在读写NAND 芯片时用到了DMA 操作
|
|
其中num_resources 和resource 与具体的硬件相关,主要包括一些寄存器地址范围和中断的定义。caorr_platform_default_nand 待会儿再说。需要注意的是,这个platform_device 中name 的值必须与platform_driver->driver->name 的值完全一致,因为platform_bus_type 的match 函数是根据这两者的name 值来进行匹配的。
|
|
(1)probe 函数中与MTD 相关的结构体
在probe 函数中,我们需要为三个与MTD 相关的结构体分配内存以及初始化,它们是struct mtd_info 、struct mtd_partition 和struct nand_chip 。其中前两者已经在四节做过说明,这里只对struct nand_chip 做一些介绍。struct nand_chip 是一个与NAND 芯片密切相关的结构体,主要包含三方面内容:
A)指向一些操作NAND 芯片的函数的指针,稍后将对这些函数指针作一些说明;
B)表示NAND 芯片特性的成员变量,主要有:
unsigned int options :与具体的NAND 芯片相关的一些选项,如NAND_BUSWIDTH_16 等,可以参考<linux/mtd/nand.h>
int page_shift :用位表示的NAND 芯片的page 大小,如某片NAND 芯片的一个page 有512 个字节,那么page_shift 就是9 ;
int phys_erase_shift :用位表示的NAND 芯片的每次可擦除的大小,如某片NAND 芯片每次可擦除16K 字节( 通常就是一个block 的大小) ,那么phys_erase_shift 就是14 ;
int bbt_erase_shift :用位表示的bad block table 的大小,通常一个bbt 占用一个block ,所以bbt_erase_shift 通常与phys_erase_shift 相等;
int numchips :表示系统中有多少片NAND 芯片;
unsigned long chipsize :NAND 芯片的大小;
int pagemask :计算page number 时的掩码,总是等于chipsize/page 大小 - 1 ;
int pagebuf :用来保存当前读取的NAND 芯片的page number ,这样一来,下次读取的数据若还是属于同一个page ,就不必再从NAND 芯片读取了,而是从data_buf 中直接得到;
int badblockpos :表示坏块信息保存在oob 中的第几个字节。对于绝大多数的NAND 芯片,若page size > 512 ,那么坏块信息从Byte 0 开始存储,否则就存储在Byte 5 ,即第六个字节。
C. 与ecc ,oob 和bbt (bad block table) 相关的一些结构体,对于坏块及坏块管理,将在稍后做专门介绍。
(2)对NAND 芯片进行实际操作的函数
前面已经说过,MTD 为我们提供了许多default 的操作NAND 的函数,这些函数与具体的硬件( 即NAND controller) 相关,而现有的NAND controller 都有各自的特性和配置方式,MTD 当然不可能为所有的NAND controller 都提供一套这样的函数,所以在MTD 中定义的这些函数只适用于通用的NAND controller( 使用PIO 模式) 。
如果你的NAND controller 在操作或者说读写NAND 时有自己独特的方式,那就必须自己定义适用于你的NAND controller 的函数。一般来说,这些与硬件相关的函数都在struct nand_chip 结构体中定义,或者应该说是给此结构体中的函数指针赋值。
struct nand_chip {
void __iomem * IO_ADDR_R;
void __iomem * IO_ADDR_W;
uint8_t ( * read_byte) ( struct mtd_info * mtd) ;
u16 ( * read_word) ( struct mtd_info * mtd) ;
void ( * write_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
void ( * read_buf) ( struct mtd_info * mtd, uint8_t * buf, int len) ;
int ( * verify_buf) ( struct mtd_info * mtd, const uint8_t * buf, int len) ;
void ( * select_chip) ( struct mtd_info * mtd, int chip) ;
int ( * block_bad) ( struct mtd_info * mtd, loff_t ofs, int getchip) ;
int ( * block_markbad) ( struct mtd_info * mtd, loff_t ofs) ;
void ( * cmd_ctrl) ( struct mtd_info * mtd, int dat, unsigned int ctrl) ;
int ( * dev_ready) ( struct mtd_info * mtd) ;
void ( * cmdfunc) ( struct mtd_info * mtd, unsigned command, int column, int page_addr) ;
int ( * waitfunc) ( struct mtd_info * mtd, struct nand_chip * this ) ;
void ( * erase_cmd) ( struct mtd_info * mtd, int page) ;
int ( * scan_bbt) ( struct mtd_info * mtd) ;
int ( * errstat) ( struct mtd_info * mtd, struct nand_chip * this , int state, int status, int page) ;
int ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf, int page, int cached, int raw) ;
……
struct nand_ecc_ctrl ecc;
……
}
IO_ADDR_R 和IO_ADDR_W :8 位NAND 芯片的读写地址,如果你的NAND controller 是用PIO 模式与NAND 芯片交互,那么只要把这两个值赋上合适的地址,就完全可以使用MTD 提供的default 的读写函数来操作NAND 芯片了。所以这两个变量视具体的NAND controller 而定,不一定用得着;
read_byte 和read_word :从NAND 芯片读一个字节或一个字,通常MTD 会在读取NAND 芯片的ID ,STATUS 和OOB 中的坏块信息时调用这两个函数,具体是这样的流程,首先MTD 调用cmdfunc 函数,发起相应的命令,NAND 芯片收到命令后就会做好准备,最后MTD 就会调用read_byte 或read_word 函数从NAND 芯片中读取芯片的ID ,STATUS 或者OOB ;
read_buf 、write_buf 和verify_buf:分别是从NAND 芯片读取数据到buffer、把buffer 中的数据写入到NAND 芯片、和从NAND 芯片中读取数据并验证。调用read_buf 时的流程与read_byte 和read_word 类似,MTD 也是先调用cmdfunc 函数发起读命令( 如NAND_CMD_READ0 命令) ,接着NAND 芯片收到命令后做好准备,最后MTD 再调用read_buf 函数把NAND 芯片中的数据读取到buffer 中。调用write_buf 函数的流程与read_buf 相似;
select_chip :因为系统中可能有不止一片NAND 芯片,所以在对NAND 芯片进行操作前,需要这个函数来指定一片NAND 芯片;
cmdfunc :向NAND 芯片发起命令;
waitfunc :NAND 芯片在接收到命令后,并不一定能立即响应NAND controller 的下一步动作,对有些命令,比如erase ,program 等命令,NAND 芯片需要一定的时间来完成,所以就需要这个waitfunc 来等待NAND 芯片完成命令,并再次进入准备好状态;
write_page :把一个page 的数据写入NAND 芯片,这个函数一般不需我们实现,因为它会调用struct nand_ecc_ctrl 中的write_page_raw 或者write_page 函数,关于这两个函数将在稍后介绍。
以上提到的这些函数指针,都是REPLACEABLE 的,也就是说都是可以被替换的,根据你的NAND controller ,如果你需要自己实现相应的函数,那么只需要把你的函数赋值给这些函数指针就可以了,如果你没有赋值,那么MTD 会把它自己定义的default 的函数赋值给它们。在本地代码上,以上函数指针都采用的默认的方式,通过s3c_nand_probe-》nand_scan-》nand_scan_ident-》nand_set_defaults,在该函数中以上的函数指针都被nand_base.c定义的默认函数赋值。
顺便提一下,以上所说的读写NAND 芯片的流程并不是唯一的,如果你的NAND controller 在读写NAND 芯片时有自己独特的方式,那么完全可以按照自己的方式来做。就比如我们公司芯片的NAND controller ,因为它使用DMA 的方式从NAND 芯片中读写数据,所以在我的NAND driver 中,读数据的流程是这样的:首先在cmdfunc 函数中初始化DMA 专用的buffer ,配置NAND 地址,发起命令等,在cmdfunc 中我几乎做了所有需要与NAND 芯片交互的事情,总之等cmdfunc 函数返回后,NAND 芯片中的数据就已经在DMA 专用的buffer 中了,之后MTD 会再调用read_buf 函数,所以我的read_buf 函数其实只是把数据从DMA 专用的buffer 中,拷贝到MTD 提供的buffer 中罢了。
(3)最后,struct nand_chip 结构体中还包含了一个很重要的结构体,即struct nand_ecc_ctrl ,它的定义如下:
struct nand_ecc_ctrl {
……
void ( * hwctl) ( struct mtd_info * mtd, int mode) ;
int ( * calculate) ( struct mtd_info * mtd, const uint8_t * dat, uint8_t * ecc_code) ;
int ( * correct) ( struct mtd_info * mtd, uint8_t * dat, uint8_t * read_ecc, uint8_t * calc_ecc) ;
int ( * read_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
void ( * write_page_raw) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
int ( * read_page) ( struct mtd_info * mtd, struct nand_chip * chip, uint8_t * buf) ;
void ( * write_page) ( struct mtd_info * mtd, struct nand_chip * chip, const uint8_t * buf) ;
int ( * read_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page, int sndcmd) ;
int ( * write_oob) ( struct mtd_info * mtd, struct nand_chip * chip, int page) ;
} ;
hwctl :这个函数用来控制硬件产生ecc ,其实它主要的工作就是控制NAND controller 向NAND 芯片发出NAND_ECC_READ 、NAND_ECC_WRITE 和NAND_ECC_READSYN 等命令,与struct nand_chip 结构体中的cmdfunc 类似,只不过发起的命令是ECC 相关的罢了;
calculate :根据data 计算ecc 值;
correct :根据ecc 值,判断读写数据时是否有错误发生,若有错,则立即试着纠正,纠正失败则返回错误;
read_page_raw 和write_page_raw :从NAND 芯片中读取一个page 的原始数据和向NAND 芯片写入一个page 的原始数据,所谓的原始数据,即不对读写的数据做ecc 处理,该读写什么值就读写什么值。另外,这两个函数会读写整个page 中的所有内容,即不但会读写一个page 中MAIN 部分,还会读写OOB 部分。
read_page 和write_page :与read_page_raw 和write_page_raw 类似,但不同的是,read_page 和write_page 在读写过程中会加入ecc 的计算,校验,和纠正等处理。
read_oob 和write_oob :读写oob 中的内容,不包括MAIN 部分。
其实,以上提到的这几个read_xxx 和write_xxx 函数,最终都会调用struct nand_chip 中的read_buf 和write_buf 这两个函数,所以如果没有特殊需求的话,我认为不必自己实现,使用MTD 提供的default 的函数即可
由前面的说明可知,我们在要对NAND 芯片进行实际操作前已经为struct mtd_info 、struct mtd_partition 和struct nand_chip 这三个结构体分配好了内存,接下来就要为它们做一些初始化工作。 其中,我们需要为struct mtd_info 所做的初始化工作并不多,因为MTD Core 会在稍后为它做很多初始化工作(这些工作在nand_scan_tail这个函数的后半部分来填充),但是有一点必须由我们来做,那就是把指向struct nand_chip 结构体的指针赋给struct mtd_info 的priv 成员变量,因为MTD Core 中很多函数之间的调用都只传递struct mtd_info ,它需要通过priv 成员变量得到struct nand_chip 。如下,在s3c_nand.c中:
struct mtd_info *s3c_mtd = NULL; //mtd_info结构体指针
struct nand_chip *nand; //nand_chip结构体指针
/* allocate memory for MTD device structure and private data */
s3c_mtd = kmalloc(sizeof(struct mtd_info) + sizeof(struct nand_chip), GFP_KERNEL); //一起分内存
nand = (struct nand_chip *) (&s3c_mtd[1]); //得到划分的内存
memset((char *) s3c_mtd, 0, sizeof(struct mtd_info)); //初始化0
memset((char *) nand, 0, sizeof(struct nand_chip));
s3c_mtd->priv = nand;
所以,为struct nand_chip 的初始化,才是我们在probe 函数中的主要工作。其实这里所谓的初始化,主要就是为struct nand_chip 结构体中的众多函数指针赋值。现在假定你定义好了所有需要的与NAND 芯片交互的函数,并已经把它们赋给了struct nand_chip 结构体中的函数指针。当然,此时你还不能保证这些函数一定能正确工作,但是没有关系,probe 函数在接下来的工作中会调用到几乎所有的这些函数,你可以依次来验证和调试。当你的probe 函数能顺利通过后,那么这些函数也就基本没什么问题了,你的NAND 驱动也就已经完成了80 %了。
接下来,probe 函数就会开始与NAND 芯片进行交互了,它要做的事情主要包括这几个方面:读取NAND 芯片的ID ,然后查表得到这片NAND 芯片的如厂商,page size ,erase size 以及chip size 等信息,接着,根据struct nand_chip 中options 的值的不同,或者在NAND 芯片中的特定位置查找bad block table ,或者scan 整个NAND 芯片,并在内存中建立bad block table 。说起来复杂,但其实所有的这些动作,都可以在MTD 提供的一个叫做nand_scan 的函数中完成。
关于nand_scan 函数,在使用时我想有一个地方值得一提。nand_scan 函数主要有两个两个函数组成,即nand_scan_ident 函数和nand_scan_tail 函数。其中nand_scan_ident 函数会读取NAND 芯片的ID,而nand_scan_tail 函数则会查找或者建立bbt (bad block table) 。
在一般情况下,我们可以直接调用nand_scan 函数来完成所要做的工作,然而却并不总是如此,在有些情况下,我们必须分别调用nand_scan_ident 函数和nand_scan_tail 函数,因为在这两者之间,我们还需要做一些额外的工作。在《基于MTD 的NAND 驱动开发( 一) 》中介绍过一个叫做struct nand_ecclayout 的结构体,它用来定义ecc 在oob 中的布局。对于small page( 每页512 Byte) 和big page( 每页2048 Byte) 的两种NAND 芯片,它们的ecc 在oob 中的布局不尽相同。如果你的driver 中对这两种芯片的ecc 布局与MTD 中定义的default 的布局一致,那么就很方便,直接调用nand_scan 函数即可。
但如果不是,那你就需要为这两种不同的NAND 芯片分别定义你的ecc 布局。于是问题来了,因为我们在调用nand_scan_ident 函数之前,是不知道系统中的NAND 芯片是small page 类型的,还是big page 类型,然而在调用nand_scan_tail 函数之前,却必须确定NAND 芯片的oob 布局( 包括ecc 布局和坏块信息pattern) ,因为nand_scan_tail 函数在读取oob 以及处理ecc 时需要这个信息。所以在这种情况下,我们就需要首先调用nand_scan_ident 函数,它会调用一个叫做nand_get_flash_type 的函数,MTD 就是在这个函数中读取NAND 芯片的ID ,然后就能查表( 即全局变量nand_flash_ids) 知道这片NAND 芯片的类型( 即writesize 的大小) 了。接下来,你就可以在你的NAND 驱动中,根据writesize 的大小来区分ecc 的布局了。最后,我们就可以顺利地调用nand_scan_tail 函数了。
六、NAND驱动中的坏块管理
|
|
pages:bbt专用。在查找bbt的时候,若找到了bbt,就把bbt所在的page号保存在这个成员变量中。若没找到bbt,就会把新建立的bbt的保存位置赋值给它。因为系统中可能会有多个NAND芯片,我们可以为每一片NAND芯片建立一个bbt,也可以只在其中一片NAND 芯片中建立唯一的一个bbt,所以这里的pages是个维数为NAND_MAX_CHIPS的数值,用来保存每一片NAND芯片的bbt位置。当然,若只建立了一个bbt,那么就只使用pages[0]。
offs、len和pattern:MTD会从oob的offs中读出len长度的内容,然后与pattern指针指向的内容做比较,若相等,则表示找到了bbt,或者表示这个block是好的。
veroffs和version:bbt专用。MTD会从oob的veroffs中读出一个字节的内容,作为bbt的版本值保存在version中。
maxblocks:bbt专用。MTD在查找bbt的时候,不会查找NAND芯片中所有的block,而是最多查找maxblocks个block。
2、bbt存储在内存中时的工作流程
前文说过,不管bbt是存储在NAND芯片中,还是存储在内存中,nand_default_bbt函数都会调用nand_scan_bbt函数。
nand_scan_bbt函数会判断bbt_td的值,若是NULL,则表示bbt存储在内存中,它就在调用nand_memory_bbt函数后返回。nand_memory_bbt函数的主要工作就是在内存中建立bbt,其实就是调用了create_bbt函数。
create_bbt函数的工作方式很简单,就是扫描NAND芯片所有的block,读取每个block中第一个page的oob内容,然后根据oob中的坏块信息建立起bbt,可以参见上节关于struct nand_bbt_descr中的offs、len和pattern成员变量的解释。
3、bbt存储在NAND芯片时的工作流程
相对于把bbt存储在内存中,这种方式的工作流程稍显复杂一点。
nand_scan_bbt函数首先从NAND芯片中读取bbt的内容,它读取的方式分为两种:
其一是调用read_abs_bbts函数直接从给定的page地址读取,那么这个page地址在什么时候指定呢?就是在你的NAND driver中指定。前文说过,在struct nand_chip结构体中有两个成员变量,分别是bbt_td和bbt_md,MTD为它们附上了default的值,但是你也可以根据你的需要为它们附上你自己定义的值。假如你为bbt_td和bbt_md的options成员变量定义了NAND_BBT_ABSPAGE,同时又把你的bbt所在的 page地址保存在bbt_td和bbt_md的pages成员变量中,MTD就可以直接在这个page地址中读取bbt了。值得一提的是,在实际使用时一般不这么干,因为你不能保证你保存bbt的那个block就永远不会坏,而且这样也不灵活;
其二是调用那个search_read_bbts函数试着在NAND芯片的maxblocks(请见上文关于struct nand_bbt_descr中maxblocks的说明)个block中查找bbt是否存在,若找到,就可以读取bbt了。
MTD查找bbt的过程为:如果你在bbt_td和bbt_md的options 成员变量中定义了 NAND_BBT_LASTBLOCK,那么MTD就会从NAND芯片的最后一个block开始查找(在default情况下,MTD就是这么干的),否则就从第一个block开始查找。
与查找oob中的坏块信息时类似,MTD会从所查找block的第一个page的oob中读取内容,然后与bbt_td或bbt_md 中patter指向的内容做比较,若相等,则表示找到了bbt,否则就继续查找下一个block。顺利的情况下,只需查找一个block中就可以找到 bbt,否则MTD最多会查找maxblocks个block。
若找到了bbt,就把该bbt所在的page地址保存到bbt_td或bbt_md的pages成员变量中,否则pages的值为-1。
如果系统中有多片NAND芯片,并且为每一片NAND芯片都建立一个bbt,那么就会在每片NAND芯片上重复以上过程。
接着,nand_scan_bbt函数会调用check_create函数,该函数会判断是否找到了bbt,其实就是判断bbt_td 或者bbt_md中pages成员变量的值是否有效。若找到了bbt,就会把bbt从NAND芯片中读取出来,并保存到struct nand_chip中bbt指针指向的内存中;若没找到,就会调用create_bbt函数建立bbt(与bbt存储在内存中时情况一样),同时把bbt 写入到NAND芯片中去。
自从写了《基于MTD的NAND驱动开发(一)》后,好久没有动笔,时隔一年才把这篇文章写完,真是惭愧!不过,不管怎么样,总算是写完了,除了还有一些ECC相关的内容外,也基本把我想表达的内容都表达出来了。
本文没有纠缠于MTD中每一句code怎么实现这种细节,因为一来本文主要是写给我自己的,二来我觉得对于开发一个基于NAND的驱动来说,并不需要对MTD中的每一条代码都彻底细致的研究,只要能在总体或者大局上有所把握,能了解MTD中主要函数的工作流程,也就可以了。而且,我觉得对于太细节的东西,只依靠讲解是不起什么作用的,还得自己去研读代码才能明白和掌握。
bbt_td、bbt_md和badblock_pattern就是在nand_default_bbt函数中赋值的3个结构体。它们虽然是相同的结构体类型,但却有不同的作用和含义。
其中bbt_td和bbt_md是主bbt和镜像bbt的描述符(镜像bbt主要用来对bbt的update和备份),它们只在把bbt存储在NAND芯片的情况下使用,用来从NAND芯片中查找bbt。若bbt存储在内存中,bbt_td和bbt_md将会被赋值为NULL。
badblock_pattern就是坏块信息的pattern,其中定义了坏块信息在oob中的存储位置,以及内容(即用什么值表示这个block是个坏块)。
通常用1或2个字节来标志一个block是否为坏块,这1或2个字节就是坏块信息,如果这1或2个字节的内容是0xff,那就说明这个 block是好的,否则就是坏块。对于坏块信息在NAND芯片中的存储位置,small page(每页512 Byte)和big page(每页2048 Byte)的两种NAND芯片不尽相同。一般来说,small page的NAND芯片,坏块信息存储在每个block的第一个page的oob的第六个字节中,而big page的NAND芯片,坏块信息存储在每个block的第一个page的oob的第1和第2个字节中。
我不能确定是否所有的NAND芯片都是如此布局,但应该绝大多数NAND芯片是这样的,不过,即使某种NAND芯片的坏块信息不是这样的存储方式也没关系,因为我们可以在badblock_pattern中自己指定坏块信息的存储位置,以及用什么值来标志坏块(其实这个值表示的应该是 “好块”,因为MTD会把从oob中坏块信息存储位置读出的内容与这个值做比较,若相等,则表示是个“好块”,否则就是坏块)。
bbt_td、bbt_md和badblock_pattern的结构体类型定义如下:
在nand_scan_tail函数中,会首先检查struct nand_chip结构体中的options成员变量是否被赋上了NAND_SKIP_BBTSCAN,这个宏表示跳过扫描bbt。所以,只有当你的 driver中没有为options定义NAND_SKIP_BBTSCAN时,MTD才会继续与bbt相关工作,即调用struct nand_chip中的scan_bbt函数指针所指向的函数,在MTD中,这个函数指针指向nand_default_bbt函数。
bbt有两种存储方式,一种是把bbt存储在NAND芯片中,另一种是把bbt存储在内存中。对于前者,好处是驱动加载更快,因为它只会在第一次加载NAND驱动时扫描整个NAND芯片,然后在NAND芯片的某个block中建立bbt,坏处是需要至少消耗NAND芯片一个block的存储容量;而对于后者,好处是不会耗用NAND芯片的容量,坏处是驱动加载稍慢,因为存储在内存中的bbt每次断电后都不会保存,所以在每次加载NAND 驱动时,都会扫描整个NAND芯片,以便建立bbt。
如果你系统中的NAND芯片容量不是太大的话,我建议还是把bbt存储在内存中比较好,因为根据本人的使用经验,对一块容量为2G bits的NAND芯片,分别采用这两种存储方式的驱动的加载速度相差不大,甚至几乎感觉不出来。
建立bbt后,以后在做擦除等操作时,就不用每次都去验证当前block是否是个坏块了,因为从bbt中就可以得到这个信息。另外,若在读写等操作时,发现产生了新的坏块,那么除了标志这个block是个坏块外,也还需更新bbt。
接下来,介绍一下MTD是如何查找或者建立bbt的。
1、MTD中与bbt相关的结构体
struct nand_chip中的scan_bbt函数指针所指向的函数,即nand_default_bbt函数会首先检查struct nand_chip中options成员变量,如果当前NAND芯片是AG-AND类型的,会强制把bbt存储在NAND芯片中,因为这种类型的NAND 芯片中含有厂家标注的“好块”信息,擦除这些block时会导致丢失坏块信息。
接着nand_default_bbt函数会再次检查struct nand_chip中options成员变量,根据它是否定义了NAND_USE_FLASH_BBT,而为struct nand_chip中3个与bbt相关的结构体附上不同的值,然后再统一调用nand_scan_bbt函数,nand_scan_bbt函数会那3个结构体的不同的值做不同的动作,或者把bbt存储在NAND芯片中,或者把bbt存储在内存中。
在struct nand_chip中与bbt相关的结构体如下:
更多推荐
所有评论(0)