Linux内核MTD驱动程序与SD卡驱动程序(转载)

编程入门 行业动态 更新时间:2024-10-19 19:29:28

Linux<a href=https://www.elefans.com/category/jswz/34/1769575.html style=内核MTD驱动程序与SD卡驱动程序(转载)"/>

Linux内核MTD驱动程序与SD卡驱动程序(转载)

转自:.shtml

文章目录

  • 1. 引言
  • 2. MTD内存技术设备
    • 2.1. MTD内存技术设备层次结构
    • 2.2. 设备层和原始设备层的函数调用关系
    • 2.3. MTD相关结构
    • 2.4. MTD块设备初始化
    • 2.5. MTD块设备的读写操作
    • 2.6. MTD核心初始化
    • 2.7. MTD字符设备
    • 2.8. 具体flash芯片的探测及映射
    • 2.9. 驱动程序实例分析

1. 引言

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

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

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

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

2. 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、/dev/mtd1、/dev/mtd2等,MTD_BLOCK块设备文件是 /dev/mtdblock0、/dev/mtdblock1等等。

NAND和NOR是制作Flash的工艺,CFI和JEDEC是flash硬件提供的接口,linux通过这些用通用接口抽象出MTD设备。JFFS2文件系统就建立在MTD设备上。

NOR flash带有SRAM接口,可以直接存取内部的每一个字节。NAND器件使用串行I/O口来存取数据, 8个引脚用来传送控制、地址和数据信息。NAND读和写操作用512字节的块。

2.1. MTD内存技术设备层次结构

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

每个原始设备可能分成多个设备分区,设备分区是将一个内存分成多个块,每个设备分区用一个结构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设备在内核中的层次图如下图。

MTD原始设备层中封装了三大类设备,分别是Inverse Flash、NAND Flash和MTD。它们的上体读写方法不一样。这里只分析了MTD,因为它是最常用的。

2.2. 设备层和原始设备层的函数调用关系

原始设备层主要是通过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所示:

2.3. 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块大小,例如:512u_int32_t oobsize;   //每个块oob数据量,例如16u_int32_t ecctype;	 //ecc类型u_int32_t eccsize;	  //自动ecc可以工作的范围// Kernel-only stuff starts here.char *name;int index;//可变擦除区域的数据,如果是0,意味着整个设备为erasesizeint numeraseregions; //不同erasesize的区域的数目(通常是1)struct mtd_erase_region_info *eraseregions;u_int32_t bank_size;struct module *module;//此routine用于将一个erase_info加入erase queueint (*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 ofwhich 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;
};

2.4. MTD块设备初始化


在具体的设备驱动程序初始化时,它会添加一个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 isregistered, to prevent the link/init ordering from fuckingus 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_requesttr->blkcore_priv->rq = blk_init_queue(mtd_blktrans_request,&tr->blkcore_priv->queue_lock);…tr->blkcore_priv->rq->queuedata = tr;//赋上MTD翻译层块设备操作函数集//创建线程mtd_blktrans_threadret = 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;}
}

结构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 stillhaving 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;
}

2.5. MTD块设备的读写操作

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中。

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

2.7. 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 %dfor 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;elselen = count;kbuf=kmalloc(len,GFP_KERNEL);//分配bufferif (!kbuf) {printk(“kmalloc is null\n”);return -ENOMEM;}//从用户空间buf拷贝数据到内核空间kbufif (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 */

2.8. 具体flash芯片的探测及映射

(1)flash芯片映射信息结构

flash芯片映射信息结构map_info描述了每一排闪存的映射信息。如:每一排闪存的驱动程序、物理地址、读写操作函数和映射地址 等。如果设备需要它,系统就必须把它传递到芯片探测例程do_map_probe中。JEDEC和CFI接口的芯片都用这个探测函数。如果芯片被识别,就 会激活合适的芯片驱动程序并返回一个mtd_info结构。同时,系统使用这个驱动程序的模块地址填充mtd->module,并把它注册到MTD 核心代码中。或者如果有分区,就注册分区。map_info结构保存在mtd->priv域,芯片驱动程序需要的更多的信息通过链接 mtd->priv->fldrv_priv可得到。

flash芯片映射信息结构map_info列出如下(在include/linux/mtd/mtd.h中):

