Linux Mtd子系统4

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自身特性关注以下几点:

  1. nandflash存在bit翻转的情况,因此需要相应的纠错算法,而纠错与检测操作由具体的nandflash controller实现,可以实现硬件纠错(如汉明纠错、bch4、bch8纠错算法等),也可使用软件纠错算法;
  2. Nandflash controller与nandflash之间通信命令是统一的,即针对一个nandflash controller可以与任意一个nandflash通信;
  3. 针对每一个nandflash controller,访问该nandflash controller的方式有所不同,如寄存器定义、读写地址等有所不同;
  4. 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驱动模块而言,基本上也就是按照上面的特点进行抽象,其驱动模型大概可分为如下几部分:

  1. 针对每一个nandflash controller,抽象结构体变量struct nand_chip;所有的接口以及变量均由该结构体去实现关联
  2. 基于nandflash controller与nandflash的通信命令是统一的,抽象出统一的读、写、擦除等接口,作为mtd_info的_write、_read、badblock等接口;
  3. 基于nandflash存在bit翻转且每一个nandcontroller实现的纠错算法不同,因此每一个nandflash controler需要实现自己的纠错接口、校验接口等;
  4. 针对每一个nandflash controller而言,需要提供访问该nandflash controller的接口(包括向nandflash发送命令的接口、读写数据接口(通过nandflash controller,间接实现访问nandflash))。

       在驱动模块中,主要抽象了nand_chip结构体,以及对mtd层而抽象出来的统一的处理接口。下面是nand_chip结构体、对应的抽象接口以及mtd_info之间的关联关系图。下具体说明它们的关联,并与nandflash驱动模块进行关联说明。

  1. mtd_info->priv指向nand_chip,实现mtd与nand flash驱动模块的关联;
  2. nandflash驱动模型对上抽象出统一的接口,用于抽象针对nandflash的操作(与vfs接口抽象、mtd模块向上抽象统一读写接口类似),并赋值给mtd_info中对应的函数指针(即mtd_info的_erase、_write、_read等),为nand_write、nand_read、nand_read_oob等等,此处的读写数据接口,可读取任意长度的数据。
  3. 因nandflash存在纠错、校验等因此nandflash驱动模块又抽象针对ecc的结构体struct nand_ecc_ctrl,而该结构体中定义了读写一页数据的接口(针对nandflash而言,读写的单位为页,而擦除的单位为块大小),而不同的nandflash控制器,其支持的校验及纠错方式有所不同,因此这部分接口需要各nandflash控制器驱动实现。
  4. 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的属性以及接口,主要包括:

  1. 与nand_controller通信的地址空间(包括命令下发、读写操作的接口,即IO_ADDR_R/IO_ADDR_W);
  2. 通过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相关的操作接口及属性等);
  3. 芯片的个数、芯片的总大小、页大小、block大小、坏块标记在oob的位置及个数等
  4. oob中ecc位置及大小相关的结构体变量nand_ecclayout
  5. 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计算、校验及纠错相关的内容:

  1. 定义了ecc类型(无、软件ecc、硬件ecc等);
  2. 定义了一个page需要进行ecc计算的次数,每次ecc计算对应的数据大小(如每512字节进行一次ecc计算),每次ecc计算所产生的ecc字节数等等;
  3. 定义了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的位置信息及大小等。

  1. eccbytes表明了ecc数据的大小,这个针对选择的不同的纠错算法有所不同,比如bch4和bch8相比,每512字节所产生的ecc数据大小则是不同的(bch8产生的ecc大概为13bytes@per512bytes)。
  2. eccpos则用于说明每一个ecc对应的位置;
  3. oobavail则说明该oob除去ecc字节,所剩余的可用字节;
  4. 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;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值