MTD分析

概述:本文对mtd的整个结构进行了分析,分析得并非很深入,但可以了解大体框架和目录结构,另外本文会对源码文件进行分析,大致描述其作用,针对本文的内容中,如有不恰当的地方,请留言指教,多谢

3 驱动结构

3.1 源码结构

3.1.1 文件结构

drivers/mtd/:
在这里插入图片描述
chips目录:
nor flash的驱动程序位于该目录,目录下的文件包含nor flash的一些操作接口,其中nor flash分cfi和jedec两种,其他则是除前面两种以外的,cfi_cmdset_00XX.c是不同厂商的nor flash的操作接口,比如读写擦除等等,chipreg.c的左右就是调用cfi_probe.c这些文件中的probe函数
在这里插入图片描述
devices目录:
提供具体设备的驱动,例如m25p80,其他的暂不了解
在这里插入图片描述
maps目录:
这个目录下的文件,主要是对读写擦除这些接口实现,以及分区解析和创建,对比了该目录下几个文件的代码,不管函数经过了几次调用,最终基本步骤都是do_map_probe、mtd_device_parse_register,说是nor flash(大部分文件是针对nor flash)驱动代码的具体实现也可以,与之类似的还是nand目录下的文件,基本上也是先实现mtd_info结构体里的函数接口,然后调用mtd_device_parse_register,使用这里我理解为maps是nor flash不同芯片驱动具体实现,nand则是nand flash不同芯片驱动的具体实现
在这里插入图片描述
nand目录:
nand flash不同芯片驱动的实现,描述参见maps目录
在这里插入图片描述
spi-nor目录:
和maps、nand类似,针对spi nor flash
在这里插入图片描述

3.1.2 MTD框架

在这里插入图片描述
PS:图片来源于网络

flash硬件驱动: maps目录、nand目录、devices目录
mtd原始设备: mtdpart.c(分区相关接口实现)、mtdcore.c(提供分区解析以及mtd注册等一系列通用的接口)、mtdoops.c、
MTD字符设备: mtdchar.c(注册字符设备)
MTD块设备: mtdblock.c(缓存读写及注册块设备)、mtd_blkdevs.c

3.1.3 结构体

mtd_info: mtd原始设备,记录设备总大小、擦除大小、写大小以及读写擦除接口等,每一个分区里都有一个mtd_info结构体成员,用于记录该分区的大小以及读写擦除操作时调用的接口
mtd_part: 每一个mtd_part表示一个MTD分区,结构体包含了两个mtd_info类型的成员,一个mtd和一个*parent,一般来说,parent指向一整个flash,而mtd只是这片flash上的一个分区,而mtd_part里的offset表示mtd在parent里的偏移
mtd_partitions: 这个结构体用于flash驱动里解析分区时,用于存放分区信息及个数以及解析分区的函数接口
mtd_partition: 每一个mtd_partition表示一个分区,记录了分区名字和分区大小和偏移
mtd_part_parser: 记录分区解析的函数接口
mtdblk_dev: 结构体记录了打开的计数,每open一次,count值加一,结构体里的mdb成员记录了读写函数接口、分区信息、request_queue、workqueue_struct、work_struct、gendisk,后边这几个主要的成员没有深挖,应该是和读写有关的,猜测应该是写操作时有一个工作队列,我们知道块设备随机写肯定是需要对写入的顺序进行优化的,这几个成员应该就是干这个工作,其中gendisk成员根据名字来看就是表示的一个磁盘设备,从代码来看显然也是一个分区
mtd_blktrans_ops: 提供访问的接口,在mtdblock.c中实现了具体的函数接口

3.1.4 mtdblock.c

这是个例外,后来感觉每个文件贴出来太多了,就只保留了这一节,这一块分析得很少,写了这么些了就懒得删了
mtdblock.c提供块设备的缓存读写接口以及注册字符设备,在文件开始处,定义了以下结构体:

struct mtdblk_dev {
	struct mtd_blktrans_dev mbd;
	int count;				//计数值,每次open一次加一,主要用于在第一次打开时初始化一些信息,见mtdblock_open函数
	struct mutex cache_mutex;		//互斥锁
	unsigned char *cache_data;		//缓冲区数据指针
	unsigned long cache_offset;		//数据偏移
	unsigned int cache_size;		//缓冲区大小
	enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; //缓冲区的状态,空状态或其他
};