struct map_info {char *name;unsigned long size;  //flash大小unsigned long phys;    //起始物理地址#define NO_XIP (-1UL)void __iomem *virt;  //I/O映射的虚拟地址void *cached; //8位的字节,它不是实际总线的必要宽度。//在再次与第一个芯片通信之前,它是字节上重复的间隔int bankwidth;
#ifdef CONFIG_MTD_COMPLEX_MAPPINGSmap_word (*read)(struct map_info *, unsigned long);void (*copy_from)(struct map_info *, void *, unsigned long, ssize_t);void (*write)(struct map_info *, const map_word, unsigned long);void (*copy_to)(struct map_info *, unsigned long, const void *, ssize_t);/* We can perhaps put in ‘point’ and ‘unpoint’ methods, if we reallywant to enable XIP for non-linear mappings. Not yet though. */
#endif/*在映射驱动程序的copy_from应用中,映射驱动程序使用缓存是可能的。然而,当芯片驱动程序知道一些flash区域已改变内容时,系统在必要时将通过这个例程发信号给芯片驱动程序,让映射驱动程序使缓存无效。如果没有缓存时,把这个域设为NULL。*/void (*inval_cache)(struct map_info *, unsigned long, ssize_t);/* set_vpp() must handle being reentered—enable, enable, disablemust leave it enabled. */void (*set_vpp)(struct map_info *, int);unsigned long map_priv_1;unsigned long map_priv_2;void *fldrv_priv;struct mtd_chip_driver *fldrv; //flash芯片驱动程序
};

结构mtd_chip_driver是flash芯片驱动程序的描述,列出如下:

struct mtd_chip_driver {struct mtd_info *(*probe)(struct map_info *map);//探测函数void (*destroy)(struct mtd_info *);struct module *module; //驱动程序的模块结构char *name; //驱动程序名struct list_head list;};

(2)flash芯片探测方法及接口标准
每种flash控制芯片可控制多种闪存,这个控制芯片的驱动程序有自己的读写和探测操作函数或者使用通用的操作函数,它注册MTD驱动程序结构 mtd_chip_driver到一个全局链表chip_drvs_list中。当用户使用一种flash闪存时,用户在称为"映射驱动程序"的文件中分 配好地址、进行闪存空间分区后,使用探测程序查找相应的控制芯片驱动程序。映射驱动程序用来填充一些闪存空间分配的一些信息,代码放在 drivers/mtd/map目录下。

在/drivers/mtd/chips目录下有各种flash控制芯片的驱动程序及芯片探测程序,这些文件有chipreg.c、 gen_probe.c、cfi_probe.c、jedec_probe.c、cfi_cmdset_0001.c、 cfi_cmdset_0002.c、map_rom.c、map_ram.c、map_absent.c、amd_flash.c、jedec.c和 sharp.c。CFI设备和JEDEC设备都要用到gen_probe.c文件。

确定flash闪存芯片是否支持CFI接口的方法是:向flash闪存的地址0x55H写入数据0x98H,再从flash闪存的地址 0x10H处开始,读取3个存储单元,如果字符分别为’Q’,’R’和’Y’,那么flash闪存芯片是支持CFI接口的。这个方法在文件 cfi_probe.c函数qry_present中实现。支持CFI接口flash闪存芯片的类型名称为 “cfi_probe”。

也可以用JEDEC(电子电器设备联合会)标准设备模仿CFI接口,探测JEDEC设备的程序在jedec_probe.c中,JEDEC设备的类型为"jedec_probe"。

对于flash芯片,不同的制造商使用不同的命令集,目前Linux的MTD实现的命令集有AMD/Fujitsu的标准命令集和 Intel/Sharp的扩展命令集(兼容Intel/Sharp标准命令集)两个,这两个命令集分别在cfi_cmdset_0002.c和 cfi_cmdset_0001.c中实现。

此外还有一些非CFI标准的Flash,其中"jedec"类型的Flash的探测程序在jedec.c中,"sharp"类型的Flash的探测程序在sharp.c中,"amd_flash"类型的Flash的探测程序在amd_flash.c中。

最后,还有一些非Flash的MTD,比如ROM或absent(无)设备。这些设备的探测程序在map_rom.c、map_ram.c和map_absent.c中。

chip_drvs_list是所有芯片类型的驱动器链表,flash控制芯片的驱动程序通过调用register_mtd_chip_driver() 和unregister_mtd_chip_driver()向此链表中添加或去除MTD芯片驱动结构。这两个函数列出如下(在drivers/mtd /chips/chipreg.c中):

void register_mtd_chip_driver(struct mtd_chip_driver *drv)
{spin_lock(&chip_drvs_lock);list_add(&drv->list, &chip_drvs_list);spin_unlock(&chip_drvs_lock);
}
void unregister_mtd_chip_driver(struct mtd_chip_driver *drv)
{spin_lock(&chip_drvs_lock);list_del(&drv->list);spin_unlock(&chip_drvs_lock);
}

映射驱动程序调用函数do_map_probe来查找对应的控制芯片驱动程序。函数中参数name是控制芯片类型名称,参数是映射驱动程序中设置的flash闪存空间信息。若调用成功时返回MTD设备的结构mtd_info,失败时返回NULL。
函数do_map_probe分析如下(在drivers/mtd/chips/chipreg.c中):

struct mtd_info *do_map_probe(const char *name, struct map_info *map)
{struct mtd_chip_driver *drv;struct mtd_info *ret;//查找得到name类型的控制芯片驱动程序结构drv = get_mtd_chip_driver(name);if (!drv && !request_module(“%s”, name))drv = get_mtd_chip_driver(name);if (!drv)return NULL;ret = drv->probe(map); //具体控制芯片驱动程序的探测函数//使用计数减1,它可能已是一个探测过的模块,在这不需要再探测, //而在实际的驱动程序中已做处理。module_put(drv->module);if (ret)return ret;return NULL;
}

2.9. 驱动程序实例分析

(1)CFI控制芯片驱动程序

CFI控制芯片驱动程序cfi_probe在drivers/mtd/chip/cfi_probe.c中,这里只做了简单的说明。

static struct mtd_chip_driver cfi_chipdrv = {.probe		= cfi_probe,.name		= “cfi_probe”,.module		= THIS_MODULE
};

函数cfi_probe_init注册驱动程序cfi_chipdrv到全局链表中,函数列出如下:

int __init cfi_probe_init(void)
{register_mtd_chip_driver(&cfi_chipdrv);return 0;
};
static void __exit cfi_probe_exit(void)
{unregister_mtd_chip_driver(&cfi_chipdrv);
};

函数cfi_probe调用mtd_do_chip_probe函数来完成了探测操作,在函数cfi_chip_probe中,它调用 qry_present来查询是否是CFI接口,调用函数cfi_chip_setup)初始化cfi_private结构,调用函数 cfi_chip_setup则读出CFI查询结构中的数据。然后,函数mtd_do_chip_probe调用函数check_cmd_set根据 map_info中的信息来设备不同的命令集:cfi_cmdset_0001()或cfi_cmdset_0002(),如果符合的类型没有则调用 cfi_cmdset_unkown。 函数cfi_probe列出如下:

