MTD的概述
1、MTD(memory technology device)是用於訪問memory設備(比如NOR Flash、NAND Flash)的Linux的子系統。
2、MTD在硬件和上層之間提供了一個抽象的接口。
MTD將 Nand Flash,nor flash 和其他類型的 flash 等設備,統一抽象成MTD 設備來管理,根據這些設備的特點,上層實現了常見的操作函數封裝,而底層具體的內部實現(具體的內部硬件設備的讀/寫/擦除函數),就需要驅動設計者自己來實現了。
而針對sd、tf等存儲設備,則主要由mmc子系統模塊進行管理並創建對應的塊設備。
3、MTD的主要目的是為了使新的存儲設備的驅動更加簡單並有通用接口函數可用。
MTD將文件系統與底層的Flash存儲器進行了隔離,使Flash驅動工程師無須關心Flash作為字符設備和塊設備與Linux內核的接口。
4、MTD 的所有源代碼在kernel/drivers/mtd 子目錄下。
5、MTD子系統的層次框圖。

如下圖所示:在引入MTD后,linux系統中的flash設備驅動及接口可分為4層:設備節點、MTD設備層、MTD原始層和硬件驅動。

設備節點:
通過mknod在dev子目錄下建立MTD字符設備節點(90)和MTD塊設備節點(31)基於MTD原始設備,由linux系統定義出MTD的塊設備和字符設備構成。
字符設備的定義在mtdchar.c中實現,通過注冊一系列file_operation函數可實現對MTD設備的讀寫和控制。
MTD的塊設備則是定義了一個描述MTD塊設備的結構mtd_dev,並聲明了一個名為mtdblks的指針數組,該數組中的每一個mtd_dev都與mtd_info一一對應。
完成flash的基本操作。
用戶通過訪問此設備節點即可訪問MTD字符設備和塊設備。
設備層:
基於MTD原始設備,由linux系統定義出MTD的塊設備和字符設備構成。
字符設備的定義在mtdchar.c中實現,通過注冊一系列file_operation函數可實現對MTD設備的讀寫和控制。
MTD的塊設備則是定義了一個描述MTD塊設備的結構mtd_dev,並聲明了一個名為mtdblks的指針數組,該數組中的每一個mtd_dev都與mtd_info一一對應。
完成flash的基本操作。
原始設備層:
MTD原始設備的通用代碼(mtdcore.c),(mtdpart.c).其中mtdcore.c中定義了描述mtd設備的核心結構mtdinfo.
硬件驅動層:
負責Flash硬件設備的讀、寫、擦除。
了解了上面的知識,我們就可以了解下他們各個層的接口關系,這樣我們能更好的熟悉代碼,和他們之間的接口調用關系,接口圖如下所示:

設備間的邏輯關聯圖與抽象
下圖是norflash 、nandflash與cpu之間的關聯。針對norflash,可直接與cpu關聯,也可通過spi controller與cpu相連;而針對nandflash,即可以通過nandflash controller、spi controlloer與cpu相連。 而mtd驅動模型則通過對nor flash、nandflash等閃存設備進行抽象,對上層模塊抽象成統一成設備模型(mtd_info),對下則通過mtd_info完成與具體閃存設備驅動的綁定,從而完成對閃存設備的訪問操作。此處mtd的抽象,就和vfs抽象類似。

MTD設備驅動模型與VFS及FLASH驅動的關聯
如下為mtd設備驅動模型、vfs、flash設備驅動之間的關聯圖。

