uboot mtdparts
uboot对mtdparts的支持
本文以u-boot-2020.07版本为例分析。
需要打开宏 CONFIG_CMD_MTD 和 宏 CONFIG_CMD_MTDPARTS;
uboot中用struct mtd_inifo来描述一个mtd设备,每个mtd设备又可以进行分区,其中:
struct mtd_info {
//...
/* MTD devices do not have any parent. MTD partitions do. */
struct mtd_info *parent;
/*
* Offset of the partition relatively to the parent offset.
* Is 0 for real MTD devices (ie. not partitions).
*/
u64 offset;
/*
* List node used to add an MTD partition to the parent
* partition list.
*/
struct list_head node;
/*
* List of partitions attached to this MTD device (the parent
* MTD device can itself be a partition).
*/
struct list_head partitions;
}
- parent:分区的父设备
- offset:分区相对于父设备的偏移,若offset为0,则代表一个父设备
- node:用于添加到父设备分区链表的节点
- partitions:此mtd设备的所有分区节点都会挂在这个链表头上
通过上述几个成员,就可以把mtd设备及其分区组织成一个树状结构。
uboot mtd命令
用 mtd --help 命令可以查看mtd相关的命令使用:

mtd list:列出所有的mtd设备及其分区,比如:

上图表示,有一个 mtd name为 "nor1"的mtd设备,是个norflash,块大小为64k,总的地址范围为 0x0~0x2000000(256Mbytes),其下又分了6个分区,包含了每个分区的name和相对于父设备的地址
mtd read <name> <addr> [<off> [<size>]]:读取mtd
<name>:mtd设备name,可以是mtd根设备(如上面的nor1),也可以是分区设备(如上面的boot)
<addr>:读取的数据存放的memory地址
<off>:可选,读取的偏移地址(相对于该mtd设备),默认为0。
<size>:可选,读取的字节数,不能超过该mtd设备的大小
比如 mtd read zsp 0x80000000 0x10 0x100:表示从zsp分区0x10处(也是"nor1"设备的0x100010地址处)开始, 读取0x100字节到0x80000000处。
其他mtd write、erase类似。
mtd_probe_devices函数
mtd的相关命令,也支持对分区的读写,那uboot是怎么知道分区的信息呢?uboot的分区是在哪初始化的呢?
其实mtd的这些命令,都有一个函数的身影:mtd_probe_devices(),定义在文件 drivers/mtd/mtd_uboot.c 。该函数主要实现:
int mtd_probe_devices(void)
{
static char *old_mtdparts;
static char *old_mtdids;
const char *mtdparts = get_mtdparts(); //获取分区信息
const char *mtdids = get_mtdids();//获取mtdids信息
const char *mtdparts_next = mtdparts;
struct mtd_info *mtd;
//确保所有的mtd设备probe
mtd_probe_uclass_mtd_devs();
//如果之前调用过该函数,并且mtdparts和mtdids无变化,则直接跳出;否则就要重新初始化分区
if ((!mtdparts && !old_mtdparts && !mtdids && !old_mtdids) ||
(mtdparts && old_mtdparts && mtdids && old_mtdids &&
!mtd_dev_list_updated() && !mtd_del_all_parts_failed &&
!strcmp(mtdparts, old_mtdparts) &&
!strcmp(mtdids, old_mtdids)))
return 0;
/* Update the local copy of mtdparts */
free(old_mtdparts);
free(old_mtdids);
old_mtdparts = strdup(mtdparts);
old_mtdids = strdup(mtdids);
//删除以前所有的分区,但如果有程序正在使用该分区就卸载失败
mtd_del_all_parts();
mtd_dev_list_updated();
/* If either mtdparts or mtdids is empty, then exit */
if (!mtdparts || !mtdids)
return 0;
/* Start the parsing by ignoring the extra 'mtdparts=' prefix, if any */
if (!strncmp(mtdparts, "mtdparts=", sizeof("mtdparts=") - 1))
mtdparts += 9;
/* For each MTD device in mtdparts */
for (; mtdparts[0] != '\0'; mtdparts = mtdparts_next) {
char mtd_name[MTD_NAME_MAX_LEN], *colon;
struct mtd_partition *parts;
unsigned int mtd_name_len;
int nparts, ret;
//...省略部分代码,主要是字符串解析,得到mtd_name
//找到代表父设备的mtd_info
mtd = get_mtd_device_nm(mtd_name);
/*
* 再次尝试删除该mtd的所有分区,如果仍在用,则跳过该mtd设备
*/
ret = mtd_del_parts(mtd, true);
if (ret < 0)
continue;
/*
* 解析分区信息并填充
*/
ret = mtd_parse_partitions(mtd, &mtdparts, &parts, &nparts);
if (ret) {
printf("Could not parse device %s\n", mtd->name);
put_mtd_device(mtd);
return -EINVAL;
}
if (!nparts)
continue;
/* 创建分区*/
add_mtd_partitions(mtd, parts, nparts);
/* 释放之前解析时分配的内存 */
mtd_free_parsed_partitions(parts, nparts);
put_mtd_device(mtd);
}
/*
* Call mtd_dev_list_updated() to clear updates generated by our own
* parts registration loop.
*/
mtd_dev_list_updated();
return 0;
}
上面的mtd_probe_devices函数在获取分区信息时调用了get_mtdparts函数:
static const char *get_mtdparts(void)
{
__maybe_unused const char *mtdids = NULL;
static char tmp_parts[MTDPARTS_MAXLEN];
const char *mtdparts = NULL;
if (gd->flags & GD_FLG_ENV_READY)
mtdparts = env_get("mtdparts");
else if (env_get_f("mtdparts", tmp_parts, sizeof(tmp_parts)) != -1)
mtdparts = tmp_parts;
if (mtdparts)
return mtdparts;
#if defined(CONFIG_SYS_MTDPARTS_RUNTIME)
board_mtdparts_default(&mtdids, &mtdparts);
#elif defined(MTDPARTS_DEFAULT)
mtdparts = MTDPARTS_DEFAULT;
#elif defined(CONFIG_MTDPARTS_DEFAULT)
mtdparts = CONFIG_MTDPARTS_DEFAULT;
#endif
if (mtdparts)
env_set("mtdparts", mtdparts);
return mtdparts;
}
先从环境变量中获取,环境变量不存在的话,再依次从board_mtdparts_default函数、宏MTDPARTS_DEFAULT、宏CONFIG_MTDPARTS_DEFAULT中获取。
get_mtdids函数类似。
根据上面的分析,我们知道mtd_probe_devices函数最后会调用add_mtd_partitions函数来注册分区。只要使用了mtd的相关命令,都会调用mtd_probe_devices函数。当然也可以主动调用该函数来初始化mtd分区。
uboot mtdparts命令
用 mtdparts --help命令可以查看mtd相关的命令使用:

