http://blog.sina.com.cn/s/blog_87f8cc4e0102v7d9.html
闲来无事,追踪了下linux内核中对nand的坏块管理代码。大致记录一下。
内核中对nand的坏块管理是在nand的驱动中实现的,一般情况下,我们在实现nand控制器的驱动时不用考虑坏块的管理,这些机制已经在nand驱动的通用框架中实现了,我们要做的就是在nand驱动的框架上对接上nand控制器私有的操作与参数就可以了,例如读写函数以及nand控制器支持的ecc布局等。当然,这里主要是分析坏块管理的部分,就不多说驱动的开发了。
这里的分析是在bcm7231平台的nand驱动上面分析的。
nand驱动在加载的时候会调用nand_scan_tail函数,此函数的前面大部分是在确定所使用ecc方式和函数指针,以及一个页的读写需要作几次ecc校验等,这些都是根据nand驱动初始化的时候指定的一些参数决定的。在此函数的最后面开始关于BBT的操作:
-
- if
(chip->options & NAND_SKIP_BBTSCAN) -
return 0; -
-
- return
chip->scan_bbt(mtd);
首先我们看下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_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的实现:
-
- 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); - }
-
- 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; - };
maxblocks:bbt专用。MTD在查找bbt的时候,不会查找NAND芯片中所有的block,而是最多查找maxblocks个block。
在最后调用了nand_scan_bbt函数。此函数的注释有这么一句话:
- 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芯片中去。