Linux内核MTD驱动程序与SD卡驱动程序

更新时间:2024-06-17 00:25:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

Linux内核MTD驱动程序与SD卡驱动程序

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

目录 [隐藏] ? 1 MTD内存技术设备 1.1 MTD内存技术设备层次结构 o 1.2 设备层和原始设备层的函数调用关系 o o o 1.3 MTD相关结构 1.4 MTD块设备初始化 o 1.5 MTD块设备的读写操作 o o 1.6 MTD核心初始化 1.7 MTD字符设备 o 1.8 具体flash芯片的探测及映射 o 1.9 驱动程序实例分析 ? 2 SD/MMC卡块设备驱动程序 o 2.1 MMC抽象设备层相关结构 2.1.1 (1)设备描述结构 ? 2.1.2 (2) 读写请求相关结构 ? o 2.2 MMC抽象设备层MMC块设备驱动程序 ? 2.2.1 (1)MMC块设备驱动程序初始化 ? ? 2.2.2 (2)MMC块设备驱动程序探测函数 2.2.3 (3)MMC卡请求的处理 o 2.3 具体MMC控制器驱动程序示例 ? ? 2.3.1 (1)amba控制器驱动程序相关结构 2.3.2 (2)amba控制器的初始化 ? 2.3.3 (3)设备探测函数mmci_probe ? 2.3.4 (4)amba控制器操作函数 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中间层层次结构图如下:

图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设备在内核中的层次图如下图。

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

图2 设备层和原始设备层的函数调用关系

图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

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

u_int32_t oobsize; //每个块oob数据量,例如16 u_int32_t ecctype; u_int32_t eccsize;

//ecc类型

//自动ecc可以工作的范围

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

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

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

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

函数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 .llseek

= THIS_MODULE, = mtd_lseek,

};

.read .write .ioctl .open .release

= mtd_read, = mtd_write, = mtd_ioctl, = mtd_open, = 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 */

具体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_MAPPINGS

map_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 really

want 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, disable must 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闪存芯片的类型名称为 \。

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

对于flash芯片,不同的制造商使用不同的命令集,目前Linux的MTD实现的命令集有AMD/Fujitsu的标准命令集和Intel/Sharp的扩展命令集(兼容

Intel/Sharp标准命令集)两个,这两个命令集分别在cfi_cmdset_0002.c和cfi_cmdset_0001.c中实现。

此外还有一些非CFI标准的Flash,其中\类型的Flash的探测程序在jedec.c中,\类型的Flash的探测程序在sharp.c中,\类型的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; }

驱动程序实例分析

(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?pan>

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

SD/MMC卡块设备驱动程序

SD/MMC卡组成的存储系统是许多嵌入设备的主要存储设备,相当于PC机的硬盘,在嵌入设备上的SD/MMC卡控制器通过MMC协议来解析命令控制SD/MMC卡的操作。SD/MMC卡上有一些寄存器来控制卡的状态及读写操作。MMC协议规定的寄存器有:CID寄存器,128位,是卡的鉴别寄存器,存有卡的鉴别信息;RCA寄存器是16位,存有卡的本地系统的相对地址,在初始化时由控制器动态指定。DSR寄存器是16位,是配置卡的驱动程序的寄存器,是可选的。CSD寄存器是卡特定数据信息描述寄存器,是可选的。OCR寄存器是操作控制寄存器。MMC卡的系统定义及相关协议请查询《MMC卡系统定义3.1版本》。

MMC驱动程序以分通用设备层、MMC抽象设备层、MMC协议层和具体设备层四层来构建,上一层抽象出下一层的共有特性,每一层以相应的结构来描述。通用设备层对于块设备来说,主要负责设备内核对象在sysfs文件系统中的管理、请求队列管理、及与文件系统的接口,MMC抽象设备层抽出MMC卡的共有特性,如: MMC卡的请求管理、电源管理等。MMC协议层将MMC操作分解成标准的MMC协议,具体设备层则负责具体物理设备的寄存器控制等。这种分层结构层次分明,管理有效。MMC驱动程序的层次结构如下图。

MMC驱动程序主要处理两部分的内容,一是创建通用硬盘结构向系统注册,以便系统对MMC设备的管理。另一方面,要完成系统分发过来的读写请求的处理。

图 MMC驱动程序的层次结构

MMC抽象设备层相关结构

(1)设备描述结构

图 MMC卡设备相关结构关系图

MMC设备由控制器及插卡组成,对应的设备结构为mmc_host结构和mmc_card结构。MMC卡设备相关结构关系图如上图。下面分别说明设备相关结构: 每个卡的插槽对应一个块的数据结构mmc_blk_data,结构列出如下(在drivers/mmc/mmc_block.c中):

struct mmc_blk_data { spinlock_t lock; struct gendisk *disk; //通用硬盘结构 struct mmc_queue queue; //MMC请求队列结构

本文来源:https://www.bwwdw.com/article/hes3.html

Top