1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. NAND 初始化
下面以 Micron MT29F2G08AAD
型号的 Nand Flash
为例,说明 ARMv7
架构下 U-Boot
对 Nand Flash 设备的初始化
过程。首先,可以将相关代码划分为 U-Boot NAND 驱动硬件无关通用部分
和 硬件相关的 NAND FLASH 以及其 控制器 驱动
两部分。另外,抽象层次上,U-Boot
将 Nor Flash,Nand Flash
等类型设备,统一抽象为 MTD(Memory Technology Device)
类型设备,对这些类型设备的访问,都是通过 MTD 接口来间接进行。
board_init_r() /* common/board_r.c */
/* 特定 板型 初始化:这里重点关注 NAND 控制器 的 初始化 */
board_init() /* board/myirtech/myd_c335x/myd_c335x.c */
...
gpmc_init();
/* putting a blanket check on GPMC based on ZeBu for now */
gpmc_cfg = (struct gpmc *)GPMC_BASE;
/* NAND 控制器 的一些寄存器配置 */
...
initr_nand()
nand_init() /* drivers/mtd/nand/nand.c */
nand_init_chip()
struct mtd_info *mtd;
#ifndef CONFIG_DM_NAND
struct nand_chip *nand = &nand_chip[i];
ulong base_addr = base_address[i];
#endif
...
mtd = &nand_info[i]; /* NAND 设备 的 MTD 数据对象 */
mtd->priv = nand; /* MTD NAND 设备数据 */
nand->IO_ADDR_R = nand->IO_ADDR_W = (void __iomem *)base_addr; /* NAND 设备 IO 地址空间 */
/* 1. 特定 板型 的 NAND 初始化 */
if (board_nand_init(nand)) /* drivers/mtd/nand/omap_gpmc.c */
return;
/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */
if (nand_scan(mtd, maxchips))
return;
/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */
nand_register(i);
/* 1. 特定 板型 的 NAND 初始化 */
board_nand_init(nand)
...
/*
* xloader/Uboot's gpmc configuration would have configured GPMC for
* nand type of memory. The following logic scans and latches on to the
* first CS with NAND type memory.
* TBD: need to make this logic generic to handle multiple CS NAND
* devices.
*/
while (cs < GPMC_MAX_CS) {
/* Check if NAND type is set */
if ((readl(&gpmc_cfg->cs[cs].config1) & 0xC00) == 0x800) {
/* Found it!! */
break;
}
cs++;
}
...
nand->IO_ADDR_R = (void __iomem *)&gpmc_cfg->cs[cs].nand_dat;
nand->IO_ADDR_W = (void __iomem *)&gpmc_cfg->cs[cs].nand_cmd;
...
nand->priv = &omap_nand_info[cs];
nand->cmd_ctrl = omap_nand_hwcontrol;
nand->options |= NAND_NO_PADDING | NAND_CACHEPRG;
nand->chip_delay = 100;
nand->ecc.layout = &omap_ecclayout; /* NAND ECC 数据 layout */
...
nand->options &= ~NAND_BUSWIDTH_16; /* 8 位数据宽度 */
...
/* select ECC scheme */
#if defined(CONFIG_NAND_OMAP_ECCSCHEME)
/* NAND ECC 模式选择 */
err = omap_select_ecc_scheme(nand, CONFIG_NAND_OMAP_ECCSCHEME,
CONFIG_SYS_NAND_PAGE_SIZE, CONFIG_SYS_NAND_OOBSIZE);
...
switch (ecc_scheme) {
...
case OMAP_ECC_BCH8_CODE_HW: /* ECC 使用 硬件 BCH8 码 */
#ifdef CONFIG_NAND_OMAP_ELM
...
/* intialize ELM for ECC error detection */
elm_init(); /* ECC 错误检测硬件模块 ELM 初始化 */
...
/* populate ecc specific fields */
nand->ecc.mode = NAND_ECC_HW; /* 硬件 ECC */
nand->ecc.strength = 8;
nand->ecc.size = SECTOR_BYTES;
nand->ecc.bytes = 14;
/* ECC 操作接口 */
nand->ecc.hwctl = omap_enable_hwecc;
nand->ecc.correct = omap_correct_data_bch;
nand->ecc.calculate = omap_calculate_ecc;
nand->ecc.read_page = omap_read_page_bch;
...
#else
...
#endif
...
}
...
info->ecc_scheme = ecc_scheme; /* OMAP_ECC_BCH8_CODE_HW */
return 0;
#else
...
#endif
#ifdef CONFIG_NAND_OMAP_GPMC_PREFETCH
nand->read_buf = omap_nand_read_prefetch;
#else
...
#endif
nand->dev_ready = omap_dev_ready;
return 0;
/* 2. 扫描识别并配置 NAND 控制器 上 挂接的 NAND 设备 */
nand_scan(mtd, maxchips) /* drivers/mtd/nand/nand_base.c */
...
/* 扫描识别 NAND 设备 和 参数, 设置操作接口 等 */
ret = nand_scan_ident(mtd, maxchips, NULL);
...
struct nand_chip *chip = mtd->priv;
struct nand_flash_dev *type;
/* Set the default functions */
nand_set_defaults(chip, chip->options & NAND_BUSWIDTH_16); /* 设置 NAND 设备缺省操作接口(读写等) */
...
/* check, if a user supplied command function given */
if (chip->cmdfunc == NULL)
chip->cmdfunc = nand_command;
/* check, if a user supplied wait function given */
if (chip->waitfunc == NULL)
chip->waitfunc = nand_wait;
if (!chip->select_chip)
chip->select_chip = nand_select_chip;
...
/* If called twice, pointers that depend on busw may need to be reset */
if (!chip->read_byte || chip->read_byte == nand_read_byte)
chip->read_byte = busw ? nand_read_byte16 : nand_read_byte;
if (!chip->read_word)
chip->read_word = nand_read_word;
if (!chip->block_bad)
chip->block_bad = nand_block_bad; /* 坏块 判定接口 */
if (!chip->block_markbad)
chip->block_markbad = nand_default_block_markbad;
if (!chip->write_buf || chip->write_buf == nand_write_buf)
chip->write_buf = busw ? nand_write_buf16 : nand_write_buf;
if (!chip->write_byte || chip->write_byte == nand_write_byte)
chip->write_byte = busw ? nand_write_byte16 : nand_write_byte;
if (!chip->read_buf || chip->read_buf == nand_read_buf)
chip->read_buf = busw ? nand_read_buf16 : nand_read_buf;
if (!chip->scan_bbt)
chip->scan_bbt = nand_default_bbt; /* 坏块 选择/建立 接口 */
...
/* Read the flash type */
/* 识别 NAND 设备类型 */
type = nand_get_flash_type(mtd, chip, &nand_maf_id,
&nand_dev_id, table);
...
u8 id_data[8];
/* Select the device */
chip->select_chip(mtd, 0);
/*
* Reset the chip, required by some chips (e.g. Micron MT29FxGxxxxx)
* after power-up.
*/
chip->cmdfunc(mtd, NAND_CMD_RESET, -1, -1); /* 复位 NAND 设备 ID */
/* Send the command for reading device ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 发送读取 NAND 设备 ID 命令 */
/* Read manufacturer and device IDs */
*maf_id = chip->read_byte(mtd); /* 读取 NAND 设备 制造商 ID */
*dev_id = chip->read_byte(mtd); /* 读取 NAND 设备 ID */
chip->cmdfunc(mtd, NAND_CMD_READID, 0x00, -1); /* 读取 NAND 芯片 ID */
/* Read entire ID string */
for (i = 0; i < 8; i++)
id_data[i] = chip->read_byte(mtd);
...
if (!type)
type = nand_flash_ids; /* 预定义的 NAND 设备列表: drivers/mtd/nand/nand_ids.c */
/*
* 对比 读取到的 NAND 设备 ID 和 预定义表 nand_flash_ids[],
* 看是否能找到匹配的表项.
*/
for (; type->name != NULL; type++) {
if (is_full_id_nand(type)) { /* 对于 全 ID 标识的设备, 进行全 ID 匹配 */
if (find_full_id_nand(mtd, chip, type, id_data, &busw)) /* 如果 是 设备全 ID 匹配, */
goto ident_done; /* 完成识别工作 */
} else if (*dev_id == type->dev_id) { /* 如果 不是 设备全 ID 匹配, 而是 设备 ID 匹配 */
break; /* 做进一步的识别工作(如符合 ONFI/JEDEC 规范的设备匹配工作) */
}
}
/* 可能满足 ONFI/JEDEC 规范 的 设备 识别 */
chip->onfi_version = 0;
if (!type->name || !type->pagesize) {
/* Check if the chip is ONFI compliant */
if (nand_flash_detect_onfi(mtd, chip, &busw)) /* 识别到 符合 ONFI 接口规范 的 NAND 设备 */
goto ident_done; /* 完成识别工作 */
/* Check if the chip is JEDEC compliant */
if (nand_flash_detect_jedec(mtd, chip, &busw)) /* 识别到 符合 JEDEC 接口规范 的 NAND 设备 */
goto ident_done;
}
/*
* 目前无法识别设备:
* . 无法从预定义 NAND 芯片列表 nand_flash_ids[] 匹配设备
* . 设备不符合 ONFI/JEDEC 接口规范
*/
if (!type->name)
return ERR_PTR(-ENODEV);
if (!mtd->name)
mtd->name = type->name; /* 设置 MTD NAND 设备名称 */
/* 设置 NAND 设备容量 */
chip->chipsize = (uint64_t)type->chipsize << 20;
/* 设置 NAND 的 page size, OOB size, erase size */
if (!type->pagesize && chip->init_size) {
/* Set the pagesize, oobsize, erasesize by the driver */
busw = chip->init_size(mtd, chip, id_data);
} else if (!type->pagesize) {
/* Decode parameters from extended ID */
nand_decode_ext_id(mtd, chip, id_data, &busw);
} else {
nand_decode_id(mtd, chip, type, id_data, &busw);
}
/* Get chip options */
chip->options |= type->options; /* 设置 NAND 设备选项 */
...
ident_done:
...
/* 坏块管理相关配置 */
nand_decode_bbm_options(mtd, chip, id_data);
int maf_id = id_data[0];
/* Set the bad block position */
if (mtd->writesize > 512 || (chip->options & NAND_BUSWIDTH_16))
chip->badblockpos = NAND_LARGE_BADBLOCK_POS;
else
chip->badblockpos = NAND_SMALL_BADBLOCK_POS;
/*
* Bad block marker is stored in the last page of each block on Samsung
* and Hynix MLC devices; stored in first two pages of each block on
* Micron devices with 2KiB pages and on SLC Samsung, Hynix, Toshiba,
* AMD/Spansion, and Macronix. All others scan only the first page.
*/
if (!nand_is_slc(chip) &&
(maf_id == NAND_MFR_SAMSUNG ||
maf_id == NAND_MFR_HYNIX))
/* 三星 和 海力士 的 非 SLC 类型设备, 坏块标记存储在每个 block 的最后一个 page */
chip->bbt_options |= NAND_BBT_SCANLASTPAGE;
else if ((nand_is_slc(chip) &&
(maf_id == NAND_MFR_SAMSUNG ||
maf_id == NAND_MFR_HYNIX ||
maf_id == NAND_MFR_TOSHIBA ||
maf_id == NAND_MFR_AMD ||
maf_id == NAND_MFR_MACRONIX)) ||
(mtd->writesize == 2048 &&
maf_id == NAND_MFR_MICRON))
/* 一些厂家的 SLC 类型设备,坏块标记存储在 block 的 第1个 或 第2个 page */
chip->bbt_options |= NAND_BBT_SCAN2NDPAGE;
...
chip->badblockbits = 8;
chip->erase = single_erase; /* 设置 擦除 接口 */
/* Do not replace user supplied command function! */
if (mtd->writesize > 512 && chip->cmdfunc == nand_command)
chip->cmdfunc = nand_command_lp; /* 覆盖命令为 大 page 接口 */
/* 报告设备信息 (需要开启 CONFIG_MTD_DEBUG) */
pr_info("device found, Manufacturer ID: 0x%02x, Chip ID: 0x%02x\n",
*maf_id, *dev_id);
...
pr_info("%d MiB, %s, erase size: %d KiB, page size: %d, OOB size: %d\n",
(int)(chip->chipsize >> 20), nand_is_slc(chip) ? "SLC" : "MLC",
mtd->erasesize >> 10, mtd->writesize, mtd->oobsize);
return type; /* 返回是被的 NAND 设备类型 */
/* 通过 NAND 控制器,选择 NAND 芯片 */
chip->select_chip(mtd, -1);
/* Store the number of chips and calc total size for mtd */
chip->numchips = i;
mtd->size = i * chip->chipsize; /* 记录 MTD NAND 设备容量 */
return 0;
if (!ret)
/*
* 扫描识别收尾工作:
* 从 前面 扫描识别到的 NAND 设备 和 参数,
* 为 NAND 设备建立缓冲,设置 NAND 设备对应的 MTD NAND 设备对象。
*/
ret = nand_scan_tail(mtd);
int i;
struct nand_chip *chip = mtd->priv;
struct nand_ecc_ctrl *ecc = &chip->ecc;
struct nand_buffers *nbuf;
/* 为 NAND 设备 创建 缓冲: ECC, 数据 */
if (!(chip->options & NAND_OWN_BUFFERS)) {
nbuf = kzalloc(sizeof(struct nand_buffers), GFP_KERNEL);
chip->buffers = nbuf;
} else {
if (!chip->buffers)
return -ENOMEM;
}
/* Set the internal oob buffer location, just after the page data */
/* 和 NAND 页面存储一样, OOB 缓冲 紧邻 page 缓冲之后 */
chip->oob_poi = chip->buffers->databuf + mtd->writesize;
...
switch (ecc->mode) {
...
case NAND_ECC_HW: /* 设置没有配置的 ECC 接口 */
if (!ecc->read_page)
ecc->read_page = nand_read_page_hwecc;
if (!ecc->write_page)
ecc->write_page = nand_write_page_hwecc;
if (!ecc->read_page_raw)
ecc->read_page_raw = nand_read_page_raw;
if (!ecc->write_page_raw)
ecc->write_page_raw = nand_write_page_raw;
if (!ecc->read_oob)
ecc->read_oob = nand_read_oob_std;
if (!ecc->write_oob)
ecc->write_oob = nand_write_oob_std;
if (!ecc->read_subpage)
ecc->read_subpage = nand_read_subpage;
if (!ecc->write_subpage)
ecc->write_subpage = nand_write_subpage_hwecc;
...
}
/* For many systems, the standard OOB write also works for raw */
if (!ecc->read_oob_raw)
ecc->read_oob_raw = ecc->read_oob;
if (!ecc->write_oob_raw)
ecc->write_oob_raw = ecc->write_oob;
// 一些其它 ECC 相关配置
...
/* Initialize state */
chip->state = FL_READY; /* NAND 设备标记为 READY 状态 */
...
/* Fill in remaining MTD driver data */
mtd->type = nand_is_slc(chip) ? MTD_NANDFLASH : MTD_MLCNANDFLASH; /* 设置 MTD NAND 设备类型: SLC 或 MLC */
mtd->flags = (chip->options & NAND_ROM) ? MTD_CAP_ROM :
MTD_CAP_NANDFLASH; /* MTD NAND 读写属性设置:只读、可读写 */
/* 设置 MTD NAND 设备接口: 调用 NAND 设备接口 */
mtd->_erase = nand_erase;
mtd->_read = nand_read;
mtd->_write = nand_write;
mtd->_panic_write = panic_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->_block_isreserved = nand_block_isreserved;
mtd->_block_isbad = nand_block_isbad;
mtd->_block_markbad = nand_block_markbad;
mtd->writebufsize = mtd->writesize;
// MTD NAND ECC 相关设置
...
return 0;
return ret;
/* 3. 注册 扫描到的、挂接在 NAND 控制器 上 NAND 设备 */
nand_register(i);
struct mtd_info *mtd;
mtd = get_nand_dev_by_index(devnum);
sprintf(dev_name[devnum], "nand%d", devnum);
mtd->name = dev_name[devnum];
#ifdef CONFIG_MTD_DEVICE
/*
* Add MTD device so that we can reference it later
* via the mtdcore infrastructure (e.g. ubi).
*/
add_mtd_device(mtd);
#endif
total_nand_size += mtd->size / 1024;
if (nand_curr_device == -1)
nand_curr_device = devnum; /* 设置当前 NAND 设备编号 */
return 0;
3. 访问 NAND 设备
3.1 查看 NAND 设备信息
3.1.1 查看 NAND 设备基本信息
U-Boot
提供一些 nand info
命令,可以查看 NAND
设备信息:
# nand info
Device 0: nand0, sector size 128 KiB
Page size 2048 b
OOB size 64 b
Erase size 131072 b
subpagesize 512 b
options 0x4000000c
bbt options 0x 8000
从上面看到,Nand Flash 设备:
o page 是 2KB 大小
o page 后跟的 OOB(Spare Area) 是 64 Bytes
o 擦除 size 是 128KB,也就是 block size
3.1.2 查看 NAND 设备 MTD 分区
前面有说过,U-Boot 将 NAND 抽象为 MTD 设备进行访问,通过 mtdparts
命令,可以查看 NAND 设备的分区信息:
# mtdparts
device nand0 <nand.0>, # parts = 11
#: name size offset mask_flags
0: NAND.SPL 0x00020000 0x00000000 0
1: NAND.SPL.backup1 0x00020000 0x00020000 0
2: NAND.SPL.backup2 0x00020000 0x00040000 0
3: NAND.SPL.backup3 0x00020000 0x00060000 0
4: NAND.u-boot-spl-os 0x00040000 0x00080000 0
5: NAND.u-boot 0x00100000 0x000c0000 0
6: NAND.u-boot-env 0x00020000 0x001c0000 0
7: NAND.u-boot-env.backup10x00020000 0x001e0000 0
8: NAND.kernel 0x00800000 0x00200000 0
9: NAND.rootfs 0x0d600000 0x00a00000 0
10: NAND.userdata 0x02000000 0x0e000000 0
active partition: nand0,0 - (NAND.SPL) 0x00020000 @ 0x00000000
defaults:
mtdids : nand0=nand.0
mtdparts: mtdparts=nand.0:128k(NAND.SPL),128k(NAND.SPL.backup1),128k(NAND.SPL.backup2),128k(NAND.SPL.backup3),256k(NAND.u-boot-spl-os),1m(NAND.u-boot),128k(NAND.u-boot-env),128k(NAND.u-boot-env.backup1),8m(NAND.kernel),214m(NAND.rootfs),-(NAND.userdata)
3.1.3 查看 NAND 设备坏块
# nand bad
Device 0 bad blocks:
03e40000
086c0000
发现了两个坏块,数据标记了它们的字节偏移
位置。
3.2 NAND 擦除操作
通过 nand erase
命令,可以对 NAND 发起对 NAND 的擦除
操作:
# nand erase c0000 100000
其中参数 c0000
是要擦除的起始位置
,相对于 NAND 设备开始位置的字节偏移
;100000
是要擦除的长度
字节数。这两个参数都是十六进制数字
。来看一下擦除过程的具体实现:
do_nand() /* cmd/nand.c */
...
nand_info_t *nand;
...
int dev = nand_curr_device; /* 默认选择当前 NAND 设备进行操作 */
...
...
cmd = argv[1]; /* "erase" */
...
nand = get_nand_dev_by_index(dev); /* 获取 NAND 设备 */
if (strncmp(cmd, "erase", 5) == 0 || strncmp(cmd, "scrub", 5) == 0) {
nand_erase_options_t opts;
...
...
printf("\nNAND %s: ", cmd); /* "NAND erase: " */
/* 解析 擦除起始位置 和 长度 参数 到 @off 和 @size */
if (mtd_arg_off_size(argc - o, argv + o, &dev, &off, &size,
&maxsize, MTD_DEV_TYPE_NAND,
nand->size) != 0)
return 1;
/* 切换到 要操作 的 目标 NAND 设备 */
if (set_dev(dev))
return 1;
nand = get_nand_dev_by_index(dev);
memset(&opts, 0, sizeof(opts));
opts.offset = off;
opts.length = size;
...
ret = nand_erase_opts(nand, &opts); /* 擦除操作 */
...
}
ret = nand_erase_opts(nand, &opts); /* drivers/mtd/nand/nand_util.c */
...
/*
* @erase_length:要擦除的 block 数目
* @erased_length: 已经擦除的 block 数目
*/
unsigned long erase_length, erased_length; /* in blocks */
...
erase_length = lldiv(opts->length + meminfo->erasesize - 1,
meminfo->erasesize); /* 向上对齐到 erase size (block 大小) */
...
for (erased_length = 0;
erased_length < erase_length;
erase.addr += meminfo->erasesize) {
...
if (!opts->scrub) {
/* 检查位于 @ofs 位置的 block 是不是坏块 */
int ret = mtd_block_isbad(meminfo, erase.addr);
if (ret > 0) { /* 坏块 */
...
if (!opts->spread)
erased_length++; /* 非 nand erase.spread 命令, 坏块也计入擦除 block 数目 */
continue; /* 跳过坏块: 坏块不做擦除动作,擦除坏块是非法操作 */
}
}
erased_length++; /* 已擦除 block 数目 +1 */
result = mtd_erase(meminfo, &erase); /* 擦除当前块 */
...
return mtd->_erase(mtd, instr);
nand_erase()
return nand_erase_nand(mtd, instr, 0);
...
struct nand_chip *chip = mtd->priv; /* MTD 转入 NAND 层操作 */
...
/* Grab the lock and see if the device is available */
nand_get_device(mtd, FL_ERASING);
/* Shift to get first page */
page = (int)(instr->addr >> chip->page_shift); /* 擦除位置转换为 page 位置 */
chipnr = (int)(instr->addr >> chip->chip_shift);
/* Calculate pages in each block */
pages_per_block = 1 << (chip->phys_erase_shift - chip->page_shift);
/* Select the NAND device */
chip->select_chip(mtd, chipnr);
/* Loop through the pages */
len = instr->len;
instr->state = MTD_ERASING;
while (len) {
...
status = chip->erase(mtd, page & chip->pagemask);
single_erase()
struct nand_chip *chip = mtd->priv;
/* Send commands to erase a block */
/* 发送 page 擦除操作命令 */
chip->cmdfunc(mtd, NAND_CMD_ERASE1, -1, page);
chip->cmdfunc(mtd, NAND_CMD_ERASE2, -1, -1);
/* 等待擦除操作完成 */
return chip->waitfunc(mtd, chip);
/* Increment page address and decrement length */
/* 移向下一个 page */
len -= (1ULL << chip->phys_erase_shift);
page += pages_per_block;
...
}
instr->state = MTD_ERASE_DONE;
...
}
3.3 NAND 写操作
假定以命令 nand write 0x82000000 c0000 ${filesize}
发起写操作
。写操作
基本流程和擦除操作差不多,来看细节:
do_nand()
...
if (strncmp(cmd, "read", 4) == 0 || strncmp(cmd, "write", 5) == 0) {
...
addr = (ulong)simple_strtoul(argv[2], NULL, 16); // 0x82000000
read = strncmp(cmd, "read", 4) == 0; /* 1 = read, 0 = write */
printf("\nNAND %s: ", read ? "read" : "write");
s = strchr(cmd, '.');
if (s && !strcmp(s, ".raw")) { // nand write.raw
...
} else {
// @off = 0xc0000
// @size = ${filesize}
if (mtd_arg_off_size(argc - 3, argv + 3, &dev, &off,
&size, &maxsize,
MTD_DEV_TYPE_NAND,
nand->size) != 0)
return 1;
if (set_dev(dev))
return 1;
...
rwsize = size;
if (!s || !strcmp(s, ".jffs2") ||
!strcmp(s, ".e") || !strcmp(s, ".i")) {
if (read)
ret = nand_read_skip_bad(nand, off, &rwsize,
NULL, maxsize,
(u_char *)addr); /* 读取,会自动跳过坏块 */
else
ret = nand_write_skip_bad(nand, off, &rwsize,
NULL, maxsize,
(u_char *)addr,
WITH_WR_VERIFY); /* 写入,会自动跳过坏块 */
}
}
nand = get_nand_dev_by_index(dev);
}
nand_write_skip_bad()
...
size_t left_to_write = *length; /* 要写入的长度, 字节数 */
...
int need_skip;
blocksize = nand->erasesize;
/* 检查写入过程中, 要跳过的坏块数目 */
need_skip = check_skip_len(nand, offset, *length, &used_for_write);
...
while (left_to_write > 0) {
size_t block_offset = offset & (nand->erasesize - 1); /* 计算 block 内偏移 */
...
/* 写入时跳过坏块 */
if (nand_block_isbad(nand, offset & ~(nand->erasesize - 1))) {
printf("Skip bad block 0x%08llx\n",
offset & ~(nand->erasesize - 1));
offset += nand->erasesize - block_offset;
continue;
}
/* 调整 写入 大小 */
if (left_to_write < (blocksize - block_offset))
write_size = left_to_write;
else
write_size = blocksize - block_offset;
/* 写入 block */
/*
* 注意前后的两个 nand_write() 不是同一个函数:
* . 前一个 nand_write() 是 MTD 驱动层的 接口
* . 后一个是 NAND 驱动层的 接口
*/
rval = nand_write(nand, offset, &truncated_write_size, p_buffer); /* drivers/mtd/mtcore.c */
mtd_write()
mtd->_write(mtd, to, len, retlen, buf);
nand_write() /* drivers/mtd/nand/nand_base.c */
struct mtd_oob_ops ops;
nand_get_device(mtd, FL_WRITING);
memset(&ops, 0, sizeof(ops));
ops.len = len;
ops.datbuf = (uint8_t *)buf;
ops.mode = MTD_OPS_PLACE_OOB;
ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */
*retlen = ops.retlen;
nand_release_device(mtd);
return ret;
/* 写入后,再读回来验证一下数据是否正确 */
if ((flags & WITH_WR_VERIFY) && !rval)
rval = nand_verify(nand, offset,
truncated_write_size, p_buffer);
offset += write_size;
p_buffer += write_size;
...
left_to_write -= write_size;
}
ret = nand_do_write_ops(mtd, to, &ops); /* NAND 设备写操作 */
...
struct nand_chip *chip = mtd->priv;
uint32_t writelen = ops->len;
...
...
while (1) {
int bytes = mtd->writesize; /* 按 page 写入 */
...
uint8_t *wbuf = buf; /* 数据 */
...
if (unlikely(oob)) {
...
} else {
/* We still need to erase leftover OOB data */
memset(chip->oob_poi, 0xff, mtd->oobsize); /* 没有指定 OOB 数据, 则将 Spare Area 全部填充 0xff */
}
ret = chip->write_page(mtd, chip, column, bytes, wbuf,
oob_required, page, cached,
(ops->mode == MTD_OPS_RAW)); /* 写入一个 page 的数据 */
nand_write_page()
...
chip->cmdfunc(mtd, NAND_CMD_SEQIN, 0x00, page);
if (unlikely(raw))
status = chip->ecc.write_page_raw(mtd, chip, buf, oob_required);
else if (subpage)
status = chip->ecc.write_subpage(mtd, chip, offset, data_len, buf, oob_required);
else /* 这里只考虑这一种情形 */
status = chip->ecc.write_page(mtd, chip, buf, oob_required);
nand_write_page_hwecc()
int i, eccsize = chip->ecc.size;
int eccbytes = chip->ecc.bytes;
int eccsteps = chip->ecc.steps;
uint8_t *ecc_calc = chip->buffers->ecccalc;
const uint8_t *p = buf; /* 要写入的数据 */
uint32_t *eccpos = chip->ecc.layout->eccpos;
/* 写数据到 NAND, 每次写 @eccsize 个字节 (@eccsize 是计算 ECC 的单元) */
for (i = 0; eccsteps; eccsteps--, i += eccbytes, p += eccsize) {
chip->ecc.hwctl(mtd, NAND_ECC_WRITE);
chip->write_buf(mtd, p, eccsize); /* 将数据写入到 NAND */
chip->ecc.calculate(mtd, p, &ecc_calc[i]); /* 计算数据 @p 的 ECC, 记录到 ecc_calc[i] */
}
for (i = 0; i < chip->ecc.total; i++)
chip->oob_poi[eccpos[i]] = ecc_calc[i];
chip->write_buf(mtd, chip->oob_poi, mtd->oobsize); /* 将 ECC 数据写入到 Spare Area */
return 0;
/* 写完一个 page */
cached = 0;
if (!cached || !NAND_HAS_CACHEPROG(chip)) {
chip->cmdfunc(mtd, NAND_CMD_PAGEPROG, -1, -1);
status = chip->waitfunc(mtd, chip);
} else {
...
}
return 0;
...
writelen -= bytes;
if (!writelen)
break;
...
}
...
3.4 NAND 读操作
可以通过 nand read 0x82000000 200000 800000
发起对 NAND 的读
操作:将 NAND 从 0x200000
位置开始的 0x800000
个字节,读取到内存地址 0x82000000
开始的位置。NAND 读操作入口函数为 nand_read_skip_bad()
,其逻辑和 nand_write_skip_bad()
非常相似,在此不再赘述。值得一提的是,nand_read_skip_bad()
读操作,会跳过坏块。
3.5 其它 NAND 操作
nand device # 输出当前 NAND 设备信息
nand device <dev> # 切换到 NAND 设备 dev
nand dump[.oob] <offset> # 导出 NAND page 和 OOB, 带 .oob 后缀仅导出 OOB
nand scrub # 擦除 NAND, 慎用!!! 会连厂家标记的坏块信息一起擦除掉
......
4. NAND 坏块管理
最后来聊一聊 U-Boot 下的 NAND 坏块管理。在正式讨论 U-Boot 下的 NAND 坏块管理细节之前,我们先简单的说一说 常见的 NAND 坏块管理方法,以及它们的基本工作原理。
4.1 常见 NAND 坏块管理方法
4.1.1 基于 FTL 芯片的坏块管理
它使用一个额外的 FTL (Flash Translation Layer)
芯片对 NAND 进行管理,对外部屏蔽了坏块信息,U 盘、SD 卡、MMC 卡以及固态硬盘都使用这种管理方法。这种方式简化了 NAND 操作,但也使坏块信息对外部而言不可见,如果系统中出现了可能和坏块相关的问题,定位和调试变得困难,另外,FTL 芯片也需要额外的硬件成本。
4.1.2 基于 NAND 文件系统的坏块管理
JFFS2
、YAFFS2
、FlashFx
这些专门针对 NAND 的文件系统可以对坏块进行管理。
4.1.3 NAND 管理中间件
有一些中间件(Middleware)专门用于 NAND 管理,比如 UBI(Unsorted Block Images)
,具体如文件系统 ubifs
。
4.1.4 轻量级 NAND 坏块管理
对 NAND 进行管理的硬件或软件模块
,不仅提供坏块管理,同时也支持
对 NAND 的擦写操作进行负载均衡
。而轻量级的坏块管理
只专注于坏块,并不提供擦写负载平均的支持,而且,它也不依赖于任何第三方的库。因此,轻量级的坏块管理方式降低了系统的复杂度,而且免去了加载文件系统或初始化中间件的时间,在嵌入式系统中有着广泛的应用。
常见的轻量级 NAND 坏块管理方法有如下一些:
1. 跳过坏块:写入时跳过坏块,但不做记录,这样无法知道有哪些坏块,也不知道数据写入到了哪些块。
2. 替换表:保留一块好的区域,当写入时遇到坏块时,用保留区的块替换坏块来写入数据。
3. BBT(Bad Block Table):写入时跳过坏块,并记录坏块信息到 BBT(Bad Block Table) 表中。
4.1.5 NAND 坏块管理方法小结
如果需要频繁地对 NAND 写入各种数据,最好使用 NAND 文件系统或者 NAND 管理中间件对需要写入的区域进行管理。而那些很少需要更新的区域,比如 bootloader 和 kernel,只需进行轻量级的坏块管理,不需要进行负载平均。很多的嵌入式系统中,需要写入 NAND 的数据量很少,频度也较低,比如路由器、打印机、PLC 等,这些系统完全可以仅使用轻量级的坏块管理方式。
4.2 NAND 坏块的 识别 和 标记
NAND 的存储空间,由一系列的 plane
组成;而每个 plane
,又由一系列的 block
组成;最后,每个 block
,又由一组 page
组成。每个 page
空间,包括 数据空间
和 Spare Area 空间
两部分。Spare Area
用来存储每个 page 的 坏块标记
和 数据的 ECC 校验码
。通常情况下,page 的 坏块标记
和 ECC 校验码
不会占满整个 Spare Area
,剩余的空间,可用作用户定制的用途,如 YAFFS2
文件系统,用 Spare Area
的剩余空间来存储文件系统相关信息。Spare Area
也被称为 OOB(Out Of Band)
。
对 NAND 坏块的识别,就是通过读取 Spare Area
存储的 page 的 坏块标记
,这些 page 的 坏块标记
的来源有两个:
1. NAND 厂家出厂的标记的坏块
2. 随着使用过程中,对设备的读写擦除等操作产生的坏块
对 NAND 坏块的标记,就是在 page 的 Spare Area
中的坏块标记区域写入相应的值,具体的细节,我们在后面对 U-Boot 中坏块标记流程分析中会看到。
4.3 U-Boot 的 NAND 坏块管理
在 U-Boot 中执行 nand bad, nand read, nand write
命令时,如果发现没有建立 NAND 坏块信息,如果没有设置 NAND_SKIP_BBTSCAN
标志位,会建立 NAND 坏块信息表 BBT(Bad Block Table)
。 BBT(Bad Block Table)
建立过程都是从 include/nand.h
中的函数 nand_block_isbad()
发起, nand_block_isbad()
是用来判定 NAND 的某个位置是否是一个坏块,其判定的方法有两种:第一,没有设定 NAND_SKIP_BBTSCAN
标志位的情形下,在内存中建立整个 NAND 所有坏块的 BBT 表(仅第一次触发
),然后从 BBT
表的信息,来判定某个位置是不是一个坏块;第二,设定了 NAND_SKIP_BBTSCAN
标志位的情形下,直接调用 NAND 设备的坏块判定接口。显然,第一种方法要更快,因为它不用每次都去访问 NAND 设备的 Spare Area
。下面来看细节:
/* include/nand.h */
static inline int nand_block_isbad(nand_info_t *info, loff_t ofs)
{
return mtd_block_isbad(info, ofs);
}
mtd_block_isbad() /* drivers/mtd/mtdcore.c */
mtd->_block_isbad(mtd, ofs);
nand_block_isbad() /* drivers/mtd/nand/nand_base.c */
nand_block_checkbad(mtd, offs, 1, 0);
struct nand_chip *chip = mtd->priv;
if (!(chip->options & NAND_SKIP_BBTSCAN) &&
!(chip->options & NAND_BBT_SCANNED)) {
/* 第一次扫描坏块 */
chip->options |= NAND_BBT_SCANNED; /* 标记已经扫描过坏块了 */
chip->scan_bbt(mtd); /* 创建 NAND 坏块表: 扫描检查, 并记录坏块信息到 BBT */
nand_default_bbt()
...
/* Is a flash based bad block table requested? */
if (this->bbt_options & NAND_BBT_USE_FLASH) {
...
} else {
this->bbt_td = NULL;
this->bbt_md = NULL;
}
...
/* 创建 NAND 坏块表 BBT: 扫描检查, 并记录坏块信息到 BBT */
return nand_scan_bbt(mtd, this->badblock_pattern);
...
/* 分配 BBT(Bad Block Table) */
this->bbt = kzalloc(len, GFP_KERNEL);
...
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; /* 返回 */
}
...
}
if (!chip->bbt) /* 如果没有建立 BBT(如设置 NAND_SKIP_BBTSCAN 标记), */
return chip->block_bad(mtd, ofs, getchip); /* 直接调用 NAND 芯片的坏块判定接口 */
/* Return info from the table */
return nand_isbad_bbt(mtd, ofs, allowbbt); /* 查询 NAND 的 BBT 表(在内存中), 看位于 @ofs 位置的 block 是不是坏块 */
res = nand_memory_bbt(mtd, bd) /* drivers/mtd/nand/nand_bbt.c */
struct nand_chip *this = mtd->priv;
return create_bbt(mtd, this->buffers->databuf, bd, -1);
...
pr_info("Scanning device for bad blocks\n");
...
/* NAND_BBT_SCAN2NDPAGE 指示从 block 的 第1 或 第2 个 page 的 Spare Area 读取坏块标记 */
if (bd->options & NAND_BBT_SCAN2NDPAGE)
numpages = 2;
else /* 其它情形 block 坏块标记 存在 block 某个 page 的 Spare Area */
numpages = 1;
if (chip == -1) { /* 默认选择 第1个 chip */
numblocks = mtd->size >> this->bbt_erase_shift; /* 计算每个 chip 包含的 block 数目 */
/* 从第1个 chip 开始 */
startblock = 0;
from = 0;
} else { /* 选择 @chip 指定的 chip */
...
numblocks = this->chipsize >> this->bbt_erase_shift; /* 计算每个 chip 包含的 block 数目 */
/* 跳过前面的 @chip 个 chip: 每个 chip 都是 @numblocks 个 block */
startblock = chip * numblocks;
/* 第 @chip 个 chip 的 第一个 block 编号: 从第 @chip 个 chip 开始 */
from = (loff_t)startblock << this->bbt_erase_shift;
}
/*
* NAND_BBT_SCANLASTPAGE 标记指示:
* 坏块标记 存储在每个 block 最后一个 page 的 Spare Area
*/
if (this->bbt_options & NAND_BBT_SCANLASTPAGE)
from += mtd->erasesize - (mtd->writesize * numpages);
/* 检查并记录坏块信息到 BBT */
for (i = startblock; i < numblocks; i++) {
int ret;
/* 检查 block 包含页面的 Spare Area, 确定 block 是不是 坏块 */
ret = scan_block_fast(mtd, bd, from, buf, numpages);
...
if (ret) { /* 检查到坏块, 记录坏块信息到 BBT */
/*
* 记录坏块信息到 BBT: BBT 表中对应块位置, 标记为 BBT_BLOCK_FACTORY_BAD (0x03).
* 每个 block 占据 BBT 表的 2-bit
*/
bbt_mark_entry(this, i, BBT_BLOCK_FACTORY_BAD);
uint8_t msk = (mark & BBT_ENTRY_MASK) << ((block & BBT_ENTRY_MASK) * 2);
chip->bbt[block >> BBT_ENTRY_SHIFT] |= msk;
...
}
from += (1 << this->bbt_erase_shift); /* 移动到下一块 */
}
/* 检查 block 包含的页面, 确定 block 是不是 坏块 */
ret = scan_block_fast(mtd, bd, from, buf, numpages);
struct mtd_oob_ops ops;
int j, ret;
ops.ooblen = mtd->oobsize;
ops.oobbuf = buf;
ops.ooboffs = 0;
ops.datbuf = NULL;
ops.mode = MTD_OPS_PLACE_OOB;
/*
* Micron MT29F2G08AAD 检查头两个 page (@numpages==2) 的 Spare Area.
* 不同厂家有不一样的 坏块标记位置 规则.
*/
for (j = 0; j < numpages; j++) {
ret = mtd_read_oob(mtd, offs, &ops); /* 读取 page 的 Spare Area */
/* Ignore ECC errors when checking for BBM */
if (ret && !mtd_is_bitflip_or_eccerr(ret))
return ret; /* NAND 错误: bit flip(位翻转) 或 ECC 错误 */
/*
* 检测 page 的 Spare Area, 看是不是坏块。这个具体的匹配规则,每家芯片存在差异。
* 如 Micron MT29F2G08AAD,如果不匹配模式 (0xFF,0xFF), 则说明是标记的坏块。
*/
if (check_short_pattern(buf, bd))
return 1;
offs += mtd->writesize; /* 移动到下一个 page */
}
return 0; /* 非坏块 */
5. 参考资料
[1] Nand Flash基础知识与坏块管理机制的研究
[2] 遇到Nand Flash坏块如何处理
[3] 【NAND文件系统】UBI介绍