由于NAND Flash的工艺不能保证NAND的Memory Array在其生命周期中保持性能的可靠,因此,在NAND的生产中及使用过程中会产生坏块。这时产生了BBT(bad block table),即坏块表来进行管理。在uboot代码中NAND BBT主要涉及到的文件是nand_bbt.c。
BBT分为BBT数据和BBT标识(也可称为BBT描述符)。
BBT数据实际上是一段数据空间,存储着NAND FLASH每一块的形态。在UBOOT中NAND FLASH的每一个块都有四种形态。
#define BBT_BLOCK_GOOD 好块
#define BBT_BLOCK_WORN 坏块
#define BBT_BLOCK_RESERVED 预留块(给BBT数据使用的块)
#define BBT_BLOCK_FACTORY_BAD 厂家固有坏块
由于最多四种形态,UBOOT下使用2bit表示1个块的形态,即1个字节可表示4个块。BBT描述符包含BBT的固有标识、标识的存放位置、BBT版本号的存放位置等。
在UBOOT中BBT描述符有两种方式存储,一种是BBT的特有标识放到OOB区域,一种是随着BBT数据一起存放在数据页上。
static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' }; /*主bbt表的特有标识。*/ static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' }; /*备bbt表的特有标识。*/ /*1、BBT数据和BBT标识分别存放在数据页及OOB上。*/ static struct nand_bbt_descr bbt_main_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, .offs = 8, /*BBT标识的偏移位置(OOB偏移8个字节)。*/ .len = 4, /*BBT标识的长度。*/ .veroffs = 12, /*BBT的版本号偏移(OOB偏移12个字节)。*/ .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, /*BBT数据最大能使用的块数(默认4块)。*/ .pattern = bbt_pattern /*主bbt表的特有标识。*/ }; static struct nand_bbt_descr bbt_mirror_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP, .offs = 8, /* BBT标识的偏移位置(OOB偏移8个字节)。*/ .len = 4, /* BBT标识的长度。*/ .veroffs = 12, /* BBT的版本号偏移(OOB偏移12个字节)。*/ .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, /* BBT数据最大能使用的块数(默认4块)。*/ .pattern = mirror_pattern /* 备bbt表的特有标识。*/ }; 2、BBT数据和BBT一起存放在数据页上。*/ static struct nand_bbt_descr bbt_main_no_oob_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP | NAND_BBT_NO_OOB, /* BBT标识的偏移位置(默认从零字节开始)。*/ .len = 4, /* BBT标识的长度。*/ .veroffs = 4, /* BBT的版本号偏移。*/ .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, /*BBT数据最大能使用的块数(默认4块)。*/ .pattern = bbt_pattern /* 主bbt表的特有标识。*/ }; static struct nand_bbt_descr bbt_mirror_no_oob_descr = { .options = NAND_BBT_LASTBLOCK | NAND_BBT_CREATE | NAND_BBT_WRITE | NAND_BBT_2BIT | NAND_BBT_VERSION | NAND_BBT_PERCHIP | NAND_BBT_NO_OOB, /*BBT标识的偏移位置(默认从零字节开始)。*/ .len = 4, /*BBT标识的长度。 .veroffs = 4, /* BBT的版本号偏移。 .maxblocks = NAND_BBT_SCAN_MAXBLOCKS, /*BBT数据最大能使用的块数(默认4块)。*/ .pattern = mirror_pattern /* 备bbt表的特有标识。*/ };
根据上述BBT数据和BBT描述符可知,UBOOT有两种存储方式,总结如下:
无论哪个BBT描述符,结构体中的options都有NAND_BBT_LASTBLOCK宏,此宏代表BBT存放在NANDFLASH的最后几个block中,我们暂且将这几个block称为BBT_BLOCK.
UBOOT代码中NAND BBT大致调用关系如下:
总结初始化流程如下:
int nand_default_bbt(struct mtd_info *mtd)
{
struct nand_chip *this = mtd_to_nand(mtd);
int ret;
/* Is a flash based bad block table requested? */
if (this->bbt_options & NAND_BBT_USE_FLASH) {
/* Use the default pattern descriptors */
if (!this->bbt_td) {
if (this->bbt_options & NAND_BBT_NO_OOB) { /*BBT数据不放在OOB区*/
this->bbt_td = &bbt_main_no_oob_descr; /*主描述符*/
this->bbt_md = &bbt_mirror_no_oob_descr; /*备用描述符*/
} else { /* BBT数据放在OOB区*/
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) {
ret = nand_create_badblock_pattern(this); /*创建坏块描述符。*/
if (ret)
return ret;
}
return nand_scan_bbt(mtd, this->badblock_pattern);
}
总结如下:
1、获取BBT的主备描述符。
2、创建坏块parttern,用于在没有bbt表时,对好坏块进行匹配。
3、进入nand_scan_bbt流程。
static int nand_scan_bbt(struct mtd_info *mtd, struct nand_bbt_descr *bd)
{
struct nand_chip *this = mtd_to_nand(mtd);
int len, res;
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)) ? : 1; /*通过flash的大小计算出BBT所用的字节数。*/
/*
* Allocate memory (2bit per block) and clear the memory bad block
* table.
*/
this->bbt = kzalloc(len, GFP_KERNEL); /*申请所需内存*/
if (!this->bbt)
return -ENOMEM;
/*
* If no primary table decriptor is given, scan the device to build a
* memory based bad block table.
*/
if (!td) { /*如果不使用BBT描述符,则进入此分支。*/
if ((res = nand_memory_bbt(mtd, bd))) {
pr_err("nand_bbt: can't scan flash and build the RAM-based BBT\n");
goto err;
}
return 0;
}
verify_bbt_descr(mtd, td); /*校验主描述符*/
verify_bbt_descr(mtd, md); /*校验备描述符*/
/* Allocate a temporary buffer for one eraseblock incl. oob */
len = (1 << this->bbt_erase_shift);
len += (len >> this->page_shift) * mtd->oobsize;
buf = vmalloc(len); /*申请擦数块大小的内存。*/
if (!buf) {
res = -ENOMEM;
goto err;
}
/* Is the bbt at a given page? */
if (td->options & NAND_BBT_ABSPAGE) { /*如果选项中有此宏则bbt数据在固定页上。*/
read_abs_bbts(mtd, buf, td, md);
} else { /*BBT在任意预留的block上。(一般使用此分支)*/
/* Search the bad block table using a pattern in oob */
search_read_bbts(mtd, buf, td, md); /*在所有的预留块中查找正确的BBT数据。*/
}
res = check_create(mtd, buf, bd); /* 读取BBT数据或者创建BBT数据表。*/
if (res)
goto err;
/* Prevent the bbt regions from erasing / writing */
mark_bbt_region(mtd, td); /*将bbt所使用的块全部设置成预留形态。*/
if (md)
mark_bbt_region(mtd, md);
vfree(buf);
return 0;
err:
kfree(this->bbt);
this->bbt = NULL;
return res;
}
总结如下:
static void search_read_bbts(struct mtd_info *mtd, uint8_t *buf,
struct nand_bbt_descr *td,
struct nand_bbt_descr *md)
{
/* Search the primary table */
search_bbt(mtd, buf, td); /*查找主描述符*/
/* Search the mirror table */
if (md)
search_bbt(mtd, buf, md); /*查找备描述符*/
}
static int search_bbt(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *td)
{
struct nand_chip *this = mtd_to_nand(mtd);
int i, chips;
int startblock, block, dir;
int scanlen = mtd->writesize + mtd->oobsize;
int bbtblocks;
int blocktopage = this->bbt_erase_shift - this->page_shift;
/* Search direction top -> down? */
if (td->options & NAND_BBT_LASTBLOCK) { /*我们将BBT数据及描述符放在最后几个块上*/
startblock = (mtd->size >> this->bbt_erase_shift) - 1;
dir = -1;
} else {
startblock = 0;
dir = 1;
}
/* Do we have a bbt per chip? */
if (td->options & NAND_BBT_PERCHIP) {
chips = this->numchips;
bbtblocks = this->chipsize >> this->bbt_erase_shift;
startblock &= bbtblocks - 1;
} else {
chips = 1;
bbtblocks = mtd->size >> this->bbt_erase_shift;
}
/* 遍历bbt的预留块,通过pattern来查找正确的bbt表。*/
for (i = 0; i < chips; i++) {
/* Reset version information */
td->version[i] = 0;
td->pages[i] = -1;
/* Scan the maximum number of blocks */
for (block = 0; block < td->maxblocks; block++) {
int actblock = startblock + dir * block;
loff_t offs = (loff_t)actblock << this->bbt_erase_shift;
/* Read first page */
scan_read(mtd, buf, offs, mtd->writesize, td); /*读取此块的第一页数据*/
if (!check_pattern(buf, scanlen, mtd->writesize, td)) {/*是否能匹配正确的bbt标识*/
td->pages[i] = actblock << blocktopage; /*如果匹配成功则记录bbt数据所在的位置*/
if (td->options & NAND_BBT_VERSION) {
offs = bbt_get_ver_offs(mtd, td);
td->version[i] = buf[offs]; /*获取bbt表的版本号*/
}
break;
}
}
startblock += this->chipsize >> this->bbt_erase_shift;
}
/* Check, if we found a bbt for each requested chip */
for (i = 0; i < chips; i++) {
if (td->pages[i] == -1)
pr_warn("Bad block table not found for chip %d\n", i);
else
pr_info("Bad block table found at page %d, version 0x%02X\n",
td->pages[i], td->version[i]);
}
return 0;
}
总结如下:
1、从BBT_BLOCK中找BBT标识。
static int check_create(struct mtd_info *mtd, uint8_t *buf, struct nand_bbt_descr *bd)
{
int i, chips, writeops, create, chipsel, res, res2;
struct nand_chip *this = mtd_to_nand(mtd);
struct nand_bbt_descr *td = this->bbt_td;
struct nand_bbt_descr *md = this->bbt_md;
struct nand_bbt_descr *rd, *rd2;
/* Do we have a bbt per chip? */
if (td->options & NAND_BBT_PERCHIP)
chips = this->numchips;
else
chips = 1;
for (i = 0; i < chips; i++) {
writeops = 0;
create = 0;
rd = NULL;
rd2 = NULL;
res = res2 = 0;
/* Per chip or per device? */
chipsel = (td->options & NAND_BBT_PERCHIP) ? i : -1;
/* Mirrored table available? */
if (md) { 当前系统支持主备bbt表时,进入此分支。
if (td->pages[i] == -1 && md->pages[i] == -1) { /*主备描述符都没有匹配成功*/
create = 1; /*需要重新创建bbt表*/
writeops = 0x03;
} else if (td->pages[i] == -1) { /*只找到备区bbt表*/
rd = md;
writeops = 0x01;
} else if (md->pages[i] == -1) { /*只找到主区bbt表*/
rd = td;
writeops = 0x02;
} else if (td->version[i] == md->version[i]) { /*主备bbt表都已找到,并且版本号相同*/
rd = td;
if (!(td->options & NAND_BBT_VERSION))
rd2 = md;
} else if (((int8_t)(td->version[i] - md->version[i])) > 0) { /*主bbt表的版本比备bbt表大,则以主表为正确表,将主表同步到备表上*/
rd = td;
writeops = 0x02;
} else { /*备bbt表的版本比主bbt表大,则以备表为正确表,将备表同步到主表上*/
rd = md;
writeops = 0x01;
}
} else { /*当前系统只有1个bbt表时,进入此分支*/
if (td->pages[i] == -1) { /*bbt表不存在,则进行创建bbt表。*/
create = 1;
writeops = 0x01;
} else { /* bbt表存在。*/
rd = td;
}
}
if (create) { /*如果主备bbt表都不存在,则进入bbt表的创建流程*/
/* Create the bad block table by scanning the device? */
if (!(td->options & NAND_BBT_CREATE))
continue;
/* Create the table in memory by scanning the chip(s) */
if (!(this->bbt_options & NAND_BBT_CREATE_EMPTY))
create_bbt(mtd, buf, bd, chipsel);
td->version[i] = 1;
if (md)
md->version[i] = 1;
}
/* Read back first? */
if (rd) { /*rd存在,代表至少有一个bbt表存在。
res = read_abs_bbt(mtd, buf, rd, chipsel); /*读取bbt表中的数据*/
if (mtd_is_eccerr(res)) {
/* Mark table as invalid */
rd->pages[i] = -1;
rd->version[i] = 0;
i--;
continue;
}
}
/* If they weren't versioned, read both */
if (rd2) {/*rd2只存在于bbt表不记录版本号时,我们实际使用中一定会记录版本号,我们暂且忽略它*/
res2 = read_abs_bbt(mtd, buf, rd2, chipsel);
if (mtd_is_eccerr(res2)) {
/* Mark table as invalid */
rd2->pages[i] = -1;
rd2->version[i] = 0;
i--;
continue;
}
}
/* Scrub the flash table(s)? */
if (mtd_is_bitflip(res) || mtd_is_bitflip(res2))
writeops = 0x03;
/* Update version numbers before writing */
if (md) { /*如果备用bbt表存在,需将主备版本号设置成一致。*/
td->version[i] = max(td->version[i], md->version[i]);
md->version[i] = td->version[i];
}
/* Write the bad block table to the device? */
if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) {
res = write_bbt(mtd, buf, td, md, chipsel); /* 将buf中的数据写入主bbt表所在位置*/
if (res < 0)
return res;
}
/* Write the mirror bad block table to the device? */
if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) {
res = write_bbt(mtd, buf, md, td, chipsel);/*将buf中的数据写入主bbt表所在位置*/
if (res < 0)
return res;
}
}
return 0;
}
总结如下:
1、如果找到BBT标识,则直接读取BBT数据。
2、如果没有找到BBT标识,则对NAND FLASH进行好坏块扫描,对BBT数据进行重新创建。
static int create_bbt(struct mtd_info *mtd, uint8_t *buf,
struct nand_bbt_descr *bd, int chip)
{
struct nand_chip *this = mtd_to_nand(mtd);
int i, numblocks, numpages;
int startblock;
loff_t from;
pr_info("Scanning device for bad blocks\n");
/* 对所有的块进行好坏快检查*/
if (bd->options & NAND_BBT_SCAN2NDPAGE)
numpages = 2; /*检查两页*/
else
numpages = 1; /* 检查一页*/
if (chip == -1) {
numblocks = mtd->size >> this->bbt_erase_shift;
startblock = 0;
from = 0;
} else {
if (chip >= this->numchips) {
pr_warn("create_bbt(): chipnr (%d) > available chips (%d)\n",
chip + 1, this->numchips);
return -EINVAL;
}
numblocks = this->chipsize >> this->bbt_erase_shift;
startblock = chip * numblocks;
numblocks += startblock;
from = (loff_t)startblock << this->bbt_erase_shift;
}
if (this->bbt_options & NAND_BBT_SCANLASTPAGE) /*从最后一页开始检查*/
from += mtd->erasesize - (mtd->writesize * numpages);
for (i = startblock; i < numblocks; i++) {
int ret;
BUG_ON(bd->options & NAND_BBT_NO_OOB);
/*通过读取此页的oob的第一个字节判断好坏快,如果是0xff,代表好块,返回值为0;否则是坏块,返回值是1。*/
ret = scan_block_fast(mtd, bd, from, buf, numpages);
if (ret < 0)
return ret;
if (ret) {
/* 如果ret = 1;代表此块为坏块,将此快标记为BAD。*/
bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD);
pr_warn("Bad eraseblock %d at 0x%012llx\n",
i, (unsigned long long)from);
mtd->ecc_stats.badblocks++;
}
from += (1 << this->bbt_erase_shift); /*偏移到下一块。*/
}
return 0;
}
总结如下:
1、对NAND FLASH进行好坏块扫描,并对BBT数据进行重新创建。
static int write_bbt(struct mtd_info *mtd, uint8_t *buf,
struct nand_bbt_descr *td, struct nand_bbt_descr *md,
int chipsel)
{
struct nand_chip *this = mtd_to_nand(mtd);
struct erase_info einfo;
int i, res, chip = 0;
int bits, startblock, dir, page, offs, numblocks, sft, sftmsk;
int nrchips, pageoffs, ooboffs;
uint8_t msk[4];
uint8_t rcode = td->reserved_block_code;/*td->reserved_block_code此字段一般设置为0,代表bbt表所在的预留区,在flash中存储不以预留形态存储,是以好块的形态去存储。*/
size_t retlen, len = 0;
loff_t to;
struct mtd_oob_ops ops;
ops.ooblen = mtd->oobsize;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.mode = MTD_OPS_PLACE_OOB;
if (!rcode)
rcode = 0xff;
/* Write bad block table per chip rather than per device? */
if (td->options & NAND_BBT_PERCHIP) {
numblocks = (int)(this->chipsize >> this->bbt_erase_shift);
/* Full device write or specific chip? */
if (chipsel == -1) {
nrchips = this->numchips;
} else {
nrchips = chipsel + 1;
chip = chipsel;
}
} else {
numblocks = (int)(mtd->size >> this->bbt_erase_shift);
nrchips = 1;
}
/* Loop through the chips */
for (; chip < nrchips; chip++) {
/*
* There was already a version of the table, reuse the page
* This applies for absolute placement too, as we have the
* page nr. in td->pages.
*/
if (td->pages[chip] != -1) {
page = td->pages[chip];
goto write;
}
/*
* Automatic placement of the bad block table. Search direction
* top -> down?
*/
if (td->options & NAND_BBT_LASTBLOCK) {
startblock = numblocks * (chip + 1) - 1;
dir = -1;
} else {
startblock = chip * numblocks;
dir = 1;
}
从BBT_BLOCK中获取获取一个好块,用于存放bbt表。*/
for (i = 0; i < td->maxblocks; i++) {
int block = startblock + dir * i;
/* Check, if the block is bad */
switch (bbt_get_entry(this, block)) {
case BBT_BLOCK_WORN:
case BBT_BLOCK_FACTORY_BAD:
continue;
}
page = block <<
(this->bbt_erase_shift - this->page_shift);
/* Check, if the block is used by the mirror table */
if (!md || md->pages[chip] != page)
goto write;
}
pr_err("No space left to write bad block table\n");
return -ENOSPC;
write:
/* Set up shift count and masks for the flash table */
bits = td->options & NAND_BBT_NRBITS_MSK;
/* 由于内存存储的bbt数据与flash中存储的数据是互为取反的,通过下面的代码进行取反。*/
msk[2] = ~rcode;
switch (bits) {
case 1: sft = 3; sftmsk = 0x07; msk[0] = 0x00; msk[1] = 0x01;
msk[3] = 0x01;
break;
case 2: sft = 2; sftmsk = 0x06; msk[0] = 0x00; msk[1] = 0x01;
msk[3] = 0x03;
break;
case 4: sft = 1; sftmsk = 0x04; msk[0] = 0x00; msk[1] = 0x0C;
msk[3] = 0x0f;
break;
case 8: sft = 0; sftmsk = 0x00; msk[0] = 0x00; msk[1] = 0x0F;
msk[3] = 0xff;
break;
default: return -EINVAL;
}
to = ((loff_t)page) << this->page_shift;
/* Must we save the block contents? */
if (td->options & NAND_BBT_SAVECONTENT) {
/* 如果定义此字段,需将此块的数据读出作为buf的初始值,在此基础上将buf中存放bbt数据的位置做“清FF”处理。*/
/* Make it block aligned */
to &= ~(((loff_t)1 << this->bbt_erase_shift) - 1);
len = 1 << this->bbt_erase_shift;
res = mtd_read(mtd, to, len, &retlen, buf);
if (res < 0) {
if (retlen != len) {
pr_info("nand_bbt: error reading block for writing the bad block table\n");
return res;
}
pr_warn("nand_bbt: ECC error while reading block for writing bad block table\n");
}
/* Read oob data */
ops.ooblen = (len >> this->page_shift) * mtd->oobsize;
ops.oobbuf = &buf[len];
res = mtd_read_oob(mtd, to + mtd->writesize, &ops);
if (res < 0 || ops.oobretlen != ops.ooblen)
goto outerr;
/* Calc the byte offset in the buffer */
pageoffs = page - (int)(to >> this->page_shift);
offs = pageoffs << this->page_shift;
/* Preset the bbt area with 0xff */
memset(&buf[offs], 0xff, (size_t)(numblocks >> sft));
ooboffs = len + (pageoffs * mtd->oobsize);
} else if (td->options & NAND_BBT_NO_OOB) {
/* 若进入此分支,则bbt数据和bbt标识都放到数据页上。*/
ooboffs = 0;
offs = td->len;
/* The version byte */
if (td->options & NAND_BBT_VERSION)
offs++;
/* Calc length */
len = (size_t)(numblocks >> sft);
len += offs;
/* Make it page aligned! */
len = ALIGN(len, mtd->writesize);
/* Preset the buffer with 0xff */
memset(buf, 0xff, len);
/* Pattern is located at the begin of first page */
memcpy(buf, td->pattern, td->len);
} else {
/*若进入此分支,则bbt数据放到数据页上,bbt标识放到bbt数据所在所有数据页对应的oob区域中。*/
/* Calc length */
len = (size_t)(numblocks >> sft);
/* Make it page aligned! */
len = ALIGN(len, mtd->writesize);
/* Preset the buffer with 0xff */
memset(buf, 0xff, len +
(len >> this->page_shift)* mtd->oobsize);
offs = 0;
ooboffs = len;
/* Pattern is located in oob area of first page */
memcpy(&buf[ooboffs + td->offs], td->pattern, td->len);
}
if (td->options & NAND_BBT_VERSION)
buf[ooboffs + td->veroffs] = td->version[chip]; /* 设置bbt版本号。*/
/* Walk through the memory table */
for (i = 0; i < numblocks; i++) {
uint8_t dat;
int sftcnt = (i << (3 - sft)) & sftmsk;
dat = bbt_get_entry(this, chip * numblocks + i);
/* Do not store the reserved bbt blocks! */
buf[offs + (i >> sft)] &= ~(msk[dat] << sftcnt); /* 将内存的bbt表取反赋值到buf中。*/
}
memset(&einfo, 0, sizeof(einfo));
einfo.mtd = mtd;
einfo.addr = to;
einfo.len = 1 << this->bbt_erase_shift;
res = nand_erase_nand(mtd, &einfo, 1);
if (res < 0)
goto outerr;
res = scan_write_bbt(mtd, to, len, buf, /*将buf写入到BBT_BLOCK中。*/
td->options & NAND_BBT_NO_OOB ? NULL :
&buf[len]);
if (res < 0)
goto outerr;
pr_info("Bad block table written to 0x%012llx, version 0x%02X\n",
(unsigned long long)to, td->version[chip]);
/* Mark it as used */
td->pages[chip] = page;
}
return 0;
outerr:
pr_warn("nand_bbt: error while writing bad block table %d\n", res);
return res;
}
总结如下:
1、由于内存下与flash中nandflash块的形态是互为取反的形式存储的,需要对数据进行取反转换。
2、将BBT数据与BBT描述符按照预期的方式写入到BBT_BLOCK的某一个块中。