mtdparts
列出所有的mtd分区信息,比如:

同时它打印了默认的mtdids和mtdparts,分别由宏 MTDIDS_DEFAULT和宏MTDPARTS_DEFAULT指定。这2个宏没定义就由CONFIG_MTDIDS_DEFAULT和CONFIG_MTDPARTS_DEFAULT指定。
mtdparts delall
删除所有的分区

mtdparts del part-id
删除指定的分区,比如 mtdparts del nor1,5 表示删除nor1设备的第5个分区,及jffs2分区,删除后分区如下:

mtdparts add <mtd-dev> <size>[@<offset>] [<name>] [ro]
添加一个分区。
mtd-dev:mtd设备的名称,如nor1
size:新加分区的大小,不能超过父设备的范围,必须为mtd block大小的整数倍
offset:新加分区在父设备的偏移,必须与mtd block大小对齐
name:新加分区的name
ro:可选,表示分区只读

mtdparts default
恢复为默认分区

mtdparts_init函数
- 所有的mtdparts命令,都会调用mtdparts_init函数:
- 先从环境变量中获取mtdids、mtdparts、partition
- 调用parse_mtdids函数解析mtdids字符串,此函数会把解析出来的所有mtdid加到一个全局的链表:static struct list_head mtdids
- 调用parse_mtdparts函数,此函数会调用device_parse函数解析mtdparts字符串,并将解析出的所有分区加到全局链表:static struct list_head devices
- device_parse函数会解析每个mtd设备的type、name和mtd-id,并调用part_parse函数解析每个分区的信息,将所有的分区加到父设备的parts链表中
- part_parse函数会解析一个分区的信息,包括name、size、offset等,并为这个分区分配和初始化struct part_info结构体
- device_parse函数会解析每个mtd设备的type、name和mtd-id,并调用part_parse函数解析每个分区的信息,将所有的分区加到父设备的parts链表中
mtdids分析
在上述描述中说到mtdids这个概念,通常会从环境变量中获取mtdids:

形如nor1=spi-nor,那mtd-id是什么,它的格式又是什么?官方解释:

dev-id追溯
从源码中追溯dev-id的由来,我们知道要注册一个mtd设备的核心函数是 add_mtd_device,当然还有其他接口也能注册mtd设备,比如mtd_device_register、mtd_device_paese_register。
我们以spi nor flash为例分析,uboot中spi norlfash的驱动代码在 drivers/mtd/spi目录下,在这个目录中搜索上面几个函数,发现在sf_mtd.c中调用了add_mtd_device。我们通过gdb在add_mtd_device打一断点,然后通过bt查看函数调用栈:

通过上图可知,uboot在board_init_r函数中会进行flash的初始化,这里会初始化spi norflash,我们看一下spi_flash_mtd_register函数:
static struct mtd_info sf_mtd_info;
static char sf_mtd_name[8];
static int spi_flash_mtd_number(void)
{
#ifdef CONFIG_SYS_MAX_FLASH_BANKS
return CONFIG_SYS_MAX_FLASH_BANKS;
#else
return 0;
#endif
}
int spi_flash_mtd_register(struct spi_flash *flash)
{
//...省略部分代码
memset(&sf_mtd_info, 0, sizeof(sf_mtd_info));
sprintf(sf_mtd_name, "nor%d", spi_flash_mtd_number());
sf_mtd_info.name = sf_mtd_name;
ret = add_mtd_device(&sf_mtd_info);
//...
}
我这里定义了CONFIG_SYS_MAX_FLASH_BANKS 为1,所有最终确定dev-id为 nor1。
这里我们是以spi norflash为例分析的,其他的mtd设备其实类似。
总结
1.u-boot使用环境变量mtdids 和 mtdparts 来指定mtd分区
2.可通过 mtd --help 来获取mtd相关命令的使用
3.可通过mtdparts --help 来获取mtdparts相关命令的使用
4.mtdids的idmap,uboot端可用mtd list来得到<dev-id>,linux端可通过 cat /proc/mtd 来得到 <mtd-id>
浙公网安备 33010602011771号