由 于NAND Flash的现有工艺不能保证NAND的Memory Array在其生命周期中保持性能的可靠,因此在NAND芯片出厂的时候,厂家只能保证block 0不是坏块,对于其它block,则均有可能存在坏块,而且NAND芯片在使用的过程中也很容易产生坏块。因此,我们在读写NAND FLASH 的时候,需要检测坏块,同时还需在NAND驱动中加入坏块管理的功能。
NAND驱动在加载的时候,会调用nand_scan函数,对bad block table的搜寻,建立等操作就是在这个函数的第二部分,即nand_scan_tail函数中完成的。
在 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相关的结构体如下:
struct nand_chip {
……
uint8_t *bbt
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;
struct nand_bbt_descr *badblock_pattern;
……
};
bbt 指向 一块在nand_default_bbt函数中分配的内存,若options中没有定义NAND_USE_FLASH_BBT,MTD就直接在bbt指向 的内存中建立bbt,否则就会先从NAND芯片中查找bbt是否存在,若存在,就把bbt的内容读出来并保存到bbt指向的内存中,若不存在,则在bbt 指向的内存中建立bbt,最后把它写入到NAND芯片中去。
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的结构体类型定义如下:
struct nand_bbt_descr {
int options;
int pages[NAND_MAX_CHIPS];
int offs;
int veroffs;
uint8_t version[NAND_MAX_CHIPS];
int len;
int maxblocks;
int reserved_block_code;
uint8_t *pattern;
};
options:bad block table或者bad block的选项,可用的选择以及各选项具体表示什么含义,可以参考<linux/mtd/nand.h>。
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_chip,在此以TCC8900-Linux中MTD的坏块管理为例作一次介绍。
MTD在Linux内核中同样以模块的形式被启用,TCC_MTD_IO_Init()函数完成了nand_chip初始化、mtd_info初始注册,
坏块表的管理机制建立等工作。
nand_chip在TCC_MTD_IO_Init函数中的实例名称是this,mtd_info 的实例名称为TCC_mtd,这里有一个比较巧妙的处理方法:
TCC_mtd=kmalloc(sizeof(struct mtd_info)+sizeof(struct nand_chip),GFP_KERNEL);
this=(struct nand_chip*)(&TCC_mtd[1]);
在以后的操作中,只需得知TCC_mtd即可找到对应的nan_chip实例。
获得必要的信息后(包括nand_chip方法的绑定),流程进入nand_scan(TCC_mtd,1).
nand_scan(struct mdt_info *mtd, int maxchips);
调用nand_scan_ident(mtd,maxchips)和nand_scan_tail(mtd);
nand_scan_ident(...)调用了一个很重要的函数:nand_get_flash_type(...)
*从nand_get_flash_type(...)函数中可以看出每个nandflash前几个字节所代表的意思都是约定好了的:
第一个字节:制造商ID
第二个字节:设备ID
第三个字节:MLC 数据
第四个字节:extid (比较总要)
其中设备ID是访问nand_flash_ids表的参照,该表在drivers/mtd/nand/nand_ids.c中定义
Linux内核在nand_flash_ids参照表中,通过匹配上述设备ID来查找nandflash的详细信息,
nand_flash_ids中的举例如下:
struct nand_flash_dev nand_flash_ids[]={
......
{"NAND 16MiB 1,8V 8-bit", 0x33, 512, 16, 0x4000, 0},
{"NAND 16MiB 3,3V 8-bit", 0x73, 512, 16, 0x4000, 0},
{"NAND 16MiB 1,8V 16-bit", 0x43, 512, 16, 0x4000, NAND_BUSWIDTH_16},
{"NAND 16MiB 3,3V 16-bit", 0x53, 512, 16, 0x4000, NAND_BUSWIDTH_16},
......
}
466 struct nand_flash_dev {
467 char *name;
468 int id;
469 unsigned long pagesize;
470 unsigned long chipsize;
471 unsigned long erasesize;
472 unsigned long options;
473 };
值得一提的是,MTD子系统会把从nand_flash_ids表中找到的chipsize复制给mtd->size,这在有些应用中显得不合适,
在有些方案中,并不是把nandflash的所有存储空间都划分为MTD分区,Telechips的TCC89XX方案就是这样,4G的nandflash
上,可以划分任意大小的MTD分区,错误的mtd->size的后果非常严重,造成系统启动慢,整个MTD的坏块管理机制瘫痪等等。
随后,nand_get_flash_type通过extid计算出了以下信息:
mtd可写区大小:mtd->writesize=1024<<(extid&0x03);
这里可以看成1024*(1*2的(extid&0x03)次方),
mtdoob区大小:extid>>=2;mtd->oobsize = (8<<(extid&0x1))*(mtd->writesize>>9);
每512字节对应(8*2的(extid&0x1)次方)字节oob数据
mtd擦写块大小:extid>>=2;mtd->erasesize=(64*1024)<<(extid&0x03);
nand数据宽度 :extid>>=2;busw=(extid&0x01)?NAND_BUSEWIDTH_16:0; 现在大多为8位数据宽度
可以看出第四个字节extid的意义:
高|0 | 0 | 00 | 0 | 0 | 00 |低
|无用|数据宽度|擦写块算阶|无用|oob算阶| 可写区算阶|
nand_get_flash_type(...)还确立了nandflash中的坏块标记在oob信息中的位置:
if(mtd->writesize>512||(busw&NAND_BUSWIDTH_16))
chip->badblockpos = NAND_LARGE_BADBLOCKS_POS;//大页面flash的坏块信息存储地址为oob信息中的第1个字节开始处
else
chip->badblockpos = NAND_SMALL_BADBLOCKS_POS;//大页面flash的坏块信息存储地址为oob信息中的第6个字节开始处
对于Samsun和Hynix的MLC型nandflash,坏块标记所在的页是每块的最后一个页,而Samsung,Hynix,和AMD的SLC型nandflash
中,坏块标记分别保存在每块开始的第1,2个页中,其他型号的nandflash大多都保存在第一个也中,为此需要作下标记:
坏块标记保存在块的最后一页中:chip->options |= NAND_BBT_SCANLASTPAGE;
坏块标记保存在块的第1,2页中 :chip->options |= NAND_BBT_SCAN2NDPAGE;
nand_scan之后调用nand_scan_tail(mtd)函数,
nand_scan_tail(...)函数主要完成MTD实例中各种方法的绑定,例如:
3338 mtd->read = nand_read;
3339 mtd->write = nand_write;
3340 mtd->panic_write = panic_nand_write;
3341 mtd->read_oob = nand_read_oob;
3342 mtd->write_oob = nand_write_oob;
3343 mtd->sync = nand_sync;
nand_scan_tail(...)调用chip->scan_bbt()完成坏块表的有关操作。
chip->scan_bbt的绑定过程是在nand_scan_ident()->nand_set_defaults():chip->scan_bbt = nand_default_bbt.
即真正用于坏块操作的是nand_default_bbt函数,该函数在nand_bbt.c中被定义。