结构体中的cache_data指针用于读写数据时存放数据,分析到具体函数时再去探究怎么使用该指针

struct mtd_blktrans_dev mbd,后续分析作用
mtd_blktrans_dev结构体如下:
	struct mtd_blktrans_dev {
	struct mtd_blktrans_ops *tr;//提供接口、函数指针以及一个devs链表和一个list链表
	struct list_head list;//list链表
	struct mtd_info *mtd;//提供块设备的各种信息,比如oobsize、writesize、erasesize以及块设备的操作函数(读写擦除、lock、unlock等)
	struct mutex lock;//互斥锁
	int devnum;
	bool bg_stop;
	unsigned long size;
	int readonly;
	int open;
	struct kref ref;
	struct gendisk *disk;
	struct attribute_group *disk_attributes;
	struct workqueue_struct *wq;
	struct work_struct work;
	struct request_queue *rq;
	spinlock_t queue_lock;
	void *priv;
	fmode_t file_mode;
};

mtdblock主要接口如下:
在这里插入图片描述
结构体mtd_blktrans_ops定义如下:
在这里插入图片描述
其中mtdblock_readsect和mtdblock_writesect是读写接口,mtdblock_add_mtd,mtdblock_remove_dev

3.1.4.1 register_mtd_blktrans函数

该函数原型在mtd_blkdevs.c中,实现如下:
该文件中定义struct mtd_notifier变量,结构体如下:

struct mtd_notifier {
	void (*add)(struct mtd_info *mtd);
	void (*remove)(struct mtd_info *mtd);
	struct list_head list;
};

一个添加函数,一个移除函数,一个链表,在mtd_blkdevs.c中定义了一个链表static LIST_HEAD(blktrans_majors); ,看一下add和remove函数

static void blktrans_notify_remove(struct mtd_info *mtd)
{
	struct mtd_blktrans_ops *tr;
	struct mtd_blktrans_dev *dev, *next;
	//循环blktrans_majors链表,在每一个链表里遍历它的devs链表,找到与mtd相等的项,移除
	list_for_each_entry(tr, &blktrans_majors, list)
		list_for_each_entry_safe(dev, next, &tr->devs, list)
			if (dev->mtd == mtd)
tr->remove_dev(dev);//经过调用mtdblock.c中的mtdblock_tr 的mtdblock_remove_dev 调用mtd_blkdevs.c中实现的del_mtd_blktrans_dev函数
}

static void blktrans_notify_add(struct mtd_info *mtd)
{
	struct mtd_blktrans_ops *tr;
	//非有效的类型,直接返回
	if (mtd->type == MTD_ABSENT)
		return;
//遍历blktrans_majors链表中的每一项,调用它的add_mtd函数,遍历tr里的devs链表,add_mtd将mtd加入到tr的devs链表里
	list_for_each_entry(tr, &blktrans_majors, list)
		tr->add_mtd(tr, mtd);
}
static struct mtd_notifier blktrans_notifier = {
	.add = blktrans_notify_add,
	.remove = blktrans_notify_remove,
};
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
	struct mtd_info *mtd;
	int ret;

	/* Register the notifier if/when the first device type is
	   registered, to prevent the link/init ordering from fucking
	   us over. */
	//第一次注册
	if (!blktrans_notifier.list.next)
		register_mtd_user(&blktrans_notifier);


	mutex_lock(&mtd_table_mutex);
	//注册块设备,返回0成功,根据传入的major,如果为0,则会找到第一个空闲的位置,然后添加自己的信息,实际上就是在genhd.c中定义了一个结构体(blk_major_name)指针数组,里边包括major,name,和next指针
	ret = register_blkdev(tr->major, tr->name);
	if (ret < 0) {
		printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
		       tr->name, tr->major, ret);
		mutex_unlock(&mtd_table_mutex);
		return ret;
	}

	if (ret)
		tr->major = ret;

	tr->blkshift = ffs(tr->blksize) - 1;
	//初始化tr的devs链表,将list链表添加到blktrans_majors
	INIT_LIST_HEAD(&tr->devs);
	list_add(&tr->list, &blktrans_majors);
	//循环mtd_idr里不为空的每一项,调用add_mtd,mtd_idr在mtdcore.c中定义,
	mtd_for_each_device(mtd)
		if (mtd->type != MTD_ABSENT)
			tr->add_mtd(tr, mtd);

	mutex_unlock(&mtd_table_mutex);
	return 0;
}

