学习目的:
分析Linux内核中MTD系统层次,为后面编写Nand Flash、NOR Flash驱动打下基础
前面我们实现了用内存模拟磁盘的块设备驱动程序,由于操作的是内存,优化合并后的bio请求在队列请求处理函数中被取出后,可直接根据请求数据传输方向、大小使用memcpy完成数据读写。但像Nand Flash、NOR Flash这类存储设备,读写请求需要遵从特定协议,那么内核是如何支持这一类设备,这就引出了我们今天的主角——MTD层,下面我们一点点进行分析。
1、MTD简介
MTD(Memory Technology Device)即内存技术设备,在Linux内核中,为了使新的memory设备(主要就是为Nor Flash和Nand Flash设计的,其余像接口映射、RAM、ROM等都是辅助功能)的驱动更加简单,引入MTD层为memory设备在硬件和上层之间提供了一个抽象的接口。
MTD设备通常可分为四层,从上到下依次是:设备节点、MTD设备层、MTD原始设备层、硬件驱动层。其框图如下图所示:
设备节点:在/dev子目录下建立MTD块设备节点(主设备号为31)和MTD字符设备节点(主设备号为90),通过访问此设备节点即可访问MTD字符设备和块设备
MTD设备层:基于MTD原始设备,linux系统可以定义出MTD的块设备(主设备号31)和字符设备(设备号90)
- mtdchar.c : MTD字符设备接口相关实现
- mtdblock.c : MTD块设备接口相关实现
MTD原始设备层:用于描述MTD原始设备的数据结构是mtd_info,它定义了大量的关于MTD的数据和操作函数
- mtdcore.c : MTD原始设备接口相关实现
- mtdpart.c : MTD分区接口相关实现
硬件驱动层:Flash硬件驱动层负责对Flash硬件的读、写和擦除操作。MTD设备的Nand Flash芯片的驱动则drivers/mtd/nand/子目录下,Nor Flash芯片驱动位于drivers/mtd/chips/子目录下
- drivers/mtd/chips : CFI/JEDEC接口通用驱动
- drivers/mtd/nand : NAND通用驱动和部分底层驱动程序
- drivers/mtd/maps : NOR Flash映射关系相关函数
- drivers/mtd/devices : NOR Flash底层驱动
2、MTD设备层
MTD设备层分别实现了MTD字符设备和MTD块设备,允许应用程序通过打开/dev目录中的不同设备节点,以字符设备或块设备的访问形式操作MTD设备。下面对两者实现过程进行分析:
2.1 MTD设备层——字符设备(drivers/mtd/mtdchar.c)
入口函数init_mtdchar
static int __init init_mtdchar(void) { register_chrdev(MTD_CHAR_MAJOR, "mtd", &mtd_fops)-------------->① .... mtd_class = class_create(THIS_MODULE, "mtd");------------------>② ... register_mtd_user(¬ifier);---------------------------------->③ return 0; }
① 向内核注册字符设备,file_operation结构体中实现了操作MTD设备的打开、读、写等函数
② 创建了一个mtd_class,class下可以创建设备,uevent机制会自动获取class下设备信息在/dev目录下创建设备节点
③ 注册MTD user,register_mtd_user实现如下,将传入参数notifier结构体挂入到以mtd_notifiers为头部的链表中,并通过mtd_table数组查找已经注册的MTD设备,对于每个mtd设备调用新注册notifier中的add函数。
void register_mtd_user (struct mtd_notifier *new) { .... list_add(&new->list, &mtd_notifiers); .... for (i=0; i< MAX_MTD_DEVICES; i++) if (mtd_table[i]) new->add(mtd_table[i]); ... }
对于字符设备入口函数中的register_mtd_user(¬ifier),最终将调用到mtd_notify_add函数,mtd_notify_add函数实现如下:
static void mtd_notify_add(struct mtd_info* mtd) { if (!mtd) return; class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2), NULL, "mtd%d", mtd->index); class_device_create(mtd_class, NULL, MKDEV(MTD_CHAR_MAJOR, mtd->index*2+1), NULL, "mtd%dro", mtd->index); }
以mtd->index为注册字符设备次设备号(即注册MTD设备在mtd_table数组中的位置),在mtd_class下创建两个设备,一个设备节点可读可写,另一个设备节点只能进行读操作
2.2 MTD设备层——块设备(drivers/mtd/mtdblock.c)
入口函数init_mtdblock
static int __init init_mtdblock(void) { return register_mtd_blktrans(&mtdblock_tr); }
入口函数调用register_mtd_blktrans注册mtd_blktrans_ops类型结构体mtdblock_tr,mtdblock_tr结构体同MTD注册字符设备的file_operation结构体一样,都实现了open、readsect、writesect等函数
static struct mtd_blktrans_ops mtdblock_tr = { .name = "mtdblock", .major = 31, .part_bits = 0, .blksize = 512, .open = mtdblock_open, .flush = mtdblock_flush, .release = mtdblock_release, .readsect = mtdblock_readsect, .writesect = mtdblock_writesect, .add_mtd = mtdblock_add_mtd, .remove_dev = mtdblock_remove_dev, .owner = THIS_MODULE, };
继续看register_mtd_blktrans如何注册mtd_blktrans_ops
int register_mtd_blktrans(struct mtd_blktrans_ops *tr) { ... if (!blktrans_notifier.list.next)----------------------------------->① register_mtd_user(&blktrans_notifier); tr->blkcore_priv = kzalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL); if (!tr->blkcore_priv) return -ENOMEM; ... ret = register_blkdev(tr->major, tr->name);------------------------>② ... tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);----------->③ ... tr->blkcore_priv->rq->queuedata = tr; blk_queue_hardsect_size(tr->blkcore_priv->rq, tr->blksize); tr->blkshift = ffs(tr->blksize) - 1; tr->blkcore_priv->thread = kthread_run(mtd_blktrans_thread, tr,--------------------------------------->④ "%sd", tr->name); ... list_add(&tr->list, &blktrans_majors);---------------------------------------------------------------->⑤ for (i=0; i<MAX_MTD_DEVICES; i++) {------------------------------------------------------------------->⑥ if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT) tr->add_mtd(tr, mtd_table[i]); } ... }
① 注册一个mtd_user,register_mtd_user将blktrans_notifier添加到最终调用到mtd_notifiers为头部链表,并调用到blktrans_notifier中的add成员函数。此处的add函数最终会调用到mtdblock_tr结构体的add_mtd指针指向函数
② 注册一个块设备
③ 分配并初始化一个块设备队列,设置队列请求处理函数为mtd_blktrans_request
④ 创建一个内核线程
⑤ 将mtdblock_tr结构体添加到头部为blktrans_majors链表中
⑥ 在mtd_info数组中找到已经注册的MTD设备,调用mtdblock_tr结构体的add_mtd函数
再接着看③中队列的请求处理函数mtd_blktrans_request
static void mtd_blktrans_request(struct request_queue *rq) { struct mtd_blktrans_ops *tr = rq->queuedata; wake_up_process(tr->blkcore_priv->thread); }
我们知道上层构造的bio读写请求被优化合并后,块设备的队列请求处理函数被自动调用。在队列请求处理函数中一般是取出队列中请求,根据请求完成数据的读写操作。然而mtd_blktrans_request函数中只调用了wake_up_process函数唤醒内核中的一个线程,我们可猜测唤醒的线程中肯定实现了从队列中取出合并后的请求,根据请求完成数据读写等操作。
继续看创建的内核线程函数mtd_blktrans_thread
static int mtd_blktrans_thread(void *arg) { ... while (!kthread_should_stop()) { struct request *req; struct mtd_blktrans_dev *dev; int res = 0; req = elv_next_request(rq); if (!req) { set_current_state(TASK_INTERRUPTIBLE); ... continue; } ... res = do_blktrans_request(tr, dev, req); ... end_request(req, res); } ... }
mtd_blktrans_thread线程不断以电梯调度算法取出请求,如果没有读写请求就让该线程进入休眠,如果有读写请求就调用do_blktrans_request函数。
继续看do_blktrans_request函数
static int do_blktrans_request(struct mtd_blktrans_ops *tr, struct mtd_blktrans_dev *dev, struct request *req) { .... switch(rq_data_dir(req)) { case READ: for (; nsect > 0; nsect--, block++, buf += tr->blksize) if (tr->readsect(dev, block, buf)) return 0; return 1; case WRITE: if (!tr->writesect) return 0; for (; nsect > 0; nsect--, block++, buf += tr->blksize) if (tr->writesect(dev, block, buf)) return 0; return 1; ... }
do_blktrans_request函数根据传入请求,获取数据的传输方向和大小,最终调用mtdblock_tr结构体中的读扇区readsect和写扇区函数writesect
由此可见,我们上述猜想正常,队列请求处理函数中唤醒的线程,在唤醒线程中获取队列中被优化合并的请求,根据请求完成了数据的读写操作。
那么问题又来了,对于块设备驱动,我们虽然创建了队列,这个队列最后肯定要赋给gendisk结构体成员,还要设置和注册gendisk结构体,那么这些是在哪里实现的呢?
经过分析这些是在mtdblock_tr结构体成员add_mtd指针指向函数mtdblock_add_mtd实现的
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr, struct mtd_info *mtd) { struct mtd_blktrans_dev *dev = kzalloc(sizeof(*dev), GFP_KERNEL);---------------------------->① if (!dev) return; dev->mtd = mtd;------------------------------------------------------------------------------>② dev->devnum = mtd->index; dev->size = mtd->size >> 9; dev->tr = tr; dev->readonly = 1; add_mtd_blktrans_dev(dev);------------------------------------------------------------------->③ }
① 分配一个mtd_blktrans_dev结构体
② 设置mtd_blktrans_dev结构体mtd成员指向mtd_table[]
③ 调用add_mtd_blktrans_dev函数,gendisk结构体的分配和注册就是在这个函数中完成的
再来看mtdblock_tr结构体读扇区函数
static int mtdblock_readsect(struct mtd_blktrans_dev *dev, unsigned long block, char *buf) { size_t retlen; if (dev->mtd->read(dev->mtd, (block * 512), 512, &retlen, buf)) return 1; return 0; }
mtdblock_readsect函数直接调用mtd_blktrans_dev结构体中mtd成员中的read函数,从上面分析mtd_blktrans_dev结构体是在mtdblock_add_mtd函数中构造的,它的mtd成员指向mtd_table[]中未被注册的设备。
从上面对MTD设备层分析可以看出,不管是MTD创建的字符设备还是块设备,它的设备节点信息都是在发现mtd_table数组中有注册设备时,调用自己的mtd_notifier结构体的add函数创建的。我们可以看出mtd_table数组是MTD原始设备层和其下面一层MTD原始设备层交流的桥梁。
3、MTD原始设备层
由上面分析,我们知道mtd_table数组是MTD设备层和原始设备层沟通的桥梁,那就以mtd_table为线索进行分析。先来看mtd_table数组在原始设备层何处被构造的。
经搜索找到了add_mtd_device函数,函数内容如下:
int add_mtd_device(struct mtd_info *mtd) { ... for (i=0; i < MAX_MTD_DEVICES; i++) if (!mtd_table[i]) {---------------------------------------->① struct list_head *this; mtd_table[i] = mtd;------------------------------------->② mtd->index = i; mtd->usecount = 0; ... list_for_each(this, &mtd_notifiers) {------------------->② struct mtd_notifier *not = list_entry(this, struct mtd_notifier, list); not->add(mtd); } ... } ... }
① 找到mtd_table数组中未被注册的位置
② 将传入的mtd参数内容,存放到找到的mtd_table数组中未被注册的位置
② 遍历mtd_notifiers链表,调用mtd_notfiers链表中挂接的所有对象的add函数(即MTD设备层,实现的mtd_notifier结构体成员的add函数)
add_mtd_device函数根据传入参数填充了mtd_table数组,并调用mtd_notifiers链表中挂接的所有对象的add函数,使用add函数在MTD设备层创建相应设备节点信息。该函数肯定是在MTD原始设备层下面一层Flash驱动程序中使用的,我们继续搜索,查看add_mtd_device在那些地方被调用,发现内核中有多处调用这个函数地方。
我们找到了一个nand flash的驱动和nor flash的驱动的例子
1)drivers/mtd/nand/at91_nand.c的probe函数
static int __init at91_nand_probe(struct platform_device *pdev) { ... struct mtd_info *mtd; struct nand_chip *nand_chip; ... mtd = &host->mtd;----------------------------------------------------------->① nand_chip = &host->nand_chip;----------------------------------------------->② host->board = pdev->dev.platform_data; nand_chip->priv = host; /* link the private data structures */ mtd->priv = nand_chip;------------------------------------------------------>① mtd->owner = THIS_MODULE; ... nand_chip->IO_ADDR_R = host->io_base;--------------------------------------->① nand_chip->IO_ADDR_W = host->io_base; nand_chip->cmd_ctrl = at91_nand_cmd_ctrl; nand_chip->dev_ready = at91_nand_device_ready; nand_chip->ecc.mode = NAND_ECC_SOFT; /* enable ECC */ nand_chip->chip_delay = 20; /* 20us command delay time */ ... if (nand_scan(mtd, 1)) {---------------------------------------------------->③ res = -ENXIO; goto out; } ... res = add_mtd_partitions(mtd, partitions, num_partitions);------------------>④ ... res = add_mtd_device(mtd); ... }
① 分配nand_chip内存,根据目标板及NAND控制器初始化nand_chip中成员函数(若未初始化则使用nand_base.c中的默认函数)
② 分配mtd_info结构体,将mtd_info中的priv指向nand_chip(或板相关私有结构),设置ecc模式及处理函数
③ 以mtd_info为参数调用nand_scan()探测NAND FLash。 nand_scan()会读取nand芯片ID,并根据mtd->priv即nand_chip中成员初始化mtd_info
④ 若有分区,则以mtd_info和mtd_partition为参数调用add_mtd_partitions()添加分区信息
2)drivers/mtd/maps/omap_nor.c的probe函数
static int __devinit omapflash_probe(struct platform_device *pdev) { ... struct omapflash_info *info; ... info = kzalloc(sizeof(struct omapflash_info), GFP_KERNEL);---------------------->① ... info->map.virt = ioremap(res->start, size); ... info->map.name = pdev->dev.bus_id; info->map.phys = res->start; info->map.size = size; info->map.bankwidth = pdata->width; info->map.set_vpp = omap_set_vpp; simple_map_init(&info->map);--------------------------------------------------->② info->mtd = do_map_probe(pdata->map_name, &info->map);------------------------->③ ... info->mtd->owner = THIS_MODULE; #ifdef CONFIG_MTD_PARTITIONS------------------------------------------------------->④ err = parse_mtd_partitions(info->mtd, part_probes, &info->parts, 0); if (err > 0) add_mtd_partitions(info->mtd, info->parts, err); else if (err < 0 && pdata->parts) add_mtd_partitions(info->mtd, pdata->parts, pdata->nr_parts); else #endif add_mtd_device(info->mtd); ... }
① 定义map_info结构体, 初始化成员name, size, phys, bankwidth,通过ioremap映射成员virt(虚拟内存地址)
② 通过函数simple_map_init初始化map_info成员函数read,write,copy_from,copy_to
③ 调用do_map_probe进行cfi接口探测, 返回mtd_info结构体
④ 通过parse_mtd_partitions, add_mtd_partitions注册mtd原始设备
至此,我们通过层层分析找出了MTD系统中Nand Flash和Nor Flash驱动框架,后面将参考内核中提供驱动程序,编写自己Nand Flash和Nor Flash驱动