U-Boot: NAND 驱动简介

1. 前言

限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。

2. NAND 初始化

下面以 Micron MT29F2G08AAD 型号的 Nand Flash 为例,说明 ARMv7 架构下 U-BootNand Flash 设备的初始化过程。首先,可以将相关代码划分为 U-Boot NAND 驱动硬件无关通用部分硬件相关的 NAND FLASH 以及其 控制器 驱动 两部分。另外,抽象层次上,U-BootNor 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 文件系统的坏块管理

JFFS2YAFFS2FlashFx 这些专门针对 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介绍

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值