針對mtd驅動模型,其對上關聯文件系統、對下則主要關聯具體的存儲驅動。它們之間的關聯圖如下所示。
其中mtd block、mtd char均屬於mtd驅動模型的部分,而FTL、NFTL則不屬於mtd驅動模型部分 ,由具體的文件系統來實現,如ubi文件系統掛載時,則是通過設備文件/dev/ubiX_Y進行掛載;而針對yaffs2文件系統,其可以直接使用/dev/mtdblockX進行掛載(當然其也可以不借助mtdblock)。而針對mtd char,主要實現對相應的flash設備進行順序訪問,那mtd char可具體實現什么功能呢?可能的應用一:當我們想在文件系統下實現對bootloader、kernel、文件系統鏡像的升級操作時,則可以通過讀寫/dev/mtdX(mtd 字符設備),實現針對bootloader、kernel、filesystem的升級操作。
針對mtd block、mtd char,其均是同調用mtd_info(即mtd設備),實現對具體flash芯片的讀寫操作的。而mtd_info則代表一個flash芯片或一個flash芯片的分區,當flash設備(nandflash設備、nor flash設備)的驅動初始化時,則會根據分區划分,創建對應的mtd_info,並完成mtd_info與該芯片驅動的綁定操作。而在mtd_info的創建過程中,則會創建對應的mtd char、mtd block,從而完成與vfs的關聯。
mtd設備驅動模型的架構說明
接口抽象層
針對接口抽象層,對於上層主要包括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等。
數據結構關聯
針對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設備驅動相關的數據結構說明
struct mtd_partition
在上面已經說了,該結構體主要用於定義分區的大小、偏移位置、是否只讀等功能的結構體,請具體定義如下。請記住,該數據結構類型變量的定義一般在板級文件或者在flash設備驅動文件進行mtd_info分區的注冊時使用。屬於mtd設備驅動模型對外的數據結構。
/*
* Partition definition structure:
*
* An array of struct partition is passed along with a MTD object to
* mtd_device_register() to create them.
*
* For each partition, these fields are available:
* name: string that will be used to label the partition's MTD device.
* types: some partitions can be containers using specific format to describe
* embedded subpartitions / volumes. E.g. many home routers use "firmware"
* partition that contains at least kernel and rootfs. In such case an
* extra parser is needed that will detect these dynamic partitions and
* report them to the MTD subsystem. If set this property stores an array
* of parser names to use when looking for subpartitions.
* size: the partition size; if defined as MTDPART_SIZ_FULL, the partition
* will extend to the end of the master MTD device.
* offset: absolute starting position within the master MTD device; if
* defined as MTDPART_OFS_APPEND, the partition will start where the
* previous one ended; if MTDPART_OFS_NXTBLK, at the next erase block;
* if MTDPART_OFS_RETAIN, consume as much as possible, leaving size
* after the end of partition.
* mask_flags: contains flags that have to be masked (removed) from the
* master MTD flag set for the corresponding MTD partition.
* For example, to force a read-only partition, simply adding
* MTD_WRITEABLE to the mask_flags will do the trick.
* add_flags: contains flags to add to the parent flags
*
* Note: writeable partitions require their size and offset be
* erasesize aligned (e.g. use MTDPART_OFS_NEXTBLK).
*/
struct mtd_partition {
const char *name; /* identifier string */
const char *const *types; /* names of parsers to use if any */
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 */
uint32_t add_flags; /* flags to add to the partition */
struct device_node *of_node;
};
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設備字符設備與塊設備相關的初始化接口已在系統初始化時完成主設備的注冊)
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);
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 (*_max_bad_blocks) (struct mtd_info *mtd, loff_t ofs, size_t len);
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);
/*
* flag indicates a panic write, low level drivers can take appropriate
* action if required to ensure writes go through
*/
bool oops_panic_write;
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;
struct mtd_debug_info dbg;
struct nvmem_device *nvmem;
/*
* Parent device from the MTD partition point of view.
*
* MTD masters do not have any parent, MTD partitions do. The parent
* MTD device can itself be a partition.
*/
struct mtd_info *parent;
/* List of partitions attached to this MTD device */
struct list_head partitions;
union {
struct mtd_part part;
struct mtd_master master;
};
};
mtd設備相關的注冊與注銷接口
針對mtd設備相關的注冊,主要包括如下幾個方面:
- 提供mtd設備注冊的接口,完成mtd info設備注冊至設備驅動模型子系統中,並完成mtd字符設備與mtd塊設備的創建(從而完成mtd info與設備驅動子系統、vfs子系統的關聯);
- 若閃存設備支持分區,則提供分區注冊接口,主要完成每一個分區對應的mtd_info設備注冊至設備驅動模型子系統中;並將每一個分區對應的mtd_part類型的變量注冊至鏈表mtd_partitions中;
基本上也就以上兩個功能,而在mtd子系統中,針對注冊的接口即為mtd_device_parse_register,而在
該接口中,則根據是否進行分區,分別調用add_mtd_partitions、add_mtd_device的注冊。
extern int mtd_device_parse_register(struct mtd_info *mtd, const char * const *part_probe_types, struct mtd_part_parser_data *parser_data, const struct mtd_partition *defparts, int defnr_parts);
若一個閃存芯片,在注冊至mtd子系統中時,不需要進行分區,則只需要使用接口add_mtd_device即可,下面我們對該接口進行分析。
add_mtd_device接口
該接口的處理流程如下圖所示(省略了異常處理),其主要實現如下功能:
- 設置mtd_info相關的信息,包括writesize_shift等;另外針對設置了可寫信息且上電時鎖定芯片的mtd設備,需要執行unlock操作(在某些場景下,我們做flash的設計時,可以讓硬件設計人員將wp引腳默認為有效,用於保證系統上電時可能導致的寫異常。然后在驅動中再將wp引腳關閉即可,而mtd子系統支持該功能,只需要實現mtd_info中_unlock成員函數指針即可);
- 設置該mtd_info所包含的device類型成員的信息,主要設置其所屬的class以及其device_type信息

