前言
主要是想对Linux mmc子系统(包含mmc sd sdio)驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动、块设备驱动、设备模型等也不进行详细说明原理,涉及到sd/mmc/sdio协议部分也只会简单带过,因为linux内核mmc子系统里面已经实现了这些协议,我们以后并不需要重新实现这些,只需要对协议有个简单的了解,基于内核版本:2.6.35.6。如果有任何错误地方,请指出,谢谢!
mmc、sd、sdio介绍
SD/MMC卡是一种大容量、性价比高、体积小、访问接口简单的存储卡。SD/MMC 卡大量应用于数码相机、MP3 机、手机、大容量存储设备,作为这些便携式设备的存储载体,它还具有低功耗、非易失性、保存数据无需消耗能量等特点。
总的来说,sd/mmc/sdio采用的协议绝大部分是通用的,而且硬件电气特性也相似,接口可以相兼容,mmc是比较老的存储卡了,sd是mmc的替代者,sdio是基于sd而额外开发出的一种io接口卡 就好比pcmcia接口,以前主要用来存储卡上,现在wifi、蓝牙等很多设备也用pcmcia这一接口。下文涉及到具体驱动时以sd卡为主。 Sd卡有很多标准,不同的标准主要是对容量及速度进行的改进,当然支持的命令也会有变化。这些标准包括sdc、sdhc、sdxc、uhs等,sdc最大支持2g,超过2g,小于32g属于sdhc范畴。因为以sd卡为主要对象,下面对sd卡简单介绍。
更多的信息参考: http://zh.wikipedia.org/wiki/SD卡
http://zh.wikipedia.org/wiki/SDHC
SD存储卡结构图
SD卡支持两种模式,分别为spi模式和sd模式(sd模式又有1bit和4bit之分)
引脚说明如下图所示:
Linux下Mmc/sd/sdio驱动分析
Mmc/sd/sdio子系统代码都在linux-2.6.35driversmmc下,如下图
三个目录的作用分别为:
Card 这个目录是衔接最上层应用的接口,应用层使用sd卡一般都是通过文件系统来操作的,card目录里面的代码就是让sd卡成为一个块设备,这样应用层就可以把它当作磁盘来操作了。
Core 这个目录是MMC子系统的核心,里面实现了card和host要用到的一些通用的函数接口和数据结构,它起到衔接作用,可以看作成中间层。Mmc/sd/sdio协议部分就是在这个文件夹里面实现的(Card目录里面也会涉及到协议)。
Host 这个目录存放了各个mmc/sd/sdio控制器的代码,最终操作mmc/sd/sdio卡的部分就是在这里实现的。
Mmc/sd/sdio框架(下面简称mmc子系统)我打算分三个部分来分析
第一部分、核心部分
核心部分肯定是在core目录里。文件core.c里mmc_init
负责初始化mmc子系统subsys_initcall(mmc_init)
;
主要工作是:
mmc_register_bus
注册mmc总线,这个总线主要是为card目录里实现的mmc设备驱动层和mmc控制器实例化一个mmc(包括sd/sdio)设备对象建立的。sdio_register_bus
这是sdio的部分,它比较特殊,需要额外的一条总线,这里不进行过多说明,以sd卡为主线
第二部分、mmc控制器驱动部分
这部分肯定是在host目录里。以三星的文件s3cmci.c为例
static int __init s3cmci_init(void)
{
return platform_driver_register(&s3cmci_driver);
}
类似于i2c、spi这些控制器,都是以平台设备的方式实现控制器的驱动。平台驱动就不多说了,直接看s3cmci_probe
它主要完成的工作是:
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
这个基本上是所有相类似驱动的必备过程,实例化一个控制器对象,不过一般都会额外需要自己定义一些数据来维护整个驱动,可以说是上下文数据,这个函数的第一个参数sizeof(struct s3cmci_host)
就是分配私有的数据。mmc_alloc_host
这个接口就是core目录里面的host.c提供的。在控制器驱动这边看来,mmc_alloc_host
就是核心层为它隐藏了很多细节。所以分析控制器驱动的时候我们暂时不关心那些细节。- 初始化自己私有部分数据,比如配置gpio、提取平台设备的信息注册中断、建立寄存器资源映射、分配dma(如果有使能)、请求检测管脚的gpio及注册中断(如果有使能detect)、请求写保护管脚gpio(如果有使能写保护)、开启控制器时钟,这部分是和具体平台的控制器有关的,不同平台控制器需要的资源可能有多又少。
- 初始化核心层定义的控制器对象
mmc->ops = &s3cmci_ops;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
mmc->caps = MMC_CAP_4_BIT_DATA;
#endif
mmc->f_min = host->clk_rate / (host->clk_div * 256);
mmc->f_max = host->clk_rate / host->clk_div;
if (host->pdata->ocr_avail)
mmc->ocr_avail = host->pdata->ocr_avail;
mmc->max_blk_count = 4095;
mmc->max_blk_size = 4095;
mmc->max_req_size = 4095 * 512;
mmc->max_seg_size = mmc->max_req_size;
mmc->max_phys_segs = 128;
mmc->max_hw_segs = 128;
这些是核心层会用到的,所以需要初始化它们,其中s3cmci_ops
是控制器操作集,编写控制器驱动的一个主要任务就是实现这个操作集,
static struct mmc_host_ops s3cmci_ops = {
.request = s3cmci_request, //最终执行硬件操作的函数
.set_ios = s3cmci_set_ios, //配置控制器的函数
.get_ro = s3cmci_get_ro, //判断是否写保护,其实就是读写保护的gpio
.get_cd = s3cmci_card_present, //判断是否卡存在,其实就是读卡侦测的gpio
.enable_sdio_irq = s3cmci_enable_sdio_irq, //和sdio相关,暂时忽略
};
下面简单分析下每个函数,毕竟我们写mmc驱动绝大部分工作就是写mmc控制器的驱动,S3cmci_ops
提供的操作集只实现了5个函数
第一个函数s3cmci_request
这个是最重要的函数吧,它是最终执行硬件操作流程的函数。但是它处理的是发送什么呢?是命令还是数据 这个就得看s3cmci_request
的参数来,至于参数的格式就是由核心层提供,核心层更上一层的card设备驱动层调用核心层的函数,将命令或者数据发往控制器,当然,在控制器初始化过程中也会调用这些函数。下面看s3cmci_request
:
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
这两个参数都是核心层定义的数据类型,第一个是在probe的时候我们通过mmc_alloc_host
分配的,里面还有我们自己的私有数据!第二个是mmc_request
类型,想一下就知道它的作用肯定是告诉我们是命令还是数据,什么命令,什么数据等等,看一下它的定义:
struct mmc_request {
struct mmc_command *cmd;
struct mmc_data *data;
struct mmc_command *stop;
void *done_data; /* completion data */
void (*done)(struct mmc_request *);/* completion function */
};
很明了了吧,命令有命令的结构体描述(struct mmc_command
),数据有数据的结构体描述(struct mmc_data
),同时还有回调函数。
看一下s3c怎么利用传入的参数实现最终的操作的!
static void s3cmci_request(struct mmc_host *mmc, struct mmc_request *mrq)
{
struct s3cmci_host *host = mmc_priv(mmc);//提取我们私有的数据
host->status = "mmc request";
host->cmd_is_stop = 0; //表明我们不是stop命令
host->mrq = mrq; //将请求对象附属到我们私有的数据中
if (s3cmci_card_present(mmc) == 0) {//判断卡是否存在,如果卡不存在,就没必要发送请求了
dbg(host, dbg_err, "%s: no medium present
", __func__);
host->mrq->cmd->error = -ENOMEDIUM;
mmc_request_done(mmc, mrq);
} else //如果卡存在,那么我们就进行具体的send 请求
s3cmci_send_request(mmc);
}
我们可以想一下,要通过控制器发送数据,一般的步骤? 我想一般都是操作控制器对应的寄存器吧! 这组寄存器的地址在probe里面已经申请并建立映射了,我们是可以直接拿来用的(host->base)。
s3cmci_send_request
里面的具体实现:
如果命令附带有数据(表示是数据操作),那么先进行数据的初始化操作,这类操作稍后再谈,先谈命令的发送。
最终调用了s3cmci_send_command
,s3cmci_send_command
主要的工作:
设置s3c中断寄存器使能一些中断,至于具体使能哪些可以对照代码和s3c手册。
设置s3c参数寄存器,这其实就是mmc/sd/sdio协议里面发送命令6字节里面的参数部分。
设置s3c命令控制寄存器,这主要设置命令码(比如由card层传来的)、是否等待响应、是否是长响应、启动命令的发送。
实际上到这一步,上层要求的命令或者数据请求就已经送出去了,只是最后的处理得根据具体情况来。
如果发送的是命令,且是有响应的命令,那么会在中断处理函数里面处理。
如果发送的是数据,那么该命令的发送会导致前面一步(就是我说稍后再谈的那个地方)填充到fifo的数据发送。
如果有超时,也是在中断处理里面处理。中断处理的注册在probe里面,它是根据具体平台的指示来注册相应的中断的。
request_irq(host->irq, s3cmci_irq, 0, DRIVER_NAME, host)
同时这里要额外说明的是中断的处理一般会用到下半部的机制
这里采用的是tasklet,在probe里面初始化的
tasklet_init(&host->pio_tasklet, pio_tasklet, (unsigned long) host);
所以我们应该猜到具体的读、写、错误处理都是在pio_tasklet
里面,而中断处理函数s3cmci_irq
里面一般就是读取寄存器状态,然后根据这些状态来具体最终的动作,当然动作就直接交给下半部处理了。
s3cmci_irq
比较长,这里暂时不分析了,总之处理完一次请求我们都需要调用mmc_request_done
,这是规定好的(毕竟请求里面有回调,这个函数是核心层实现的,它里面就调用了那个回调函数)。
第三部分、mmc驱动层
Mmc层驱动实际上就实现了mmc/sd/sdio/三类设备的驱动。它具体的实现都在card目录下。
下面简单分析下block.c中的mmc_blk_init
负责初始化mmc层。主要工作:
因为是向上层展示为块设备,所以第一步就是
res = register_blkdev(MMC_BLOCK_MAJOR, "mmc");
申请了块设备号MMC_BLOCK_MAJOR(179),然后就将自己注册到mmc总线上去了,这个总线的初始化是在核心层的初始化中,上文有讲到。
res = mmc_register_driver(&mmc_driver);
这会导致与mmc总线上由控制器注册的设备匹配(至于控制器是怎么添加的后文在讲解),于是mmc_blk_probe
函数会调用它的实现很简单:
- 分配一个上下文数据
struct mmc_blk_data
, 实际上该对象内嵌了块设备对象,同时也初始化了块队列处理函数和块操作集 add_disk
将struct mmc_blk_data
内部已经初始化好的块设备对象添加的块子系统中
情景分析
下面分几个情景来讲述mmc子系统的一些细节实现
卡(mmc/sd/sdio)的热插拔处理
卡的热插拔的第一步初始化是在mmc_alloc_host中,也就是由核心层负责这事:
mmc_alloc_host
{
….
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
…..
}
mmc_rescan
负责扫描设备并添加设备,但是因为采用的中断方式触发扫描,所以同时还需要控制器的帮助,这就涉及到probe里面的:
if (!host->pdata->no_detect) {
ret = gpio_request(host->pdata->gpio_detect, "s3cmci detect");
if (ret) {
dev_err(&pdev->dev, "failed to get detect gpio
");
goto probe_free_irq;
}
host->irq_cd = gpio_to_irq(host->pdata->gpio_detect);
if (host->irq_cd >= 0) {
if (request_irq(host->irq_cd, s3cmci_irq_cd,
IRQF_TRIGGER_RISING |
IRQF_TRIGGER_FALLING,
DRIVER_NAME, host)) {
dev_err(&pdev->dev,
"can't get card detect irq.
");
ret = -ENOENT;
goto probe_free_gpio_cd;
}
} else {
dev_warn(&pdev->dev,
"host detect has no irq available
");
gpio_direction_input(host->pdata->gpio_detect);
}
}
}
控制器从控制器平台设备端拿到监控相关的数据,比如哪个管脚。然后控制器为该管脚注册中断。
看一下s3cmci_irq_cd
:
static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
dbg(host, dbg_irq, "card detect
");
mmc_detect_change(host->mmc, msecs_to_jiffies(500));
return IRQ_HANDLED;
}
由此我们可以知道控制器监测管脚的中断处理很简单,只需要调用核心层指定的函数mmc_detect_change
即可。至于内部的细节就不分析了,多说两句:里面通过工作队列异步触发工作者线程kmmcd(由核心层分配的),至于是什么工作呢这个就是上面说的mmc_rescan
。上面说的就是热插拔的第一步,事件的触发监控。
第二步就是具体的热插拔处理流程了。
mmc_rescan
的主要工作是(注意:这些都是核心层完成的,控制器驱动不用关心,但需要实现核心层要求的操作集,比如s3cmci_ops
):
- 判断当前控制器上的卡是否之前已经存在,这种情况下只需要调用操作集里的监测函数:
if ((host->bus_ops != NULL) && host->bus_ops->detect && !host->bus_dead)
host->bus_ops->detect(host);
这里的总线操作集bus_ops是在添加卡的时候赋值的,可以为
mmc_sdio_ops
mmc_sd_ops(mmc_sd_ops_unsafe)
mmc_ops
对于sd卡,就是mmc_sd_ops
static const struct mmc_bus_ops mmc_sd_ops = {
.remove = mmc_sd_remove,
.detect = mmc_sd_detect,
.suspend = NULL,
.resume = NULL,
.power_restore = mmc_sd_power_restore,
};
这里面的实现就牵涉到具体的协议了,这里就不分析了。
2. 如果确实是卡存在,则退出,如果不是,则调用控制器注册的函数
在s3cmci_ops
里就是s3cmci_card_present
,这个函数简单,就是读gpio管脚电平,不多分析了。然后进入到扫描最核心的部分:
mmc_claim_host(host);//锁定控制器
mmc_power_up(host);//控制器上电
sdio_reset(host);
mmc_go_idle(host); //进入空闲状态,这个是规范里面有的,怎么进入规范里面也有说明
mmc_send_if_cond(host, host->ocr_avail); //发送控制器兼容的电压
/*
* First we search for SDIO...
*///判断是否为sdio接口的设备
err = mmc_send_io_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...then normal SD...
*///判断是否为sd卡
err = mmc_send_app_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
goto out;
}
/*
* ...and finally MMC.
*///判断是否为mmc卡
err = mmc_send_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
mmc_power_off(host);
goto out;
}
还是以sd卡为例:
mmc_send_app_op_cond
主要工作是:
- 发送命令41(该命令是sd支持的,但mmc卡不支持的命令
- 如果有正确响应,就代表是sd卡了,进入到
mmc_attach_sd
。
mmc_attach_sd
的主要实现:
设置sd卡的总线操作集:mmc_sd_attach_bus_ops(host); mmc_sd_init_card
分配并初始化一个sd卡对象mmc_add_card
将sd卡对象添加的mmc总线这次的添加动作可能会引起card目录里面注册的mmc_driver
的匹配,最终会在mmc_blk_probe
实现向应用层提供块设备。
应用层读、写、控制卡流程
注意:这些都是核心层完成的,控制器驱动不用关心,但需要实现核心层要求的操作集,比如
s3cmci_ops
):
应用层一般都是操作基于sd卡块设备上的文件系统,所以会先经过vfs—>具体的文件系统的read、write-->块子系统submit_bio
-->块请求队列处理函数即在card目录里面
mmc_blk_probe-->mmc_blk_allocàmmc_init_queueàmmc_request
函数,该函数会唤醒队列处理线程mmcqd,mmcqd结合mmc_blk_issue_rq
将上层的struct request_queue *q
转为协议相关的请求,最终调用到控制器注册的 函数集里面的s3cmci_request
现在我们看控制器怎么实现请求的处理的:
- 调用
s3cmci_send_request
它会判断是否附带有数据的传输,如果是,那先对数据处理一下(比如如果是pio传输,那么采用pio方式将数据放到控制器fifo里去,如果是dma传输,则申请dma资源并配置好地址 s3cmci_send_command
触发命令的发送,这部分可以参考前文中的描述。
完!
2014年5月