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

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

原文地址:https://www.cnblogs.com/idyllcheung/p/14016919.html