3.2 NOR FLASH代码分析

(分析代码过程中总结记录的,排版不是很好,下面的分析比较杂乱,主要是函数的调用流程,缩进相同的表示平级)
比如:

	a_func
		b_func
			c_func
		d_func

上边的例子表示在a_func函数里调用了两个函数,一个b_func和一个d_func函数,其中b_func 调用了c_func函数

这里内核版本为4.14,下面的分析针对内核版本4.14
CONFIG_MTD_PHYSMAP_OF必须要,CONFIG_MTD_PHYSMAP无关紧要

mtdblock.c
注册块设备,主设备号为31
提供缓存读写接口
将分区信息填充到一个新的mtd_blktrans_dev结构体中,将该结构体的list链表加入到tr的devs链表中
每一个mtd_blktrans_dev结构体里包含一个mtd_info结构体成员,用于记录分区信息

mtdchar.c
注册主设备号为90的mtd字符设备
init_mtdchar #define MTD_CHAR_MAJOR 90 #define MINORBITS 20 ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,"mtd", &mtd_fops);
提供应用层读写接口

mtdcore.c

static LIST_HEAD(mtd_notifiers);

在文件系统下生成对应的名字,如下边的erasesize,可在设备目录下看到该名字,可以通过cat查看值或echo更改值
static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL);

DEVICE_ATTR的参数:
DEVICE_ATTR(_name, _mode, _show, _store)
_name:在sys fs中的名字
_mode:访问权限
_show:显示函数
_store:写函数

mtdcore提供了很多DEVICE_ATTR,可以在文件系统中查看erasesize,writesize等属性


在主设备号90下创建字符设备mtdX和mtdXro
	add_mtd_device
		
		#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
		dev_set_name(&mtd->dev, "mtd%d", i);
		error = device_register(&mtd->dev);
		device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
		      "mtd%dro", i);
		使用idr_alloc函数通过传入的mtd参数在mtd_idr中分配一个ID
		根据该ID的值,创建/dev/mtdX和/dev/mtdXro两个节点
		轮询mtd_notifiers链表中的每一项,调用它的add接口,add接口在mtd_blkdevs.c中实现,在mtdblock.c中的init_mtdblock函数中初始化
		该函数的作用就是将每个分区添加到系统的MTD设备链表里,调用mtd_notifiers中每一个MTD 'user'(这里的每一个user就是一个mtd_notifier)中的add接口
		mtd_notifiers中的MTD 'user'在mtd_blkdevs.c中的register_mtd_blktrans函数中通过register_mtd_user函数添加
		该函数还添加mtd_blktrans_ops到blktrans_majors链表中,然后对mtd_idr中的每一个mtd_info(也就是每一个分区)调用mtdblock.c中mtdblock_tr的add_mtd函数,也就是mtdblock_add_mtd
		mtdblock_add_mtd函数会分配一个mtdblk_dev结构体,填充mbd成员(mtd_blktrans_dev类型)的mtd及tr成员,然后调用add_mtd_blktrans_dev函数
		add_mtd_blktrans_dev函数将mdb加入到tr的devs链表中,并调用device_add_disk添加disk设备
		
	mtd_device_parse_register
		该函数负责解析分区信息和分区个数,最终调用mtd_add_device_partitions函数添加分区
	mtd_add_device_partitions
		函数中调用add_mtd_partitions函数添加分区,函数实现在mtdpart.c中
	mtd_device_unregister
	
	mtdcore.c中init_mtd调用mtdchar.c中的init_mtdchar注册字符设备
	
	module_init(init_mtd);
		init_mtd
			ret = class_register(&mtd_class);
			//这部分还没完全了解,网上的解释大概是后端存储设备I/O较慢,用于缓存延迟写入,这个应该比较重要
			//个人理解应该是对写操作进行优化,对于不同顺序的扇区写操作进行优化,
			mtd_bdi = mtd_bdi_init("mtd");
			//proc
			proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops);
			//调用mtdchar.c中的函数
			ret = init_mtdchar();
			//debugfs
			dfs_dir_mtd = debugfs_create_dir("mtd", NULL);

