块设备是Linux最复杂的设备之一,但是作为执着于知其然的Geek,我们总会把代码翻个遍,把道理弄个透。当然了,快速地学习一种新的东西,方法是最重要的,个人觉得: 内核当中 MMC/SD 卡驱动程序构架是学习EMMC 驱动程序的重点,只有理解了它才能真正理解该块设备驱动程序,同时才能真正理解 LINUX 块设备驱动程序。
一.需要的基础知识:
1. LINUX 设备驱动的基本结构。
2. 块设备驱动程序的基本构架(相信研究过 LDD3 当中的 sbull 的人应该都不成问题,如果只是走马观花的话,那可得好好再补补了)
3. LINUX 设备驱动模型。
4. EMMC的原理,是Nand Flash的基础上加上一个负责:ECC、负载均衡和坏块管理功能的controler。
二.驱动程序分析
首先,说明一下EMMC驱动涉及到的文件。另外,我们重点是分析驱动程序的基本构架,所以不同内核版本的差异并不是很大。 MMC/SD 卡驱动程序位于 drivers/mmc 目录下
Card/
block.c
queue.c/queue.h
core/
bus.c/bus.h
core.c/core.h
host.c/host.h
mmc.c
mmc_ops.c/mmc_ops.h 拿 MMC 卡来分析, SD 卡驱动程序流程类似。
host/
s3cmci.c/s3cmci.h 以 S3C24XX 的 MMC/SD 卡控制器为例,其它类型的控制器类似。
LINUX 当中对目录的划分是很有讲究的,这些文件被分布在 3 个目录下,正好对应 MMC/SD 驱动程序的 3 个层次(关于层的划分这里浏览一下,有个概念即可,当我们分析完了后再回头来看,你会觉得很形象):
(1) 区块层
主要是按照 LINUX 块设备驱动程序的框架实现一个卡的块设备驱动,这 block.c 当中我们可以看到写一个块设备驱动程序时需要的 block_device_operations 结构体变量的定义,其中有 open/release/request 函数的实现,而 queue.c 则是对内核提供的请求队列的封装,我们暂时不用深入理解它,只需要知道一个块设备需要一个请求队列就可以了。
(2) 核心层
核心层封装了 MMC/SD 卡的命令,例如存储卡的识别,设置,读写。例如不管什么卡都应该有一些识别,设置,和读写的命令,这些流程都是必须要有的,只是具体对于不同的卡会有一些各自特有的操作。 Core.c 文件是由 sd.c 、 mmc.c 两个文件支撑的, core.c 把 MMC 卡、 SD 卡的共性抽象出来,它们的差别由 sd.c 和 sd_ops.c 、 mmc.c 和 mmc_ops.c 来完成。
(3) 主机控制器层
主机控制器则是依赖于不同的平台的,例如 s3c2410 的卡控制器和 atmel 的卡控制器必定是不一样的,所以要针对不同的控制器来实现。以 s3cmci.c 为例,它首先要进行一些设置,例如中断函数注册,全能控制器等等。然后它会向 core 层注册一个主机( host ),用结构 mmc_host_ops 描述,这样核心层就可以拿着这个 host 来操作 s3c24xx 的卡控制器了,而具体是 s3c24xx 的卡控制器还是 atmel 的卡控制器, core 层是不用知道的。
对这几个目录有一个大概认识以后,我们来看几个重要的数据结构:
struct mmc_host 用来描述卡控制器
struct mmc_card 用来描述卡
struct mmc_driver 用来描述 mmc 卡驱动
struct mmc_host_ops 用来描述卡控制器操作集,用于从主机控制器层向 core 层注册操作函数,从而将 core 层与具体的主机控制器隔离。也就是说 core 要操作主机控制器,就用这个 ops 当中给的函数指针操作,不能直接调用具体主控制器的函数。
第一阶段:
从 s3cmci_init 开始往下看
static int __init s3cmci_init(void)
{
platform_driver_register(&s3cmci_driver_2410);
}
有 platform_driver_register 函数,根据设备模型的知识,我们知道那一定会有对应的 platform_device_register 函数的,可是在哪里呢?没有看到,那是不是这个 s3cmci_driver_2410 当中给的 probe 函数就不执行了???当然不是, mci 接口一般都是硬件做好的(我认为是这样),所以在系统启动时一定会有调用 platform_device_register 对板上的资源进行注册,如果没有这个硬件资源,那我们这个驱动也就没有用了。好,我们就假定是有 mci 接口的,而且也有与 s3cmci_driver_2410 对应的硬件资源注册了,那自己就会去跑 probe 函数。来看一下 s3cmci_driver_2410:
static struct platform_driver s3cmci_driver_2410 = {
.driver.name = "s3c2410-sdi",
.probe = s3cmci_probe_2410,
.remove = s3cmci_remove,
.suspend = s3cmci_suspend,
.resume = s3cmci_resume,
};
我们到 s3cmci_probe_2410 函数中看,还是干脆直接看 s3cmci_probe 算了:
static int s3cmci_probe(struct platform_device *pdev, int is2440) // 来自 /host/s3cmci.c
{
struct mmc_host *mmc;
struct s3cmci_host *host;
int ret;
……
mmc = mmc_alloc_host (sizeof(struct s3cmci_host), &pdev->dev);
if (!mmc) {
ret = -ENOMEM;
goto probe_out;
}
……
mmc->ops = &s3cmci_ops;
……
ret = mmc_add_host (mmc);
if (ret) {
dev_err(&pdev->dev, "failed to add mmc host./n");
goto free_dmabuf;
}
……
platform_set_drvdata(pdev, mmc);
return 0;
……
}
这个函数很长,做的事件也很多,但我们关心的整个驱动的构架 / 流程,所以过滤掉一些细节的东西,只看 2 个最重要的函数: mmc_alloc_host 、 mmc_add_host 。函数命名已经很形象了,前者是申请一个 mmc_host ,而后者是添加一个 mmc_host 。中间还有一个操作,就是给 mmc 的 ops 成员赋上了 s3cmci_ops 这个值。申请 mmc_host 当然很简单,就是申请一个结构体(我们暂且这样认为,因为他里面还做的其它事情,后面会看到),而添加又是添加到哪里去呢?看 mmc_add_host 函数:
int mmc_add_host(struct mmc_host *host) // 来自 core/host.c
{
int err;
……
err = device_add(&host->class_dev);
if (err)
return err;
mmc_start_host(host);
return 0;
}
很简单,就是增加了一个 device ,然后就调用 mmc_start_host 了,那就先跳过 device_add 这个动作,来看 mmc_start_host:
void mmc_start_host(struct mmc_host *host) // 来自 /host/core.c
{
mmc_power_off(host); // 掉电一下
mmc_detect_change(host, 0); // ???
}
看上去只有两行代码,不过浓缩才是精华, mmc_power_off(host) 光看名子都知道是在干什么,先跳过,来看 mmc_detect_change