Linux nand flash设备驱动详解
本文基于mini2440开发板,Linux版本号是:linux-2.6.32.2
一. Nand Flash 设备注册
- nand flash控制器的起始地址
/* NAND flash controller */
#define S3C2410_PA_NAND (0x4E000000)
2.该nand flash设备的名称
.name = "s3c2410-nand",
3.该nand flash的平台设备信息在friendly_arm_nand_info全局变量中。
static struct mtd_partition friendly_arm_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "param",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "Kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00300000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
static struct s3c2410_nand_set friendly_arm_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(friendly_arm_default_nand_part),
.partitions = friendly_arm_default_nand_part,
},
};
/* choose a set of timings which should suit most 512Mbit
* chips and beyond.
*/
static struct s3c2410_platform_nand friendly_arm_nand_info = {
.tacls = 20,
.twrph0 = 60,
.twrph1 = 20,
.nr_sets = ARRAY_SIZE(friendly_arm_nand_sets),
.sets = friendly_arm_nand_sets,
.ignore_unset_ecc = 1,
};
从上面可以看出,Flash只有一个
.nr_chips = 1,
Flash有4个分区,分别是bootloader,param, Kernel, root
static struct mtd_partition friendly_arm_default_nand_part[] = {
[0] = {
.name = "bootloader",
.size = 0x00040000,
.offset = 0,
},
[1] = {
.name = "param",
.offset = MTDPART_OFS_APPEND,
.size = 0x00020000,
},
[2] = {
.name = "Kernel",
.offset = MTDPART_OFS_APPEND,
.size = 0x00300000,
},
[3] = {
.name = "root",
.offset = MTDPART_OFS_APPEND,
.size = MTDPART_SIZ_FULL,
}
};
-
上面的结构体中有三个变量tacls,twrph0,twrph1,这三个变量的意思分别是:
TACLS: 发出 CLE/ALE 之后多长时间才发出 nWE 信号
TWRPH0: nWE 的脉冲宽度
TWRPH1: nWE 变为高电平后多长时间 CLE/ALE 才能变为低电平 -
nand flash设备的注册,调用的是platform_add_devices函数注册设备
platform_add_devices(mini2440_devices, ARRAY_SIZE(mini2440_devices));
int platform_add_devices(struct platform_device **devs, int num)
{
int i, ret = 0;
for (i = 0; i < num; i++) {
ret = platform_device_register(devs[i]);
if (ret) {
while (--i >= 0)
platform_device_unregister(devs[i]);
break;
}
}
return ret;
}
二. nand flash 设备和驱动的匹配
注册设备时会去自动查找该总线下的所有driver,根据名称来匹配,匹配上了就调用driver的probe函数。
搜索设备名称“s3c2410-nand”,发现在driver/mtd/nand/s3c2410.c文件中注册了对应的driver。
三. nand flash 的driver的注册
static struct platform_driver s3c24xx_nand_driver = {
.probe = s3c24xx_nand_probe,
.remove = s3c24xx_nand_remove,
.suspend = s3c24xx_nand_suspend,
.resume = s3c24xx_nand_resume,
.id_table = s3c24xx_driver_ids,
.driver = {
.name = "s3c24xx-nand",
.owner = THIS_MODULE,
},
};
static int __init s3c2410_nand_init(void)
{
printk("S3C24XX NAND Driver, (c) 2004 Simtec Electronics\n");
return platform_driver_register(&s3c24xx_nand_driver);
}
调用的platform_driver_register函数来注册平台driver。
nand flash 设备和驱动匹配上了之后调用s3c24xx_nand_probe函数。
四. nand flash driver的probe函数
- 申请了一个info结构体
info = kmalloc(sizeof(*info), GFP_KERNEL);
- 获取nand时钟并使能时钟
info->clk = clk_get(&pdev->dev, "nand");
clk_enable(info->clk);
- 寄存器地址获取,这里寄存器的起始地址是:0x4E000000
res = pdev->resource;
size = res->end - res->start + 1;
- 寄存器地址区域映射
info->regs = ioremap(res->start, size);
- nand thw的初始化
err = s3c2410_nand_inithw(info);
- 获取有几个nand 设备,可能是单nand,也可能是多个nand,我们这里假设只有一个nand设备。
nr_sets = (plat != NULL) ? plat->nr_sets : 1;
-
nand 简单的初始化,调用s3c2410_nand_init_chip函数
①保存nand data寄存器地址,寄存器地址是 0x4E000010chip->IO_ADDR_W = regs + S3C2440_NFDATA;
②保存Flash控制寄存器,以及片选控制
info->sel_reg = regs + S3C2440_NFCONT; info->sel_bit = S3C2440_NFCONT_nFCE;
③Flash的读写函数
chip->read_buf = s3c2440_nand_read_buf; chip->write_buf = s3c2440_nand_write_buf;
④chip和mtd绑定
nmtd->mtd.priv = chip;
⑤设置ECC模式,硬件ECC校验,还是软件ECC校验
chip->ecc.mode = NAND_ECC_SOFT
⑥是否使用Flash中的坏块表,使用的话kernel将不再扫描坏块建立坏块表,这样将缩短0.5s的时间。
if (set->flash_bbt) chip->options |= NAND_USE_FLASH_BBT | NAND_SKIP_BBTSCAN;
-
第一阶段的nand scan,调用函数nand_scan_ident
nmtd->scan_res = nand_scan_ident(&nmtd->mtd, (sets) ? sets->nr_chips : 1);
- 第二阶段的nand scan
nand_scan_tail(&nmtd->mtd);
- 添加mtd分区
s3c2410_nand_add_partition(info, nmtd, sets);
五. nand thw的初始化
nand硬件的初始化调用的函数是:s3c2410_nand_inithw,该函数里面调用s3c2410_nand_setrate设置频率。
分析s3c2410_nand_setrate函数。
- 获取设备的平台信息
s3c2410_platform_nand *plat = info->platform
- 获取主频时钟
unsigned long clkrate = clk_get_rate(info->clk);
- 计算tacls,twrph0,twrph1三个值,计算调用的函数是s3c_nand_calc_rate。
以计算tacls值为例:
tacls = s3c_nand_calc_rate(plat->tacls, clkrate, tacls_max);
—>DIV_ROUND_UP((wanted * clk), NS_IN_KHZ)
—>(((n) + (d) - 1) / (d))
(((n) + (d) - 1) / (d))到底是个什么意思呢?
n、d都是整数,且n > 1, d > 1,求 n / d的向上取整,即:
当 n / d整除时,向上取整值为 n / d;
当n / d不整除时,向上取整值为(n / d) + 1;
即tacls = plat->tacls * clkrate/10^6。
tacls * (10^9ns)/(clkrate * 10^3) = plat->tacls(ns)
tacls = plat->tacls * clkrate / (10^6)
tacls,twrph0,twrph1几个值的寄存器如下:
- 设置tacls
cfg &= ~((3)<<12)
cfg |= (tacls - 1) << 12
- 设置twrph0
cfg &= ~(7 << 8)
cfg |= (twrph0 -1) << 8
- 设置twrph1
cfg &= ~(7 << 4)
cfg |= (twrph1 -1) << 4
- 写NFCONF寄存器
writel(cfg, info->regs + S3C2410_NFCONF);
- 使能nand Flash控制器
writel(S3C2440_NFCONT_ENABLE, info->regs + S3C2440_NFCONT);
- mini2440的开机这几个值的打印如下:
s3c24xx-nand s3c2440-nand: Tacls=2, 20ns Twrph0=6 60ns, Twrph1=2 20ns
六. nand的片选函数s3c2410_nand_select_chip
static void s3c2410_nand_select_chip(struct mtd_info *mtd, int chip)函数中,
chip=-1,info->sel_bit设置为1,禁止片选,关掉nand 时钟
info->sel_bit = (1<<1)
cur = readl(info->sel_reg);
cur |= info->sel_bit;
writel(cur, info->sel_reg);
clk_disable(info->clk);
chip != -1, info->sel_bit设置为0,使能片选
cur = readl(info->sel_reg);
cur &= ~info->sel_bit;
writel(cur, info->sel_reg);
七. nand 的控制函数,判断是写命令还是写地址
if (ctrl & NAND_CLE)
writeb(cmd, info->regs + S3C2440_NFCMD);
else
writeb(cmd, info->regs + S3C2440_NFADDR);
NFCMD的地址是: 0x4E000008
NFADDR的地址是: 0x4E00000C
八.判断Flash是否忙
判断 NFSTAT寄存器的第0位,等于0表示Flash正忙,等于1表示Flash可以操作
return readb(info->regs + S3C2440_NFSTAT) & S3C2440_NFSTAT_READY;
九. 使能硬件ECC
ctrl = readl(info->regs + S3C2440_NFCONT);
writel(ctrl | S3C2412_NFCONT_INIT_MAIN_ECC, info->regs + S3C2440_NFCONT);
十. Flash的读写buffer,向 NFDATA寄存器读写数据
readsb(this->IO_ADDR_R, buf, len);
writesb(this->IO_ADDR_W, buf, len);
十一. 获取nand的写保护状态
- 发送读状态命令
chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1);
读一个字节,如果最高位为0,表示写保护
chip->read_byte(mtd) & NAND_STATUS_WP) ? 0 : 1;
为什么是最高位表示写保护状态呢?
十二. nand flash的常用命令
命令 | 第一个访问周期 | 第二个访问周期 | 第三个访问周期 |
---|---|---|---|
read1 (读) | 00h/01h | - | - |
read2(读) | 50h | - | - |
read ID(读芯片ID) | 90h | - | - |
page program(写页) | 80h | 10h | - |
block erase(擦除块) | 60h | d0h | - |
read status(读状态) | 70h | - | - |
read multi-plane status | 71h | - | - |
reset(复位) | FFh | - | - |
page program(dummy) | 80h | 11h | - |
copy-back program(true) | 00h | 8ah | 10h |
copy-back program(dummy) | 03h | 8ah | 11h |
multi-plane block erase | 60h-60h | d0h | - |
十三. nand flash的地址发送
-
nand Flash的地址寄存器地址为 0x4E00000C,8位有效数据
假设Flash容量为1G,1G = 2^30, 30 / 8 = 3.75,因此需要4个8位的地址来表示,写地址需要4个cycle。 -
行地址:也就是页地址
-
列地址:也就是在一个页内的具体地址
-
小页的地址的发送
小页指的是每页256byte或者512byte。页的大小为256byte时,需要8个脉冲传送地址。
页的大小为512byte,加上OOB区的16byte,一共是528byte。那么528byte的页的地址是怎么传送的呢?
一页的pagesize为512byte,分为两部分,上半部和下半部。列地址在半页中寻址。当发出读命令00h时,将在上半部寻址;当发出读命令01h时,将在下半部寻址;当发出50h时,将在页的OOB区寻址。
①上半部寻址
先发00h命令readcmd = NAND_CMD_READ0; chip->cmd_ctrl(mtd, readcmd, ctrl);
再发列地址
chip->cmd_ctrl(mtd, column, ctrl);
最后发页地址,三个字节(Flash大于32M)
chip->cmd_ctrl(mtd, page_addr, ctrl); chip->cmd_ctrl(mtd, page_addr >> 8, ctrl); /* One more address cycle for devices > 32MiB */ if (chip->chipsize > (32 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16, ctrl);
②下半部寻址
先发01h命令readcmd = NAND_CMD_READ1; chip->cmd_ctrl(mtd, readcmd, ctrl);
列地址减256,发列地址
column -= 256; chip->cmd_ctrl(mtd, column, ctrl);
最后发页地址,三个字节(Flash大于32M)
chip->cmd_ctrl(mtd, page_addr, ctrl); chip->cmd_ctrl(mtd, page_addr >> 8, ctrl); /* One more address cycle for devices > 32MiB */ if (chip->chipsize > (32 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16, ctrl);
③OOB区寻址
先发0x50命令readcmd = NAND_CMD_READOOB; chip->cmd_ctrl(mtd, readcmd, ctrl);
列地址减writesize(页大小),发列地址
column -= mtd->writesize; chip->cmd_ctrl(mtd, column, ctrl);
最后发页地址,三个字节(Flash大于32M)
chip->cmd_ctrl(mtd, page_addr, ctrl); chip->cmd_ctrl(mtd, page_addr >> 8, ctrl); /* One more address cycle for devices > 32MiB */ if (chip->chipsize > (32 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16, ctrl);
-
大页地址的发送
大页一般是2048byte每页,加上OOB的64byte,一共是2112个字节,它的列地址需要12个脉冲来传送,也就是2个字节的地址。
①先发命令chip->cmd_ctrl(mtd, command & 0xff,NAND_NCE | NAND_CLE | NAND_CTRL_CHANGE);
②发列地址
chip->cmd_ctrl(mtd, column, ctrl); chip->cmd_ctrl(mtd, column >> 8, ctrl);
③发页地址
chip->cmd_ctrl(mtd, page_addr, ctrl); chip->cmd_ctrl(mtd, page_addr >> 8,NAND_NCE | NAND_ALE); if (chip->chipsize > (128 << 20)) chip->cmd_ctrl(mtd, page_addr >> 16,NAND_NCE | NAND_ALE);
-
地址的发送顺序
十四. 本文中nand flash的结构
十五. nand flash的command函数
nand flash的大页和小页的command函数是不一样的,原因是大页需要2个字节来传送列地址,小页只需要1个字节来传送页地址。
大页使用的函数是nand_command_lp,小页使用的函数是nand_command。
- nand_command函数
static void nand_command(struct mtd_info *mtd, unsigned int command,int column, int page_addr)
column:列地址,column > 512,读OOB数据; 256 < column < 512,读页的上半部;否则,读页的下半部
page_addr:页地址,就是Flash的第几页
- nand_command_lp函数
static void nand_command_lp(struct mtd_info *mtd, unsigned int command,int column, int page_addr)
column:列地址,如果是读OOB区,传入0~OOBsize,函数里面会自己偏移
if (command == NAND_CMD_READOOB)
{
column += mtd->writesize;
command = NAND_CMD_READ0;
}
page_addr:页地址,就是Flash的第几页
十六. 获取Flash型号
- Flash片选
chip->select_chip(mtd, 0);
- 发送reset命令,0xff
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1);
- 发送读取device ID命令,0x90
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
- 读取厂商ID和设备ID
*maf_id = chip->read_byte(mtd);
dev_id = chip->read_byte(mtd);
- 再读一次,如果两次得到的结果不一样,返回错误
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1);
/* Read manufacturer and device IDs */
tmp_manf = chip->read_byte(mtd);
tmp_id = chip->read_byte(mtd);
if (tmp_manf != *maf_id || tmp_id != dev_id) {
printk(KERN_INFO "%s: second ID read did not match "
"%02x,%02x against %02x,%02x\n", __func__,
*maf_id, dev_id, tmp_manf, tmp_id);
return ERR_PTR(-ENODEV);
}
- 从nand flash 支持列表里面查找设备ID,看该款Flash是否在驱动的兼容列表里面,如果不在,返回ENODEV
for (i = 0; nand_flash_ids[i].name != NULL; i++)
{
if (dev_id == nand_flash_ids[i].id)
{
type = &nand_flash_ids[i];
break;
}
}
if (!type)
return ERR_PTR(-ENODEV);
- 设置erasesize,就是一个block,为0x20000
mtd->erasesize = type->erasesize;
- 设置writesize,这里是大页,2KB
mtd->writesize = type->pagesize;
- 设置OOB大小,为64byte
- 设置数据线宽度,这里是8位数据线
- 设置page_shift
chip->page_shift = ffs(mtd->writesize) - 1;
ffs函数是返回整形的最低位1的位置,mtd->writesize=2048=100000000000
ffs(mtd->writesize) = 12,chip->page_shift = 11
大页为2048byte,即2^11, addr >>chip->page_shift就能求出addr所在的页地址。
- 设置pagemask
chip->pagemask = (chip->chipsize >> chip->page_shift) - 1;
chip->pagemask = (2^10 * 2^10 * 2^10) / (2 ^ 11) - 1 = 0x7ffff
chip->pagemask的意思是页的范围只能在0 ~ 0x7ffff
- 设置bbt_erase_shift和phys_erase_shift
chip->bbt_erase_shift = chip->phys_erase_shift = ffs(mtd->erasesize) - 1;
mtd->erasesize = 100000000000000000
ffs(mtd->erasesize) = 18, chip->bbt_erase_shift = chip->phys_erase_shift = 17
擦除以块为单位,一个块为128k, 即 (2^7) * (2^10) = 2^17。
addr >> chip->phys_erase_shift就能求出addr所在的块的位置。
- 设置chip_shift
if (chip->chipsize & 0xffffffff)
chip->chip_shift = ffs((unsigned)chip->chipsize) - 1;
else
chip->chip_shift = ffs((unsigned)(chip->chipsize >> 32)) + 32 - 1;
如果chip->chipsize < 4G,走上面。这里我们的chipsize = 1G
chip->chipsize = 1000000000000000000000000000000
chip->chip_shift = ffs((unsigned)chip->chipsize) - 1 = 30
- 设置坏块的起始位置
chip->badblockpos = mtd->writesize > 512 ?NAND_LARGE_BADBLOCK_POS : NAND_SMALL_BADBLOCK_POS;
坏块存储位置:
如果是512Byte的small page,坏块标记在每个block的第一个page的spare area的第六个字节;如果是2048Byte的large page,坏块标记在每个block的第一个page的spare area的第一个字节;
我这里是大页,所以,坏块的位置就是0。
十七.获取坏块状态
有坏块管理表就从坏块管理表获取,没有的话调用nand_block_checkbad函数获取。
if (!chip->bbt)
return chip->block_bad(mtd, ofs, getchip);
/* Return info from the table */
return nand_isbad_bbt(mtd, ofs, allowbbt);
下面是nand_block_checkbad函数的执行过程。
- 获取页地址,flash地址除以page大小
page = (int)(ofs >> chip->page_shift) & chip->pagemask;
大页为2048byte,就是2^11, 刚好chip->page_shift = 11,chip->pagemask = 0x7ffff,page的范围在0 ~ 0x7ffff
- 发送read OOB命令
chip->cmdfunc(mtd, NAND_CMD_READOOB, chip->badblockpos, page);
chip->badblockpos = 0,因为大页的坏块标记在OOB区的第一个字节
- 读数据,只读一个字节
if (chip->read_byte(mtd) != 0xff)
res = 1;
如果是好块,读出来的值应该是0xff,不然就是坏块。
那么怎么从坏块表获取坏块状态呢?
从坏块表获取坏块状态的函数是nand_isbad_bbt。分析一下该函数。
- 获取块的位置,但是这个是block number * 2
block = (int)(offs >> (this->bbt_erase_shift - 1));
- 获取坏块表中坏块的状态
res = (this->bbt[block >> 3] >> (block & 0x06)) & 0x03;
this->bbt[block >> 3],因为上面的block实际上乘了2,等同于this->bbt[block >> 2]。什么意思呢,就是每个block的坏块状态由2bit位表示,一个this->bbt[0]可以记录4个block的坏块状态。
(block & 0x06)的结果是0,2,4,6
十八. 设置坏块
- 设置坏块首先设置坏块表状态
block = (int)(ofs >> chip->bbt_erase_shift);
if (chip->bbt)
chip->bbt[block >> 2] |= 0x01 << ((block & 0x03) << 1);
获取到block所在的位置,一个chip->bbt数组元素记录4个坏块状态。
block & 0x03为0,1,2,3,(block & 0x03) << 1为0,2,4,6
假设0,1,2,3几个都是坏块
chip->bbt[0] |= 1 <<0;
chip->bbt[0] |= 1 <<2;
chip->bbt[0] |= 1 <<4
chip->bbt[0] |= 1 <<6;
- 如果坏块表存放在Flash中,调用nand_update_bbt更新坏块表
ret = nand_update_bbt(mtd, ofs);
- 如果坏块表存放在内存中,调用nand_do_write_oob函数。
ofs += mtd->oobsize;
chip->ops.len = chip->ops.ooblen = 2;
chip->ops.datbuf = NULL;
chip->ops.oobbuf = buf;
chip->ops.ooboffs = chip->badblockpos & ~0x01;
ret = nand_do_write_oob(mtd, ofs, &chip->ops);
十九. oob区及ECC
OOB大小的计算
- 读取extid信息,计算得出OOB大小
extid = chip->read_byte(mtd);
/* Calc pagesize */
mtd->writesize = 1024 << (extid & 0x3);
extid >>= 2;
/* Calc oobsize */
mtd->oobsize = (8 << (extid & 0x01)) * (mtd->writesize >> 9);
- 直接根据writesize得出
mtd->oobsize = mtd->writesize / 32;
现在的Flash的OOB区域大小是64byte。
- nand_ecclayout
static struct nand_ecclayout nand_oob_64 = {
.eccbytes = 24,
.eccpos = {
40, 41, 42, 43, 44, 45, 46, 47,
48, 49, 50, 51, 52, 53, 54, 55,
56, 57, 58, 59, 60, 61, 62, 63},
.oobfree = {
{.offset = 2,
.length = 38}}
};
即每256byte数据产生3byte ECC校验数据,每页(2kb)产生24byte ECC校验数据
chip->ecc.size = 256;
chip->ecc.bytes = 3;
对256字节的数据共生成了6个Bit的列校验结果,16个Bit的行校验结果,共22个Bit。在Nand中使用3个字节存放校验结果,多余的两个Bit位置1。存放次序如下表所示。
二十. 坏块表的建立
-
坏块表的存储知识
bbt有两种存储方式,一种是把bbt存储在NAND芯片中,另一种是把bbt存储在内存中。
对于前者,好处是驱动加载更快,因为它只会在第一次加载NAND驱动时扫描整个NAND芯片,然后在NAND芯片的某个block中建立bbt,坏处是需要至少消耗NAND芯片一个block的存储容量;
而对于后者,好处是不会耗用NAND芯片的容量,坏处是驱动加载稍慢,因为存储在内存中的bbt每次断电后都不会保存,所以在每次加载NAND驱动时,都会扫描整个NAND芯片, 以便建立bbt。
建立bbt后,以后在做擦除等操作时,就不用每次都去验证当前block是否是个坏块了,因为从bbt中就可以得到这个信息。另外,若在读写等操作时,发现产生了新的坏块,那么除了标志这个block是个坏块外,也还需更新bbt。 -
坏块表的建立调用nand_default_bbt函数。
从上图可以看出,流程是:
①判断当前如果是AND类型的芯片,强制把bbt存储在nand中。这种类型的nand芯片,每个block的出厂坏块不是0xff,而是特定的字符。
②若不是AND芯片,NAND_USE_FLASH_BBT表示坏块表存放在Flash中,否则存放在内存中。
③如果bbt存放在Flash中,bbt_td和bbt_md将会赋值默认值,bbt_main_descr和bbt_mirror_descr。
NAND_BBT_LASTBLOCK:表示查找Flash坏块表时从最后一个块查找。
.maxblocks = 4:表示连续查找4个块,说明该Flash的最后4个块存放的是坏块表信息。
NAND_BBT_CREATE:表示如果没有找到坏块表就创建一个。
NAND_BBT_2BIT:表示2bit位记录一个坏块信息。
NAND_BBT_PERCHIP:表示每个Flash单独建立一张坏块表。
.offs = 8,.len = 4,.pattern = {‘B’, ‘b’, ‘t’, ‘0’ }:在Flash中查找坏块表,在某个块的第一个page的OOB区偏移8个字节,存放的是’B’, ‘b’, ‘t’, ‘0’,表示找到了坏块表。
.veroffs = 12:表示坏块表版本在OOB中的存储位置,1个字节。
NAND_BBT_VERSION:表示更新坏块表版本。
static uint8_t bbt_pattern[] = {'B', 'b', 't', '0' };
static uint8_t mirror_pattern[] = {'1', 't', 'b', 'B' };
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,
.len = 4,
.veroffs = 12,
.maxblocks = 4,
.pattern = bbt_pattern
};
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,
.len = 4,
.veroffs = 12,
.maxblocks = 4,
.pattern = mirror_pattern
};
其中bbt_td和bbt_md是主bbt和镜像bbt的描述符(镜像bbt主要用来对bbt的update和备份),它们只在把bbt存储在NAND芯片的情况下使用,用来从NAND芯片中查找bbt。
④如果bbt存放在内存中,bbt_td和bbt_md将会被赋值为NULL。
⑤对badblock_pattern赋值。badblock_pattern就是坏块信息的pattern,其中定义了坏块信息在oob中的存储位置,以及内容(即用什么值表示这个block是个坏块)。
.offs = 0:表示坏块信息存放在OOB区的第0个字节。
.len = 2:坏块信息长度为2
.pattern = { 0xff, 0xff }:表示连续两个字节都是0xff才是好块,否则就是坏块。
NAND_BBT_SCAN2NDPAGE:表示连续扫描2个page
static uint8_t scan_ff_pattern[] = { 0xff, 0xff };
static struct nand_bbt_descr largepage_flashbased = {
.options = NAND_BBT_SCAN2NDPAGE,
.offs = 0,
.len = 2,
.pattern = scan_ff_pattern
};
⑥调用nand_scan_bbt函数。
-
nand_scan_bbt函数
①申请内存存放bbt表。申请内存的大小为blocknum / 4。len = mtd->size >> (this->bbt_erase_shift + 2); this->bbt = kzalloc(len, GFP_KERNEL);
②判断bbt_td,如果是空,在内存中建立bbt表,然后返回,否则在Flash中建立bbt表。
if (!td) { res = nand_memory_bbt(mtd, bd); return res; }
③申请一个临时buf,大小为 (0x20000 + 64(页) * 64(oobsize))
len = 2^17,this->page_shift = 2^11,mtd->oobsize = 64bytelen = (1 << this->bbt_erase_shift); len += (len >> this->page_shift) * mtd->oobsize; buf = vmalloc(len);
④如果设置了NAND_BBT_ABSPAGE,从给定的page地址中读取bbt表,这个page的地址可以在nand driver中指定。
res = read_abs_bbts(mtd, buf, td, md);
⑤试着在NAND芯片的maxblocks个block中查找bbt是否存在,若找到,就可以读取bbt了。
res = search_read_bbts(mtd, buf, td, md); /* Search the primary table */ search_bbt(mtd, buf, td);
⑥从最后一个block开始查找
if (td->options & NAND_BBT_LASTBLOCK) { startblock = (mtd->size >> this->bbt_erase_shift) - 1; dir = -1; }
⑦读第一个页的OOB数据
/* Read first page */ scan_read_raw(mtd, buf, offs, mtd->writesize);
⑧OOB数据偏移8个字节( td->offs = 8),和{‘B’, ‘b’, ‘t’, ‘0’ }比较,相等就是找到了bbt表。
static int check_pattern(uint8_t *buf, int len, int paglen, struct nand_bbt_descr *td) { int i, end = 0; uint8_t *p = buf; end = paglen + td->offs; p += end; /* Compare the pattern */ for (i = 0; i < td->len; i++) { if (p[i] != td->pattern[i]) return -1; } return 0; }
⑨找到bbt表后,记录page num,更新td的版本号。
if (!check_pattern(buf, scanlen, mtd->writesize, td)) { td->pages[i] = actblock << blocktopage; if (td->options & NAND_BBT_VERSION) { td->version[i] = buf[mtd->writesize + td->veroffs]; } break; }
⑩如果在前面的search_read_bbts已经查找到了td或md,那么就要比较他们的版本号version,以版本号大的为准,读取bbt到内存中。如果没有版本号,td和md都读。
read_abs_bbt(mtd, buf, rd, chipsel);
如果没有search到md表,将读到内存的bbt表写到Flash的md表中。
if ((writeops & 0x02) && md && (md->options & NAND_BBT_WRITE)) { res = write_bbt(mtd, buf, md, td, chipsel); }
如果没有search到td表,将读到内存的bbt表写到Flash的td表中。
if ((writeops & 0x01) && (td->options & NAND_BBT_WRITE)) { res = write_bbt(mtd, buf, td, md, chipsel); }
⑪如果前面td和md都没有search到,在内存中重新创建一个bbt表,并写入到Flash的td和md表中。并将版本号设置为1.
/* Create the table in memory by scanning the chip(s) */ create_bbt(mtd, buf, bd, chipsel); td->version[i] = 1; if (md) md->version[i] = 1;
⑫写bbt数据函数的调用流程
-
如果flash之前就有相应的bbt,就在原来的page重写,否则要重新寻找一个block写。
if (td->pages[chip] != -1) { page = td->pages[chip]; goto write; }
-
如果找到的是坏块,继续到下一个block寻找
block & 0x03 = 0,1,2,3;
(2 * (block & 0x03)) = 0,2,4,6;
this->bbt[block >> 2] >> (2 * (block & 0x03))就是取block的坏块标记。
如果找到的block被md占用了,继续寻找下一个。for (i = 0; i < td->maxblocks; i++) { int block = startblock + dir * i; /* Check, if the block is bad */ switch ((this->bbt[block >> 2] >> (2 * (block & 0x03))) & 0x03) { case 0x01: case 0x03: 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; }
-
len = (size_t) (numblocks >> sft); 假设我的Flash是128M, 就是1024个block,len = 256byte。Flash是按页写的,不满一个页,按一个页处理。
len = (len + (mtd->writesize - 1)) & ~(mtd->writesize - 1);
-
把一个页数据包括OOB区,全部填充为0xff。因为0xff表示全部都是好块。
memset(buf, 0xff, len + (len >> this->page_shift)* mtd->oobsize);
-
写入bbt的pattern,写入OOB的第八个字节,长度为4, 值为{‘B’, ‘b’, ‘t’, ‘0’ }
memcpy(&buf[ooboffs + td->offs], td->pattern, td->len);
-
写入bbt的版本值,初始化是1,写入OOB的第12字节,长度为1。
buf[ooboffs + td->veroffs] = td->version[chip];
-
-
bbt数据反转,存入Flash。this->bbt= 0x00,0x01,0x11, 反转后变成0x11,0x10,0x00。
for (i = 0; i < numblocks;) { uint8_t dat; dat = this->bbt[bbtoffs + (i >> 2)]; for (j = 0; j < 4; j++, i++) { int sftcnt = (i << (3 - sft)) & sftmsk; /* Do not store the reserved bbt blocks ! */ buf[offs + (i >> sft)] &= ~(msk[dat & 0x03] << sftcnt); dat >>= 2; } }
假设第0块是坏块,this->bbt[0] = 0x03; data = 0x03;
sftcnt = (0<< 1) & 0x06 = 0;
buf[0] = 0xff;
dat & 0x03 = 0x03, msk[3] = 0x03, 0x03 << 0 = 0x03;
~0x03 = 0xfc;
buf[0] & 0xfc = 0xfc,相当最低二位取反。 -
把allowbbt赋值为1,允许擦除bbt所在的block,而在上层应用erase block的时候,是不能赋值的,也就是说上层看到的bbt所在的block是坏块,这样会保护bbt,上层应用不会操作到bbt。
nand_erase_nand(mtd, &einfo, 1);
-
把data和oob都写到page里面
scan_write_bbt(mtd, to, len, buf, &buf[len]);
⑬读bbt数据的函数调用流程
-
uint8_t msk = (uint8_t) ((1 << bits) - 1): msk = 0x03;
-
totlen = (num * bits) >> 3: 8192(block) / 4 = 2048byte
-
读bbt数据
mtd->read(mtd, from, len, &retlen, buf);
-
对每个数据进行转换后存放在内存的bbt表中,如果tmp != 0x03,表明是坏块
tmp = 0, bbt |= 0x03, 否则 bbt |= 0x01if (tmp == 0) this->bbt[offs + (act >> 3)] |= 0x3 << (act & 0x06); else this->bbt[offs + (act >> 3)] |= 0x1 << (act & 0x06);
⑭把md和td两个bbt手动标记成坏块,保护它以免被上层擦除。但是这里是把坏块标记成0x02,只有allowbbt = 1时,才允许当成好块来访问,否则当成坏块来访问。
mark_bbt_region(mtd, td);
if (md)
mark_bbt_region(mtd, md);
二十一. 第一阶段的nand scan函数nand_scan_ident
- 设置chip的读写,坏块管理等函数
nand_set_defaults(chip, busw);
- 获取第一个Flash的型号
type = nand_get_flash_type(mtd, chip, busw, &nand_maf_id);
- 如果有多个Flash,获取它们的厂商ID和device ID,只有和第一个Flash是一样的才保存。mtd的大小是所有Flash的总大小。
chip->numchips = i;
mtd->size = i * chip->chipsize;
二十二. 第二阶段的nand scan函数nand_scan_tail
- 填充chip->ecc
case 64:
chip->ecc.layout = &nand_oob_64;
case NAND_ECC_SOFT:
chip->ecc.calculate = nand_calculate_ecc;
chip->ecc.correct = nand_correct_data;
chip->ecc.read_page = nand_read_page_swecc;
chip->ecc.read_subpage = nand_read_subpage;
chip->ecc.write_page = nand_write_page_swecc;
chip->ecc.read_page_raw = nand_read_page_raw;
chip->ecc.write_page_raw = nand_write_page_raw;
chip->ecc.read_oob = nand_read_oob_std;
chip->ecc.write_oob = nand_write_oob_std;
if (!chip->ecc.size)
chip->ecc.size = 256;
chip->ecc.bytes = 3;
- 填充mtd,给mtd基本操作的函数指针赋值
mtd->type = MTD_NANDFLASH;
mtd->flags = MTD_CAP_NANDFLASH;
mtd->erase = nand_erase;
mtd->point = NULL;
mtd->unpoint = NULL;
mtd->read = nand_read;
mtd->write = nand_write;
mtd->read_oob = nand_read_oob;
mtd->write_oob = nand_write_oob;
mtd->sync = nand_sync;
mtd->lock = NULL;
mtd->unlock = NULL;
mtd->suspend = nand_suspend;
mtd->resume = nand_resume;
mtd->block_isbad = nand_block_isbad;
mtd->block_markbad = nand_block_markbad;
/* propagate ecc.layout to mtd_info */
mtd->ecclayout = chip->ecc.layout;
- 扫描坏块并建立坏块表
chip->scan_bbt(mtd);