Linux NAND Flash建立分区表的几种方式
关键词:cmdline parser、dts parser、cmdline、mtdparts等等。
MTD支持三种创建分区表的方式:
- cmdline:通过bootargs传入。
- dts:在dts中编写。
- struct mtd_partition结构体代码:代码中固定配置。
他们的优先级是:分区结构体代码 > cmdline > dts。
mtd_device_parse_register--解析MTD分区表,并注册MTD设备。
parse_mtd_partitions--根据types指定的parserr名称解析分区表到parser_data中。
mtd_part_of_parse--解析DTS中分区表,使用parse_fixed_partitions()处理。。
mtd_part_do_parse
mtd_part_parser_get--获取parser,比如cmdline parser。
mtd_part_do_parse--调用parser_fn完成解析,比如parse_cmdline_partitions()。
add_mtd_partitions
add_mtd_partitions--在cmdline和dts partition都失败的情况下,使用代码中配置MTD分区表。
MTD设备注册包括两种,区别是:
mtd_device_parse_register相对于mtd_device_register多了types和parser_data的设置。
extern int mtd_device_parse_register(struct mtd_info *mtd, const char * const *part_probe_types, struct mtd_part_parser_data *parser_data, const struct mtd_partition *defparts, int defnr_parts); #define mtd_device_register(master, parts, nr_parts) \ mtd_device_parse_register(master, NULL, NULL, parts, nr_parts) extern int mtd_device_unregister(struct mtd_info *master);
mtd_device_parse_register()是MTD注册的入口,并负责解析创建分区表:
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) { ... ret = parse_mtd_partitions(mtd, types, parser_data);--根据types指定的parser,或者遍历parser解析出MTD分区表,并创建分区。 if (ret > 0) ret = 0; else if (nr_parts) ret = add_mtd_partitions(mtd, parts, nr_parts);--根据提供的parts/nr_parts分区信息创建分区。 else if (!device_is_registered(&mtd->dev)) ret = add_mtd_device(mtd); else ret = 0; ... }
parse_mtd_partitions()负责解析cmdline和dts配置的MTD分区表:
static const char * const default_mtd_part_types[] = { "cmdlinepart", "ofpart", NULL }; /* Check DT only when looking for subpartitions. */ static const char * const default_subpartition_types[] = { "ofpart", NULL }; int parse_mtd_partitions(struct mtd_info *master, const char *const *types, struct mtd_part_parser_data *data) { struct mtd_partitions pparts = { }; struct mtd_part_parser *parser; int ret, err = 0; if (!types) types = mtd_is_partition(master) ? default_subpartition_types : default_mtd_part_types;--如果没有指定types名称,则选择系统指定的parser列表。 for ( ; *types; types++) {--遍历types列表,逐个进行解析。正确找到分区表后返回。对于default_mtd_part_types,则依次进行cmdline和dts的分区解析。 if (!strcmp(*types, "ofpart")) { ret = mtd_part_of_parse(master, &pparts);--解析dts的MTD分区表。 } else { parser = mtd_part_parser_get(*types);--解析非dts的MTD分区表,已知的有cmdline。 ... ret = mtd_part_do_parse(parser, master, &pparts, data); if (ret <= 0) mtd_part_parser_put(parser); } /* Found partitions! */ if (ret > 0) { err = add_mtd_partitions(master, pparts.parts, pparts.nr_parts);--解析成功后即添加MTD设备和分区属性。 mtd_part_parser_cleanup(&pparts); return err ? err : pparts.nr_parts; } ... } return err; }
1 代码中创建分区表
在代码中编写struct mtd_partition结构体,比如:
static const struct mtd_partition partition_info[] = { { .name = "Kernel", .offset = 0, .size = 3 * SZ_1M + SZ_512K }, { .name = "u-boot", .offset = 3 * SZ_1M + SZ_512K, .size = SZ_256K }, ... };
然后调用mtd_device_register()/mtd_device_parse_register()注册即可。
2 dts中创建分区表
dts中分区表如下:
nand: nand@0x12345678 {
partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;
partition@xx {
label = "xxx";
reg = <start size>;
read-only;
};
...
}
}
mtd_of_parser()根据dts指定的parser进行解析,否则使用默认的fixed-partitions parser。
static int mtd_part_of_parse(struct mtd_info *master, struct mtd_partitions *pparts) {
... const char *fixed = "fixed-partitions"; int ret, err = 0; ... of_property_for_each_string(np, "compatible", prop, compat) {--读取partitions的compatible属性,获取parser名称。 parser = mtd_part_get_compatible_parser(compat); if (!parser) continue; ret = mtd_part_do_parse(parser, master, pparts, NULL); if (ret > 0) { of_node_put(np); return ret; } mtd_part_parser_put(parser); if (ret < 0 && !err) err = ret; } of_node_put(np); parser = mtd_part_parser_get(fixed);--如果partitions中没有指定parser名称,或者解析失败。则使用默认的fixed-partition parser。 if (!parser && !request_module("%s", fixed))--请求加载fixed-partitions模组。 parser = mtd_part_parser_get(fixed); if (parser) { ret = mtd_part_do_parse(parser, master, pparts, NULL); if (ret > 0) return ret; mtd_part_parser_put(parser); if (ret < 0 && !err) err = ret; } return err; }
ofpart_parser_init注册dts parser:
MODULE_ALIAS("fixed-partitions"); MODULE_ALIAS("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); static struct mtd_part_parser ofpart_parser = { .parse_fn = parse_fixed_partitions, .name = "fixed-partitions", .of_match_table = parse_ofpart_match_table, };
parse_fixed_partitions()解析dts中每一个partition,包括offset、size、label、read-only、lock等:
static int parse_fixed_partitions(struct mtd_info *master, const struct mtd_partition **pparts, struct mtd_part_parser_data *data) { struct mtd_partition *parts; struct device_node *mtd_node; struct device_node *ofpart_node; const char *partname; struct device_node *pp; int nr_parts, i, ret = 0; bool dedicated = true; /* Pull of_node from the master device node */ mtd_node = mtd_get_of_node(master); if (!mtd_node) return 0; ofpart_node = of_get_child_by_name(mtd_node, "partitions"); ... nr_parts = 0; for_each_child_of_node(ofpart_node, pp) { if (!dedicated && node_has_compatible(pp)) continue; nr_parts++;--获取总分区数。 } if (nr_parts == 0) return 0; parts = kcalloc(nr_parts, sizeof(*parts), GFP_KERNEL);--为每个分区分配结构体。 if (!parts) return -ENOMEM; i = 0; for_each_child_of_node(ofpart_node, pp) { ... reg = of_get_property(pp, "reg", &len);--reg包括两个成员,依次是offset和size。 ... a_cells = of_n_addr_cells(pp); s_cells = of_n_size_cells(pp); if (len / 4 != a_cells + s_cells) { pr_debug("%s: ofpart partition %pOF (%pOF) error parsing reg property.\n", master->name, pp, mtd_node); goto ofpart_fail; } parts[i].offset = of_read_number(reg, a_cells); parts[i].size = of_read_number(reg + a_cells, s_cells); parts[i].of_node = pp; partname = of_get_property(pp, "label", &len);--分区名称设置。 if (!partname) partname = of_get_property(pp, "name", &len); parts[i].name = partname; if (of_get_property(pp, "read-only", &len))--read-only属性设置。 parts[i].mask_flags |= MTD_WRITEABLE; if (of_get_property(pp, "lock", &len))--lock属性设置。 parts[i].mask_flags |= MTD_POWERUP_LOCK; i++; } ... }
3 cmdline创建分区表
内核中支持cmdline分区表,需要如下配置:
Device Drivers ->Memory Technology Device(MTD) support ->Partition parsers ->Command line partition table parsing
在内核启动时通过register_mtd_parser()注册cmdline_parser,parser的名称为cmdlinepart:
static int __init mtdpart_setup(char *s) { cmdline = s; return 1; } __setup("mtdparts=", mtdpart_setup);--当bootargs设置了mtdparts=xxx后被调用,并将xxx赋给cmdline。 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);
parse_cmdline_partitions()从cmdline中解析出MTD分区信息,填充到pparts中:
static int parse_cmdline_partitions(struct mtd_info *master, const struct mtd_partition **pparts, struct mtd_part_parser_data *data) { ... /* parse command line */ if (!cmdline_parsed) { err = mtdpart_setup_real(cmdline);--根据mtdparts内容,解析MTD分区信息到partitions中。 if (err) return err; } ...
for (part = partitions; part; part = part->next) {
if ((!mtd_id) || (!strcmp(part->mtd_id, mtd_id)))--mtdparts的开始存放nand名称必须匹配,根据名称和NAND设备匹配在进行分区表创建。
break;
}
for (i = 0, offset = 0; i < part->num_parts; i++) {--对offset/size做检查。 if (part->parts[i].offset == OFFSET_CONTINUOUS) part->parts[i].offset = offset; else offset = part->parts[i].offset; if (part->parts[i].size == SIZE_REMAINING) part->parts[i].size = master->size - offset; if (offset + part->parts[i].size > master->size) { pr_warn("%s: partitioning exceeds flash size, truncating\n", part->mtd_id); part->parts[i].size = master->size - offset; } offset += part->parts[i].size; if (part->parts[i].size == 0) { pr_warn("%s: skipping zero sized partition\n", part->mtd_id); part->num_parts--; memmove(&part->parts[i], &part->parts[i + 1], sizeof(*part->parts) * (part->num_parts - i)); i--; } } *pparts = kmemdup(part->parts, sizeof(*part->parts) * part->num_parts, GFP_KERNEL); ... }
mtdpart_setup_real()调用netpart()解析每一个分区:
static struct mtd_partition * newpart(char *s, char **retptr, int *num_parts, int this_part, unsigned char **extra_mem_ptr, int extra_mem_size) { ... /* fetch the partition size */ if (*s == '-') {--表示分区占用剩下所有空间。 /* assign all remaining space to this partition */ size = SIZE_REMAINING; s++; } else { size = memparse(s, &s);--获取分区大小。 ... } /* fetch partition name and flags */ mask_flags = 0; /* this is going to be a regular partition */ delim = 0; /* check for offset */ if (*s == '@') { s++; offset = memparse(s, &s);--获取offset大小。 } ... /* record name length for memory allocation later */ extra_mem_size += name_len + 1; /* test for options */ if (strncmp(s, "ro", 2) == 0) {--对应dts的read-only属性。 mask_flags |= MTD_WRITEABLE; s += 2; } if (strncmp(s, "lk", 2) == 0) {--对应dts的lock属性。 mask_flags |= MTD_POWERUP_LOCK; s += 2; } /* test if more partitions are following */ if (*s == ',') { if (size == SIZE_REMAINING) { pr_err("no partitions allowed after a fill-up partition\n"); return ERR_PTR(-EINVAL); } /* more partitions follow, parse them */ parts = newpart(s + 1, &s, num_parts, this_part + 1, &extra_mem, extra_mem_size); if (IS_ERR(parts)) return parts; } else { /* this is the last partition: allocate space for all */ int alloc_size; *num_parts = this_part + 1; alloc_size = *num_parts * sizeof(struct mtd_partition) + extra_mem_size; parts = kzalloc(alloc_size, GFP_KERNEL); if (!parts) return ERR_PTR(-ENOMEM); extra_mem = (unsigned char *)(parts + *num_parts); } /* * enter this partition (offset will be calculated later if it is * OFFSET_CONTINUOUS at this point) */ parts[this_part].size = size; parts[this_part].offset = offset; parts[this_part].mask_flags = mask_flags; ... /* return partition table */ return parts; }
3.1 bootargs中mtdparts设置
其中edb7312-nor一定要和驱动设备中的mtd->name匹配:
bootargs="mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)"
详细规则如下:
mtdparts=<mtddef>[;<mtddef] <mtddef> := <mtd-id>:<partdef>[,<partdef>] <partdef> := <size>[@<offset>][<name>][ro][lk] <mtd-id> := unique name used in mapping driver/device (mtd->name) <size> := standard linux memsize OR "-" to denote all remaining space size is automatically truncated at end of device if specified or truncated size is 0 the part is skipped <offset> := standard linux memsize if omitted the part will immediately follow the previous part or 0 if the first part <name> := '(' NAME ')' NAME will appear in /proc/mtd
3.2 uboot传递mtdparts参数
uboot支持通过env bootargs替换原有的dtb中的bootargs:
General setup
->Enable kernel command line setup
处理流程如下:
do_bootz
bootz_start
bootm_find_images
boot_get_fdt--查找合适的fdt文件,返回fdt的起始地址和大小。
do_bootm_states
bootm_find_os
boot_get_kernel
genimg_get_format--根据os_hdr,判定kernel image类型,比如IMAGE_FORMAT_LEGACY/IMAGE_FORMAT_FIT/IMAGE_FORMAT_ANDROID。
fit_image_get_type
fit_image_get_comp--获取压缩类型。
fit_image_get_os--获取os类型。
fit_image_get_arch--获取架构类型。
fit_get_end
fit_image_get_load--获取镜像加载地址。
bootm_load_os--有需要则解压,然后检查加载和启动参数是存在overlap。
do_bootm_linux--根据images->os.os类型,比如为IH_OS_LINUX则启动Linux内核。
boot_prep_linux
image_setup_linux--对于打开IMAGE_ENABLE_OF_LIBFDT并且存在DTB时,将env bootargs附加到dtb的bootargs。
boot_fdt_add_mem_rsv_regions--从reserved-memory节点获取保留内存信息。
boot_get_cmdline--在使能IMAGE_BOOT_GET_CMDLINE时,从环境变量bootargs获取参数替换cmdline。
boot_relocate_fdt--为dtb重新分配内存。
image_setup_libfdt--更新fdt配置。
fdt_root--更新serial-number。
fdt_chosen--从环境变量bootargs获取参数替换dtb中的bootargs。
fdt_fixup_stdout--更新linux,stdout-path。
arch_fixup_fdt
fdt_fixup_memory_banks--更新memory节点。
setup_start_tag--对于没有DTB的情况,使能BOOTM_ENABLE_TAGS的情况。
setup_commandline_tag--附加env bootargs到cmdline。
boot_jump_linux
但是需要保留dtb中原有的bootargs,然后将mtdparts附加到后面,所以需要修改流程。
在fdt_chosen()中按照如下流程修改:
- 初始化一个空字符串bootargs_mtdparts。
- 从环境变量中获取bootargs值,并赋值给bootargs_mtdparts。
- 附加" mtdparts="到bootargs_mtdparts。
- 从环境变量中获取mtdparts值,并附加到bootargs_mtdparts。
自此bootarts就附加了mtdparts属性,在Linux启动过程中优先解析bootargs中的分区表。
3.4 linux中cmdline调试
打开drivers/mtd/parser/cmdlinepart.c,使能dbg:
#if 0 #define dbg(x) do { printk("DEBUG-CMDLINE-PART: "); printk x; } while(0) #else #define dbg(x) #endif
浙公网安备 33010602011771号