Linux NAND Flash建立分区表的几种方式

发布时间 2023-12-15 16:56:49作者: ArnoldLu

 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中分区表。       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);--根据cmdline内容,解析MTD分区信息到partitions中。
        if (err)
            return err;
    }
...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设置

bootargs="mtdparts=edb7312-nor:256k(ARMboot)ro,-(root);edb7312-nand:-(home)"