分类: LINUX
2010-11-10 10:18:24
flash闪存设备和SD插卡设备是嵌入式设备用到的主要存储设备,它们相当于PC机的硬盘。在嵌入设备特别是手持设备中,flash闪存是焊接在嵌入设备主板上的flash闪存芯片。在嵌入设备上有MMC/SD卡控制器及插槽,可通过MMC/SD来扩充存储空间。
嵌入设备的存储设备的空间划分及所有逻辑设备和文件系统示例列出如下图:
在嵌入设备上的flash芯片上blob和zImage直接按内存线性地址存储管理,对于flash芯片上留出的供用户使用的存储空间,使用MTDBLOCK块设备和JFFS2文件系统。对于flash芯片的分区表信息则以MTDCHAR字符设备来存储管理。
在嵌入设备上的MMC/SD插卡则由MMCBLOCK驱动程序和VFAT文件系统进行存储管理。本章分析了MTD设备和MMC/SD驱动程序。
Figure 3-1. UBI/MTD Integration
目录 |
Linux中MTD子系统在系统的硬件驱动程序和文件系统之间提供通用接口。在MTD上常用的文件文件系统是JFFS2日志闪存文件系统版本 2(Journaling Flash File System)。JFFS2用于微型嵌入式设备的原始闪存芯片的文件系统。JFFS2文件系统是日志结构化的,这意味着它基本上是一长列节点。每个节点包 含有关文件的部分信息 ― 可能是文件的名称、也许是一些数据。与Ext2文件系统相比,JFFS2因为有以下这些优点:
JFFS2在扇区级别上执行闪存擦除/写/读操作要比Ext2文件系统好。JFFS2提供了比Ext2fs更好的崩溃/掉电安全保护。当需 要更改少量数据时,Ext2文件系统将整个扇区复制到内存(DRAM)中,在内存中合并新数据,并写回整个扇区。这意味着为了更改单个字,必须对整个扇区 (64 KB)执行读/擦除/写例程 ,这样做的效率非常低。JFFS2是附加文件而不是重写整个扇区,并且具有崩溃/掉电安全保护这一功能。
JFFS2是是为FLASH定制的文件系统,JFFS1实现了日志功能,JFFS2实现了压缩功能。它的整个设计提供了更好的闪存管理。JFFS2的 缺点很少,主要是当文件系统已满或接近满时,JFFS2会大大放慢运行速度。这是因为垃圾收集的问题。
MTD驱动程序是专门为基于闪存的设备所设计的,它提供了基于扇区的擦除和读写操作的更好的接口。MTD子系统支持众多的闪存设备,并且有越来越多的驱动程序正被添加进来以用于不同的闪存芯片。
MTD子系统提供了对字符设备MTD_CHAR和块设备MTD_BLOCK的支持。MTD_CHAR提供对闪存的原始字符访问,象通常的 IDE硬盘一样,在MTD_BLOCK块设备上可创建文件系统。MTD_CHAR字符设备文件是 /dev/mtd0、mtd1、mtd2等,MTD_BLOCK块设备文件是 /dev/mtdblock0、mtdblock1等等。
NAND和NOR是制作Flash的工艺,CFI和JEDEC是flash硬件提供的接口,linux通过这些用通用接口抽象出MTD设备。JFFS2文件系统就建立在MTD设备上。
NOR flash带有SRAM接口,可以直接存取内部的每一个字节。NAND器件使用串行I/O口来存取数据, 8个引脚用来传送控制、地址和数据信息。NAND读和写操作用512字节的块。
MTD(memory technology device内存技术设备) 在硬件和文件系统层之间的提供了一个抽象的接口,MTD是用来访问内存设备(如:ROM、flash)的中间层,它将内存设备的共有特性抽取出来,从而使 增加新的内存设备驱动程序变得更简单。MTD的源代码都在/drivers/mtd目录中。
MTD中间层细分为四层,按从上到下依次为:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。MTD中间层层次结构图如下:
Flash硬件驱动层对应的是不同硬件的驱动程序,它负责驱动具体的硬件。例如:符合CFI接口标准的Flash芯片驱动驱动程序在drivers/mtd/chips目录中,NAND型Flash的驱动程序在/drivers/mtd/nand中。
在原始设备层中,各种内存设备抽象化为原始设备,原始设备实际上是一种块设备,MTD字符设备的读写函数也调用原始设备的操作函数来实现。 MTD使用MTD信息结构mtd_info来描述了原始设备的操作函数、各种信息,所有原始设备的信息也用一个全局的结构数组来描述,列出如下(在 drivers/mtd/mtdcore.c中):
struct mtd_info *mtd_table[MAX_MTD_DEVICES];
/* Our partition linked list */ static LIST_HEAD(mtd_partitions);
static struct mtdblk_dev { struct mtd_info *mtd; int count; struct semaphore cache_sem; unsigned char *cache_data; unsigned long cache_offset; unsigned int cache_size; enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state; } *mtdblks[MAX_MTD_DEVICES];
MTD原始设备层中封装了三大类设备,分别是Inverse Flash、NAND Flash和MTD。它们的上体读写方法不一样。这里只分析了MTD,因为它是最常用的。
原始设备层主要是通过mtd_info结构来管理设备,函数add_mtd_partitions()和del_mtd_partitions() 将的设备分区的mtd_info结构加入mtd_table数组中,mtdpart.c中还实现了part_read、part_write等函数,这些 函数注册在每个分区中,指向主分区的read、write函数,之所以这样做而不直接将主分区的read、write函数连接到每个分区中的原因是因为函 数中的参数mtd_info会被调用者置为函数所属的mtd_info,即mtd->read(mtd…),而参数mtd_info其实应该指向主 分区。
设备层和原始设备层的函数调用关系图如图2。MTD各种结构之间的关系图如图3。
struct mtdblk_dev { struct mtd_info mtd; / Locked */ 下层原始设备层的MTD设备结构 int count; struct semaphore cache_sem; unsigned char *cache_data; //缓冲区数据地址 unsigned long cache_offset;//在缓冲区中读写位置偏移 //缓冲区中的读写数据大小(通常被设置为MTD设备的erasesize) unsigned int cache_size; enum { STATE_EMPTY, STATE_CLEAN, STATE_DIRTY } cache_state;//缓冲区状态 }
struct mtd_info {u_char type; //内存技术的类型 u_int32_t flags; //标志位 u_int32_t size; // mtd设备的大小
//“主要的”erasesize(同一个mtd设备可能有数种不同的erasesize) u_int32_t erasesize; u_int32_t oobblock; // oob块大小,例如:512u_int32_t oobsize; //每个块oob数据量,例如16 u_int32_t ecctype; //ecc类型 u_int32_t eccsize; //自动ecc可以工作的范围
// Kernel-only stuff starts here. char *name; int index;
//可变擦除区域的数据,如果是0,意味着整个设备为erasesize int numeraseregions; //不同erasesize的区域的数目(通常是1) struct mtd_erase_region_info *eraseregions; u_int32_t bank_size; struct module *module; //此routine用于将一个erase_info加入erase queue int (*erase) (struct mtd_info *mtd, struct erase_info *instr); /* This stuff for eXecute-In-Place */ int (*point) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char **mtdbuf); /* We probably shouldn’t allow XIP if the unpoint isn’t a NULL */ void (*unpoint) (struct mtd_info *mtd, u_char * addr); 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 (*read_ecc) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf, u_char *eccbuf); int (*write_ecc) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf, u_char *eccbuf); int (*read_oob) (struct mtd_info *mtd, loff_t from, size_t len, size_t *retlen, u_char *buf); int (*write_oob) (struct mtd_info *mtd, loff_t to, size_t len, size_t *retlen, const u_char *buf); /* iovec-based read/write methods. We need these especially for NAND flash, with its limited number of write cycles per erase. NB: The ‘count’ parameter is the number of vectors, each of which contains an (ofs, len) tuple. */ int (*readv) (struct mtd_info *mtd, struct iovec *vecs, unsigned long count, loff_t from, size_t *retlen); int (*writev) (struct mtd_info *mtd, const struct iovec *vecs, unsigned long count, loff_t to, size_t *retlen); /* Sync */ void (*sync) (struct mtd_info *mtd);
/* Chip-supported device locking */ int (*lock) (struct mtd_info *mtd, loff_t ofs, size_t len); int (*unlock) (struct mtd_info *mtd, loff_t ofs, size_t len); /* Power Management functions */ int (*suspend) (struct mtd_info *mtd); void (*resume) (struct mtd_info *mtd);
void *priv; //指向map_info结构}
static struct mtd_notifier notifier = {mtd_notify_add, mtd_notify_remove, NULL};
/* Our partition linked list */ static LIST_HEAD(mtd_partitions); MTD原始设备分区的链表 struct mtd_part { struct mtd_info mtd; //分区的信息(大部分由其master决定) struct mtd_info *master; //该分区的主分区 u_int32_t offset; //该分区的偏移地址 int index; //分区号 struct list_head list; };
struct mtd_partition { char *name; //分区名 u_int32_t size; //分区大小 u_int32_t offset; //在主MTD空间的偏移 u_int32_t mask_flags; };
在具体的设备驱动程序初始化时,它会添加一个MTD设备结构到mtd_table数组中。MTD翻译层通过查找这个数组,可访问到各个具体设备驱动程序。
函数init_mtdblock注册一个MTD翻译层设备,初始化处理请求的线程,赋上MTD翻译层设备操作函数集实例,注册这个设备的通用硬盘结构。函数init_mtdblock调用层次图如上图。
mtd块设备驱动程序利用一个线程,当有读写请求时,从缓冲区将数据写入块设备或从块设备读入到缓冲区中。
函数init_mtdblock分析如下(在drivers/mtd/mtdblock.c中):static int __init init_mtdblock(void) { return register_mtd_blktrans(&mtdblock_tr); }
static struct mtd_blktrans_ops mtdblock_tr = {.name = “mtdblock”, .major = 31, .part_bits = 0, .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, }; static LIST_HEAD(blktrans_majors); int register_mtd_blktrans(struct mtd_blktrans_ops *tr) {
int ret, i; //如果第一个设备类型被注册了,注册notifier来阻止 /* 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)//如果不存在 //注册MTD翻译层块设备,创建通用硬盘结构并注册 register_mtd_user(&blktrans_notifier); tr->blkcore_priv = kmalloc(sizeof(*tr->blkcore_priv), GFP_KERNEL);
if (!tr->blkcore_priv) return -ENOMEM; memset(tr->blkcore_priv, 0, sizeof(*tr->blkcore_priv)); down(&mtd_table_mutex);
//创建blk_major_name结构初始化后加到&major_names[]数组中 ret = register_blkdev(tr->major, tr->name);…
spin_lock_init(&tr->blkcore_priv->queue_lock); init_completion(&tr->blkcore_priv->thread_dead); init_waitqueue_head(&tr->blkcore_priv->thread_wq);
//创建请求队列并初始化,赋上块设备特定的请求处理函数mtd_blktrans_request tr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request, &tr->blkcore_priv->queue_lock);…
tr->blkcore_priv->rq->queuedata = tr;//赋上MTD翻译层块设备操作函数集 //创建线程mtd_blktrans_thread ret = kernel_thread(mtd_blktrans_thread, tr, CLONE_KERNEL);…
//在devfs文件系统中创建设备的目录名 devfs_mk_dir(tr->name);
INIT_LIST_HEAD(&tr->devs);//初始化设备的链表 list_add(&tr->list, &blktrans_majors); for (i=0; i<MAX_MTD_DEVICES; i++) { if (mtd_table[i] && mtd_table[i]->type != MTD_ABSENT) //创建MTD翻译层设备结构并初始化,然后到MTD设备链表中 tr->add_mtd(tr, mtd_table[i]); }
up(&mtd_table_mutex); return 0;}
static void mtd_blktrans_request(struct request_queue *rq){
struct mtd_blktrans_ops *tr = rq->queuedata; wake_up(&tr->blkcore_priv->thread_wq);}
static int mtd_blktrans_thread(void *arg){
struct mtd_blktrans_ops *tr = arg; struct request_queue *rq = tr->blkcore_priv->rq; /* we might get involved when memory gets low, so use PF_MEMALLOC */ current->flags |= PF_MEMALLOC | PF_NOFREEZE; //变成以init为父进程的后台进程 daemonize(“%sd”, tr->name);
//因为一些内核线程实际上要与信号打交道,daemonize()没有做后台化工作。 //我们不能仅调用exit_sighand函数, //因为当最终退出时这样将可能引起oop(对象指针溢出错误)。 spin_lock_irq(¤t->sighand->siglock); sigfillset(¤t->blocked);
// 重新分析是否有挂起信号并设置或清除TIF_SIGPENDING标识给当前进程 recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); spin_lock_irq(rq->queue_lock);
while (!tr->blkcore_priv->exiting) { struct request *req; struct mtd_blktrans_dev *dev; int res = 0; DECLARE_WAITQUEUE(wait, current); //声明当前进程的等待队列
req = elv_next_request(rq);//从块设备的请求队列中得到下一个请求
if (!req) {//如果请求不存在 //将设备的等待线程加到等待队列中 add_wait_queue(&tr->blkcore_priv->thread_wq, &wait); set_current_state(TASK_INTERRUPTIBLE); spin_unlock_irq(rq->queue_lock); schedule(); //调度让CPU有机会执行等待的线程 remove_wait_queue(&tr->blkcore_priv->thread_wq, &wait); spin_lock_irq(rq->queue_lock); continue;}
//如果请求存在 dev = req->rq_disk->private_data;//得到请求的设备 tr = dev->tr; //得到MTD翻译层设备操作函数集实例 spin_unlock_irq(rq->queue_lock); down(&dev->sem); res = do_blktrans_request(tr, dev, req);//处理请求 up(&dev->sem); spin_lock_irq(rq->queue_lock); end_request(req, res); //从请求队列中删除请求并更新统计信息 } spin_unlock_irq(rq->queue_lock); //调用所有请求处理完的回调函数,并调用do_exit函数退出线程 complete_and_exit(&tr->blkcore_priv->thread_dead, 0);}
static int do_blktrans_request(struct mtd_blktrans_ops *tr, struct mtd_blktrans_dev *dev, struct request *req) { unsigned long block, nsect; char *buf; block = req->sector; nsect = req->current_nr_sectors; buf = req->buffer; if (!(req->flags & REQ_CMD)) return 0; //如果读写的扇区数超出了块设备的容量,返回 if (block + nsect > get_capacity(req->rq_disk)) return 0; //根据(rq)->flags & 1标识来判断操作方式,调用具体的设备操作函数 switch(rq_data_dir(req)) { case READ: for (; nsect > 0; nsect--, block++, buf += 512) if (tr->readsect(dev, block, buf)) return 0; return 1; case WRITE: if (!tr->writesect) return 0; for (; nsect > 0; nsect--, block++, buf += 512) if (tr->writesect(dev, block, buf)) return 0; return 1; default: printk(KERN_NOTICE “Unknown request %ld\n”, rq_data_dir(req)); return 0; } }
static struct mtd_notifier blktrans_notifier = { .add = blktrans_notify_add, .remove = blktrans_notify_remove, };
static LIST_HEAD(mtd_notifiers); void register_mtd_user (struct mtd_notifier *new) { int i; down(&mtd_table_mutex); //将MTD块设备的通知结构实例blktrans_notifier加入 //到全局链表mtd_notifiers上 list_add(&new->list, &mtd_notifiers); //模块引用计数加1 __module_get(THIS_MODULE); //对每个MTD块设备调用MTD通知结构实例的加设备函数 for (i=0; i< MAX_MTD_DEVICES; i++) if (mtd_table[i]) new->add(mtd_table[i]); up(&mtd_table_mutex); }
static LIST_HEAD(blktrans_majors); static void blktrans_notify_add(struct mtd_info *mtd) { struct list_head *this; if (mtd->type == MTD_ABSENT)//设备不存在 return; //遍历每个MTD主块设备 list_for_each(this, &blktrans_majors) { struct mtd_blktrans_ops *tr = list_entry(this, struct mtd_blktrans_ops, list); tr->add_mtd(tr, mtd); } }
static void mtdblock_add_mtd(struct mtd_blktrans_ops *tr,struct mtd_info *mtd) {
struct mtd_blktrans_dev *dev = kmalloc(sizeof(*dev), GFP_KERNEL); if (!dev) return; memset(dev, 0, sizeof(*dev)); dev->mtd = mtd; dev->devnum = mtd->index; dev->blksize = 512; dev->size = mtd->size >> 9; dev->tr = tr;
if (!(mtd->flags & MTD_WRITEABLE)) dev->readonly = 1; add_mtd_blktrans_dev(dev);}
函数add_mtd_blktrans_dev分析如下(在drivers/mtd/mtd_blkdevs.c中):
int add_mtd_blktrans_dev(struct mtd_blktrans_dev *new) { struct mtd_blktrans_ops *tr = new->tr; struct list_head *this; int last_devnum = -1; struct gendisk *gd; if (!down_trylock(&mtd_table_mutex)) { up(&mtd_table_mutex); BUG(); } //遍历MTD每个主块设备 list_for_each(this, &tr->devs) { struct mtd_blktrans_dev *d = list_entry(this, struct mtd_blktrans_dev,list); if (new->devnum == -1) {//如果没有设备号 //使用第一个空闲的设备号 if (d->devnum != last_devnum+1) { //找到空闲设备号,并把设备加到链表的尾部 new->devnum = last_devnum+1; list_add_tail(&new->list, &d->list); goto added; } } else if (d->devnum == new->devnum) {//设备号已被使用 /* Required number taken */ return -EBUSY; } else if (d->devnum > new->devnum) { //申请的设备号是空闲的,加到链表的尾部 list_add_tail(&new->list, &d->list); goto added; } last_devnum = d->devnum; } if (new->devnum == -1)//如果新设备的设备号为-1,就赋上(最后一个设备号+1) new->devnum = last_devnum+1; //所有的设备号*分区数 > 256 if ((new->devnum << tr->part_bits) > 256) { return -EBUSY; } init_MUTEX(&new->sem); list_add_tail(&new->list, &tr->devs);//加到链表尾部 added: if (!tr->writesect) new->readonly = 1; //分配通知硬盘结构gendisk,每分区一个 gd = alloc_disk(1 << tr->part_bits); if (!gd) { list_del(&new->list); return -ENOMEM; } //初始化通用硬盘结构 gd->major = tr->major; gd->first_minor = (new->devnum) << tr->part_bits; gd->fops = &mtd_blktrans_ops; snprintf(gd->disk_name, sizeof(gd->disk_name), “%s%c”, tr->name, (tr->part_bits?’a’:’0’) + new->devnum); snprintf(gd->devfs_name, sizeof(gd->devfs_name), “%s/%c”, tr->name, (tr->part_bits?’a’:’0’) + new->devnum); /* 2.5 has capacity in units of 512 bytes while still having BLOCK_SIZE_BITS set to 10. Just to keep us amused. */ set_capacity(gd, (new->size * new->blksize) >> 9); gd->private_data = new; //通用硬盘结构的私有数据指向翻译层的MTD设备 new->blkcore_priv = gd; gd->queue = tr->blkcore_priv->rq; //设置请求队列 if (new->readonly) set_disk_ro(gd, 1); //设置硬盘读写模式 add_disk(gd);//加通用硬盘结构到全局链表中 return 0; }
MTD翻译层设备操作函数集实例mtdblock_tr有对MTD设备的各种操作函数,这些操作函数调用了mtd_info结构中的操作函数。这里 只分析了函数mtdblock_writesect,它的源代码都在drivers/mtd/mtdblock.c中。由于flash设备需要先擦除一个 扇区,再才能写一个扇区,因而,使用了缓存来帮助不是正好一个扇区的数据的写操作。
函数mtdblock_writesect将数据写入到flash设备中。函数分析如下:static int mtdblock_writesect(struct mtd_blktrans_dev *dev, unsigned long block, char *buf) { //从MTD块设备数组中得到块设备结构 struct mtdblk_dev *mtdblk = mtdblks[dev->devnum]; if (unlikely(!mtdblk->cache_data && mtdblk->cache_size)) { //分配块设备用于擦除的缓存空间 mtdblk->cache_data = vmalloc(mtdblk->mtd->erasesize); if (!mtdblk->cache_data) return -EINTR; } //从位置block开始写一个扇区(512字节) return do_cached_write(mtdblk, block<<9, 512, buf); }
static int do_cached_write (struct mtdblk_dev *mtdblk, unsigned long pos, int len, const char *buf) { struct mtd_info *mtd = mtdblk->mtd; //得到擦除缓冲区大小 unsigned int sect_size = mtdblk->cache_size; size_t retlen; int ret; if (!sect_size)//如果块设备的缓冲大小为0,直接写设备 return MTD_WRITE (mtd, pos, len, &retlen, buf); while (len > 0) { //将要写的在设备上的位置pos地址处,长度为len // |<-offset-->|<-size-->| // ----------sect_start---|pos-----len-| // |<- sect_size ->| //计算扇区开始位置 unsigned long sect_start = (pos/sect_size)*sect_size; //计算出相对扇区开始位置的偏移 unsigned int offset = pos - sect_start; //计算出所写的大小 unsigned int size = sect_size - offset; if( size > len ) size = len; if (size == sect_size) {//正好是擦除缓冲区大小 //直接写入,不需要通过缓冲区 ret = erase_write (mtd, pos, size, buf); if (ret) return ret; } else { //只有部分扇区大小的数据,需通过缓冲区补充成扇区大小 //方法是:先从设备中读出数据到缓冲区,再将buf中数据拷贝到缓冲区, //这样,凑合成一个扇区大小的数据,再把缓冲区数据写入设备。 //如果缓冲区数据是脏的,把缓冲区数据写设备 if (mtdblk->cache_state == STATE_DIRTY && mtdblk->cache_offset != sect_start) { ret = write_cached_data(mtdblk); if (ret) return ret; } if (mtdblk->cache_state == STATE_EMPTY || mtdblk->cache_offset != sect_start) { //把当前的扇区数据填充缓冲区 mtdblk->cache_state = STATE_EMPTY; ret = MTD_READ(mtd, sect_start, sect_size, &retlen, mtdblk->cache_data); if (ret) return ret; if (retlen != sect_size) return -EIO; mtdblk->cache_offset = sect_start; mtdblk->cache_size = sect_size; mtdblk->cache_state = STATE_CLEAN; } //将数据从buf中拷贝到缓冲区中 memcpy (mtdblk->cache_data + offset, buf, size); mtdblk->cache_state = STATE_DIRTY; } buf += size; pos += size; len -= size; } return 0; }
static int write_cached_data (struct mtdblk_dev *mtdblk){
struct mtd_info *mtd = mtdblk->mtd; int ret;
if (mtdblk->cache_state != STATE_DIRTY) return 0; ret = erase_write (mtd, mtdblk->cache_offset, mtdblk->cache_size, mtdblk->cache_data);
if (ret) return ret; mtdblk->cache_state = STATE_EMPTY; return 0;}
static int erase_write (struct mtd_info *mtd, unsigned long pos,int len, const char *buf){
struct erase_info erase; DECLARE_WAITQUEUE(wait, current); wait_queue_head_t wait_q; size_t retlen; int ret;
//首先,擦除flash闪存块 init_waitqueue_head(&wait_q); erase.mtd = mtd; erase.callback = erase_callback; erase.addr = pos; erase.len = len; erase.priv = (u_long)&wait_q;
set_current_state(TASK_INTERRUPTIBLE); add_wait_queue(&wait_q, &wait);
ret = MTD_ERASE(mtd, &erase); if (ret) {//如果擦除完成 set_current_state(TASK_RUNNING);//运行当前进程 remove_wait_queue(&wait_q, &wait);//清除等待队列 return ret; }
schedule(); //调度来等待擦除工作的完成 remove_wait_queue(&wait_q, &wait); //清除等待队列 //第二步,写数据到flash设备 ret = MTD_WRITE (mtd, pos, len, &retlen, buf); if (ret) return ret; if (retlen != len) return -EIO; return 0;}
static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, int len, char *buf)
MTD核心主要工作是进行电源管理及在/proc文件系统中输出MTD设备的信息。函数init_mtd初始化proc文件系统函数、注册电源管理函数、初始化mtd设备函数,清除模块函数做相反的一些清除工作。
函数init_mtd分析如下(在linux/drivers/mtd/mtd_core.c中):int __init init_mtd(void) { if ((proc_mtd = create_proc_entry( “mtd”, 0, 0 ))) proc_mtd->read_proc = mtd_read_proc; mtd_pm_dev = pm_register(PM_UNKNOWN_DEV, 0, mtd_pm_callback); return 0; } static void __exit cleanup_mtd(void) { if (mtd_pm_dev) { pm_unregister(mtd_pm_dev); mtd_pm_dev = NULL; } if (proc_mtd) remove_proc_entry( “mtd”, 0); }
mtd_pm_callback函数通过各个设备的MTD设备结构mtd_info将电源管理请求传给具体的设备驱动程序。mtd_pm_callback函数列出如下:
static int mtd_pm_callback(struct pm_dev *dev, pm_request_t rqst, void *data) { int ret = 0, i; if (down_trylock(&mtd_table_mutex)) return -EAGAIN; if (rqst == PM_SUSPEND) {//电源挂起状态 for (i = 0; ret == 0 && i < MAX_MTD_DEVICES; i++) { if (mtd_table[i] && mtd_table[i]->suspend) ret = mtd_table[i]->suspend(mtd_table[i]); } } else i = MAX_MTD_DEVICES-1; if (rqst == PM_RESUME || ret) {//电源恢复 for ( ; i >= 0; i--) { if (mtd_table[i] && mtd_table[i]->resume) mtd_table[i]->resume(mtd_table[i]); } } up(&mtd_table_mutex); return ret; }
当系统打开flash设备上的文件,它建立好了文件的操作函数集实例,当对文件操作时,就调用了这个文件操作函数集实例中的函数。当flash设备当作字符设备时,这些操作函数通过MTD设备的操作函数把数据直接读入/写出flash设备。
函数init_mtdchar注册了一个字符设备,列出如下(在drivers/mtd/mtdchar.c中):static int __init init_mtdchar(void) { if (register_chrdev(MTD_CHAR_MAJOR, “mtd”, &mtd_fops)) { printk(KERN_NOTICE “Can’t allocate major number %d for Memory Technology Devices.\n”, MTD_CHAR_MAJOR); return -EAGAIN; } mtdchar_devfs_init(); return 0; }
static struct file_operations mtd_fops = {.owner = THIS_MODULE, .llseek = mtd_lseek, .read = mtd_read, .write = mtd_write, .ioctl = mtd_ioctl, .open = mtd_open, .release = mtd_close,
};
函数mtd_write分析如下:
static ssize_t mtd_write(struct file *file, const char __user *buf, size_t count,loff_t *ppos) { struct mtd_info *mtd = file->private_data; //得到MTD设备结构 char *kbuf; size_t retlen; size_t total_retlen=0; int ret=0; int len; DEBUG(MTD_DEBUG_LEVEL0,”MTD_write\n”); if (*ppos == mtd->size) return -ENOSPC; if (*ppos + count > mtd->size) count = mtd->size - *ppos; if (!count) return 0; while (count) { if (count > MAX_KMALLOC_SIZE) len = MAX_KMALLOC_SIZE; else len = count; kbuf=kmalloc(len,GFP_KERNEL);//分配buffer if (!kbuf) { printk(“kmalloc is null\n”); return -ENOMEM; } //从用户空间buf拷贝数据到内核空间kbuf if (copy_from_user(kbuf, buf, len)) { kfree(kbuf); return -EFAULT; } //调用设备的写函数 ret = (*(mtd->write))(mtd, *ppos, len, &retlen, kbuf); if (!ret) { *ppos += retlen; total_retlen += retlen; count -= retlen; buf += retlen; } else { kfree(kbuf); return ret; } kfree(kbuf); } return total_retlen; } /* mtd_write */
chinaunix网友2010-11-10 19:52:25
很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com