mtd_blkdevs.c
注册块设备
static LIST_HEAD(blktrans_majors);
提供mtdblock.c使用的register_mtd_blktrans、blktrans_notifier.add等接口
同时维护一个blktrans_majors链表,将mtdblock.c中的mtdblock_tr添加到链表中,然后对每一个分区调用mtdblock.c中tr(mtd_blktrans_ops)的add_mtd接口

cmdlinepart.c
和ofpart.c差不多,只是各自解析的分区信息来源不同

ofpart.c
提供解析设备数分区的接口,解析分区个数及偏移和大小
struct mtd_partition结构体记录分区的大小和偏移

	struct mtd_partition {
		const char *name;		分区名字
		const char *const *types;	cmdlinepart or ofpart
		uint64_t size;			分区大小
		uint64_t offset;		分区偏移
		uint32_t mask_flags;		MTD_WRITEABLE or MTD_BIT_WRITEABLE or MTD_NO_ERASE or other
		struct device_node *of_node; 设备树节点
		
	};

上边两个文件分别是针对命令行分区的设备树分区的解析,程序运行时会轮询两种方式,当然,也可以自己添加新的方式,如下:

/*
 * Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you
 * are changing this array!
 */
static const char * const default_mtd_part_types[] = {
	"cmdlinepart",
	"ofpart",
	NULL
};

在mtdpart.c中,该函数中可以看到for循环轮询了default_mtd_part_types数组

int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
			 struct mtd_partitions *pparts,
			 struct mtd_part_parser_data *data)
{
	struct mtd_part_parser *parser;
	int ret, err = 0;

	if (!types)
		types = default_mtd_part_types;

	for ( ; *types; types++) {
		pr_debug("%s: parsing partitions %s\n", master->name, *types);
		parser = mtd_part_parser_get(*types);
		if (!parser && !request_module("%s", *types))
			parser = mtd_part_parser_get(*types);
		pr_debug("%s: got parser %s\n", master->name,
			 parser ? parser->name : NULL);
		if (!parser)
			continue;
		ret = mtd_part_do_parse(parser, master, pparts, data);
		/* Found partitions! */
		if (ret > 0)
			return 0;
		mtd_part_parser_put(parser);
		/*
		 * Stash the first error we see; only report it if no parser
		 * succeeds
		 */
		if (ret < 0 && !err)
			err = ret;
	}
	return err;
}

cmdlinepart.c和ofpart.c都将在flash驱动中被调用,一般是在probe函数中,
例如jz4740_nand.c(随便找的一个nand flash的驱动)和physmap_core_of.c
的probe函数中都会调用mtd_device_parse_register函数,而该函数则通过层层调用,
最终轮询cmdlinepart.c和ofpart.c中定义的static struct mtd_part_parser结构体中的parse_fn函数,
cmdlinepart.c最终会调用parse_cmdline_partitions函数,而ofpart.c则调用parse_ofpart_partitions

mtdpart.c
定义了一个mtd_partitions链表,用于管理分区,每一个新的分区都会被添加到该链表中
static LIST_HEAD(mtd_partitions);
为分区提供读写擦除函数

	struct mtd_partitions {
		const struct mtd_partition *parts;		//分区信息
		int nr_parts;							//分区个数
		const struct mtd_part_parser *parser;	//解析分区的函数接口	
	};
	
	struct mtd_part_parser {
		struct list_head list;
		struct module *owner;
		const char *name;
		int (*parse_fn)(struct mtd_info *, const struct mtd_partition **,		//解析函数
				struct mtd_part_parser_data *);
		void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
	};
	
	add_mtd_partitions
		根据分区个数,分配对应个mtd_part,并添加到mtd_partitions链表中
		调用add_mtd_device函数,函数在mtdcore.c中实现

cfi_cmdset_0002.c

	实现mtd_info结构体的接口等
	struct mtd_info *mtd;
	..........
	mtd->_suspend = cfi_staa_suspend;
	mtd->_resume = cfi_staa_resume;
	..........

gen_probe.c

	mtd_do_chip_probe
		mtd = check_cmd_set(map, 1);
			check_cmd_set
				cfi_cmdset_0002(map, primary);

