Memory Technology Device (MTD) 设备分析

MTD(Memory Technology Device )设备分析

基于linux4.4
原创文章转载请标明出处。

什么是MTD设备

driver/mtd/Kconfig 文件中有关于mtd设备的定义

  Memory Technology Devices are flash, RAM and similar chips, often
  used for solid state file systems on embedded devices. 

由上面的说明可知内核定义了MTD设备,就是为了对FLASH设备(nor/nand flash)以及RAM等类似存储设备进行抽象。
所谓"内存技术设备"(MTD),即这类存储设备的访问方式和内存类似,都是通过"地址"的方式。例如NOR FLASH,
其可以实现基于任意地址的单字节读取,这已经与内存的访问方式一致了。

而此类存储设备在访问方式上有别于的传统的“块设备”(如:硬盘,EMMC,SSD等),这类块设备访问方式通常是基于"扇区"的,主控设备看到的是一个个扇区号,通过发布扇区号来读取硬盘上的数据,一个扇区通常是512字节。

所以linux 区分MTD设备和传统的块设备的主要依据就是访问方式的不同!例如NOR FLASH中也有扇区,块等概念但是依然不影响它是MTD设备,因为它是基于地址访问的。

数据结构说明

  1. 用’struct mtd_info’ 表示一个mtd设备,里面主要封装了设备的操作方法。

举例,具体见详细定义。

struct mtd_info {
	u_char type;
	uint32_t flags;
	uint64_t size;	 // Total size of the MTD
	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 (*_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);
	struct module *owner;
	struct device dev; //设备模型,表示mtd设备
	int usecount;
}
  1. mtd设备支持分区,用struct mtd_partition保存分区信息,用struct mtd_part 表示一个分区实例,可以看到每一个分区实例也是一个mtd设备。
struct mtd_part {
	struct mtd_info mtd;// 每一个分区其本质也是一个mtd设备。
	struct mtd_info *master;//表示整体的(未分区)mtd设备,分区设备最终会用到整体设备中定义的mtd设备读写操作方法。
	uint64_t offset; //分区的地址偏移。
	struct list_head list;
};

struct mtd_partition {
	const 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 */
	struct nand_ecclayout *ecclayout;	/* out of band layout for this partition (NAND only) */
};

注册流程分析

以nor flash 驱动m25p80.c 为例说明mtd的注册流程。
整个注册流程框架如下所示,由下到上一次为spi nor flash 芯片,spi总线控制驱动,spi设备驱动,以及spi nor flash抽象层,最后是mtd设备抽象层。
我们由m25p80设备驱动开始说明,由图可以看出驱动的主要目的就是抽象出一个spi nor flash层,最终注册mtd设备。

   4            MTD               mtd 设备
     ------------------------
   3       SPI NOR framework      spi nor flash 抽象层
     ------------------------
   2           m25p80             spi nor flash 设备驱动
     ------------------------
   1    SPI bus driver            spi 总线控制器驱动
     ------------------------
   0    SPI NOR chip              硬件芯片

1 ./driver/mtd/device/m25p80.c spi设备驱动中完成的主要工作包括构建spi nor flash 抽象层,初始化mtd设备并注册。

m25p_probe 函数中主要进行了如下操作:
㈠. 构建spi nor flash抽象层,抽象层的核心数据结构为struct spi_nor, 表示一个nor flash存储设备。
首先初始化了nor flash 的读写擦除等回调函数。

	struct m25p *flash;
	struct spi_nor *nor;
	flash = devm_kzalloc(&spi->dev, sizeof(*flash), GFP_KERNEL);
	nor = &flash->spi_nor;
	/* install the hooks */
	nor->read = m25p80_read;
	nor->write = m25p80_write;
	nor->erase = m25p80_erase;

㈡.调用spi_nor_scan函数,进一步获取flash信息如容量,擦除块大小等,初始化nor flash 设备以及mtd设备。
struct spi nor flash设备的具体信息如容量,擦除块大小等信息,当然要符合真实的芯片信息。
真实的flash信息用struct flash_info 结构体描述, drivers/mtd/spi-nor/spi-nor.c 文件中通过
struct flash_info spi_nor_ids[] 数组描述了内核支持的nor flash芯片信息。

static const struct flash_info spi_nor_ids[] = {
	/* Atmel -- some are (confusingly) marketed as "DataFlash" */
	{ "at25fs010",  INFO(0x1f6601, 0, 32 * 1024,   4, SECT_4K) },
	{ "at25fs040",  INFO(0x1f6604, 0, 64 * 1024,   8, SECT_4K) },

	{ "at25df041a", INFO(0x1f4401, 0, 64 * 1024,   8, SECT_4K) },
	{ "at25df321a", INFO(0x1f4701, 0, 64 * 1024,  64, SECT_4K) },
	{ "at25df641",  INFO(0x1f4800, 0, 64 * 1024, 128, SECT_4K) },

	...
}

