Linux mtd system

作者: Creator_Ly | 来源:发表于2017-03-15 11:31 被阅读1985次
    题图:gratisography

    Linux mtd system

    MTD(Memory Technology Device),内存技术设备是Linux的存储设备中的一个子系统。其设计此系统的目的是,对于内存类的设备,提供一个抽象层,一个接口,使得对于硬件驱动设计者来说,只需要去提供最简单的底层硬件设备的读/写/擦除函数就可以了,数据对于上层使用者来说是如何表示的,可以不关心,因为MTD存储设备子系统都帮你做好了。

    1.MTD框架


    Linux的MTD设备位于drivers/mtd/下面,这边只对其目录结构进行大致的分析,如果想了解具体的细节可以查看crifan的博客,里面有很多连接和文章可供参考,分析的很透彻,MTD文件下的内容如下:

    目录结构

    MTD设备通常可分为四层

    上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。

    设备框架

    1.cmdlinepart.c

    当mtd分区表由u-boot通过cmd参数传输给linux时,linux内核可以不用对mtdparts进行注册添加,只需要将MTD中的command line partition选项开启即可。使用这种的方法u-boot下需要对MTD进行支持,且所传输的mtd分区参数要符合格式要求。

    2.devices文件夹

    当我们有一个spi flash设备时且要使用mtd进行管理,我们一般会将其放在devices文件夹下,如devices文件夹下面的m25p80.c就是一个典型的spi flash设备。

    3.chips/nand/onenand文件夹

    nand flash 驱动在nand文件夹下;

    onenand flash 驱动在onenand文件夹下;

    nor flash比较杂,下面几个文件下都会有:

    • chips:cfi/jedec接口通用驱动
    • devices:nor flash底层驱动(spi flash)
    • maps:nor flash映射关系相关函数

    4.核心文件

    mtdchar.c : MTD字符设备接口相关实现,设备号31;

    mtdblock.c : MTD块设备接口相关实现,设备号90,;

    mtdcore.c: MTD原始设备接口相关实现;

    mtdpart.c : MTD分区接口相关实现。

    5.ubi

    ubifs文件的支持层,当使用ubifs文件系统时,需要将Device Drivers -> Memory Technology Device (MTD) support -> UBI -Unsorted block image 中的Enable UBI选中。

    将File systems -> Miscellaneous filesystems中的UBIFS file system support选中。

    2.MTD分区表的实现


    在开机过程从console经常可以看到类似以下信息,

    0x000000000000-0x000000100000 : "Bootloade"
    0x000000100000-0x000002000000 : "Kernel"
    0x000002000000-0x000003000000 : "User"
    0x000003000000-0x000008000000 : "File System"
    

    这就是MTD给我们一种最直观的表示形式,给我们展示了内存中各模块的分区结构,但这些分区是怎样实现的呢?分区表的实现方式有几种,下面进行分别说明:

    注:分区表实现的前提是MTD设备驱动已经成功了,否则连驱动都没成功就无分区可说了。

    1.内核中添加

    在内核中添加这是一个比较经常使用的方法,随便一本驱动移植的书上应该都有,主要就是在平台设备里面添加mtd_partition,添加类似下面的信息,这边就不过多描述

    struct mtd_partition s3c_nand_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 s3c_nand_set s3c_nand_sets[] = {
        [0] = {
            .name       = "nand",
            .nr_chips   = 1,
            .nr_partitions  = ARRAY_SIZE(s3c_nand_part),
            .partitions = ok6410_nand_part,
        },
    };
    
    static struct s3c_platform_nand s3c_nand_info = {
        .tacls      = 25,
        .twrph0     = 55,
        .twrph1     = 40,
        .nr_sets    = ARRAY_SIZE(s3c_nand_sets),
        .sets       = ok6410_nand_sets,
    };
    
    static void __init s3c_machine_init(void)
    {
        s3c_nand_set_platdata(&s3c_nand_info); 
    }
    
    

    因为我们的MTD驱动已经完成了,当device和driver匹配后会调用驱动中的probe接口函数,我们需要在probe函数里面调用add_mtd_partitions(s3c_mtd, sets->partitions, sets->nr_partitions);实现分区表的添加。

    2.u-boot传参

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

    mtdparts=nand.0:1M(Bootloader)ro,31M(Kernel)ro,16M(User),96M(File System),更具体的mtdparts格式可以查阅下相关资料。

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

    在内核中添加分区表的时候,我们是在平台设备里面加入mtd_partition信息。这边通过u-boot传参则取消平台设备里面的partition信息,那我们需要怎样解析u-boot的传过来的mtdparts呢。

    u-boot传参过来后,cmdlinepart.c中会将这些参数解析好,存在里面LIST_HEAD(part_parsers)链表里面,然后我们在驱动的probe函数中,通过调用mtd_device_parse_register(mtd, probe_types,&ppdata, NULL, 0);函数。

    mtd_device_parse_register()函数位于drivers/mtd/mtdcore.c 中,内容如下:

    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)
    {
        int err;
        struct mtd_partition *real_parts;
    
        err = parse_mtd_partitions(mtd, types, &real_parts, parser_data);
        if (err <= 0 && nr_parts && parts) {
            real_parts = kmemdup(parts, sizeof(*parts) * nr_parts,
                         GFP_KERNEL);
            if (!real_parts)
                err = -ENOMEM;
            else
                err = nr_parts;
        }
    
        if (err > 0) {
            err = add_mtd_partitions(mtd, real_parts, err);
            kfree(real_parts);
        } else if (err == 0) {
            err = add_mtd_device(mtd);
            if (err == 1)
                err = -ENODEV;
        }
    
        return err;
    }
    
    

    可以看到该函数会先执行parse_mtd_partitions(mtd, types, &real_parts, parser_data);函数,后面还是通过add_mtd_partitions()函数来实现分区表的添加。

    parse_mtd_partitions()函数位于drivers/mtd/mtdpart.c中,内容如下:

    int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
                 struct mtd_partition **pparts,
                 struct mtd_part_parser_data *data)
    {
        struct mtd_part_parser *parser;
        int ret = 0;
    
        if (!types)
            types = default_mtd_part_types;
    
        for ( ; ret <= 0 && *types; types++) {
            parser = get_partition_parser(*types);
            if (!parser && !request_module("%s", *types))
                parser = get_partition_parser(*types);
            if (!parser)
                continue;
            ret = (*parser->parse_fn)(master, pparts, data);
            put_partition_parser(parser);
            if (ret > 0) {
                printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
                       ret, parser->name, master->name);
                break;
            }
        }
        return ret;
    }
    

    进入parse_mtd_partitions()函数会先判断types的类型,如果为空则给默认值,types的类型一般就两种,如下:

    static const char * const default_mtd_part_types[] = {
        "cmdlinepart",
        "ofpart",
        NULL
    };
    

    第一个"cmdlinepart"即u-boot传参的方式,第二个"ofpart"即下面要讲到的使用dts传参的方式,判断完类型后,就通过get_partition_parser去解析part_parsers链表里面的数据,这样就完成u-boot参数的解析。

    3.dts传参

    在Linux3.14以后的linux版本中,加入一个新的知识DTS(Device tree),dts其实就是为了解决ARM Linux中的冗余代码,在Linux2.6版本的arch/arm/plat.xxx和arch/arm/mach.xxx中充斥着大量的垃圾代码,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码,关于dts可以自行查阅资料。

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

    如果去对比Linux2.6版本和Linux3.14版本,会发现drivers/mtd/ofpart.c和drivers/mtd/mtdpart.c文件有所不同,Linux3.8版本里面多了Device tree这一部分的内容,感兴趣的可以自己深究下。

    这边举个dts的例子:

     pinctrl-0 = <&s3c_nand_flash>;
        ranges = <0 0 0x000000000000 0x000008000000>;   /* CS0: NAND */
        nand@0,0 {
            partition@1 {
                label = "Bootloader";
                reg = <0x000000000000 0x000000100000>;
            };
            partition@2 {
                label = "Kernel";
                reg = <0x000000100000 0x000002000000>;
            };
            partition@3 {
                label = "User";
                reg = <0x000002000000 0x000003000000>;
            };
            partition@4 {
                label = "File System";
                reg = <0x000003000000 0x000008000000>;
            };
        };
    

    Linux mtd system的分析就到这边,有感悟时会持续会更新。

    注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

    相关文章

      网友评论

        本文标题:Linux mtd system

        本文链接:https://www.haomeiwen.com/subject/srutnttx.html