cfi_probe.c

	cfi_probe
		mtd_do_chip_probe

physmap_core_of.c

	do_map_probe
		drv->probe(map);
			cfi_probe
				return mtd_do_chip_probe(map, &cfi_chip_probe);
					cfi = genprobe_ident_chips(map, cp);
						for (i = 1; i < max_chips; i++) {
							cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi);
								//读flash的ID等
								cfi_probe_chip
					mtd = check_cmd_set(map, 1); /* First the primary cmdset */
						//设置读写函数等,cfi_cmdset_0001:Intel/Sharp flash chips,cfi_cmdset_0002:AMD/Fujitsu flash chips
						cfi_cmdset_0020:ST flash chips
						cfi_cmdset_0002/cfi_cmdset_0001/cfi_cmdset_0020/cfi_cmdset_unknown
							mtd->_erase   = cfi_amdstd_erase_varsize;
							mtd->_write   = cfi_amdstd_write_words;
							mtd->_read    = cfi_amdstd_read;
						
	/*解析分区并注册,该函数在mtdcore.c中*/
	mtd_device_parse_register	
		//解析分区信息
		parse_mtd_partitions
			mtd_part_do_parse(struct mtd_part_parser *parser,
				(*parser->parse_fn)
					//在ofpart.c中实现,解析设备树种分区的个数以及偏移和大小等
					parse_ofpart_partitions
		
		mtd_add_device_partitions
			//如果没有分区,则在/dev下创建mtdX、mtdXro节点以及添加分区
			if (nbparts == 0 || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
				ret = add_mtd_device(mtd);
					//分配一个没有使用的id
					i = idr_alloc
					dev_set_name(&mtd->dev, "mtd%d", i);
					//创建设备节点mtdX
					error = device_register(&mtd->dev);
					
					if (!IS_ERR_OR_NULL(dfs_dir_mtd)) {
						mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(&mtd->dev), dfs_dir_mtd);
						if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) {
							pr_debug("mtd device %s won't show data in debugfs\n",
								 dev_name(&mtd->dev));
						}
					}
					//创建mtdXro节点
					device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,"mtd%dro", i);
					list_for_each_entry(not, &mtd_notifiers, list)
						//调用mtd_blkdevs.c中提供的blktrans_notify_add函数
						not->add(mtd);
							struct mtd_blktrans_ops *tr;
							//调用mtdblock.c中的add_mtd
							tr->add_mtd(tr, mtd);
								//调用mtd_blkdevs.c中的add_mtd_blktrans_dev
								add_mtd_blktrans_dev(&dev->mbd)
									struct gendisk *gd;
									gd = alloc_disk(1 << tr->part_bits);
									//设置名字,这里的tr是mtdblock.c中的mtdblock_tr,name为"mtdblock"
									snprintf(gd->disk_name, sizeof(gd->disk_name),
										"%s%d", tr->name, new->devnum);
									//添加分区,可在文件系统下看到mtdblockX
									device_add_disk
			if (nbparts > 0) {
				ret = add_mtd_partitions(mtd, real_parts, nbparts);
					for (i = 0; i < nbparts; i++) {
						//为分区设置读写擦除函数,设置erasesize,writesize,size等,
						最终使用的是cfi_cmdset_0002中cfi_cmdset_0002函数设置的读写函数
						slave = allocate_partition(master, parts + i, i, cur_offset);
						if (IS_ERR(slave)) {
							del_mtd_partitions(master);
							return PTR_ERR(slave);
						}

						mutex_lock(&mtd_partitions_mutex);
						//添加到mtd_partitions链表
						list_add(&slave->list, &mtd_partitions);
						mutex_unlock(&mtd_partitions_mutex);

						//创建m注册tdx设备
						add_mtd_device(&slave->mtd);
						//在文件系统下创建节点
						mtd_add_partition_attrs(slave);
						if (parts[i].types)
							mtd_parse_part(slave, parts[i].types);

						cur_offset = slave->offset + slave->mtd.size;
					}
					
		if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
			mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
			register_reboot_notifier(&mtd->reboot_notifier);
		}

有关时序相关分析可以参考链接: AXI EMC使用总结.
这个文章介绍了flash相关的参数以及时序的设置

  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值