spi_nor_scan函数首先根据传入的flash芯片型号名字,或读取的芯片id在spi_nor_ids数组中查找到具体的芯片信息。然后根据获取到的芯片信息,进一步的初始化 struct spi_nor,同时初始化了struct mtd_info 成员,为下一步注册mtd设备作准备。

	spi_nor_scan(nor, flash_name, mode)
{
	mtd->priv = nor;
	mtd->type = MTD_NORFLASH;
	mtd->writesize = 1;
	mtd->flags = MTD_CAP_NORFLASH;
	mtd->size = info->sector_size * info->n_sectors;//根据struct flash_info 计数mtd设备容量
	mtd->_erase = spi_nor_erase;//初始化回调函数。
	mtd->_read = spi_nor_read;
}

㈢ 调用mtd_device_parse_register函数注册mtd设备。注册mtd设备首先要获取到mtd设备的分区信息。
分区信息用数据结构 struct mtd_partition 表示。

int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
			      struct mtd_part_parser_data *parser_data,
			      const struct mtd_partition *parts,
			      int nr_parts)
{
	int ret;
	struct mtd_partition *real_parts = NULL;

	mtd_set_dev_defaults(mtd);

	ret = parse_mtd_partitions(mtd, types, &real_parts, parser_data);//解析分区信息
	...
	ret = mtd_add_device_partitions(mtd, real_parts, ret);//注册mtd设备。
	return ret;
}

有两种方式获取分区信息: ⒈ 通过驱动直接传递分区信息。 ⒉ 动态创建分区信息。
⒈ 通过驱动传递。

驱动通过数据结构struct flash_platform_data 传递分区信息。

struct flash_platform_data {
	char		*name;
	struct mtd_partition *parts;
	unsigned int	nr_parts;

	char		*type;

	/* we'll likely add more ... use JEDEC IDs, etc */
};

static struct mtd_partition spi0_nor_partitions[] = {
	/* All the partition sizes are listed in terms of erase size */
	{
		.name       = "boot",
		.offset     = 0,			/* Offset = 0x0 */
		.size       = 512*1024,
	},
	{
		.name       = "kernel",
		.offset     = MTDPART_OFS_APPEND,	
		.size       = 8*1024*1024, 
	},
	{
		.name       = "app",
		.offset     = MTDPART_OFS_APPEND,	
		.size       = 6*1024*1024, 
	},
}
static const struct flash_platform_data spi0_nor_flash = {
	//.type      = "m25p128-nonjedec",
	.type      = "gd25q128",
	.name      = "spi0_nor_flash",
	.parts     = spi0_nor_partitions,
	.nr_parts  = ARRAY_SIZE(spi0_nor_partitions),
};

⒉ 动态创建分区信息
动态创建分区信息是指可以根据设备树,命令行等方式传递分区信息,动态生成struct mtd_partition结构。
此种方式首先需要一个解析函数用于把设备树,命令行等描述的分区信息转换为struct mtd_partition 描述的分区信息。
解析函数通过数据结构 struct mtd_part_parser 描述,可以通过函数 void register_mtd_parser(struct mtd_part_parser *parser)向内核注册定义的解析函数。如:drivers/mtd/ofpart.c 中就注册了通过设备树获取分区信息的解析函数。

struct mtd_part_parser {
	struct list_head list;//通过链表把注册的解析函数管理起来。
	struct module *owner;
	const char *name;
	int (*parse_fn)(struct mtd_info *, struct mtd_partition **,
			struct mtd_part_parser_data *);//解析函数回调函数。
};

/*drivers/mtd/ofpart.c */
static struct mtd_part_parser ofpart_parser = {
	.owner = THIS_MODULE,
	.parse_fn = parse_ofpart_partitions,
	.name = "ofpart",
};

register_mtd_parser(&ofpart_parser);

㈣ mtd设备注册

如果没有获得MTD分区信息,那么直接注册原始mtd设备。
如果有分区信息,那么会把每个分区作为独立的mtd设备进行注册。
分区mtd设备用结构体struct mtd_part 表示。
通过函数

int add_mtd_partitions(struct mtd_info *master,
		       const struct mtd_partition *parts,
		       int nbparts)

根据分区信息创建mtd设备。参数master表示的是原始的mtd设备,分区设备的读写擦除等回调函数的实现都是借助对原始mtd设备的操作加上一个偏移来实现的。最终都会通过调用 int add_mtd_device(struct mtd_info *mtd) 函数完成MTD设备的注册,示例代码如下:

