Linux Mtd子系统6(基于Linux6.6)---nand controller驱动开发介绍
一、nand controller驱动开发流程说明
- 创建一个platform driver驱动,以便实现nand_controller芯片相关私有数据与驱动的分离(关于platform模型的使用方式以及好处,请参考我之前写的文章)。
- 在platform的probe接口中,开始进行struct nand_chip类型变量内存空间的申请及初始化,需要设置的内容如下:
- 设置nand_chip的IO_ADDR_R、IO_ADDR_W地址,该地址主要用于nanflash数据的读写操作(如在nand_chip->read_buf、nand_chip->write_buf等接口中);
- 设置nand_chip->ecc变量,主要设置ecc及其相关的读写接口,若该nand_controller支持硬件ecc,则需要根据platform_device中传递的resource信息,设置硬件ecc类型,并设置ecc使能hwctl、ecc校验接口calculate、ecc纠错接口correct、read_page_raw、write_page_raw、read_page、read_subpage、write_subpage、write_page、write_oob_raw、read_oob_raw、read_oob、write_oob等函数指针,并设置每页数据执行ecc校验的次数、单次进行数据ecc计算的字节数、产生的ecc字节数等(如ti omap2的nand chip驱动,针对硬件bch4、bch8,其设置的接口为omap_elm_correct_data、omap3_calculate_ecc_bch、omap_read_page_bch、omap_write_page_bch、omap3_enable_hwecc_bch、nand_chip.ecc.size、nand_chip.ecc.mode、nand_chip.ecc.strength、nand.ecc.bytes);
- 设置与nandflash进行命令通信的函数指针,针对ti omap2的nand chip驱动而言,其设置如下nand.cmd_ctrl = omap_hwcontrol;
- 设置与nanflash进行数据通信的接口以及坏块标记以及芯片选择相关的接口,这主要涉及到如下一些函数指针的设置read_byte、read_word、write_buf、read_buf、block_bad、block_markbad、select_chip、block_bad、erase_cmd、scan_bbt、errstat等接口(这些接口不是需要全部设置的,在ti omap2的驱动中,仅设置了dev_ready、read_buf、write_buf等接口,而对其他接口并没有设置,那这些接口在哪里设置呢?在上一章我们介绍的nand_scan_ident、nand_scan_tail接口中,针对nand_chip中未进行初始化的函数指针,会设置nand驱动模块定义的通用接口)。
- 设置oob中存储ecc的字节数以及每一个ecc字节在oob中的偏移位置;
- 调用nand_scan_ident、nand_scan_tail接口,完成nand_chip中page size、block size、erase size、nand flash size等参数的设置,并针对nand_chip中尚未设置的函数指针的设置,并完成该nand_flash对应的mtd_info类型变量中flash参数的设置以及其对应的mtd接口的设置(针对这些内容已在上一篇文章中介绍,此处不再赘述)
- 调用mtd_device_parse_register接口,完成该nandflash各分区的对应的mtd_info的注册(若没有对nandflash分区,则将该master mtd_info注册至系统中,也就是完成mtd_info对应的额块设备、字符设备的注册等等,这些内容已在上几篇文章中说明)。
完成以上内容即可完成nand_controller的驱动开发。
具体的举例说明:
以下是开发 Linux NAND 控制器驱动的一般流程:
1. 硬件分析与文档研究
在开始开发之前,需要充分了解硬件平台和 NAND 控制器的规格,包括:
- 控制器硬件接口(如:SPI, parallel NAND)
- NAND 存储设备的特性(如:页大小、块大小、OOB 大小、ECC 支持等)
- 控制器的寄存器映射和功能
- 控制器的时序要求、时钟管理、DMA 支持等
这一步的核心是根据硬件文档、数据手册,或硬件描述文件(如 DTS 文件)深入了解硬件特性,并为后续开发做准备。
2. 建立基本的驱动框架
首先需要为 NAND 控制器驱动建立一个基本框架。这个框架包括:
- 驱动文件:在 Linux 中,NAND 驱动通常会放在
drivers/mtd/nand
目录下。可以根据目标硬件建立新的子目录。 - 结构体定义:定义与硬件相关的结构体(如
nand_chip
和nand_controller
),这些结构体将用于存储硬件信息和控制寄存器。 - 驱动初始化:编写驱动的初始化函数,设置基本的硬件配置,如控制器寄存器、时钟源、引脚配置等。
3. 配置 NAND 控制器的硬件寄存器
NAND 控制器通常会有一些寄存器,用于配置控制器的时序、读取和写入操作,或者启动 NAND Flash 操作(如擦除、编程、读取等)。这一步需要通过 I/O 寄存器访问 API(如 readl()
和 writel()
)来编写硬件寄存器的初始化代码。
在这个阶段,通常需要关注以下几个方面:
- 设置时序控制:控制器可能需要一些特定的时序信号,如 R/B 信号、CE 信号等。
- 配置 ECC(错误检测与校正):大多数 NAND 控制器会集成硬件 ECC 引擎,开发时需要设置 ECC 校验模式。
- 配置中断:如果 NAND 控制器支持中断处理,可以设置中断服务程序来处理操作完成后的通知。
4. 实现低级读写操作
在这一步中,需要实现与 NAND Flash 交互的低级操作,最基本的操作包括:
- 页读操作:从 NAND Flash 的指定页面读取数据。
- 页写操作:向 NAND Flash 的指定页面写入数据。
- 块擦除操作:对整个块进行擦除。
这些操作通常需要通过编写相应的函数来完成,驱动中会涉及到的常见操作包括:
- 向控制器发送命令(如读取命令、写入命令、擦除命令)。
- 通过轮询或中断方式检查命令是否完成。
- 处理 ECC 校验和错误恢复(如果启用了 ECC)。
示例代码:
static int nand_read_page(struct nand_chip *chip, uint32_t page, uint8_t *buf)
{
// 设置命令寄存器
writel(NAND_CMD_READ_PAGE, chip->regs + NAND_CMD_REG);
writel(page, chip->regs + NAND_PAGE_REG);
// 启动 NAND 读取过程
writel(NAND_CMD_START, chip->regs + NAND_START_REG);
// 等待 NAND 读取完成
while (readl(chip->regs + NAND_STATUS_REG) & NAND_STATUS_BUSY)
;
// 将数据读取到缓冲区
memcpy(buf, (void *)chip->regs + NAND_DATA_REG, chip->page_size);
return 0;
}
5. 实现 NAND 驱动的高级接口
Linux 内核的 NAND 驱动框架是基于 nand_chip
结构体的,通过实现标准的 NAND 操作接口来提供给更高层的调用。常见的接口包括:
- read:实现从 NAND 设备读取数据。
- write:实现向 NAND 设备写入数据。
- erase:实现擦除操作。
- block_isbad 和 block_markbad:实现坏块管理。
例如,NAND 驱动中的 nand_read
函数负责调用低级读操作并处理错误:
int nand_read(struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, uint8_t *buf)
{
struct nand_chip *chip = mtd_to_nand(mtd);
int page = from / chip->page_size;
int offset = from % chip->page_size;
// 调用硬件的读接口
nand_read_page(chip, page, buf);
*retlen = len;
return 0;
}
6. 坏块管理
NAND 存储设备会有坏块,这些坏块在制造过程中可能出现故障,因此需要管理坏块的检测和标记。通常,NAND 驱动会在 OOB 区域或指定位置存储坏块信息。
- 坏块检测:通过读取 OOB 区域的特定位置来检测坏块。
- 坏块标记:将坏块信息写入 OOB 区域,并在系统中标记该块为坏块,避免再次使用。
代码示例:
int nand_block_isbad(struct nand_chip *chip, uint32_t block)
{
uint8_t oob_buf[chip->oob_size];
// 读取 OOB 区域
if (nand_read_oob(chip, block, oob_buf)) {
return -1; // 读取失败
}
// 检查 OOB 区域的坏块标记
if (oob_buf[NAND_BAD_BLOCK_POS] == NAND_BAD_BLOCK_MARKER)
return 1; // 坏块
return 0; // 正常块
}
int nand_block_markbad(struct nand_chip *chip, uint32_t block)
{
uint8_t oob_buf[chip->oob_size];
// 读取 OOB 区域
if (nand_read_oob(chip, block, oob_buf))
return -1;
// 标记为坏块
oob_buf[NAND_BAD_BLOCK_POS] = NAND_BAD_BLOCK_MARKER;
// 写回 OOB 区域
return nand_write_oob(chip, block, oob_buf);
}
7. 中断处理与错误恢复
NAND 控制器通常支持中断机制,用于通知操作完成。编写中断处理程序,以便在操作完成后正确处理数据或启动下一步操作。
同时,需要处理常见的错误情况,如:
- NAND Flash 读取或写入超时
- ECC 校验失败
- 坏块管理和跳过坏块
8. 集成与测试
- 集成驱动:将驱动集成到 Linux 内核,并将驱动与硬件进行绑定。
- 功能测试:进行常规的读取、写入、擦除操作,验证是否正常工作。
- 性能测试:根据需要对性能进行测试,优化驱动代码。
- 回归测试:确保不会对其他功能产生负面影响。
9. 提交与维护
- 将开发好的 NAND 控制器驱动提交到 Linux 内核树中,通常提交到
drivers/mtd/nand
目录。 - 在开发过程中,继续修复可能出现的 bug,提升性能,并跟进硬件平台的升级或变化。
二、Nandflash适配注意事项说明
2.1、Nandflash参数设置说明
在进行开发板的适配时,若需要适配的nandflash不支持onfi规范,则需要在变量struct nand_flash_dev nand_flash_ids[] 中检测下所使用的的nandflash相关的参数是否已在定义中,若没有添加即可,添加规则如下图所示。
drivers/mtd/nand/raw/nand_ids.c
/*
* The chip ID list:
* name, device ID, page size, chip size in MiB, eraseblock size, options
*
* If page size and eraseblock size are 0, the sizes are taken from the
* extended chip ID.
*/
struct nand_flash_dev nand_flash_ids[] = {
/*
* Some incompatible NAND chips share device ID's and so must be
* listed by full ID. We list them first so that we can easily identify
* the most specific match.
*/
{"TC58NVG0S3E 1G 3.3V 8-bit",
{ .id = {0x98, 0xd1, 0x90, 0x15, 0x76, 0x14, 0x01, 0x00} },
SZ_2K, SZ_128, SZ_128K, 0, 8, 64, NAND_ECC_INFO(1, SZ_512), },
{"TC58NVG2S0F 4G 3.3V 8-bit",
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x15, 0x01, 0x08} },
SZ_4K, SZ_512, SZ_256K, 0, 8, 224, NAND_ECC_INFO(4, SZ_512) },
{"TC58NVG2S0H 4G 3.3V 8-bit",
{ .id = {0x98, 0xdc, 0x90, 0x26, 0x76, 0x16, 0x08, 0x00} },
SZ_4K, SZ_512, SZ_256K, 0, 8, 256, NAND_ECC_INFO(8, SZ_512) },
{"TC58NVG3S0F 8G 3.3V 8-bit",
{ .id = {0x98, 0xd3, 0x90, 0x26, 0x76, 0x15, 0x02, 0x08} },
SZ_4K, SZ_1K, SZ_256K, 0, 8, 232, NAND_ECC_INFO(4, SZ_512) },
{"TC58NVG5D2 32G 3.3V 8-bit",
{ .id = {0x98, 0xd7, 0x94, 0x32, 0x76, 0x56, 0x09, 0x00} },
SZ_8K, SZ_4K, SZ_1M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) },
{"TC58NVG6D2 64G 3.3V 8-bit",
{ .id = {0x98, 0xde, 0x94, 0x82, 0x76, 0x56, 0x04, 0x20} },
SZ_8K, SZ_8K, SZ_2M, 0, 8, 640, NAND_ECC_INFO(40, SZ_1K) },
{"SDTNQGAMA 64G 3.3V 8-bit",
{ .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x57} },
SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) },
{"SDTNRGAMA 64G 3.3V 8-bit",
{ .id = {0x45, 0xde, 0x94, 0x93, 0x76, 0x50} },
SZ_16K, SZ_8K, SZ_4M, 0, 6, 1280, NAND_ECC_INFO(40, SZ_1K) },
{"H27UCG8T2ATR-BC 64G 3.3V 8-bit",
{ .id = {0xad, 0xde, 0x94, 0xda, 0x74, 0xc4} },
SZ_8K, SZ_8K, SZ_2M, NAND_NEED_SCRAMBLING, 6, 640,
NAND_ECC_INFO(40, SZ_1K) },
{"H27UCG8T2ETR-BC 64G 3.3V 8-bit",
{ .id = {0xad, 0xde, 0x14, 0xa7, 0x42, 0x4a} },
SZ_16K, SZ_8K, SZ_4M, NAND_NEED_SCRAMBLING, 6, 1664,
NAND_ECC_INFO(40, SZ_1K) },
{"TH58NVG2S3HBAI4 4G 3.3V 8-bit",
{ .id = {0x98, 0xdc, 0x91, 0x15, 0x76} },
SZ_2K, SZ_512, SZ_128K, 0, 5, 128, NAND_ECC_INFO(8, SZ_512) },
{"TH58NVG3S0HBAI4 8G 3.3V 8-bit",
{ .id = {0x98, 0xd3, 0x91, 0x26, 0x76} },
SZ_4K, SZ_1K, SZ_256K, 0, 5, 256, NAND_ECC_INFO(8, SZ_512)},
LEGACY_ID_NAND("NAND 4MiB 5V 8-bit", 0x6B, 4, SZ_8K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE3, 4, SZ_8K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 4MiB 3,3V 8-bit", 0xE5, 4, SZ_8K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xD6, 8, SZ_8K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 8MiB 3,3V 8-bit", 0xE6, 8, SZ_8K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 16MiB 1,8V 8-bit", 0x33, 16, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 16MiB 3,3V 8-bit", 0x73, 16, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 16MiB 1,8V 16-bit", 0x43, 16, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 16MiB 3,3V 16-bit", 0x53, 16, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 32MiB 1,8V 8-bit", 0x35, 32, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 32MiB 3,3V 8-bit", 0x75, 32, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 32MiB 1,8V 16-bit", 0x45, 32, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 32MiB 3,3V 16-bit", 0x55, 32, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 64MiB 1,8V 8-bit", 0x36, 64, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 64MiB 3,3V 8-bit", 0x76, 64, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 64MiB 1,8V 16-bit", 0x46, 64, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 64MiB 3,3V 16-bit", 0x56, 64, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x78, 128, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 128MiB 1,8V 8-bit", 0x39, 128, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 128MiB 3,3V 8-bit", 0x79, 128, SZ_16K, SP_OPTIONS),
LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x72, 128, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 128MiB 1,8V 16-bit", 0x49, 128, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x74, 128, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 128MiB 3,3V 16-bit", 0x59, 128, SZ_16K, SP_OPTIONS16),
LEGACY_ID_NAND("NAND 256MiB 3,3V 8-bit", 0x71, 256, SZ_16K, SP_OPTIONS),
/*
* These are the new chips with large page size. Their page size and
* eraseblock size are determined from the extended ID bytes.
*/
/* 512 Megabit */
EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA2, 64, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64MiB 1,8V 8-bit", 0xA0, 64, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF2, 64, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xD0, 64, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64MiB 3,3V 8-bit", 0xF0, 64, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB2, 64, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 64MiB 1,8V 16-bit", 0xB0, 64, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC2, 64, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 64MiB 3,3V 16-bit", 0xC0, 64, LP_OPTIONS16),
/* 1 Gigabit */
EXTENDED_ID_NAND("NAND 128MiB 1,8V 8-bit", 0xA1, 128, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xF1, 128, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 128MiB 3,3V 8-bit", 0xD1, 128, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xB1, 128, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 128MiB 3,3V 16-bit", 0xC1, 128, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 128MiB 1,8V 16-bit", 0xAD, 128, LP_OPTIONS16),
/* 2 Gigabit */
EXTENDED_ID_NAND("NAND 256MiB 1,8V 8-bit", 0xAA, 256, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 256MiB 3,3V 8-bit", 0xDA, 256, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 256MiB 1,8V 16-bit", 0xBA, 256, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 256MiB 3,3V 16-bit", 0xCA, 256, LP_OPTIONS16),
/* 4 Gigabit */
EXTENDED_ID_NAND("NAND 512MiB 1,8V 8-bit", 0xAC, 512, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 512MiB 3,3V 8-bit", 0xDC, 512, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 512MiB 1,8V 16-bit", 0xBC, 512, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 512MiB 3,3V 16-bit", 0xCC, 512, LP_OPTIONS16),
/* 8 Gigabit */
EXTENDED_ID_NAND("NAND 1GiB 1,8V 8-bit", 0xA3, 1024, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 1GiB 3,3V 8-bit", 0xD3, 1024, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 1GiB 1,8V 16-bit", 0xB3, 1024, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 1GiB 3,3V 16-bit", 0xC3, 1024, LP_OPTIONS16),
/* 16 Gigabit */
EXTENDED_ID_NAND("NAND 2GiB 1,8V 8-bit", 0xA5, 2048, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 2GiB 3,3V 8-bit", 0xD5, 2048, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 2GiB 1,8V 16-bit", 0xB5, 2048, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 2GiB 3,3V 16-bit", 0xC5, 2048, LP_OPTIONS16),
/* 32 Gigabit */
EXTENDED_ID_NAND("NAND 4GiB 1,8V 8-bit", 0xA7, 4096, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 4GiB 3,3V 8-bit", 0xD7, 4096, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 4GiB 1,8V 16-bit", 0xB7, 4096, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 4GiB 3,3V 16-bit", 0xC7, 4096, LP_OPTIONS16),
/* 64 Gigabit */
EXTENDED_ID_NAND("NAND 8GiB 1,8V 8-bit", 0xAE, 8192, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 8GiB 3,3V 8-bit", 0xDE, 8192, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 8GiB 1,8V 16-bit", 0xBE, 8192, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 8GiB 3,3V 16-bit", 0xCE, 8192, LP_OPTIONS16),
/* 128 Gigabit */
EXTENDED_ID_NAND("NAND 16GiB 1,8V 8-bit", 0x1A, 16384, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 16GiB 3,3V 8-bit", 0x3A, 16384, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 16GiB 1,8V 16-bit", 0x2A, 16384, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 16GiB 3,3V 16-bit", 0x4A, 16384, LP_OPTIONS16),
/* 256 Gigabit */
EXTENDED_ID_NAND("NAND 32GiB 1,8V 8-bit", 0x1C, 32768, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 32GiB 3,3V 8-bit", 0x3C, 32768, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 32GiB 1,8V 16-bit", 0x2C, 32768, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 32GiB 3,3V 16-bit", 0x4C, 32768, LP_OPTIONS16),
/* 512 Gigabit */
EXTENDED_ID_NAND("NAND 64GiB 1,8V 8-bit", 0x1E, 65536, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64GiB 3,3V 8-bit", 0x3E, 65536, LP_OPTIONS),
EXTENDED_ID_NAND("NAND 64GiB 1,8V 16-bit", 0x2E, 65536, LP_OPTIONS16),
EXTENDED_ID_NAND("NAND 64GiB 3,3V 16-bit", 0x4E, 65536, LP_OPTIONS16),
{NULL}
};
若nandflash支持onfi接口,则无须在nand_flash_ids中设置相关参数,由nand驱动模型中的nand_flash_detect_onfi函数即可通过与nandflash通信,获取writesize、erasesize、oobsize、chipsize等信息即可。
2.2、Nandflash对应分区属性设置
在 Linux 中,NAND Flash 分区的属性设置通常是通过 MTD (Memory Technology Devices) 子系统来管理的。MTD 子系统提供了对 Flash 存储设备的访问接口,包括 NAND、NOR Flash 等。要正确配置 NAND Flash 的分区属性,你需要为每个分区设置相应的特性、大小、偏移量以及存储方式。以下是如何在 Linux 中设置 NAND Flash 分区属性的详细步骤。
1. 理解 NAND Flash 和 MTD 分区
MTD 设备通过分区和设备节点来管理 Flash 存储。每个 MTD 设备可以被划分为多个分区,每个分区可以指定大小、偏移量、存储特性等。
NAND Flash 在 MTD 中的分区通常包括:
- Block size:每个擦除块的大小(NAND Flash 的擦除单位,通常为 128KB、256KB、512KB 或 2MB 等)。
- Page size:每个页的大小(通常为 2KB、4KB、8KB、16KB 等)。
- OOB (Out-Of-Band) 区:用于存储冗余数据,如坏块标记和 ECC 校验。
2. MTD 分区配置
在 Linux 中,NAND Flash 的分区通常是通过设备树(DTS)或命令行(如 mtd
工具)进行配置的。
设备树配置 (DTS)
在设备树中,我们可以通过 mtd
节点来配置 NAND Flash 设备和其分区。分区配置通常是通过在设备树文件中定义 mtd
分区来实现的。
示例:
/ {
nand: nand@0 {
compatible = "foo,nand-controller";
reg = <0x0 0x10000000 0x0 0x1000000>;
#address-cells = <1>;
#size-cells = <1>;
status = "okay";
mtd-names = "nand0";
mtd@0 {
label = "boot";
reg = <0x0 0x400000>; // 分区的起始地址和大小
partition-size = <0x400000>;
read-only;
};
mtd@1 {
label = "rootfs";
reg = <0x400000 0x6000000>; // 起始地址和大小
partition-size = <0x6000000>;
};
};
};
在这个示例中:
mtd@0
和mtd@1
是定义的两个分区,分别用于boot
和rootfs
。reg
定义了分区的起始地址和大小。read-only
表示该分区为只读。
通过 mtd
命令行工具配置
可以通过 mtd
工具在命令行中配置 NAND Flash 分区。mtd
提供了许多有用的命令,如:
mtd_debug
用于调试flash_erase
用于擦除特定的 Flash 区域mtdinfo
用于显示 NAND Flash 设备的基本信息nandwrite
用于将数据写入 NAND Flash
配置 NAND 分区的命令通常是通过 U-Boot 或根文件系统中的脚本来完成的,或者在内核中使用 nand
设备驱动进行分区。
修改内核配置 (使用 CONFIG_MTD
配置项)
在内核配置中,需要启用 MTD 和 NAND 支持。你可以通过 make menuconfig
或 make xconfig
工具配置内核选项:
Device Drivers --->
[*] Memory Technology Device (MTD) support --->
[*] NAND Flash support
[*] NAND device driver
[*] NAND MTD device support
启用上述选项后,内核将支持 NAND Flash 存储和相关的 MTD 分区。
3. 配置分区的属性
在分区中,你可能需要配置以下几个关键属性:
- 大小 (size):分区的大小,通常以字节为单位。例如,4MB、8MB、16MB 等。
- 偏移量 (offset):分区的起始地址。此值通常基于 Flash 存储的起始位置。
- 类型 (type):分区的类型,例如
boot
、rootfs
、userdata
等。 - 只读/读写权限 (read-only):如果设置为只读,则该分区不能被写入。
- 坏块标记 (bad-block handling):NAND 存储常常有坏块,需要在分区中跳过或标记它们。
4. 坏块管理
由于 NAND Flash 的物理特性,NAND 存储通常有坏块,在分区时需要考虑坏块管理。内核的 MTD 子系统有内建的坏块管理机制,它会自动处理坏块。可以通过以下方式来启用坏块检测和处理:
自动检测和跳过坏块
内核的 MTD 子系统会在操作时自动检测坏块并跳过,但你也可以在 mtd
配置中使用坏块管理特性:
&nand {
bad-block-pos = <0x200>;
};
在这个配置中,bad-block-pos
定义了坏块标记的位置,通常是 OOB 区域中的某个位置。坏块标记通常会存储在 OOB 区域的特定位置,内核会根据这些标记来跳过坏块。
手动标记坏块
如果在开发过程中需要手动标记坏块,可以使用 flash_erase
或 nandwrite
等工具进行坏块标记。
5. 在用户空间使用分区
在 Linux 启动后,MTD 子系统将以块设备的形式挂载 NAND Flash 分区。这些分区通常会出现在 /dev/mtdblockX
或 /dev/mtdX
下,具体取决于内核配置。
例如,如果你在设备树中定义了 mtd@0
分区,内核启动时该分区会被挂载为 /dev/mtdblock0
。你可以通过常见的文件系统工具(如 mount
)或 MTD 特定工具来操作这些分区。
mount -t jffs2 /dev/mtdblock0 /mnt
2.3、Nandflash的oob设置
在 Linux 中,NAND Flash 的 OOB (Out-Of-Band) 区域 是一个存储冗余数据的区域,通常用于存储坏块标记、ECC 校验数据、标识符以及其他元数据。OOB 区域的具体用途和配置方式可能依赖于 NAND Flash 控制器的硬件特性、内核驱动以及使用的文件系统。
在 MTD (Memory Technology Device) 子系统中,OOB 区域的配置和管理通常是由 NAND 驱动程序处理的。下面是有关如何在 Linux 中配置和使用 NAND Flash 的 OOB 区域的一些重要细节。
1. OOB 区域的作用
NAND Flash 通常由多个页组成,每个页都包含两个区域:
- 数据区:存储实际的数据内容。
- OOB 区域:存储附加信息,如坏块标记、ECC 校验码、页标识符等。
每个页通常分为两个部分:数据部分和 OOB 部分。OOB 区域通常位于页的末尾,其大小和位置依赖于 NAND Flash 的具体型号。
OOB 区域的常见用途包括:
- 坏块标记:用于标记 NAND Flash 中的坏块。
- ECC (Error Correction Code) 校验码:用于纠正由于 NAND Flash 的物理特性导致的错误。
- 分页标识符和元数据:用于存储一些文件系统和驱动程序所需的元数据信息。
2. 如何配置 OOB 区域
在 Linux 中,OOB 区域的设置通常由 NAND Flash 驱动(如 nand
驱动或 mtd
驱动)控制。在内核中,OOB 区域的大小通常在驱动初始化时配置。通常情况下,OOB 区域的大小是固定的,但它可以通过设备树(DTS)或内核配置进行微调。
OOB 大小的配置
NAND Flash 的 OOB 区域的大小通常由 NAND 芯片的硬件规格决定,不同型号的 NAND Flash 的 OOB 区域大小可能不同。Linux 内核中的 NAND 驱动程序通常会根据芯片的特性来计算和设置 OOB 区域的大小。
在驱动程序中,OOB 区域的大小可以通过 nand_chip->oob_poi
和 nand_chip->oobsize
等参数来进行管理。oobsize
是表示 OOB 区域大小的一个重要参数。
设备树配置
在设备树中,你可以指定 NAND 控制器的某些属性,包括 OOB 区域的设置。以下是一个配置设备树的例子,其中配置了 NAND Flash 的 OOB 区域:
dts
&nand {
status = "okay";
reg = <0x0 0x20000000 0x0 0x1000000>;
compatible = "vendor,nand-flash";
nand-ecc-mode = "hw";
nand-oobsize = <224>; // 设置 OOB 大小为 224 字节
nand-page-size = <4096>; // 设置页大小为 4KB
};
在这个例子中,nand-oobsize
参数用于设置 OOB 区域的大小。请注意,不同的 NAND 控制器或芯片可能对 OOB 区域的配置要求不同,因此具体的配置需要参考芯片的数据手册。
内核代码中的 OOB 配置
在 Linux 内核代码中,OOB 区域的配置通常通过 mtd
子系统和相关的 NAND 驱动来完成。例如,nand_base.c
驱动程序负责初始化 NAND Flash 芯片及其特性。
内核中用于配置 NAND 驱动的代码可能如下所示:
static struct nand_chip nand_chip = {
.ecc.mode = NAND_ECC_HW,
.ecc.size = 512,
.oobsize = 224, // 配置 OOB 大小为 224 字节
.page_shift = 12, // 页大小为 4KB (2^12)
.page_size = 4096,
};
在这个例子中,oobsize
指定了 OOB 区域的大小。
3. 使用 OOB 区域
OOB 区域通常不直接由文件系统来访问,而是由底层的 NAND 驱动和文件系统共同管理。例如:
- 坏块管理:MTD 驱动会在 NAND Flash 中自动标记坏块,并在访问时跳过坏块。坏块信息通常存储在 OOB 区域中。
- ECC 校验:NAND 驱动程序可以使用 OOB 区域来存储 ECC 校验码,以便在读取数据时对数据进行纠错。
在 Linux 文件系统(如 JFFS2
、UBIFS
)中,通常会与 NAND 驱动紧密配合,利用 OOB 区域的元数据来执行错误检测和修正、坏块管理等操作。
4. 读取和写入 OOB 区域
操作 OOB 区域的最常见方式是通过 NAND 驱动提供的 API。例如,使用 nand_write_oob()
和 nand_read_oob()
函数来写入和读取 OOB 区域的数据。
读取 OOB 数据
要读取 OOB 区域的数据,可以使用 nand_read_oob()
函数。下面是一个示例:
int nand_read_oob(struct nand_chip *chip, unsigned int page, int block)
{
int ret;
unsigned char oob_data[chip->oobsize];
ret = chip->read_oob(chip, page, block, oob_data);
if (ret) {
pr_err("Failed to read OOB data from page %u\n", page);
return ret;
}
pr_info("OOB data: %*ph\n", chip->oobsize, oob_data);
return 0;
}
在此代码中,read_oob
函数会从指定页面读取 OOB 区域的数据。
写入 OOB 数据
同样的,nand_write_oob()
可以用来将数据写入 OOB 区域:
int nand_write_oob(struct nand_chip *chip, unsigned int page, int block, unsigned char *oob_data)
{
int ret;
ret = chip->write_oob(chip, page, block, oob_data);
if (ret) {
pr_err("Failed to write OOB data to page %u\n", page);
return ret;
}
pr_info("OOB data written successfully to page %u\n", page);
return 0;
}
这段代码演示了如何将数据写入 OOB 区域。