add_mtd_partitions
該接口主要針對需要對注冊設備進行分區的情況,該接口主要實現兩個功能:
- 針對每一個分區,均創建一個mtd_part類型的變量,該變量包括master mtd_info以及該分區對應的mtd_info,並初始化該分區對應slave mtd_info,而針對slave mtd_info,其上層接口主要為分區相關的接口,該接口主要會調用master mtd_info對應的接口。針對master mtd_info、slave mtd_info的關聯,在下面分析。
- 針對每一個分區,將其mtd_part中的slave mtd_info調用add_mtd_device,將其注冊至mtd子系統中。
mtd_device_unregister
Mtd_info的注銷接口為mtd_device_unregister,該接口主要是實現mtd_info的注銷操作,其實主要就是調用device_unregister進行mtd_info對應device類型設備的注銷,同時若為分區設備,則將其從mtd_partitions鏈表上刪除等。
Mtd info master與slaver的區別
- 一個master mtd_info對應一個閃存設備;
- 一個slaver mtd_info對應一個閃存設備的邏輯分區(若沒有邏輯分區,則不存在該變量)
- 若一個閃存設備沒有進行邏輯分區,則會將該master mtd_info注冊至mtd子系統中,並創建一個master mtd_info對應的字符設備、塊設備;
- 若一個閃存設備進行邏輯分區,則每一個邏輯分區對應的slaver mtd_info均注冊至mtd子系統中,並創建該slaver mtd_info對應的字符設備與塊設備,而master mtd_info不注冊至mtd子系統中;此時slaver mtd_info並沒有實現對芯片驅動的訪問接口,而對下層閃存芯片驅動的訪問接口還是由master mtd_info中的接口實現訪問操作;而slaver mtd_info中的接口主要即是對master mtd_info的簡單封裝而已。還需要將每一個邏輯分區對應的mtd_part變量注冊至mtd_partitions鏈表中。
如何進行分區設置操作
針對閃存芯片邏輯分區的設置,主要包括兩種:
- 若linux內核不支持設備樹,則定義struct mtd_partition類型的變量即可(可以在板級文件中定義,並將其作為platform 的device的參數傳遞,然后在閃存芯片或閃存芯片控制器的驅動接口中解析該參數即可);
- 若linux內核支持設備樹,則只需要在設備樹文件中,增加分區信息即可;
如何實現將一個閃存芯片注冊至mtd子系統中
- 在閃存對應驅動的probe接口中,完成針對master mtd_info變量的初始化,包括參數以及接口的設置,並完成設置master mtd_info的priv成員指針指向該芯片驅動對應的結構體變量(包含了針對該閃存芯片參數及訪問接口等信息);
- 調用mtd_device_parse_register接口,完成對該芯片邏輯分區注冊至mtd子系統中。
針對系統中nor flash驅動、nandflash驅動、spi flash驅動(如m25p80驅動)等驅動模型,均是通過這兩步完成注冊的。
參考鏈接:
Linux mtd子系統專欄分析之一 概述 :https://blog.csdn.net/lickylin/article/details/104718529?spm=1001.2014.3001.5501
MTD設備驅動模型架構及數據結構說明:https://blog.csdn.net/lickylin/article/details/104718561
MTD層相關接口說明(注冊與注銷等):https://blog.csdn.net/lickylin/article/details/104760367?spm=1001.2014.3001.5501
MTD層次框圖:https://blog.csdn.net/wang_zheng_kai/article/details/18988521
