Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1817669
  • 博文数量: 473
  • 博客积分: 13997
  • 博客等级: 上将
  • 技术积分: 5953
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-22 11:52
文章分类

全部博文(473)

文章存档

2014年(8)

2013年(38)

2012年(95)

2011年(181)

2010年(151)

分类: LINUX

2010-11-10 10:18:24

flash闪存设备和SD插卡设备是嵌入式设备用到的主要存储设备,它们相当于PC机的硬盘。在嵌入设备特别是手持设备中,flash闪存是焊接在嵌入设备主板上的flash闪存芯片。在嵌入设备上有MMC/SD卡控制器及插槽,可通过MMC/SD来扩充存储空间。

嵌入设备的存储设备的空间划分及所有逻辑设备和文件系统示例列出如下图:


Linux kernel mtd mmc sd driver 03.gif
图:嵌入设备的存储空间划分及文件系统示例图

在嵌入设备上的flash芯片上blob和zImage直接按内存线性地址存储管理,对于flash芯片上留出的供用户使用的存储空间,使用MTDBLOCK块设备和JFFS2文件系统。对于flash芯片的分区表信息则以MTDCHAR字符设备来存储管理。

在嵌入设备上的MMC/SD插卡则由MMCBLOCK驱动程序和VFAT文件系统进行存储管理。本章分析了MTD设备和MMC/SD驱动程序。

Linux kernel mtd mmc sd driver 13.png

Figure 3-1. UBI/MTD Integration

目录

[]

MTD内存技术设备

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内存技术设备层次结构

MTD(memory technology device内存技术设备) 在硬件和文件系统层之间的提供了一个抽象的接口,MTD是用来访问内存设备(如:ROM、flash)的中间层,它将内存设备的共有特性抽取出来,从而使 增加新的内存设备驱动程序变得更简单。MTD的源代码都在/drivers/mtd目录中。

MTD中间层细分为四层,按从上到下依次为:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。MTD中间层层次结构图如下:

Linux kernel mtd mmc sd driver 14 1024.png

图1 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];

每个原始设备可能分成多个设备分区,设备分区是将一个内存分成多个块,每个设备分区用一个结构mtd_part来描述,所有的分区组成一个链表mtd_partitions,这个链表的声明列出如下(在drivers/mtd/mtdpart.c中):
/* Our partition linked list */
static LIST_HEAD(mtd_partitions);

MTD原始设备到具体设备之间存在的一些映射关系数据在drivers/mtd/maps/目录下的对应文件中。这些映射数据包括分区信息、I/O映射及 特定函数的映射等。这种映射关系用映射信息结构map_info描述。 在MTD设备层中,MTD字符设备通过注册的file operation函数集来操作设备,而这些函数是通过原始设备层的操作函数来实现的,即调用了块设备的操作函数。MTD块设备实际了从块层到块设备的接 口函数。所有的块设备组成一个数组*mtdblks[MAX_MTD_DEVICES],这个结构数组列出如下(在drivers/mtd /mtdblock.c中):
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];

由于flash设备种类的多样性,MTD用MTD翻译层将三大类flash设备进行的封装。每大类设备有自己的操作函数集,它们的mtdblk_dev结构实例都存在mtdblks数组中。MTD设备在内核中的层次图如下图。


Linux kernel mtd mmc sd driver 09.gif
图 MTD设备在内核中的层次图

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。

Linux kernel mtd mmc sd driver 04.gif
图2 设备层和原始设备层的函数调用关系
Linux kernel mtd mmc sd driver 05.gif
图3 MTD各种结构之间的关系

MTD相关结构

MTD块设备的结构mtdblk_dev代表了一个闪存块设备,MTD字符设备没有相对应的结构。结构mtdblk_dev列出如下:
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;//缓冲区状态
}

结构mtd_info描述了一个MTD原始设备,每个分区也被实现为一个mtd_info,如果有两个MTD原始设备,每个上有三个分区,在系统中就一共 有6个mtd_info结构,这些mtd_info的指针被存放在名为mtd_table的数组里。结构mtd_info分析如下:
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块大小,例如:512

u_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结构
}

设备层的mtdblcok设备的notifier声明如下:
static struct mtd_notifier notifier = {	
   mtd_notify_add,
   mtd_notify_remove,
   NULL
};

