Linux MTD子系统学习(三)

Linux MTD子系统学习(三)

4 Linux mtd分区的建立

4.1 mtd分区的建立方法

4.1.1 内核添加

在内核中添加分区表是就内核常用的方法,主要是在平台设备中添加mtd_parttion,如下:

struct mtd_partition m25p80_part[] = {
    	{
        		.name       = "Bootloader",
        		.offset     = 0,
        		.size       = (1 * SZ_1M),
        		.mask_flags = MTD_CAP_NANDFLASH,
    	},
    	{
        		.name       = "Kernel",
        		.offset     = (1 * SZ_1M),
        		.size       = (31 * SZ_1M) ,
        		.mask_flags = MTD_CAP_NANDFLASH,
    	},
    	{
        		.name       = "User",
        		.offset     = (32 * SZ_1M),
        		.size       = (16 * SZ_1M) ,
    	},
    	{
       	 	.name       = "File System",
        		.offset     = (48 * SZ_1M),
        		.size       = (96 * SZ_1M),
    	}
};

static struct flash_platform_data m25p80_platform_data[] = {
    [0] = {
        .name       = "m25p80",
        .nr_parts  = ARRAY_SIZE(m25p80_part),
        .parts = m25p80_part,
    },
};

static struct spi_board_info xxx_spi_nor_device[] = {
	{
         .modalias = "m25p80",	//spi设备名字,设备驱动探测时会用到该项
          .max_speed_hz = 25000000,      
          .bus_num = 1,
         .chip_select = 1,//该spi设备的片选编号
          .mode = SPI_MODE_0,	//此spi设备支持spi总线的工作模式
         .platform_data = &m25p80_platform_data, //存放flash分区表
	},
	{},
};

4.1.2 bootargs传参

在u-boot或device_tree可以通过添加mtdparts信息到bootargs中,u-boot启动后会将bootargs中的信息传送给kernel,,kernel在启动的时候会解析bootargs中mtdparts的部分,这边举个例子:

mtdparts=spi0.0:128k(boot)ro,128k(dtb)ro,9984k(kernel)ro,3M(rootfs)ro,-(data)

为了使kernel能够解析mtdparts信息,我们需要将内核中的Device Drivers -> Memory Technology Device (MTD) support ->Command line partition table parsing选项开启。

4.1.3 dts传参

dts传参的原理其实和u-boot一样,区别在于:u-boot的时候是通过cmdlinepart.c文件实现分区信息写入LIST_HEAD(mtd_partitions)链表,dts则是用过ofpart.c文件实现分区信息写入LIST_HEAD mtd_partitions)链表,所以同样要把ofpart.c文件的宏打开,在调用mtd_device_parse_register(mtd, probe_types,&ppdata, NULL, 0);函数的时候types要设置成ofpart。
具体参考例子,如下:

&spi0 {
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&spi0_0>;
	num-cs = <1>;    
	ranges = <0 0x08000000>;
    nor_flash@0 {
        partition@1 {
            label = "Bootloader";
            reg = <0x00000000 0x00010000>;
        };
        partition@2 {
            label = "Kernel";
            reg = <0x00100000 0x002000000>;
        };
        partition@3 {
            label = "User";
            reg = <0x002000000 0x03000000>;
        };
        partition@4 {
            label = "File System";
            reg = <0x03000000 0x08000000>;
        };
    };
};

4.2 mtd分区处理流程

4.2.1 mtd分区解析

4.2.1.1 parse_mtd_partitions

该函数依据分区表的类型(以上三种),找到对应的解析方法,解析相关的分区信息,建立kernel可识别的分区表格式,用于后面添加分区表。
源码:drivers/mtd/mtdpart.c

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;
}

//一般只定义了两种类型
static const char * const default_mtd_part_types[] = {
	"cmdlinepart",
	"ofpart",
	NULL
};
4.2.1.2 mtd_part_do_parse

该函数将回调具体的解析函数解析分区表。
源码:drivers/mtd/mtdpart.c

