闲来无事,追踪了下linux内核中对nand的坏块管理代码。大致记录一下。
内核中对nand的坏块管理是在nand的驱动中实现的,一般情况下,我们在实现nand控制器的驱动时不用考虑坏块的管理,这些机制已经在nand驱动的通用框架中实现了,我们要做的就是在nand驱动的框架上对接上nand控制器私有的操作与参数就可以了,例如读写函数以及nand控制器支持的ecc布局等。当然,这里主要是分析坏块管理的部分,就不多说驱动的开发了。
这里的分析是在bcm7231平台的nand驱动上面分析的。
nand驱动在加载的时候会调用nand_scan_tail函数,此函数的前面大部分是在确定所使用ecc方式和函数指针,以及一个页的读写需要作几次ecc校验等,这些都是根据nand驱动初始化的时候指定的一些参数决定的。在此函数的最后面开始关于BBT的操作:
[cpp] view plain copy print ?
-
- if (chip->options & NAND_SKIP_BBTSCAN)
- return 0;
-
-
- return chip->scan_bbt(mtd);
首先会检查chip->options中是否有NAND_SKIP_BBTSCAN选项,如果有,则直接返回,NAND_SKIP_BBTSCAN一般是在nand控制器驱动中选择是否被赋予的,一般不会赋予NAND_SKIP_BBTSCAN选项。如果没有,则调用chip->scan_bbt()函数。chip->scan_bbt()是一个函数指针,一般在nand_set_defaults中被赋值为nand_default_bbt。
首先我们看下struct nand_chip结构体中关于BBT的部分:
[cpp] view plain copy print ?
-
-
-
-
-
-
-
-
-
-
- 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_scan_bbt中分配的,后面会说到),这块内存会存放BBT。如果定义了NAND_BBT_USE_FLASH(这个可以在编写nand控制器驱动时选择),会先在nand中查找BBT是否存在,如果存在,然后读出放在bbt中,如果不存在(全新flash会有这种情况),则在bbt中建立BBT,然后写入nand中。如果没有定义NAND_BBT_USE_FLASH,那么会直接在bbt指向的内存中建立BBT。
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芯片的坏块信息不是这样的存储方式也没关系,因为我们可以在badblock_pattern中自己指定坏块信息的存储位置,以及用什么值来标志坏块(其实这个值表示的应该是“好块”,因为MTD会把从oob中坏块信息存储位置读出的内容与这个值做比较,若相等,则表示是个“好块”,否则就是坏块)。
下面是nand_default_bbt的实现:
[cpp] view plain copy print ?
-
-
-
-
-
-
-
- int nand_default_bbt(struct mtd_info *mtd)
- {
- struct nand_chip *this = mtd->priv;
-
-
-
-
-
-
-
- if (this->options & NAND_IS_AND) {
-
- if (!this->bbt_td) {
- this->bbt_td = &bbt_main_descr;
- this->bbt_md = &bbt_mirror_descr;
- }
- this->bbt_options |= NAND_BBT_USE_FLASH;
- return nand_scan_bbt(mtd, &agand_flashbased);
- }
-
-
- if (this->bbt_options & NAND_BBT_USE_FLASH) {
-
- if (!this->bbt_td) {
- if (this->bbt_options & NAND_BBT_NO_OOB) {
- this->bbt_td = &bbt_main_no_bbt_descr;
- this->bbt_md = &bbt_mirror_no_bbt_descr;
- } else {
- this->bbt_td = &bbt_main_descr;
- this->bbt_md = &bbt_mirror_descr;
- }
- }
- } else {
- this->bbt_td = NULL;
- this->bbt_md = NULL;
- }
-
- if (!this->badblock_pattern)
- nand_create_badblock_pattern(this);
-
- return nand_scan_bbt(mtd, this->badblock_pattern);
- }
在分析nand_scan_bbt函数之前先介绍下struct nand_bbt_descr的各成员的含义。其定义如下:
[plain] view plain copy print ?
- /**
- * struct nand_bbt_descr - bad block table descriptor
- * @options: options for this descriptor
- * @pages: the page(s) where we find the bbt, used with option BBT_ABSPAGE
- * when bbt is searched, then we store the found bbts pages here.
- * Its an array and supports up to 8 chips now
- * @offs: offset of the pattern in the oob area of the page
- * @veroffs: offset of the bbt version counter in the oob are of the page
- * @version: version read from the bbt page during scan
- * @len: length of the pattern, if 0 no pattern check is performed
- * @maxblocks: maximum number of blocks to search for a bbt. This number of
- * blocks is reserved at the end of the device where the tables are
- * written.
- * @reserved_block_code: if non-0, this pattern denotes a reserved (rather than
- * bad) block in the stored bbt
- * @pattern: pattern to identify bad block table or factory marked good /
- * bad blocks, can be NULL, if len = 0
- *
- * Descriptor for the bad block table marker and the descriptor for the
- * pattern which identifies good and bad blocks. The assumption is made
- * that the pattern and the version count are always located in the oob area
- * of the first block.
- */
- 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。
下面接着分析nand_scan_bbt函数。
bcm7231的nand控制器驱动中有这么一行:chip->bbt_options |= NAND_BBT_USE_FLASH | NAND_BBT_NO_OOB;所以,上面函数的走向也很明确。
在最后调用了nand_scan_bbt函数。此函数的注释有这么一句话:
[cpp] view plain copy print ?
-
-
-
-
可见,BBT的相关工作基本都是在这个函数内完成的。其函数实现如下:
[cpp] view plain copy print ?
- int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
- {
- struct nand_chip *this = mtd->priv;
- int len, res = 0;
- uint8_t *buf;
- struct nand_bbt_descr *td = this->bbt_td;
- struct nand_bbt_descr *md = this->bbt_md;
-
- len = mtd->size >> (this->bbt_erase_shift + 2);
-
-
-
-
- this->bbt = kzalloc(len, GFP_KERNEL);
- if (!this->bbt) {
- printk(KERN_ERR "nand_scan_bbt: Out of memory\n");
- return -ENOMEM;
- }
-
-
-
-
-
- if (!td) {
- if ((res = nand_memory_bbt(mtd, bd))) {
- pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n");
- kfree(this->bbt);
- this->bbt = NULL;
- }
- return res;
- }
- verify_bbt_descr(mtd, td);
- verify_bbt_descr(mtd, md);
-
-
- len = (1 << this->bbt_erase_shift);
- len += (len >> this->page_shift) * mtd->oobsize;
- buf = vmalloc(len);
- if (!buf) {
- printk(KERN_ERR "nand_bbt: Out of memory\n");
- kfree(this->bbt);
- this->bbt = NULL;
- return -ENOMEM;
- }
-
-
- if (td->options & NAND_BBT_ABSPAGE) {
- res = read_abs_bbts(mtd, buf, td, md);
- } else {
-
- res = search_read_bbts(mtd, buf, td, md);
- }
-
- if (res)
- res = check_create(mtd, buf, bd);
-
-
- mark_bbt_region(mtd, td);
- if (md)
- mark_bbt_region(mtd, md);
-
- vfree(buf);
- return res;
- }
上面已经说了,BBT可以存放在内存中,也可以存放在flash中,nand_scan_bbt会跟据bbt_td的值判断BBT是存放在内存中还是在flash中,bbt_td为NULL,那么BBT就会存放在内存中。在内存中建立BBT的过程是这样的:
直接调用nand_memory_bbt函数,nand_memory_bbt函数的主要工作就是在内存中建立bbt,其实就是调用了create_bbt函数。create_bbt函数的工作方式很简单,就是扫描NAND芯片所有的block,读取每个block中第一个page的oob内容,然后根据oob中的坏块信息建立起bbt,这坏块信息就是根据badblock_pattern来确定的。可以参见关于struct nand_bbt_descr中的offs、len和pattern成员变量的解释。
相对与BBT存储在内存中来说,BBT存储在nand中的过程就稍微复杂一点了。
几下来就是在flash中创建或读取BBT了。
verify_bbt_descr(mtd, td);与verify_bbt_descr(mtd, md);没有做实际的动作,只是检查一下相关的参数。接下来由if (td->options & NAND_BBT_ABSPAGE)可知,从flash上读取读取BBT又分为两种情况。
第一种情况是定义了NAND_BBT_ABSPAGE,那么调用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芯片中去。