Linux Mtd子系统2(基于Linux6.6)---MTD设备驱动模型架构介绍
一、概述
在 Linux 内核中,MTD(Memory Technology Device) 是一个子系统,用于管理闪存设备(如 NAND、NOR Flash、EEPROM 等)。MTD 设备驱动模型提供了一种抽象机制,让操作系统能够以统一的方式访问不同类型的闪存设备,同时处理闪存的物理特性,如坏块管理、擦除、写入等。
MTD 设备驱动模型架构的主要目标是使不同类型的闪存设备(包括 NAND 和 NOR Flash)能够被支持并且以一致的方式进行访问,进而支持文件系统(如 JFFS2、UBIFS 等)对闪存存储设备的使用。
1. MTD 子系统架构
MTD 子系统提供了对闪存设备的底层抽象,它的设计目标是让不同类型的闪存设备通过统一的接口进行管理。MTD 本质上是一个虚拟的设备驱动框架,提供了对闪存硬件的接口,而具体的硬件控制是通过各个闪存驱动实现的。
主要组件:
- MTD 设备:每个支持的闪存设备都被表示为一个 MTD 设备。它提供了对闪存存储的原始访问接口(如擦除、写入、读取等)。
- MTD 驱动:MTD 驱动是具体的硬件驱动,负责与不同类型的闪存芯片(如 NAND、NOR)进行交互,提供底层硬件操作接口。
- MTD 子系统核心:负责将各种设备的操作抽象化并提供统一的接口供上层(如文件系统)访问。
组件架构:
+-------------------------+
| MTD Subsystem | <- 核心部分,提供对设备的抽象和管理
+-------------------------+
|
v
+---------------------------+
| MTD Device Interface | <- 提供访问和控制闪存设备的接口
+---------------------------+
|
v
+------------------------+ +------------------------+
| NAND/NOR Flash Driver| | Block/Raw Flash Driver |
+------------------------+ +------------------------+
|
v
+----------------------------+
| Flash Device (NAND/NOR) | <- 底层硬件(闪存芯片)
+----------------------------+
2. MTD 设备的基本结构
每个 MTD 设备都有一个数据结构,mtd_info
,它包含了与该设备相关的关键信息。这些信息包括设备的大小、擦除块大小、读写操作函数、坏块管理机制等。
mtd_info
结构体:
struct mtd_info {
const char *name; // 设备名称
size_t size; // 设备总大小
size_t erasesize; // 擦除块的大小
size_t writesize; // 写入操作的最小单位
int flags; // 设备的标志,表示其特性
int (*_erase)(struct mtd_info *mtd, struct erase_info *instr);
int (*_read)(struct mtd_info *mtd, uint8_t *buf, size_t len, loff_t from);
int (*_write)(struct mtd_info *mtd, const uint8_t *buf, size_t len, loff_t to);
...
};
在这个结构体中,_erase
、_read
、_write
等函数指针用来指定实际的硬件操作(擦除、读、写)的实现方式,这些操作通常由具体的硬件驱动来完成。
3. MTD 驱动层
MTD 驱动层负责与具体的硬件设备交互,提供对闪存芯片的原始读写接口。不同的闪存芯片(如 NAND 和 NOR)有不同的操作方式,MTD 驱动在硬件层面封装了这些差异,使得上层软件能够统一地操作它们。
- NAND 驱动:NAND Flash 驱动是 MTD 子系统中最复杂的一部分,因为 NAND Flash 需要处理坏块管理、页写、块擦除等复杂操作。
- NOR 驱动:NOR Flash 通常支持按字节或按块读取,驱动相对简单。
- Raw Flash 驱动:Raw Flash 是对原始闪存设备的直接访问,允许上层软件绕过文件系统直接与闪存交互。
4. MTD 操作函数
MTD 提供了一组标准的操作函数,以便上层软件进行文件操作或原始数据读写。以下是一些关键操作:
mtd_read
:从 MTD 设备读取数据。mtd_write
:向 MTD 设备写入数据。mtd_erase
:擦除 MTD 设备上的数据(通常以块为单位)。mtd_sync
:同步 MTD 设备的写操作,确保数据写入实际存储介质。
5. MTD 与文件系统的关系
MTD 设备本身并不提供文件系统功能,但它为文件系统提供了底层存储介质。Linux 支持多个专为闪存设备设计的文件系统,这些文件系统利用 MTD 设备提供的接口来管理闪存存储。常见的文件系统包括:
- JFFS2(Journaling Flash File System 2):主要用于小型闪存设备(如 NAND Flash)。
- UBIFS(UBI File System):更现代的文件系统,专为 NAND Flash 设计,提供更好的性能和可靠性。
- YAFFS2:另一种为 NAND Flash 设计的文件系统,具有较高的读写性能。
6. MTD 的管理和配置
MTD 子系统通常使用 mtd
命令行工具或 sysfs 来管理和配置 MTD 设备。在 Linux 系统中,MTD 设备通常出现在 /dev/mtdX
或 /dev/mtdblockX
中,mtdX
设备表示原始访问接口,mtdblockX
设备则提供块设备接口。
7. MTD 子系统的扩展
MTD 设备不仅可以用作文件存储,还可以与块设备接口集成,为更高级的存储应用提供支持。例如,UBI(Unsorted Block Images)是基于 MTD 提供的一个抽象层,用于管理 NAND Flash 的块设备访问。UBI 通过分层管理解决了 NAND Flash 中坏块的动态处理,支持更复杂的存储管理策略。
二、mtd设备驱动模型的架构说明
先说明mtd设备驱动模型的架构(即mtd设备驱动模型与外部模块间的联系,包括接口、数据结构之间的关联),对mtd设备驱动模型架构有一个感性认识的基础上,再分析mtd设备驱动模型相关的数据结构。
mtd驱动模型对上层的抽象以及对下层的抽象,都进行一一详细说明,如下为mtd设备驱动模型的架构,针对mtd设备驱动模型而言,主要包括接口抽象层、数据结构关联两部分。下面分别进行说明。
2.1、接口抽象层
- 针对接口抽象层,对于上层主要包括mtd_read、mtd_write、get_mtd_device、mtd_erase等接口。这些接口是对上层的抽象,主要供mtd 字符设备、mtd 块设备以及相应的闪存文件系统调用;
- mtd对下也做了抽象,为了能兼容nor flash、nandflash等闪存驱动,mtd也做了相应的抽象,而这些接口主要在struct mtd_info类型结构体中定义,主要包括_erase、_read、_write、_block_isbad、_block_markbad等接口;这些接口由具体闪存类型相关的驱动去实现,如针对nandflash驱动而言,这些接口即为nand_erase、nand_read、nand_write、nand_block_isbad、nand_block_markbad;而针对nor flash(cfi标准的norflash),则接口为cfi_amdstd_erase_varsize、cfi_amdstd_write_words、cfi_amdstd_read、cfi_amdstd_sync、cfi_amdstd_suspend、cfi_amdstd_resume等。
2.2、数据结构关联
针对mtd设备驱动层,主要涉及struct mtd_partition、struct mtd_part、struct mtd_info这几个主要的数据结构。
- struct mtd_partition用于进行闪存芯片的分区定义,针对不支持设备树的内核,则一般在开发板对应的板级文件中定义该结构体类型变量的定义,用于说明本芯片的分区情况;针对支持设备树的内核,一般在设备树文件中定义分区信息,然后在芯片对应的驱动文件中解析该分区定义;
- struct mtd_part,主要由mtd设备驱动模型内部使用的分区信息,该结构体中包括本分区对应的struct mtd_info类型的变量以及指向master mtd_info的指针。系统中所有已注册的struct mtd_part变量,均链接至链表mtd_partitions上。一般针对闪存芯片的操作接口(如mtd_info->_erase/_read/_write等),均在master mtd_info中定义。而在mtd_erase、mtd_read、mtd_write等对上层的接口中,根据传递的struct mtd_info类型变量,获取到对应的struct mtd_part类型变量,从而调用master mtd_info中对应的_erase、_read、_write等接口。
- struct mtd_info,该结构体是mtd设备驱动模型最主要的数据结构,通过该数据结构,对上完成与mtd接口层的关联;对下完成与具体类型闪存芯片驱动的关联(如针对nand flash controller driver,则通过mtd_info->priv=nand_chip,完成与nandflash controller driver的关联;针对nor flash,则同样通过mtd_info->priv=map_info完成关联;而针对其他类型的芯片,则同样是通过mtd_info->priv的关联),通过该结构体中的_erase、_read、_write等函数指针,完成针对下层设备驱动操作接口的抽象,完成对下层设备驱动接口的抽象模型的建立。
三、mtd设备驱动相关的数据结构说明
3.1、struct mtd_partition
在上面已经说了,该结构体主要用于定义分区的大小、偏移位置、是否只读等功能的结构体,请具体定义如下。请记住,该数据结构类型变量的定义一般在板级文件或者在flash设备驱动文件进行mtd_info分区的注册时使用。属于mtd设备驱动模型对外的数据结构。
include/linux/mtd/partitions.h
struct mtd_partition {
char *name; /* identifier string */
uint64_t size; /* partition size */
uint64_t offset; /* offset within the master MTD space */
uint32_t mask_flags; /* master MTD flags to mask out for this partition */
};
3.2、struct mtd_part
该结构体用于mtd设备驱动模型内部进行mtd设备分区所用,该数据结构类型变量不对外部开放。系统中所有已注册的mtd分区设备,均链接至全局链表mtd_partitions上。
include/linux/mtd/mtd.h
/* Our partition node structure */
struct mtd_part {
struct mtd_info mtd;
struct mtd_info *master;
uint64_t offset;
struct list_head list;
};
3.3、struct mtd_info
该数据结构为mtd设备驱动模型的关键,其定义的变量也比较多,下面我们从几个方面进行说明,并联合其他数据结构,说明其中的关联;
- 定义mtd设备类型、总大小、写单位、擦除单位、index等等信息;
- 抽象的闪存芯片的操作接口(读写擦除等接口)
- 提供priv指针,指向该mtd设备的私有信息,若mtd设备需要特殊的处理相关的变量,则可以将该priv指向对应的内存,如针对nandflash controller驱动而言,则通过该priv指针实现nand_chip与mtd_info的关联;
- 定义struct device类型的变量,将该mtd_info设备与linux设备驱动模型关联,该结构体实现如下几个功能:
- 可通过该变量实现mtd设备与mtd class的关联;
- 当调用device_register等接口将该变量注册至linux设备驱动模型中时,则通过netlink向应用层发送device add的uevent,而应用层的udev/mdev则在接收到该事件后,则进行该mtd_info设备对应的mtd字符设备与块设备文件的创建(通过mknod,而mtd设备字符设备与块设备相关的初始化接口已在系统初始化时完成主设备的注册)
include/linux/mtd/mtd.h
struct mtd_info {
u_char type;
uint32_t flags;
uint64_t size; // Total size of the MTD
/* "Major" erase size for the device. Naïve users may take this
* to be the only erase size available, or may use the more detailed
* information below if they desire
*/
uint32_t erasesize;
/* Minimal writable flash unit size. In case of NOR flash it is 1 (even
* though individual bits can be cleared), in case of NAND flash it is
* one NAND page (or half, or one-fourths of it), in case of ECC-ed NOR
* it is of ECC block size, etc. It is illegal to have writesize = 0.
* Any driver registering a struct mtd_info must ensure a writesize of
* 1 or larger.
*/
uint32_t writesize;
/*
* Size of the write buffer used by the MTD. MTD devices having a write
* buffer can write multiple writesize chunks at a time. E.g. while
* writing 4 * writesize bytes to a device with 2 * writesize bytes
* buffer the MTD driver can (but doesn't have to) do 2 writesize
* operations, but not 4. Currently, all NANDs have writebufsize
* equivalent to writesize (NAND page size). Some NOR flashes do have
* writebufsize greater than writesize.
*/
uint32_t writebufsize;
uint32_t oobsize; // Amount of OOB data per block (e.g. 16)
uint32_t oobavail; // Available OOB bytes per block
/*
* If erasesize is a power of 2 then the shift is stored in
* erasesize_shift otherwise erasesize_shift is zero. Ditto writesize.
*/
unsigned int erasesize_shift;
unsigned int writesize_shift;
/* Masks based on erasesize_shift and writesize_shift */
unsigned int erasesize_mask;
unsigned int writesize_mask;
/*
* read ops return -EUCLEAN if max number of bitflips corrected on any
* one region comprising an ecc step equals or exceeds this value.
* Settable by driver, else defaults to ecc_strength. User can override
* in sysfs. N.B. The meaning of the -EUCLEAN return code has changed;
* see Documentation/ABI/testing/sysfs-class-mtd for more detail.
*/
unsigned int bitflip_threshold;
// Kernel-only stuff starts here.
const char *name;
int index;
/* OOB layout description */
const struct mtd_ooblayout_ops *ooblayout;
/* NAND pairing scheme, only provided for MLC/TLC NANDs */
const struct mtd_pairing_scheme *pairing;
/* the ecc step size. */
unsigned int ecc_step_size;
/* max number of correctible bit errors per ecc step */
unsigned int ecc_strength;
/* Data for variable erase regions. If numeraseregions is zero,
* it means that the whole device has erasesize as given above.
*/
int numeraseregions;
struct mtd_erase_region_info *eraseregions;
/*
* Do not call via these pointers, use corresponding mtd_*()
* wrappers instead.
*/
int (*_erase) (struct mtd_info *mtd, struct erase_info *instr);
int (*_point) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, void **virt, resource_size_t *phys);
int (*_unpoint) (struct mtd_info *mtd, loff_t from, size_t len);
unsigned long (*_get_unmapped_area) (struct mtd_info *mtd,
unsigned long len,
unsigned long offset,
unsigned long flags);
int (*_read) (struct mtd_info *mtd, loff_t from, size_t len,
size_t *retlen, u_char *buf);
int (*_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_panic_write) (struct mtd_info *mtd, loff_t to, size_t len,
size_t *retlen, const u_char *buf);
int (*_read_oob) (struct mtd_info *mtd, loff_t from,
struct mtd_oob_ops *ops);
int (*_write_oob) (struct mtd_info *mtd, loff_t to,
struct mtd_oob_ops *ops);
int (*_get_fact_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_fact_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_get_user_prot_info) (struct mtd_info *mtd, size_t len,
size_t *retlen, struct otp_info *buf);
int (*_read_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len, size_t *retlen, u_char *buf);
int (*_write_user_prot_reg) (struct mtd_info *mtd, loff_t to,
size_t len, size_t *retlen, u_char *buf);
int (*_lock_user_prot_reg) (struct mtd_info *mtd, loff_t from,
size_t len);
int (*_writev) (struct mtd_info *mtd, const struct kvec *vecs,
unsigned long count, loff_t to, size_t *retlen);
void (*_sync) (struct mtd_info *mtd);
int (*_lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_is_locked) (struct mtd_info *mtd, loff_t ofs, uint64_t len);
int (*_block_isreserved) (struct mtd_info *mtd, loff_t ofs);
int (*_block_isbad) (struct mtd_info *mtd, loff_t ofs);
int (*_block_markbad) (struct mtd_info *mtd, loff_t ofs);
int (*_suspend) (struct mtd_info *mtd);
void (*_resume) (struct mtd_info *mtd);
void (*_reboot) (struct mtd_info *mtd);
/*
* If the driver is something smart, like UBI, it may need to maintain
* its own reference counting. The below functions are only for driver.
*/
int (*_get_device) (struct mtd_info *mtd);
void (*_put_device) (struct mtd_info *mtd);
/* Backing device capabilities for this device
* - provides mmap capabilities
*/
struct backing_dev_info *backing_dev_info;
struct notifier_block reboot_notifier; /* default mode before reboot */
/* ECC status information */
struct mtd_ecc_stats ecc_stats;
/* Subpage shift (NAND) */
int subpage_sft;
void *priv;
struct module *owner;
struct device dev;
int usecount;
};
以上即为mtd设备驱动模型相关的说明。针对mtd设备驱动模型而言,其完成了对上层xxxfs、mtd字符设备、块设备的接口抽象;对下完成了针对闪存芯片的操作接口的抽象,并且借助struct device完成与linux设备驱动模型模块的关联,并以此完成mtd设备对应的字符设备、块设备的创建。基于mtd设备注册大概完成哪些功能:
- 首先进行闪存芯片驱动的初始化,完成该闪存芯片对应master mtd_info的初始化与赋值(包括芯片对应的参数、接口的赋值等);
- 在闪存芯片驱动的probe接口中,完成针对各分区对应的mtd_part、mtd_Info的初始化与注册操作,主要包括对mtd_info类型的变量进行赋值与初始化操作,包括write_size、erase_size、size等赋值、对闪存芯片操作接口的赋值(_read、_write、_erase等接口);
- 并完成mtd_info对应的strcut device类型变量的注册,并借助注册完成mtd_info对应字符设备、块设备文件节点的创建;
- 将该mtd_info对应的分区变量注册到链表mtd_partitions中。