static int mtd_part_do_parse(struct mtd_part_parser *parser,
    			     struct mtd_info *master,
    			     struct mtd_partitions *pparts,
    			     struct mtd_part_parser_data *data)
    {
    	int ret;
	    //回调解析方法中的解析函数解析分区信息,建立分区表
		ret = (*parser->parse_fn)(master, &pparts->parts, data);
		pr_debug("%s: parser %s: %i\n", master->name, parser->name, ret);
		if (ret <= 0)//解析异常判断
			return ret;
	
		pr_notice("%d %s partitions found on MTD device %s\n", ret,
			  parser->name, master->name);
	
		pparts->nr_parts = ret;//分区数目
		pparts->parser = parser;
	
		return ret;
	}

4.2.2 mtd分区解析方法注册

该部分调用register_mtd_parser(),其为宏函数,定义如下:

#define register_mtd_parser(parser) __register_mtd_parser(parser, THIS_MODULE)

通过该函数,将相关的解析方法添加到part_parsers链表中,如下:

int __register_mtd_parser(struct mtd_part_parser *p, struct module *owner)
{
	p->owner = owner;

	if (!p->cleanup)
		p->cleanup = &mtd_part_parser_cleanup_default;

	spin_lock(&part_parser_lock);
	list_add(&p->list, &part_parsers);
	spin_unlock(&part_parser_lock);

	return 0;
}
4.2.2.1 cmdline_parser

源码:drivers/mtd/cmdlinepart.c

static struct mtd_part_parser cmdline_parser = {
	.parse_fn = parse_cmdline_partitions,//解析方法的实现,具体请查看源码
	.name = "cmdlinepart",
};

static int __init cmdline_parser_init(void)
{
	if (mtdparts)
		mtdpart_setup(mtdparts);
	register_mtd_parser(&cmdline_parser);//注册解析方法
	return 0;
}

static void __exit cmdline_parser_exit(void)
{
	deregister_mtd_parser(&cmdline_parser);
}

module_init(cmdline_parser_init);
module_exit(cmdline_parser_exit);
4.2.2.2 ofpart_parser

此处注册了两种设备树的解析方法,差别在设备树中两种的格式不一致。
源码:drivers/mtd/ofpart.c

static struct mtd_part_parser ofpart_parser = {
	.parse_fn = parse_ofpart_partitions,
	.name = "ofpart",
};

static struct mtd_part_parser ofoldpart_parser = {
	.parse_fn = parse_ofoldpart_partitions,
	.name = "ofoldpart",
};

static int __init ofpart_parser_init(void)
{
	register_mtd_parser(&ofpart_parser);
	register_mtd_parser(&ofoldpart_parser);
	return 0;
}

static void __exit ofpart_parser_exit(void)
{
	deregister_mtd_parser(&ofpart_parser);
	deregister_mtd_parser(&ofoldpart_parser);
}

module_init(ofpart_parser_init);
module_exit(ofpart_parser_exit);

4.2.3 mtd分区添加

4.2.3.1 mtd_add_device_partitions

源码:drivers/mtd/mtdcore.c

static int mtd_add_device_partitions(struct mtd_info *mtd,
				     struct mtd_partitions *parts)
{
	const struct mtd_partition *real_parts = parts->parts;
	int nbparts = parts->nr_parts;
	int ret;

    //分区数目为0,建立分区的mtd_info,添加mtd设备,建立一个分区
	if (nbparts == 0 || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER)) {
		ret = add_mtd_device(mtd);
		if (ret)
			return ret;
	}

	if (nbparts > 0) {//分区数目大于0,建立分区的mtd_info,再添加mtd设备
		ret = add_mtd_partitions(mtd, real_parts, nbparts);
		if (ret && IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
			del_mtd_device(mtd);
		return ret;
	}

	return 0;
}
4.2.3.2 add_mtd_partitions

源码:drivers/mtd/mtdcore.c