#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
int add_mtd_device(struct mtd_info *mtd)
{
	struct mtd_notifier *not;
	int i, error;

	i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
	mtd->index = i;
	mtd->usecount = 0;

	mtd->dev.type = &mtd_devtype;
	mtd->dev.class = &mtd_class;
	mtd->dev.devt = MTD_DEVT(i);
	dev_set_name(&mtd->dev, "mtd%d", i);
	dev_set_drvdata(&mtd->dev, mtd);

	error = device_register(&mtd->dev);

	device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
		      "mtd%dro", i);
	return 0;
}

通过代码可知,MTD设备注册主要完成如下操作:
⒈ 通过idr 申请id和mtd设备指针绑定,作为设备索引。

ps: idr 即“ID radix”, 内核中通过radix树对ID进行组织和管理,是一种将整数ID和指针关联在一起的一种机制。

  1. 向系统注册字符设备,一个mtd设备注册了两种字符设备,偶数次设备号注册为mtd%d是可读写的。奇数次设备号注册为mtd%ro 只读设备。

drivers/mtd/mtdchar.c 实现了具体字符设备的实现。

int __init init_mtdchar(void)
{
	int ret;

	ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,
				   "mtd", &mtd_fops);
	if (ret < 0) {
		pr_err("Can't allocate major number %d for MTD\n",
		       MTD_CHAR_MAJOR);
		return ret;
	}

	return ret;
}

mtdchar_open函数中会判断奇数设备号为只读。

static int mtdchar_open(struct inode *inode, struct file *file)
{
	int minor = iminor(inode);
	int devnum = minor >> 1;
	int ret = 0;
	struct mtd_info *mtd;
	struct mtd_file_info *mfi;

	pr_debug("MTD_open\n");

	/* You can't open the RO devices RW */
	if ((file->f_mode & FMODE_WRITE) && (minor & 1)) //奇数次设备号为只读。
		return -EACCES;

	mutex_lock(&mtd_mutex);
	mtd = get_mtd_device(NULL, devnum);

	return ret;
} /* mtdchar_open */

到此为止MTD设备就注册完成了。

MTD 设备对上接口层

MTD 设备提供的对上层调用接口定义在文件drivers/mtd/mtdcore.c 中,以mtd开头的mtd_xxx函数,例如mtd_write,mtd_read等。在mtd接口层上,可以创建字符设备,或者注册一个(虚拟的)块设备,或者直接构建文件系统如jffs2。如下所示:

------------------------VFS---------------------------
       |                 |                  |
|---------------|        |        |---------------| 
|  ext4文件系统 |        |        |  jffs2文件系统|
----------------|        |        ----------------|
        |                |                 |
	    |                |                 |
|-----------------||-----------------|     |
| 块设备mtdblock  ||字符设备/dev/mtd0|     |
|-----------------||-----------------|     |
        |                |                 |
	    |                |                 |
|-------------------------------------------------|
|对上调用接口:mtd_write(),mtd_read(), mtd_xxx()..|
|-------------------------------------------------|
|                   MTD设备                       |
|-------------------------------------------------|
             SPI NOR framework                 
--------------------------------------------------
                  m25p80
--------------------------------------------------
	       	  SPI bus driver
--------------------------------------------------
		       	SPI NOR chip

一个知识点

通过 jffs2 文件系统访问flash,如mount -t jffs2 /dev/mtdblock0 /mnt/data

jffs2 文件系统实际读写根本就没调用/dev/mtdblock 块设备提供的读写函数,而是直接调用了mtd层接口函数如mtd_read/write等函数。所以对于jffs2文件系统来说它操作的根本就不是/dev/mtdblock/块设备,而是更底层的mtd设备,只是利用/dev/mtdblock 设备节点获取到MTD设备。

具体可以看drivers/mtd/mtdsuper.c 中mount相关函数说明。
可以查看drivers/mtd/Kconfig 文件中关于MTD_BLOCK 配置的说明:

config MTD_BLOCK
	tristate "Caching block device access to MTD devices"
	depends on BLOCK
	select MTD_BLKDEVS
	---help---
	  Although most flash chips have an erase size too large to be useful
	  as block devices, it is possible to use MTD devices which are based
	  on RAM chips in this manner. This block device is a user of MTD
	  devices performing that function

	  At the moment, it is also required for the Journalling Flash File
	  System(s) to obtain a handle on the MTD device when it's mounted
	  (although JFFS and JFFS2 don't actually use any of the functionality
	  of the mtdblock device).
	  Later, it may be extended to perform read/erase/modify/write cycles
	  on flash chips to emulate a smaller block size. Needless to say,
	  this is very unsafe, but could be useful for file systems which are
	  almost never written to.
	  You do not need this option for use with the DiskOnChip devices.For
	  those, enable NFTL support (CONFIG_NFTL) instead.

如果想真正应用/dev/mtdblock设备 可以用其他文件系统,如:

mkfs.ext2 /dev/mtdblock0;mount -t ext4 /dev/mtdblock0
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值