Linux nand flash设备驱动详解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_38696651/article/details/88938625

本文基于mini2440开发板,Linux版本号是:linux-2.6.32.2

一. Nand Flash 设备注册
  1. 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, 
	}
};
  1. 上面的结构体中有三个变量tacls,twrph0,twrph1,这三个变量的意思分别是:
    TACLS: 发出 CLE/ALE 之后多长时间才发出 nWE 信号
    TWRPH0: nWE 的脉冲宽度
    TWRPH1: nWE 变为高电平后多长时间 CLE/ALE 才能变为低电平
    在这里插入图片描述

  2. 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寄存器地址,寄存器地址是 0x4E000010

    chip->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 = 64byte

    len = (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 |= 0x01

    if (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);

猜你喜欢

转载自blog.csdn.net/weixin_38696651/article/details/88938625
今日推荐