• 沒有找到結果。

第 7 章 块设备驱动程序

7.2 块设备驱动开发实例

7.2.5 MMC 驱动程序分析

现在要分析 MMC 驱动程序的实现过程,读者将会看到它的实现过程与字符设备驱动的 实现有很大的不同,从而让读者感受到块设备驱动实现方法与字符设备驱动的不同之处。首 先来分析它的初始化过程。

7.2.5.1 MMC 初始化

对于 MMC 的初始化主要完成三个任务:一是注册 MMC 块设备;二是建立 MMC 设备 文件系统的目录;三是注册具体的 MMC 介质驱动,实现代码如下所示:

static int __init mmc_blk_init(void) {

int res = -ENOMEM;

res = register_blkdev(major, "mmc");

if (res < 0) {

printk(KERN_WARNING "Unable to get major %d for MMC media: %d\n",

major, res);

用 register_blkdev 函数来实现注册 MMC 块设备,如果没有指定主设备号,则将动态的 分配一个,其中注册的块设备名为 mmc。其次用 devfs_mk_dir 函数来建立一个 MMC 的设 备文件目录。最后注册 MMC 介质的驱动,其中参数 mmc_driver 是 mmc_driver 结构的对象,

该结构对象的定义如下:

static struct mmc_driver mmc_driver = { .drv = {

.name = "mmcblk", },

.probe = mmc_blk_probe, .remove = mmc_blk_remove, .suspend = mmc_blk_suspend, .resume = mmc_blk_resume, };

该 mmc_driver 对象定义了 MMC 驱动的 probe,remove,suspend 和 resume 方法,并且 程序中相应的实现了这些方法。

顺便让我们看一下卸载 MMC 设备的实现代码,它的作用正好与初始化相反,首先完成 MMC 介质驱动的注销,接着删除 MMC 设备文件系统的目录,最后注销 MMC 块设备,具 体实现代码如下:

static void __exit mmc_blk_exit(void) {

static struct block_device_operations mmc_bdops = { .open = mmc_blk_open,

.release = mmc_blk_release, .ioctl = mmc_blk_ioctl, .owner = THIS_MODULE, };

7.2.5.2 open 和 release 方法

块设备的 open 方法作用与字符设备的 open 方法类似,它把相关的 inode 和 file 结构作 为参数,当一个 inode 指向一个块设备时,inode->i_bdev->bd_disk 成员包含了指向相应 gendisk 结构的指针,该指针可用于获得驱动程序内部的数据结构。MMC 驱动的 open 方法 实现代码如下:

static int mmc_blk_open(struct inode *inode, struct file *filp) {

struct mmc_blk_data *md;

int ret = -ENXIO;

md = mmc_blk_get(inode->i_bdev->bd_disk);

if (md) {

if (md->usage == 2)

check_disk_change(inode->i_bdev);

ret = 0;

}

return ret;

}

其中 mmc_blk_data 结构表示 MMC 设备的一个内部数据结构,它的定义以及对各成员 的注释如下:

struct mmc_blk_data {

spinlock_t lock; /*用于互斥锁*/

struct gendisk *disk; /*gendisk 结构*/

struct mmc_queue queue; /*MMC 设备请求队列*/

unsigned int usage; /*用户数目*/

unsigned int block_bits; /*块的大小,以位为单位*/

};

用 mmc_blk_get 函数来获得与参数 gendisk 结构对应的 mmc_blk_data 结构体信息,如 果正确获得则 open 方法返回 0,否则返回-ENXIO 错误信息。当获得的 mmc_blk_data 结构 对象的用户数是 2 时,调用 check_disk_change 函数来检查是否更换了磁盘介质,如果更换 了介质将使那些所有的磁盘相关的缓冲无效。

接下来讲述 release 方法,同样它的作用与字符设备中的 release 方法相似,用它来释放 open 方法打开的设备,它的实现代码如下:

static int mmc_blk_release(struct inode *inode, struct file *filp) {

struct mmc_blk_data *md = inode->i_bdev->bd_disk->private_data;

mmc_blk_put(md);

return 0;

}

该方法实现的关键是 mmc_blk_put 函数,其参数传递的是指向 mmc_blk_data 结构的指

针,该函数完成的任务有,首先将 mmc_blk_data 结构的对象用户数减 1,然后判断它的用 户数目是否为 0,如果为 0,首先释放它的 gendisk 结构对象,然后清除 MMC 的请求队列,

最后释放 mmc_blk_data 结构对象的内存。

7.2.5.3 ioctl 方法

块设备驱动程序提供了 ioctl 函数执行设备的控制功能,高层的块设备子系统在驱动程 序获得 ioctl 命令之前已经截取了大量的命令,所以实际中 ioctl 函数的实现比较简单,MMC 驱动的 ioctl 方法实现代码如下:

static int mmc_blk_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

struct block_device *bdev = inode->i_bdev;

if (cmd == HDIO_GETGEO) { struct hd_geometry geo;

memset(&geo, 0, sizeof(struct hd_geometry));

geo.cylinders = get_capacity(bdev->bd_disk) / (4 * 16);

geo.heads = 4;

geo.sectors = 16;

geo.start = get_start_sect(bdev);

return copy_to_user((void __user *)arg, &geo, sizeof(geo))? -EFAULT : 0;

}

return -ENOTTY;

}

MMC 驱动的 ioctl 方法只实现了一个命令,就是获得块设备的几何物理信息,如获得柱 面,磁头和扇区大小等信息,然后将这些信息发送给用户空间的应用程序。

7.2.5.4 MMC 驱动的 request 方法

块设备的 request 方法应该是与字符设备驱动的最大不同之处,因为块设备操作中没有 定义 read/write 等方法,所以块设备中这些方法的实现就交给了 request 方法来实现。其实 request 方法的实现是在 MMC 初始化时就被实现的,只是为了能更详细的介绍它的实现所 以专门放在这里介绍。MMC 驱动的 request 方法是由 mmc_blk_prep_rq 和 mmc_blk_issue_rq 这两个函数实现,这两个函数被 mmc_blk_alloc 函数调用,而这个函数又被 mmc_blk_probe 函数调用。mmc_blk_probe 函数是在 MMC 驱动初始化时调用的,所以说 request 方法也在 初始化时被调用。mmc_blk_prep_rq 函数用来检查是否有 MMC 设备和 MMC 主机,它的实 现代码如下所示:

static int mmc_blk_prep_rq(struct mmc_queue *mq, struct request *req) {

struct mmc_blk_data *md = mq->data;

int stat = BLKPREP_OK;

if (!md || !mq->card) {

printk(KERN_ERR "%s: killing request - no device/host\n",

req->rq_disk->disk_name);

stat = BLKPREP_KILL;

}

return stat;

}

当检测到没有 MMC 主机或没有 MMC 设备时,该函数返回一个非 0 值。否则,返回一 个 0 代表设备都正常存在。

mmc_blk_issue_rq 函数实现的块设备的真正 read/write 等操作,它的实现代码如下所示:

static int mmc_blk_issue_rq(struct mmc_queue *mq, struct request *req) {

……

do {

struct mmc_blk_request brq;

struct mmc_command cmd;

……

if (rq_data_dir(req) == READ) {

brq.cmd.opcode=brq.data.blocks>1?MMC_READ_MULTIPLE_BLOCK:

MMC_READ_SINGLE_BLOCK;

brq.data.flags |= MMC_DATA_READ;

} else {

brq.cmd.opcode = MMC_WRITE_BLOCK;

brq.cmd.flags = MMC_RSP_R1B;

brq.data.flags |= MMC_DATA_WRITE;

brq.data.blocks = 1;

}

brq.mrq.stop = brq.data.blocks > 1? &brq.stop : NULL;

brq.data.sg = mq->sg;

brq.data.sg_len = blk_rq_map_sg(req->q, req, brq.data.sg);

mmc_wait_for_req(card->host, &brq.mrq);

……

do { int err;

cmd.opcode = MMC_SEND_STATUS;

cmd.arg = card->rca << 16;

cmd.flags = MMC_RSP_R1;

err = mmc_wait_for_cmd(card->host, &cmd, 5);

if (err) {

printk(KERN_ERR "%s: error %d requesting status\n", req->rq_disk->disk_name, err);

goto cmd_err;

}

} while (!(cmd.resp[0] & R1_READY_FOR_DATA));

spin_lock_irq(&md->lock);

ret = end_that_request_chunk(req, 1, brq.data.bytes_xfered);

if (!ret) {

add_disk_randomness(req->rq_disk);

blkdev_dequeue_request(req);

end_that_request_last(req);

}

spin_unlock_irq(&md->lock);

} while (ret);

mmc_card_release_host(card);

return 1;

cmd_err:

……

return 0;

}

该函数主要完成 MMC 的命令请求,以及相应的数据传输。它可以是单个块的操作,也 可以是多个块的操作。其中 mmc_wait_for_req 函数用来实现 MMC 主机的命令请求,用来 启动一个 MMC 主机请求。mmc_wait_for_cmd 函数用来启动一个 MMC 命令并且等待这个 命令的完成。用 end_that_request_chunk 函数来结束一个 I/O 请求,当该函数返回 0 时,证 明已经处理了这个请求,返回 1 时,表明这个请求还在悬挂在缓冲中。当结束这个 I/O 请求 时,必须调用 mmc_card_release_host 函数来释放 MMC 主机,以便其他 MMC 主机声明使 用 MMC 驱动操作。

此外,还有一个一般的 MMC 请求处理函数,那就是 mmc_request 函数,这个函数被那 些 MMC 主机的队列调用,当 MMC 主机空闲时,将利用这个函数查找一个等待的请求,然 后把它唤醒处理。mmc_request 函数的实现代码如下:

static void mmc_request(request_queue_t *q) {

struct mmc_queue *mq = q->queuedata;

if (!mq->req)

wake_up(&mq->thread_wq);

}

到此为止,关于 MMC 驱动程序的主要实现代码已经分析完毕,该程序是基于 Linux 2.6.10 内核中提供的 MMC 代码为基础的,读者可以通过附件的光盘获取 MMC 驱动的参考 实现代码。