linux mtd 子系统

  • A+
所属分类:linux技术
摘要

1、MTD(memory technology device)是用于访问memory设备(比如NOR Flash、NAND Flash)的Linux的子系统。


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子系统的层次框图。

linux mtd 子系统

 

如下图所示:在引入MTD后,linux系统中的flash设备驱动及接口可分为4层:设备节点、MTD设备层、MTD原始层和硬件驱动。

 

 

 

 linux 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硬件设备的读、写、擦除。

 

了解了上面的知识,我们就可以了解下他们各个层的接口关系,这样我们能更好的熟悉代码,和他们之间的接口调用关系,接口图如下所示:

linux mtd 子系统

 

 

 

 

 

设备间的逻辑关联图与抽象

 

  下图是norflash 、nandflash与cpu之间的关联。针对norflash,可直接与cpu关联,也可通过spi controller与cpu相连;而针对nandflash,即可以通过nandflash controller、spi controlloer与cpu相连。 而mtd驱动模型则通过对nor flash、nandflash等闪存设备进行抽象,对上层模块抽象成统一成设备模型(mtd_info),对下则通过mtd_info完成与具体闪存设备驱动的绑定,从而完成对闪存设备的访问操作。此处mtd的抽象,就和vfs抽象类似。

linux mtd 子系统

 

 

 

MTD设备驱动模型与VFS及FLASH驱动的关联

如下为mtd设备驱动模型、vfs、flash设备驱动之间的关联图。

 linux mtd 子系统

 

  针对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等函数指针,完成针对下层设备驱动操作接口的抽象,完成对下层设备驱动接口的抽象模型的建立。
linux mtd 子系统

 

 

 

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设备驱动模型的关键,其定义的变量也比较多,下面我们从几个方面进行说明,并联合其他数据结构,说明其中的关联;

  1. 定义mtd设备类型、总大小、写单位、擦除单位、index等等信息;
  2. 抽象的闪存芯片的操作接口(读写擦除等接口)
  3. 提供priv指针,指向该mtd设备的私有信息,若mtd设备需要特殊的处理相关的变量,则可以将该priv指向对应的内存,如针对nandflash controller驱动而言,则通过该priv指针实现nand_chip与mtd_info的关联;
  4. 定义struct device类型的变量,将该mtd_info设备与linux设备驱动模型关联,该结构体实现如下几个功能:
    1.   可通过该变量实现mtd设备与mtd class的关联;
    2.   当调用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接口

  该接口的处理流程如下图所示(省略了异常处理),其主要实现如下功能:

  1. 设置mtd_info相关的信息,包括writesize_shift等;另外针对设置了可写信息且上电时锁定芯片的mtd设备,需要执行unlock操作(在某些场景下,我们做flash的设计时,可以让硬件设计人员将wp引脚默认为有效,用于保证系统上电时可能导致的写异常。然后在驱动中再将wp引脚关闭即可,而mtd子系统支持该功能,只需要实现mtd_info中_unlock成员函数指针即可);
  2. 设置该mtd_info所包含的device类型成员的信息,主要设置其所属的class以及其device_type信息

linux mtd 子系统

 

 

 

add_mtd_partitions 

  该接口主要针对需要对注册设备进行分区的情况,该接口主要实现两个功能:

  1. 针对每一个分区,均创建一个mtd_part类型的变量,该变量包括master mtd_info以及该分区对应的mtd_info,并初始化该分区对应slave mtd_info,而针对slave mtd_info,其上层接口主要为分区相关的接口,该接口主要会调用master mtd_info对应的接口。针对master mtd_info、slave mtd_info的关联,在下面分析。
  2. 针对每一个分区,将其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的区别

  1. 一个master mtd_info对应一个闪存设备;
  2. 一个slaver mtd_info对应一个闪存设备的逻辑分区(若没有逻辑分区,则不存在该变量)
  3. 若一个闪存设备没有进行逻辑分区,则会将该master mtd_info注册至mtd子系统中,并创建一个master mtd_info对应的字符设备、块设备;
  4. 若一个闪存设备进行逻辑分区,则每一个逻辑分区对应的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链表中。

如何进行分区设置操作

  针对闪存芯片逻辑分区的设置,主要包括两种:

  1. 若linux内核不支持设备树,则定义struct mtd_partition类型的变量即可(可以在板级文件中定义,并将其作为platform 的device的参数传递,然后在闪存芯片或闪存芯片控制器的驱动接口中解析该参数即可);
  2. 若linux内核支持设备树,则只需要在设备树文件中,增加分区信息即可;

如何实现将一个闪存芯片注册至mtd子系统中

  1. 在闪存对应驱动的probe接口中,完成针对master mtd_info变量的初始化,包括参数以及接口的设置,并完成设置master mtd_info的priv成员指针指向该芯片驱动对应的结构体变量(包含了针对该闪存芯片参数及访问接口等信息);
  2. 调用mtd_device_parse_register接口,完成对该芯片逻辑分区注册至mtd子系统中。

针对系统中nor flash驱动、nandflash驱动、spi flash驱动(如m25p80驱动)等驱动模型,均是通过这两步完成注册的。

参考链接: 

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