Linux Mtd子系统4(基于Linux6.6)---NANDFLASH驱动模型介绍
一、Nandflash概念
NAND Flash 是一种基于 NAND 逻辑门阵列的非易失性存储器,具有以下几个特点:
- 非易失性存储:即便电源断开,存储的数据仍然会被保留。
- 高密度存储:比传统的 NOR Flash 存储密度更高,常见于大容量存储设备中。
- 擦写操作:NAND Flash 无法逐字节地修改数据,只能按块(Block)擦除,因此写入操作需要通过一定的管理机制来优化。
- 页(Page)与块(Block)结构:NAND Flash 被组织为多个页(Page)和块(Block)。每个页通常有几千字节的容量(如2KB、4KB等),而一个块通常包含数十个页。一个块是擦除的基本单位。
- 耐用性:NAND Flash 的擦写次数是有限的,通常是数千到数万次,因此需要对擦写进行管理,避免对同一位置过度擦写,延长闪存的使用寿命。
NAND Flash 通常用于存储大量数据,如操作系统、应用程序、媒体文件等。与 NOR Flash 相比,NAND Flash 通常有更高的存储密度和更低的成本,因此更适合大容量存储应用。
MTD 子系统与 NAND Flash
在 Linux 中,MTD(Memory Technology Device) 子系统为 NAND Flash 和其他类型的存储设备提供了一致的访问接口。MTD 允许用户和内核模块对闪存设备进行原始的读写操作,并通过抽象化的接口屏蔽底层硬件的差异。
MTD 子系统的作用包括:
- 提供对闪存设备的统一接口,使操作系统可以进行读写、擦除等操作。
- 支持多种闪存类型,如 NAND Flash、NOR Flash、SPI Flash 等。
- 管理闪存的生命周期(如擦写次数、坏块管理等)。
二、Nandlfash及Nand controller的特点
从nandflash自身特性关注以下几点:
- nandflash存在bit翻转的情况,因此需要相应的纠错算法,而纠错与检测操作由具体的nandflash controller实现,可以实现硬件纠错(如汉明纠错、bch4、bch8纠错算法等),也可使用软件纠错算法;
- Nandflash controller与nandflash之间通信命令是统一的,即针对一个nandflash controller可以与任意一个nandflash通信;
- 针对每一个nandflash controller,访问该nandflash controller的方式有所不同,如寄存器定义、读写地址等有所不同;
- nandflash的擦除次数由显示(10万次),因此在使用过程中,存在坏块。
Linux NAND 控制器的主要特点如下:
1. 支持多种 NAND Flash 类型
Linux 内核支持多种类型的 NAND Flash 存储,包括 SLC(单级单元)、MLC(多级单元)、TLC(三层单元)、QLC(四层单元)等。Linux 控制器通过抽象层提供对这些不同类型 NAND 存储的兼容性。
2. 内核中的 NAND 驱动支持
在 Linux 内核中,NAND 控制器通常通过专门的 NAND 驱动(NAND driver) 来与硬件交互。内核中的 NAND 驱动包括对硬件的初始化、坏块管理、擦写操作、读取操作等基本功能的支持。常见的驱动包括:
- MTD(Memory Technology Device)子系统:Linux 使用 MTD 子系统来处理 NAND Flash 存储。MTD 为存储提供统一的接口,使得多种 NAND Flash 类型可以在同一平台上被支持。
- NAND Flash 驱动程序:包括特定硬件的驱动程序,如 omap2-nand(用于基于 OMAP 的平台)和 atmel-nand(用于 Atmel NAND 控制器)等。
3. 坏块管理(Bad Block Management, BBM)
由于 NAND Flash 的存储特性,某些块可能会损坏。Linux 内核通过坏块管理机制来检测和避免这些坏块,从而确保数据不会写入无法正常工作的块。内核中的 NAND 驱动会在初始化时扫描块,标记坏块,并跳过这些块以保证数据完整性。
4. 磨损平衡(Wear Leveling)
Linux 的 NAND 控制器通常支持 全局磨损平衡(Global Wear Leveling) 和 局部磨损平衡(Local Wear Leveling)。磨损平衡可以均匀地分配写入操作到 NAND Flash 的各个块上,以防止某些块因为频繁擦写而提前失效。
5. 垃圾回收(Garbage Collection)
垃圾回收是 NAND Flash 存储管理中的关键技术,用于清理无效数据并回收存储空间。Linux 的 NAND 驱动程序中通常包含垃圾回收算法,用于合并和擦除无效的块,确保在存储设备的写入操作中提供足够的可用空间。
6. 错误检测和纠正(ECC)
Linux 中的 NAND 控制器支持 错误检测与纠正(Error Correction Code, ECC),以确保数据的可靠性。NAND Flash 本身可能会由于擦写次数过多或电磁干扰导致数据出现错误。ECC 用于检测和纠正这些错误,以提高存储设备的可靠性。
7. 多通道并行操作支持
在高性能的 Linux NAND 控制器中,可能支持多通道并行写入或读取操作。Linux 内核中的驱动程序可以调度多个通道同时工作,提供更高的 I/O 吞吐量和更低的延迟。
8. 灵活的存储层次结构支持
Linux 对 NAND 控制器的支持不仅限于底层硬件的操作,还包括如何高效地组织和管理存储层次结构。例如,MTD 子系统和上层的 块设备(Block Devices) 接口允许开发者创建虚拟的磁盘驱动器(如 SSD),为用户空间应用提供可靠的存储抽象。
9. 性能优化和调度
Linux 提供了针对 NAND 存储的性能优化机制。通过合理的 I/O 调度算法,如 CFQ(Completely Fair Queuing)、deadline、noop 等,Linux 能够优化 NAND 控制器的性能。这些调度器可以根据系统负载和设备状态智能地调整读写顺序,减少延迟,优化吞吐量。
10. 支持多种闪存控制器硬件
Linux 支持多种不同厂商的 NAND 控制器硬件。例如,Linux 驱动程序通常包含对 Samsung、Micron、Hynix、Toshiba 等 NAND Flash 控制器的支持。这些控制器可能具有不同的功能和优化策略,Linux 内核通过通用接口提供对这些硬件的兼容性。
11. 闪存分区和文件系统支持
Linux 中的 NAND 控制器不仅限于提供硬件管理,它还允许通过特定的文件系统对 NAND Flash 进行分区和管理。例如,UBIFS(UBI File System) 和 JFFS2(Journaling Flash File System 2) 是针对 NAND Flash 存储优化的文件系统,它们为 NAND 存储提供了更高效的空间利用和数据恢复功能。
12. 低功耗支持
在嵌入式系统中,Linux 通常会优化 NAND 控制器的能效。通过适当的电源管理机制(如动态电压频率调整、低功耗模式等),Linux 可以最大限度地降低 NAND 控制器的功耗,延长设备的电池使用时间。
三、Linux nandflash驱动模块抽象
而针对nandflash驱动模块而言,基本上也就是按照上面的特点进行抽象,其驱动模型大概可分为如下几部分:
- 针对每一个nandflash controller,抽象结构体变量struct nand_chip;所有的接口以及变量均由该结构体去实现关联
- 基于nandflash controller与nandflash的通信命令是统一的,抽象出统一的读、写、擦除等接口,作为mtd_info的_write、_read、badblock等接口;
- 基于nandflash存在bit翻转且每一个nandcontroller实现的纠错算法不同,因此每一个nandflash controler需要实现自己的纠错接口、校验接口等;
- 针对每一个nandflash controller而言,需要提供访问该nandflash controller的接口(包括向nandflash发送命令的接口、读写数据接口(通过nandflash controller,间接实现访问nandflash))。
在驱动模块中,主要抽象了nand_chip结构体,以及对mtd层而抽象出来的统一的处理接口。下面是nand_chip结构体、对应的抽象接口以及mtd_info之间的关联关系图。下具体说明它们的关联,并与nandflash驱动模块进行关联说明。
- mtd_info->priv指向nand_chip,实现mtd与nand flash驱动模块的关联;
- nandflash驱动模型对上抽象出统一的接口,用于抽象针对nandflash的操作(与vfs接口抽象、mtd模块向上抽象统一读写接口类似),并赋值给mtd_info中对应的函数指针(即mtd_info的_erase、_write、_read等),为nand_write、nand_read、nand_read_oob等等,此处的读写数据接口,可读取任意长度的数据。
- 因nandflash存在纠错、校验等因此nandflash驱动模块又抽象针对ecc的结构体struct nand_ecc_ctrl,而该结构体中定义了读写一页数据的接口(针对nandflash而言,读写的单位为页,而擦除的单位为块大小),而不同的nandflash控制器,其支持的校验及纠错方式有所不同,因此这部分接口需要各nandflash控制器驱动实现。
- Nand_chip本身也提供了操作nandflash的接口,包括read_byte、read_buf、write_buf、block_bad等接口,这部分接口相对nand_ecc_ctrl的接口而言更加底层,实现一个字节或者多个字节的读写,同时也提供nand_chip芯片选择等接口以及命令控制相关的接口等。而这部分接口也是和nand_controller所息息相关的,因此也有相应的nand_controller驱动实现(当然若nand_controller没有特殊的操作,则可以使用nandflash模块已定义的通用接口)
通过以上几步即完成了mtd、nand_chip的关联,同时也就完成了mtd_write、mtd_read接口通过nand_write、mtd_read接口,然后再到nand_ecc_ctrl->read_page/write_page接口,最终调用nand_chip->read_buf/write_buf,实现与nandflash的读写通信。
四、Nandflash驱动模块相关的数据结构
针对nandflash驱动模块,几个数据结构nand_chip、nand_ecc_ctrl,下面详细说明下nandflash驱动模块相关的数据结构。针对linux内核各子模块而言,理解了其数据结构抽象,基本上也就对模块实现理解了60%左右,因此针对linux内核各模块的学习,强烈建议好好理解其相应的数据结构及它们之间的关联。nandflash驱动模块的结构体包括nand_chip、nand_onfi_params、nand_hw_control、nand_ecclayout、nand_ecc_ctrl、nand_hw_control、nand_bbt_descr等,这些结构体以nand_chip为主。
4.1、nand_chip
该结构体定义了nand_controller的属性以及接口,主要包括:
- 与nand_controller通信的地址空间(包括命令下发、读写操作的接口,即IO_ADDR_R/IO_ADDR_W);
- 通过nand_controller读写flash数据的接口(read_byte、read_word、write_buff、read_buff,芯片选择接口select_chip、坏块判断及坏块标记接口block_bad、block_markbad,向nandflash发送命令的接口cmd_ctrl、cmdfunc、waitfunc、erase_cmd,坏块表相关的接口scan_btt等、支持onfi规范的nandflash相关的操作接口及属性等);
- 芯片的个数、芯片的总大小、页大小、block大小、坏块标记在oob的位置及个数等
- oob中ecc位置及大小相关的结构体变量nand_ecclayout
- ecc纠错相关的接口及属性的结构体变量nand_ecc_ctrl。
include/linux/mtd/rawnand.h
struct nand_chip {
struct nand_device base;
struct nand_id id;
struct nand_parameters parameters;
struct nand_manufacturer manufacturer;
struct nand_chip_ops ops;
struct nand_legacy legacy;
unsigned int options;
/* Data interface */
const struct nand_interface_config *current_interface_config;
struct nand_interface_config *best_interface_config;
/* Bad block information */
unsigned int bbt_erase_shift;
unsigned int bbt_options;
unsigned int badblockpos;
unsigned int badblockbits;
struct nand_bbt_descr *bbt_td;
struct nand_bbt_descr *bbt_md;
struct nand_bbt_descr *badblock_pattern;
u8 *bbt;
/* Device internal layout */
unsigned int page_shift;
unsigned int phys_erase_shift;
unsigned int chip_shift;
unsigned int pagemask;
unsigned int subpagesize;
/* Buffers */
u8 *data_buf;
u8 *oob_poi;
struct {
unsigned int bitflips;
int page;
} pagecache;
unsigned long buf_align;
/* Internals */
struct mutex lock;
unsigned int suspended : 1;
wait_queue_head_t resume_wq;
int cur_cs;
int read_retries;
struct nand_secure_region *secure_regions;
u8 nr_secure_regions;
struct {
bool ongoing;
unsigned int first_page;
unsigned int pause_page;
unsigned int last_page;
} cont_read;
/* Externals */
struct nand_controller *controller;
struct nand_ecc_ctrl ecc;
void *priv;
};
4.2、nand_ecc_ctrl
该结构体变量的定义如下,主要定义了一个nand_controller所支持的ecc计算、校验及纠错相关的内容:
- 定义了ecc类型(无、软件ecc、硬件ecc等);
- 定义了一个page需要进行ecc计算的次数,每次ecc计算对应的数据大小(如每512字节进行一次ecc计算),每次ecc计算所产生的ecc字节数等等;
- 定义了ecc操作相关的接口(若开启ecc接口hwctl、计算ecc值接口calculate、纠错接口correct(主要用于读取数据时,若读取数据计算的ecc值与存储在oob中的ecc不匹配,则需要纠错,如bch8纠错算法,则每512字节可最多发现9bit错误,最大纠正8bit数据等),并提供了读写一页数据的接口,主要供mtd_info->_read/_write调用,即被nand_read、nand_write调用,还包括读写oob数据的接口,在之前我们说明oob主要存储ecc信息,但针对yaffs2文件系统,也存储文件系统相关的信息,因此针对挂载yaffs2文件系统而言,要在boot和kernel中确认好yaffs2文件系统信息存储在oob中的起始位置与大小,并保持一致)
include/linux/mtd/rawnand.h
struct nand_ecc_ctrl {
nand_ecc_modes_t mode;
enum nand_ecc_algo algo;
int steps;
int size;
int bytes;
int total;
int strength;
int prepad;
int postpad;
unsigned int options;
void *priv;
void (*hwctl)(struct mtd_info *mtd, int mode);
int (*calculate)(struct mtd_info *mtd, const uint8_t *dat,
uint8_t *ecc_code);
int (*correct)(struct mtd_info *mtd, uint8_t *dat, uint8_t *read_ecc,
uint8_t *calc_ecc);
int (*read_page_raw)(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int oob_required, int page);
int (*write_page_raw)(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int oob_required, int page);
int (*read_page)(struct mtd_info *mtd, struct nand_chip *chip,
uint8_t *buf, int oob_required, int page);
int (*read_subpage)(struct mtd_info *mtd, struct nand_chip *chip,
uint32_t offs, uint32_t len, uint8_t *buf, int page);
int (*write_subpage)(struct mtd_info *mtd, struct nand_chip *chip,
uint32_t offset, uint32_t data_len,
const uint8_t *data_buf, int oob_required, int page);
int (*write_page)(struct mtd_info *mtd, struct nand_chip *chip,
const uint8_t *buf, int oob_required, int page);
int (*write_oob_raw)(struct mtd_info *mtd, struct nand_chip *chip,
int page);
int (*read_oob_raw)(struct mtd_info *mtd, struct nand_chip *chip,
int page);
int (*read_oob)(struct mtd_info *mtd, struct nand_chip *chip, int page);
int (*write_oob)(struct mtd_info *mtd, struct nand_chip *chip,
int page);
};
4.3、nand_ecclayout
该结构体主要用于说明ecc值在oob中存储的位置及大小,在每一个nand_controller的驱动中,会定义不同page size对应的nand_ecclayout,用于指明该page size对应的oob中的ecc的位置信息及大小等。
- eccbytes表明了ecc数据的大小,这个针对选择的不同的纠错算法有所不同,比如bch4和bch8相比,每512字节所产生的ecc数据大小则是不同的(bch8产生的ecc大概为13bytes@per512bytes)。
- eccpos则用于说明每一个ecc对应的位置;
- oobavail则说明该oob除去ecc字节,所剩余的可用字节;
- oobfree则用于说明3中剩余可用字节的位置。
include/uapi/mtd/mtd-abi.h
struct nand_ecclayout_user {
__u32 eccbytes;
__u32 eccpos[MTD_MAX_ECCPOS_ENTRIES];
__u32 oobavail;
struct nand_oobfree oobfree[MTD_MAX_OOBFREE_ENTRIES];
};
该结构体变量是一个非常重要的变量,需要保证bootloader和kernel中定义大小及pos是完全相同的,否则则可能导致系统无法在kernel中使用。另外,若使用yaffs2文件系统,则在进行nandflash芯片选型时,需要确认nandflash的oob在存储完ecc后有足够的空间存储yaffs2文件系统相关数据的空间(虽然也可以将yaffs2文件系统相关的信息存储page中,但那样会导致系统的读写时间变慢)。
4.4、nand_hw_control
该结构体变量主要定义了锁、等待队列,主要用于nandflash操作临界问题的解决,如当一个nandcontroller处于读操作时,若系统又触发了针对该nandcontroller的写操作,则将该写操作放入等待队列中,当读操作完成后唤醒该等待队列实现写操作。
struct nand_hw_control {
spinlock_t lock;
struct nand_chip *active;
wait_queue_head_t wq;
};
nand_bbt_descr与nand_onfi_params主要用于坏块表和onfi规范的nandflash,针对坏块表,之前没有使用过,可以不使用坏块表,即使使用坏块表,也就使用内存坏块表即可,也没有必要在nandflash中预留一些块用于存储坏块表。而nand_onfi_params主要用于支持onfi规范的nandflash。
include/linux/mtd/onfi.h
struct nand_onfi_params {
/* rev info and features block */
/* 'O' 'N' 'F' 'I' */
u8 sig[4];
__le16 revision;
__le16 features;
__le16 opt_cmd;
u8 reserved0[2];
__le16 ext_param_page_length; /* since ONFI 2.1 */
u8 num_of_param_pages; /* since ONFI 2.1 */
u8 reserved1[17];
/* manufacturer information block */
char manufacturer[12];
char model[20];
u8 jedec_id;
__le16 date_code;
u8 reserved2[13];
/* memory organization block */
__le32 byte_per_page;
__le16 spare_bytes_per_page;
__le32 data_bytes_per_ppage;
__le16 spare_bytes_per_ppage;
__le32 pages_per_block;
__le32 blocks_per_lun;
u8 lun_count;
u8 addr_cycles;
u8 bits_per_cell;
__le16 bb_per_lun;
__le16 block_endurance;
u8 guaranteed_good_blocks;
__le16 guaranteed_block_endurance;
u8 programs_per_page;
u8 ppage_attr;
u8 ecc_bits;
u8 interleaved_bits;
u8 interleaved_ops;
u8 reserved3[13];
/* electrical parameter block */
u8 io_pin_capacitance_max;
__le16 sdr_timing_modes;
__le16 program_cache_timing_mode;
__le16 t_prog;
__le16 t_bers;
__le16 t_r;
__le16 t_ccs;
u8 nvddr_timing_modes;
u8 nvddr2_timing_modes;
u8 nvddr_nvddr2_features;
__le16 clk_pin_capacitance_typ;
__le16 io_pin_capacitance_typ;
__le16 input_pin_capacitance_typ;
u8 input_pin_capacitance_max;
u8 driver_strength_support;
__le16 t_int_r;
__le16 t_adl;
u8 reserved4[8];
/* vendor */
__le16 vendor_revision;
u8 vendor[88];
__le16 crc;
} __packed;