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各个功能存在的必要性、设计策略没有了解的前提下去看代码,只能是了解整体而无法深入细节。并且目前我找不到一个理由去看细节,待需要时再看即可。
这种抽像的代码,其存在必然是出于某种需求。而其中的某些设计,也可能是出于某种折衷和考虑。这方面的东西我还不能很好的把握,但是可提醒自己注意阅读源码时不要一上来就扎入源码中。