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)
1
为了使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)
1
通过该函数,将相关的解析方法添加到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;
}
————————————————
版权声明:本文为CSDN博主「楓潇潇」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/u013836909/article/details/93300979