struct mtd_info *cfi_probe(struct map_info *map)
{return mtd_do_chip_probe(map, &cfi_chip_probe);
}static struct chip_probe cfi_chip_probe = {.name		= “CFI”,.probe_chip	= cfi_probe_chip
};

(2)映射驱动程序
用户可设置flash空间映射信息填充在映射驱动程序中,包括该MTD原始设备的起始物理地址、大小、分区情况等。映射驱动程序都在drivers/mtd/maps子目录下。这里简单说明cfi_flagadm映射驱动程序(在cfi_flagadm.c中)。

flagadm_map是映射信息结构,它含有flash存储空间的配置信息,列出如下:

struct map_info flagadm_map = {.name =		“FlagaDM flash device”,.size =		FLASH_SIZE,.bankwidth =	2,
};

flagadm_parts是flash存储空间的分区,列出如下:

struct mtd_partition flagadm_parts[] = {{
.name =		“Bootloader”,
.offset	=	FLASH_PARTITION0_ADDR,
.size =		FLASH_PARTITION0_SIZE
},
{
.name =		“Kernel image”,
.offset =	FLASH_PARTITION1_ADDR,
.size =		FLASH_PARTITION1_SIZE
},
{
.name =		“Initial ramdisk image”,
.offset =	FLASH_PARTITION2_ADDR,
.size =		FLASH_PARTITION2_SIZE
},
{
.name =		“Persistant storage”,
.offset =	FLASH_PARTITION3_ADDR,
.size =		FLASH_PARTITION3_SIZE
}};#define PARTITION_COUNT (sizeof(flagadm_parts)/sizeof(struct mtd_partition)static struct mtd_info *mymtd;

函数init_flagadm是映射驱动程序的初始化,它得到了端口映射地址,初始化了操作函数,通过探测函数得到MTD设备结构。函数init_flagadm说明如下:
int __init init_flagadm(void)
{
printk(KERN_NOTICE “FlagaDM flash device: %x at %x\n”,
FLASH_SIZE, FLASH_PHYS_ADDR);
flagadm_map.phys = FLASH_PHYS_ADDR;

//端口映射
flagadm_map.virt = ioremap(FLASH_PHYS_ADDR,FLASH_SIZE);
if (!flagadm_map.virt) {
printk(“Failed to ioremap\n”);
return -EIO;
}

//赋上通用的读写操作函数,如:__raw_writeb等
simple_map_init(&flagadm_map);
//探测CFI类型接口得到MTD设备结构
mymtd = do_map_probe(“cfi_probe”, &flagadm_map);
if (mymtd) {
mymtd->owner = THIS_MODULE;
//将分区信息加到MTD设备结构实例mymtd中
add_mtd_partitions(mymtd, flagadm_parts, PARTITION_COUNT);
printk(KERN_NOTICE “FlagaDM flash device initialized\n”);
return 0;
}

iounmap((void *)flagadm_map.virt);//取消端口映射 
return -ENXIO;
}

static void __exit cleanup_flagadm(void)
{
if (mymtd) {
del_mtd_partitions(mymtd);
map_destroy(mymtd);
}

if (flagadm_map.virt) {
iounmap((void *)flagadm_map.virt);
flagadm_map.virt = 0;
}
}

更多推荐

Linux内核MTD驱动程序与SD卡驱动程序(转载)

本文发布于:2023-11-17 12:47:11,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1643947.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:内核   驱动程序   卡驱动   程序   Linux

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!