mtd_part结构是用于描述MTD原始设备分区的,结构mtd_part中的list成员链成一个链表mtd_partitons。每个 mtd_part结构中的mtd_info结构用于描述本分区,被加入mtd_table数组中,其中mtd_info结构大部分成员由其主分区 mtd_part->master决定,各种函数也指向主分区的相应函数。 结构mtd_part列出如下:
/* 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;
};

结构mtd_partition描述mtd设备分区的结构,在MTD原始设备层调用函数add_mtd_partions时传递分区信息使用。结构列出如下(在include/linux/mtd/partition.h中):
struct mtd_partition {	
	char *name;		//分区名	
	u_int32_t size;		//分区大小
	u_int32_t offset;		//在主MTD空间的偏移
        u_int32_t mask_flags;	
};


MTD块设备初始化

Linux kernel mtd mmc sd driver 07.gif
图 函数init_mtdblock调用层次图

在具体的设备驱动程序初始化时,它会添加一个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);
}

MTD翻译层设备操作函数集实例列出如下:
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;
}

函数mtd_blktrans_request是MTD设备的请求处理函数,当请求队列中的请求需要设备处理时调用这个函数。在MTD设备中,函数 mtd_blktrans_request唤醒了MTD块设备的线程来进行处理。函数列出如下(在drivers/mtd/mtd_blkdevs.c 中):
static void mtd_blktrans_request(struct request_queue *rq)

{

   struct mtd_blktrans_ops *tr = rq->queuedata;
   wake_up(&tr->blkcore_priv->thread_wq);
}

线程函数mtd_blktrans_thread处理块设备的读写请求,函数mtd_blktrans_thread列出如下:
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(&current->sighand->siglock);
   sigfillset(&current->blocked);

 

   // 重新分析是否有挂起信号并设置或清除TIF_SIGPENDING标识给当前进程
   recalc_sigpending();
   spin_unlock_irq(&current->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);
}

函数do_blktrans_request完成请求的具体操作,它调用MTD翻译层设备操作函数集实例中的具体函数来进行处理。函数do_blktrans_request分析如下:
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;
    }
}


Linux kernel mtd mmc sd driver 06.gif
图 函数register_mtd_user调用层次图
结构mtd_notifier是用于通知加上和去掉MTD原始设备。对于块设备来说,这个结构实例blktrans_notifier用来通知翻译层加上 和去掉MTD原始设备。结构实例blktrans_notifier列出如下(在drivers/mtd/mtd_blkdevs.c中):
static struct mtd_notifier blktrans_notifier = {
    .add = blktrans_notify_add,
    .remove = blktrans_notify_remove,
};

函数register_mtd_user注册MTD设备,通过分配通盘硬盘结构来激活每个MTD设备,使其出现在系统中。函数register_mtd_user调用层次图如上图。 函数register_mtd_user分析如下(在drivers/mtd/mtdcore.c中):
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);
}

函数blktrans_notify_add通知MTD翻译层将设备加入到链表blktrans_majors中,并分配处理每个MTD分区对应的通用硬盘结构。 函数blktrans_notify_add分析如下(在drivers/mtd/mtd_blkdevs.c中):
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);
    }
 
}

函数mtdblock_add_mtd分配了MTD翻译层块设备结构,初始化后加到MTD翻译层块设备链表中,函数mtdblock_add_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给每个MTD主设备分配设备号,并加到MTD设备链表对应位置上。然后给每个MTD设备分区分配一个通用硬盘结构,初始化这个通用硬盘结构后,再注册通用硬盘。这样通过通用硬盘就可以访问到每个MTD设备分区。

函数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块设备的读写操作

Linux kernel mtd mmc sd driver 08.gif
函数mtdblock_writesect调用层次图

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);
}

函数do_cached_write将数据写入到设备,由于flash设备需要先擦除再才能写入,因而,在数据块大小不是正好扇区大小,需要通过缓存凑合成一个扇区时,才能写入到设备。 函数do_cached_write分析如下:
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;
}

函数write_cached_data将设备缓存中的数据写入到设备,在写完缓存中数据时,缓存的状态发生变化。函数write_cached_data列出如下:
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;
}

函数erase_write写一扇区数据到设备中,写的方法是:先擦除对应扇区,擦除完成后,再写数据。函数erase_write分析如下:
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;
}

函数mtdblock_readsect调用了函数do_cached_read,从flash设备中读数据到指定位置的buf中,如果数据在设备的缓存 中,就直接从缓存中拷贝到buf中,如果不在,就从flash中读出到buf中。函数do_cached_read说明如下:
static int do_cached_read (struct mtdblk_dev *mtdblk, unsigned long pos, 
               int len, char *buf)

其中参数mtdblk是指定的MTD块设备,pos是MTD设备中指定的位置,len是长度,buf是被写入的地址,调用成功时返回0,失败时返回错误码。函数从指定的MTD块设备中缓冲读到指定位置buf中。

MTD核心初始化

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_read_proc函数是proc系统调用到的最终读函数,它以字符形式读出结构struct mtd_info相关信息。

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;
}


MTD字符设备

当系统打开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;
}

MTD字符设备的操作函数结构mtd_fops列出如下:
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函数,函数mtd_write完成此函数是对MTD字符设备的写操作。其中参数file是系统给MTD字符设备驱动程序用 于传递参数的file结构,函数mtd_write通过file得到下层的MTD设备结构,参数buf是用户空间的指针,用于存放将要写入的数据,参数 count是被写数据的长度,参数ppos是数据被写入MTD设备中的位置。当调用成功时返回返回实际读取数据的长度,若失败时返回错误码。

函数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 */



阅读(857) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2010-11-10 19:52:25

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com