zoukankan      html  css  js  c++  java
  • uboot移植与源码分析总结(5)-MTD管理(只看了部分)

    https://www.eefocus.com/lishutong/blog/13-06/295364_953ec.html

    MTD是Linux系统下的子系统,用于支持norflash、nandflash等设备的访问支持。我初步对比了uboot和linux下的mtd源码,发现uboot的源码其实就是从linux下移植过来的。

    MTD设备抽像结构及管理(mtdcore.c)

    基于MTD的子系统将所有norflash、nandflash设备抽像成struct mtd_info结构。该结构包含了设备的常见属性,如可擦除区域分布(eraseregions)、编程页大小(writesize),以及为了考虑nand特性而增加的oobsize、oobavaild属性。另外还包含了一些该设备读写操作接口的指针,同样的,有些函数也是考虑到nand设备而增加(如read_oob、write_oob)。

    struct mtd_info {   // 见driver/mtd/mtdcore.c 
        u_char type; 
        u_int32_t flags; 
        uint64_t size;     /* Total size of the MTD */  
        u_int32_t erasesize; 
        u_int32_t writesize;  
        u_int32_t oobsize;   /* Amount of OOB data per block (e.g. 16) */ 
        u_int32_t oobavail;  /* Available OOB bytes per block */  
        const char *name; 
        int index;  
        struct nand_ecclayout *ecclayout;

        int numeraseregions; 
        struct mtd_erase_region_info *eraseregions;

        int (*erase) (struct mtd_info *mtd, struct erase_info *instr);  
        int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, void **virt, phys_addr_t *phys);  
        void (*unpoint) (struct mtd_info *mtd, loff_t from, size_t len); 
         int (*read) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); 
        int (*write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  
        int (*panic_write) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf);  
        int (*read_oob) (struct mtd_info *mtd, loff_t from, struct mtd_oob_ops *ops); 
        int (*write_oob) (struct mtd_info *mtd, loff_t to,  struct mtd_oob_ops *ops);  
        int (*get_fact_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len); 
        int (*read_fact_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); 
        int (*get_user_prot_info) (struct mtd_info *mtd, struct otp_info *buf, size_t len); 
        int (*read_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); 
        int (*write_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); 
        int (*lock_user_prot_reg) (struct mtd_info *mtd, loff_t from, size_t len);  
        void (*sync) (struct mtd_info *mtd);  
        int (*lock) (struct mtd_info *mtd, loff_t ofs, uint64_t len); 
        int (*unlock) (struct mtd_info *mtd, loff_t ofs, uint64_t len);  
        int (*block_isbad) (struct mtd_info *mtd, loff_t ofs); 
        int (*block_markbad) (struct mtd_info *mtd, loff_t ofs);

        struct mtd_ecc_stats ecc_stats; 
        int subpage_sft;  
        void *priv;  
        struct module *owner; 
        int usecount;  
        int (*get_device) (struct mtd_info *mtd); 
        void (*put_device) (struct mtd_info *mtd); 
    };

    uboot将所有的MTD放置在mtd_table列表中。基于该列表提供了增加、删除、查找操作。

    struct mtd_info *mtd_table[MAX_MTD_DEVICES]; // 见driver/mtd/mtdcore.c

    当有新的MTD设备需要添加到MTD列表中时,即调用add_mtd_device(mtd),从该列表中寻找一个空闲项,然后修改其指针指向该结构,即完成了添加过程。

    而当有MTD设备需要从MTD列表移除时,即调用del_mtd_device(mtd),查找MTD是否在列表中,如果在列表中且该MTD结构没有被引用,则将MTD列表相应表项指针置NULL,即完成删除过程。

    而get_mtd_device(mtd,num)和get_mtd_device_nm(name)则完成MTD结构的查找,前者通过指定的序列来返回MTD结构,而后者则通过名称来查找MTD结构。并且从其函数名get_xxx知其意,这些操作表明获得该结构,增加mtd->usecount计数。

    而当调用put_mtd_device(mtd)则递减mtd->usecount计数,表明释放对该结构的引用。

    MTD分区管理机制(mtdpart.c)

    MTD除了可以管理整个Flash设备外,还支持将设备划分成多个区域管理,即MTD分区。如下图类似的Linux MTD分区:

    uboot使用struct mtd_part 来表示每个分区,注意到该分区结构中使用了struct mtd_info结构来表示该分区的属性和操作接口,其它的成员变量只表示该分区在整个MTD分区的偏移及其所在的MTD设备。这样做可以将MTD分区视作一个独立的MTD设备。

    struct mtd_part { 
        struct mtd_info mtd; 
        struct mtd_info *master; 
        uint64_t offset; 
        int index; 
        struct list_head list; 
        int registered; 
    };

    所有MTD分区都被加入到mtd_partitions链表中统一管理。

    struct list_head mtd_partitions;

    在特定的MTD设备上添加分区时,一般会要求先定义一个分区信息表,在该表中设定好各个分区的地址参数。该表项结构如下:

    struct mtd_partition { 
        char *name;            /* identifier string */ 
        uint64_t size;            /* partition size */ 
        uint64_t offset;        /* offset within the master MTD space */ 
        u_int32_t mask_flags;        /* master MTD flags to mask out for this partition */ 
        struct nand_ecclayout *ecclayout;    /* out of band layout for this partition (NAND only)*/ 
        struct mtd_info **mtdp;        /* pointer to store the MTD object */ 
    };

    当定义好该分区表结构后,调用add_mtd_partitions(master, parts, nbparts),将分区表传入。add_mtd_partitions将会调用add_one_partition()逐个读取分区表中的分区信息添加MTD分区。

    而del_mtd_partitions(master)则完成指定MTD设备(master)下的MTD分区。它会将所有MTD分区结构从mtd_partitions分区链表中移除,最后调用del_mtd_device删除该分区。

    add_one_partition(master,part,parno,cur_offset)完成某个特定分区的添加。一个分区可以看作是是一个MTD设备的某个区域的抽像设备,所以这个抽像的设备必然在操作接口和某些属性上直接从MTD设备继承。add_one_partition所要完成的工作就是创建一个MTD分区结构mtd_part,然后从MTD设备中取出部分属性填充mtd_part->mtd结构。然而在操作接口上,虽然分区是MTD设备的一部分,但读写接口上仍有些变化。mtdpart.c中定义了以part_开始的函数,这些函数用于实现分区操作,并最终调用所在MTD设备的读写操作接口。

    如果仔细阅读part_开头的函数,可以发现在读写分区时,指定的读写起始位置是相对于该分区开始的偏移量。而最终调用MTD设备的读写接口函数时,需要将该偏移量+分区在MTD设备中的偏移,以获得读取的绝对偏移值。示例代码如下:

    static int part_read(struct mtd_info *mtd, loff_t from, size_t len, 
            size_t *retlen, u_char *buf) 

        struct mtd_part *part = PART(mtd); 
        struct mtd_ecc_stats stats; 
        int res;

        stats = part->master->ecc_stats;

        if (from >= mtd->size) 
            len = 0; 
        else if (from + len > mtd->size) 
            len = mtd->size – from; 
        res = part->master->read(part->master, from + part->offset,   // 调用的是master读取函数,所以这里的地址是绝对地址 
                       len, retlen, buf); 
        if (unlikely(res)) { 
            if (res == -EUCLEAN) 
                mtd->ecc_stats.corrected += part->master->ecc_stats.corrected – stats.corrected; 
            if (res == -EBADMSG) 
                mtd->ecc_stats.failed += part->master->ecc_stats.failed – stats.failed; 
        } 
        return res; 
    }

    MTD设备级联机制(mtdconcat.c)

    如果说前面提到的分区机制是将一个MTD设备划分为多个抽像的MTD子设备,那么这里的级联机制则是将一个MTD设备抽像为MTD子设备,多个MTD设备级联成一个大的、抽像的MTD设备。其大致结构示意图如下:

    一个抽像的虚拟MTD设备使用struct mtd_concat表示:

    struct mtd_concat { 
        struct mtd_info mtd; 
        int num_subdev; 
        struct mtd_info **subdev; 
    };

    类似于前一小节提到的struct mtd_part, mtd_conncat中同样包含了一个mtd_info结构用于描述该设备的信息。

    这部分功能的实现机制虽然与分区机制有差别,但原理上还是很类似的,源码的编写方式也类似。mtdconcat中提供了concat_开头的函数,用于实现读写等操作。源码并不复杂,所以这篇文章将不再详细分析。

    小结

    前面的MTD源码分析内容有些简单,只是分析了个大致框架,并未深入细节。

    我认为在对MTD各个功能存在的必要性、设计策略没有了解的前提下去看代码,只能是了解整体而无法深入细节。并且目前我找不到一个理由去看细节,待需要时再看即可。

    这种抽像的代码,其存在必然是出于某种需求。而其中的某些设计,也可能是出于某种折衷和考虑。这方面的东西我还不能很好的把握,但是可提醒自己注意阅读源码时不要一上来就扎入源码中。

  • 相关阅读:
    Java Web(5) Spring 下使用Junit4 单元测试
    聊聊单元测试(三)——Spring Test+JUnit完美组合
    浅谈ELK日志分析平台
    ELK 实现 Java 分布式系统日志分析架构
    ELK(ElasticSearch, Logstash, Kibana)搭建实时日志分析平台
    开源分布式搜索平台ELK(Elasticsearch+Logstash+Kibana)入门学习资源索引
    自动补全下拉框(可输入匹配的下拉框)
    这是一篇满载真诚的微信小程序开发干货
    微服务化的多组件项目,跨地域、分布式版本管理和发布方式
    解放双手,发掘更大的价值:智能化运维
  • 原文地址:https://www.cnblogs.com/idyllcheung/p/14016919.html
Copyright © 2011-2022 走看看