int add_mtd_partitions(struct mtd_info *master,
		       const struct mtd_partition *parts,
		       int nbparts)
{
	struct mtd_part *slave;
	uint64_t cur_offset = 0;
	int i;

	printk(KERN_NOTICE "Creating %d MTD partitions on \"%s\":\n", nbparts, master->name);

	for (i = 0; i < nbparts; i++) {
        //建立当前分区信息,初始化slave->mtd
		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);
		list_add(&slave->list, &mtd_partitions);//添加分区到分区链表中
		mutex_unlock(&mtd_partitions_mutex);

		add_mtd_device(&slave->mtd);//添加当前分区为mtd设备
		mtd_add_partition_attrs(slave);
		if (parts[i].types)//判断当前分区是否再建立子分区
			mtd_parse_part(slave, parts[i].types);

		cur_offset = slave->offset + slave->mtd.size;
	}

	return 0;
}
4.2.3.3 add_mtd_device

源码:drivers/mtd/mtdcore.c

int add_mtd_device(struct mtd_info *mtd)
{
	struct mtd_notifier *not;
	int i, error;

	/*
	 * May occur, for instance, on buggy drivers which call
	 * mtd_device_parse_register() multiple times on the same master MTD,
	 * especially with CONFIG_MTD_PARTITIONED_MASTER=y.
	 */
	if (WARN_ONCE(mtd->dev.type, "MTD already registered\n"))
		return -EEXIST;

	BUG_ON(mtd->writesize == 0);
	mutex_lock(&mtd_table_mutex);

	i = idr_alloc(&mtd_idr, mtd, 0, 0, GFP_KERNEL);
	if (i < 0) {
		error = i;
		goto fail_locked;
	}

	mtd->index = i;
	mtd->usecount = 0;

	/* default value if not set by driver */
	if (mtd->bitflip_threshold == 0)
		mtd->bitflip_threshold = mtd->ecc_strength;

	if (is_power_of_2(mtd->erasesize))
		mtd->erasesize_shift = ffs(mtd->erasesize) - 1;
	else
		mtd->erasesize_shift = 0;

	if (is_power_of_2(mtd->writesize))
		mtd->writesize_shift = ffs(mtd->writesize) - 1;
	else
		mtd->writesize_shift = 0;

	mtd->erasesize_mask = (1 << mtd->erasesize_shift) - 1;
	mtd->writesize_mask = (1 << mtd->writesize_shift) - 1;

	/* Some chips always power up locked. Unlock them now */
	if ((mtd->flags & MTD_WRITEABLE) && (mtd->flags & MTD_POWERUP_LOCK)) {
		error = mtd_unlock(mtd, 0, mtd->size);
		if (error && error != -EOPNOTSUPP)
			printk(KERN_WARNING
			       "%s: unlock failed, writes may not work\n",
			       mtd->name);
		/* Ignore unlock failures? */
		error = 0;
	}

	/* Caller should have set dev.parent to match the
	 * physical device, if appropriate.
	 */
	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);    //设置设备私有数据
	of_node_get(mtd_get_of_node(mtd));
	error = device_register(&mtd->dev);  //注册可读写的字符设备
	if (error)
		goto fail_added;

	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));
		}
	}
    //创建只读的字符设备,次设备号为奇数
	device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
		      "mtd%dro", i);

	pr_debug("mtd: Giving out device %d to %s\n", i, mtd->name);
	/* No need to get a refcount on the module containing
	   the notifier, since we hold the mtd_table_mutex */
    //查找mtd_notifiers链表中合适的mtd设备类型,添加mtd设备
	list_for_each_entry(not, &mtd_notifiers, list)
		not->add(mtd);

	mutex_unlock(&mtd_table_mutex);
	/* We _know_ we aren't being removed, because
	   our caller is still holding us here. So none
	   of this try_ nonsense, and no bitching about it
	   either. :) */
	__module_get(THIS_MODULE);
	return 0;

fail_added:
	of_node_put(mtd_get_of_node(mtd));
	idr_remove(&mtd_idr, i);
fail_locked:
	mutex_unlock(&mtd_table_mutex);
	return error;
}

版权声明:本文为u013836909原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。