概述:本文对mtd的整个结构进行了分析,分析得并非很深入,但可以了解大体框架和目录结构,另外本文会对源码文件进行分析,大致描述其作用,针对本文的内容中,如有不恰当的地方,请留言指教,多谢
3 驱动结构
3.1 源码结构
3.1.1 文件结构
drivers/mtd/:
chips目录:
nor flash的驱动程序位于该目录,目录下的文件包含nor flash的一些操作接口,其中nor flash分cfi和jedec两种,其他则是除前面两种以外的,cfi_cmdset_00XX.c是不同厂商的nor flash的操作接口,比如读写擦除等等,chipreg.c的左右就是调用cfi_probe.c这些文件中的probe函数
devices目录:
提供具体设备的驱动,例如m25p80,其他的暂不了解
maps目录:
这个目录下的文件,主要是对读写擦除这些接口实现,以及分区解析和创建,对比了该目录下几个文件的代码,不管函数经过了几次调用,最终基本步骤都是do_map_probe、mtd_device_parse_register,说是nor flash(大部分文件是针对nor flash)驱动代码的具体实现也可以,与之类似的还是nand目录下的文件,基本上也是先实现mtd_info结构体里的函数接口,然后调用mtd_device_parse_register,使用这里我理解为maps是nor flash不同芯片驱动具体实现,nand则是nand flash不同芯片驱动的具体实现
nand目录:
nand flash不同芯片驱动的实现,描述参见maps目录
spi-nor目录:
和maps、nand类似,针对spi nor flash
3.1.2 MTD框架
PS:图片来源于网络
flash硬件驱动: maps目录、nand目录、devices目录
mtd原始设备: mtdpart.c(分区相关接口实现)、mtdcore.c(提供分区解析以及mtd注册等一系列通用的接口)、mtdoops.c、
MTD字符设备: mtdchar.c(注册字符设备)
MTD块设备: mtdblock.c(缓存读写及注册块设备)、mtd_blkdevs.c
3.1.3 结构体
mtd_info: mtd原始设备,记录设备总大小、擦除大小、写大小以及读写擦除接口等,每一个分区里都有一个mtd_info结构体成员,用于记录该分区的大小以及读写擦除操作时调用的接口
mtd_part: 每一个mtd_part表示一个MTD分区,结构体包含了两个mtd_info类型的成员,一个mtd和一个*parent,一般来说,parent指向一整个flash,而mtd只是这片flash上的一个分区,而mtd_part里的offset表示mtd在parent里的偏移
mtd_partitions: 这个结构体用于flash驱动里解析分区时,用于存放分区信息及个数以及解析分区的函数接口
mtd_partition: 每一个mtd_partition表示一个分区,记录了分区名字和分区大小和偏移
mtd_part_parser: 记录分区解析的函数接口
mtdblk_dev: 结构体记录了打开的计数,每open一次,count值加一,结构体里的mdb成员记录了读写函数接口、分区信息、request_queue、workqueue_struct、work_struct、gendisk,后边这几个主要的成员没有深挖,应该是和读写有关的,猜测应该是写操作时有一个工作队列,我们知道块设备随机写肯定是需要对写入的顺序进行优化的,这几个成员应该就是干这个工作,其中gendisk成员根据名字来看就是表示的一个磁盘设备,从代码来看显然也是一个分区
mtd_blktrans_ops: 提供访问的接口,在mtdblock.c中实现了具体的函数接口
3.1.4 mtdblock.c
这是个例外,后来感觉每个文件贴出来太多了,就只保留了这一节,这一块分析得很少,写了这么些了就懒得删了
mtdblock.c提供块设备的缓存读写接口以及注册字符设备,在文件开始处,定义了以下结构体:
struct mtdblk_dev {
struct mtd_blktrans_dev mbd;
int count; //计数值,每次open一次加一,主要用于在第一次打开时初始化一些信息,见mtdblock_open函数
struct mutex cache_mutex; //互斥锁
unsigned char *cache_data; //缓冲区数据指针
unsigned long cache_offset; //数据偏移
unsigned int cache_size; //缓冲区大小
enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; //缓冲区的状态,空状态或其他
};
结构体中的cache_data指针用于读写数据时存放数据,分析到具体函数时再去探究怎么使用该指针
struct mtd_blktrans_dev mbd,后续分析作用
mtd_blktrans_dev结构体如下:
struct mtd_blktrans_dev {
struct mtd_blktrans_ops *tr;//提供接口、函数指针以及一个devs链表和一个list链表
struct list_head list;//list链表
struct mtd_info *mtd;//提供块设备的各种信息,比如oobsize、writesize、erasesize以及块设备的操作函数(读写擦除、lock、unlock等)
struct mutex lock;//互斥锁
int devnum;
bool bg_stop;
unsigned long size;
int readonly;
int open;
struct kref ref;
struct gendisk *disk;
struct attribute_group *disk_attributes;
struct workqueue_struct *wq;
struct work_struct work;
struct request_queue *rq;
spinlock_t queue_lock;
void *priv;
fmode_t file_mode;
};
mtdblock主要接口如下:
结构体mtd_blktrans_ops定义如下:
其中mtdblock_readsect和mtdblock_writesect是读写接口,mtdblock_add_mtd,mtdblock_remove_dev
3.1.4.1 register_mtd_blktrans函数
该函数原型在mtd_blkdevs.c中,实现如下:
该文件中定义struct mtd_notifier变量,结构体如下:
struct mtd_notifier {
void (*add)(struct mtd_info *mtd);
void (*remove)(struct mtd_info *mtd);
struct list_head list;
};
一个添加函数,一个移除函数,一个链表,在mtd_blkdevs.c中定义了一个链表static LIST_HEAD(blktrans_majors); ,看一下add和remove函数
static void blktrans_notify_remove(struct mtd_info *mtd)
{
struct mtd_blktrans_ops *tr;
struct mtd_blktrans_dev *dev, *next;
//循环blktrans_majors链表,在每一个链表里遍历它的devs链表,找到与mtd相等的项,移除
list_for_each_entry(tr, &blktrans_majors, list)
list_for_each_entry_safe(dev, next, &tr->devs, list)
if (dev->mtd == mtd)
tr->remove_dev(dev);//经过调用mtdblock.c中的mtdblock_tr 的mtdblock_remove_dev 调用mtd_blkdevs.c中实现的del_mtd_blktrans_dev函数
}
static void blktrans_notify_add(struct mtd_info *mtd)
{
struct mtd_blktrans_ops *tr;
//非有效的类型,直接返回
if (mtd->type == MTD_ABSENT)
return;
//遍历blktrans_majors链表中的每一项,调用它的add_mtd函数,遍历tr里的devs链表,add_mtd将mtd加入到tr的devs链表里
list_for_each_entry(tr, &blktrans_majors, list)
tr->add_mtd(tr, mtd);
}
static struct mtd_notifier blktrans_notifier = {
.add = blktrans_notify_add,
.remove = blktrans_notify_remove,
};
int register_mtd_blktrans(struct mtd_blktrans_ops *tr)
{
struct mtd_info *mtd;
int ret;
/* Register the notifier if/when the first device type is
registered, to prevent the link/init ordering from fucking
us over. */
//第一次注册
if (!blktrans_notifier.list.next)
register_mtd_user(&blktrans_notifier);
mutex_lock(&mtd_table_mutex);
//注册块设备,返回0成功,根据传入的major,如果为0,则会找到第一个空闲的位置,然后添加自己的信息,实际上就是在genhd.c中定义了一个结构体(blk_major_name)指针数组,里边包括major,name,和next指针
ret = register_blkdev(tr->major, tr->name);
if (ret < 0) {
printk(KERN_WARNING "Unable to register %s block device on major %d: %d\n",
tr->name, tr->major, ret);
mutex_unlock(&mtd_table_mutex);
return ret;
}
if (ret)
tr->major = ret;
tr->blkshift = ffs(tr->blksize) - 1;
//初始化tr的devs链表,将list链表添加到blktrans_majors
INIT_LIST_HEAD(&tr->devs);
list_add(&tr->list, &blktrans_majors);
//循环mtd_idr里不为空的每一项,调用add_mtd,mtd_idr在mtdcore.c中定义,
mtd_for_each_device(mtd)
if (mtd->type != MTD_ABSENT)
tr->add_mtd(tr, mtd);
mutex_unlock(&mtd_table_mutex);
return 0;
}
3.2 NOR FLASH代码分析
(分析代码过程中总结记录的,排版不是很好,下面的分析比较杂乱,主要是函数的调用流程,缩进相同的表示平级)
比如:
a_func
b_func
c_func
d_func
上边的例子表示在a_func函数里调用了两个函数,一个b_func和一个d_func函数,其中b_func 调用了c_func函数
这里内核版本为4.14,下面的分析针对内核版本4.14
CONFIG_MTD_PHYSMAP_OF必须要,CONFIG_MTD_PHYSMAP无关紧要
mtdblock.c
注册块设备,主设备号为31
提供缓存读写接口
将分区信息填充到一个新的mtd_blktrans_dev结构体中,将该结构体的list链表加入到tr的devs链表中
每一个mtd_blktrans_dev结构体里包含一个mtd_info结构体成员,用于记录分区信息
mtdchar.c
注册主设备号为90的mtd字符设备
init_mtdchar #define MTD_CHAR_MAJOR 90 #define MINORBITS 20 ret = __register_chrdev(MTD_CHAR_MAJOR, 0, 1 << MINORBITS,"mtd", &mtd_fops);
提供应用层读写接口
mtdcore.c
static LIST_HEAD(mtd_notifiers);
在文件系统下生成对应的名字,如下边的erasesize,可在设备目录下看到该名字,可以通过cat查看值或echo更改值
static DEVICE_ATTR(erasesize, S_IRUGO, mtd_erasesize_show, NULL);
DEVICE_ATTR的参数:
DEVICE_ATTR(_name, _mode, _show, _store)
_name:在sys fs中的名字
_mode:访问权限
_show:显示函数
_store:写函数
mtdcore提供了很多DEVICE_ATTR,可以在文件系统中查看erasesize,writesize等属性
在主设备号90下创建字符设备mtdX和mtdXro
add_mtd_device
#define MTD_DEVT(index) MKDEV(MTD_CHAR_MAJOR, (index)*2)
dev_set_name(&mtd->dev, "mtd%d", i);
error = device_register(&mtd->dev);
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,
"mtd%dro", i);
使用idr_alloc函数通过传入的mtd参数在mtd_idr中分配一个ID
根据该ID的值,创建/dev/mtdX和/dev/mtdXro两个节点
轮询mtd_notifiers链表中的每一项,调用它的add接口,add接口在mtd_blkdevs.c中实现,在mtdblock.c中的init_mtdblock函数中初始化
该函数的作用就是将每个分区添加到系统的MTD设备链表里,调用mtd_notifiers中每一个MTD 'user'(这里的每一个user就是一个mtd_notifier)中的add接口
mtd_notifiers中的MTD 'user'在mtd_blkdevs.c中的register_mtd_blktrans函数中通过register_mtd_user函数添加
该函数还添加mtd_blktrans_ops到blktrans_majors链表中,然后对mtd_idr中的每一个mtd_info(也就是每一个分区)调用mtdblock.c中mtdblock_tr的add_mtd函数,也就是mtdblock_add_mtd
mtdblock_add_mtd函数会分配一个mtdblk_dev结构体,填充mbd成员(mtd_blktrans_dev类型)的mtd及tr成员,然后调用add_mtd_blktrans_dev函数
add_mtd_blktrans_dev函数将mdb加入到tr的devs链表中,并调用device_add_disk添加disk设备
mtd_device_parse_register
该函数负责解析分区信息和分区个数,最终调用mtd_add_device_partitions函数添加分区
mtd_add_device_partitions
函数中调用add_mtd_partitions函数添加分区,函数实现在mtdpart.c中
mtd_device_unregister
mtdcore.c中init_mtd调用mtdchar.c中的init_mtdchar注册字符设备
module_init(init_mtd);
init_mtd
ret = class_register(&mtd_class);
//这部分还没完全了解,网上的解释大概是后端存储设备I/O较慢,用于缓存延迟写入,这个应该比较重要
//个人理解应该是对写操作进行优化,对于不同顺序的扇区写操作进行优化,
mtd_bdi = mtd_bdi_init("mtd");
//proc
proc_mtd = proc_create("mtd", 0, NULL, &mtd_proc_ops);
//调用mtdchar.c中的函数
ret = init_mtdchar();
//debugfs
dfs_dir_mtd = debugfs_create_dir("mtd", NULL);
mtd_blkdevs.c
注册块设备
static LIST_HEAD(blktrans_majors);
提供mtdblock.c使用的register_mtd_blktrans、blktrans_notifier.add等接口
同时维护一个blktrans_majors链表,将mtdblock.c中的mtdblock_tr添加到链表中,然后对每一个分区调用mtdblock.c中tr(mtd_blktrans_ops)的add_mtd接口
cmdlinepart.c
和ofpart.c差不多,只是各自解析的分区信息来源不同
ofpart.c
提供解析设备数分区的接口,解析分区个数及偏移和大小
struct mtd_partition结构体记录分区的大小和偏移
struct mtd_partition {
const char *name; 分区名字
const char *const *types; cmdlinepart or ofpart
uint64_t size; 分区大小
uint64_t offset; 分区偏移
uint32_t mask_flags; MTD_WRITEABLE or MTD_BIT_WRITEABLE or MTD_NO_ERASE or other
struct device_node *of_node; 设备树节点
};
上边两个文件分别是针对命令行分区的设备树分区的解析,程序运行时会轮询两种方式,当然,也可以自己添加新的方式,如下:
/*
* Do not forget to update 'parse_mtd_partitions()' kerneldoc comment if you
* are changing this array!
*/
static const char * const default_mtd_part_types[] = {
"cmdlinepart",
"ofpart",
NULL
};
在mtdpart.c中,该函数中可以看到for循环轮询了default_mtd_part_types数组
int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
struct mtd_partitions *pparts,
struct mtd_part_parser_data *data)
{
struct mtd_part_parser *parser;
int ret, err = 0;
if (!types)
types = default_mtd_part_types;
for ( ; *types; types++) {
pr_debug("%s: parsing partitions %s\n", master->name, *types);
parser = mtd_part_parser_get(*types);
if (!parser && !request_module("%s", *types))
parser = mtd_part_parser_get(*types);
pr_debug("%s: got parser %s\n", master->name,
parser ? parser->name : NULL);
if (!parser)
continue;
ret = mtd_part_do_parse(parser, master, pparts, data);
/* Found partitions! */
if (ret > 0)
return 0;
mtd_part_parser_put(parser);
/*
* Stash the first error we see; only report it if no parser
* succeeds
*/
if (ret < 0 && !err)
err = ret;
}
return err;
}
cmdlinepart.c和ofpart.c都将在flash驱动中被调用,一般是在probe函数中,
例如jz4740_nand.c(随便找的一个nand flash的驱动)和physmap_core_of.c
的probe函数中都会调用mtd_device_parse_register函数,而该函数则通过层层调用,
最终轮询cmdlinepart.c和ofpart.c中定义的static struct mtd_part_parser结构体中的parse_fn函数,
cmdlinepart.c最终会调用parse_cmdline_partitions函数,而ofpart.c则调用parse_ofpart_partitions
mtdpart.c
定义了一个mtd_partitions链表,用于管理分区,每一个新的分区都会被添加到该链表中
static LIST_HEAD(mtd_partitions);
为分区提供读写擦除函数
struct mtd_partitions {
const struct mtd_partition *parts; //分区信息
int nr_parts; //分区个数
const struct mtd_part_parser *parser; //解析分区的函数接口
};
struct mtd_part_parser {
struct list_head list;
struct module *owner;
const char *name;
int (*parse_fn)(struct mtd_info *, const struct mtd_partition **, //解析函数
struct mtd_part_parser_data *);
void (*cleanup)(const struct mtd_partition *pparts, int nr_parts);
};
add_mtd_partitions
根据分区个数,分配对应个mtd_part,并添加到mtd_partitions链表中
调用add_mtd_device函数,函数在mtdcore.c中实现
cfi_cmdset_0002.c
实现mtd_info结构体的接口等
struct mtd_info *mtd;
..........
mtd->_suspend = cfi_staa_suspend;
mtd->_resume = cfi_staa_resume;
..........
gen_probe.c
mtd_do_chip_probe
mtd = check_cmd_set(map, 1);
check_cmd_set
cfi_cmdset_0002(map, primary);
cfi_probe.c
cfi_probe
mtd_do_chip_probe
physmap_core_of.c
do_map_probe
drv->probe(map);
cfi_probe
return mtd_do_chip_probe(map, &cfi_chip_probe);
cfi = genprobe_ident_chips(map, cp);
for (i = 1; i < max_chips; i++) {
cp->probe_chip(map, i << cfi.chipshift, chip_map, &cfi);
//读flash的ID等
cfi_probe_chip
mtd = check_cmd_set(map, 1); /* First the primary cmdset */
//设置读写函数等,cfi_cmdset_0001:Intel/Sharp flash chips,cfi_cmdset_0002:AMD/Fujitsu flash chips
cfi_cmdset_0020:ST flash chips
cfi_cmdset_0002/cfi_cmdset_0001/cfi_cmdset_0020/cfi_cmdset_unknown
mtd->_erase = cfi_amdstd_erase_varsize;
mtd->_write = cfi_amdstd_write_words;
mtd->_read = cfi_amdstd_read;
/*解析分区并注册,该函数在mtdcore.c中*/
mtd_device_parse_register
//解析分区信息
parse_mtd_partitions
mtd_part_do_parse(struct mtd_part_parser *parser,
(*parser->parse_fn)
//在ofpart.c中实现,解析设备树种分区的个数以及偏移和大小等
parse_ofpart_partitions
mtd_add_device_partitions
//如果没有分区,则在/dev下创建mtdX、mtdXro节点以及添加分区
if (nbparts == 0 || IS_ENABLED(CONFIG_MTD_PARTITIONED_MASTER))
ret = add_mtd_device(mtd);
//分配一个没有使用的id
i = idr_alloc
dev_set_name(&mtd->dev, "mtd%d", i);
//创建设备节点mtdX
error = device_register(&mtd->dev);
if (!IS_ERR_OR_NULL(dfs_dir_mtd)) {
mtd->dbg.dfs_dir = debugfs_create_dir(dev_name(&mtd->dev), dfs_dir_mtd);
if (IS_ERR_OR_NULL(mtd->dbg.dfs_dir)) {
pr_debug("mtd device %s won't show data in debugfs\n",
dev_name(&mtd->dev));
}
}
//创建mtdXro节点
device_create(&mtd_class, mtd->dev.parent, MTD_DEVT(i) + 1, NULL,"mtd%dro", i);
list_for_each_entry(not, &mtd_notifiers, list)
//调用mtd_blkdevs.c中提供的blktrans_notify_add函数
not->add(mtd);
struct mtd_blktrans_ops *tr;
//调用mtdblock.c中的add_mtd
tr->add_mtd(tr, mtd);
//调用mtd_blkdevs.c中的add_mtd_blktrans_dev
add_mtd_blktrans_dev(&dev->mbd)
struct gendisk *gd;
gd = alloc_disk(1 << tr->part_bits);
//设置名字,这里的tr是mtdblock.c中的mtdblock_tr,name为"mtdblock"
snprintf(gd->disk_name, sizeof(gd->disk_name),
"%s%d", tr->name, new->devnum);
//添加分区,可在文件系统下看到mtdblockX
device_add_disk
if (nbparts > 0) {
ret = add_mtd_partitions(mtd, real_parts, nbparts);
for (i = 0; i < nbparts; i++) {
//为分区设置读写擦除函数,设置erasesize,writesize,size等,
最终使用的是cfi_cmdset_0002中cfi_cmdset_0002函数设置的读写函数
slave = allocate_partition(master, parts + i, i, cur_offset);
if (IS_ERR(slave)) {
del_mtd_partitions(master);
return PTR_ERR(slave);
}
mutex_lock(&mtd_partitions_mutex);
//添加到mtd_partitions链表
list_add(&slave->list, &mtd_partitions);
mutex_unlock(&mtd_partitions_mutex);
//创建m注册tdx设备
add_mtd_device(&slave->mtd);
//在文件系统下创建节点
mtd_add_partition_attrs(slave);
if (parts[i].types)
mtd_parse_part(slave, parts[i].types);
cur_offset = slave->offset + slave->mtd.size;
}
if (mtd->_reboot && !mtd->reboot_notifier.notifier_call) {
mtd->reboot_notifier.notifier_call = mtd_reboot_notifier;
register_reboot_notifier(&mtd->reboot_notifier);
}
有关时序相关分析可以参考链接: AXI EMC使用总结.
这个文章介绍了flash相关的参数以及时序的设置
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)