原文地址:
linux sd卡驱动分析 作者:
幸福软件公司
基于S3C2410的SD卡linux驱动工作原理 Linux MMC/SD/SDIO体系结构linux sd卡驱动分析 基于S3C2410的SD卡linux驱动工作原理
我在讲嵌入式Linux驱动开发班的时候,发现不少学员对SD卡驱动这块比较感兴趣,课下来找我探讨。而在很多技术论坛,也有不少人问这方面的问题,所以就想写一下这方面的内容,希望对大家的学习能有所帮助。想了解SD卡的工作原理,首先需要了解的就是SD卡协议了,这个在网上可以轻松的下载到。在了解协议后,就可以看看下面的一些开发思路了。
首先看下脱离操作系统如何在S3C2410上实现SD卡的读写。过程可以分为3个大的步骤:初始化sd卡、写sd卡、读sd卡;下面的过程是我通过realview-MDK环境测试过的。
一、初始化sd卡
二、写sd卡
写sd卡可以分为3种方式:POLL、中断、DMA
(1) POLL写
(2)中断写
(3)DMA写
三、读sd卡
读sd卡也可分为3中方式:POLL、中断、DMA
(1) POLL读
(2)中断读
(3) DMA读
了解了脱离操作系统的工作原理后,现在可以思考linux是如何管理管理SD卡的了。Linux中SD驱动可以分为3层:块设备层(mmc_block.c
,mmc_sysfs.c,mmc_queue.c)、mmc协议层(mmc.c)、sd驱动层(s3c2410_sdi.c)。
下面从以下几个方面理解驱动:
1、s3c2410_sdi.c代码初始化过程;
2、SD卡块设备注册过程;
3、request及数据传输的实现。
下面介绍的过程参考的代码是我们华清远见培训中心在教学中使用的一套代码,内核版本是2.6.8,其它版本过程类似。
一、s3c2410_sdi.c代码初始化过程
二、SD卡块设备注册过程
三、request及数据传输的实现
Linux MMC/SD/SDIO体系结构
有关MMC/SD/SDIO相关的知识这里就不多讲了,请参考相关资料。这里主要涉及Linux下MMC相关内容。
内核版本(2.6.36)
首先说一下Linux相关MMC的代码分布,主要有两个目录,一个头文件目录和一个源代码目录。
分别位置如下:
include/linux/mmc
drivers/mmc
要阅读MMC相关代码就必须要看这两个目录。在drivers/mmc目录下分别有三个子目录,其将对应接下来要讲的MMC的体系结构。这三个子目录分别为:
card
core
host
它们的内容后面讲到体系结构时自然就明了了。
现在来说说MMC的体系结构,其分为三层
/dev下设备文件访问MMC/SD/SDIO
用户空间 |
---------------------|-----------------------------------------------------
内核空间 /
MMC Card层(对应具体的设备驱动,如MMC/SD卡块设备驱动,SDIO UART)
|
/
MMC core层(为上次设备驱动实现提供操作接口,和下层host注册提供机制)
|
/
Host层(具体MMC/SD/SDIO控制器驱动层。如S3C2440 MMC/SD控制器驱动)
|
/
-----------------------------------------------------------------------------
硬件层
对于我们来说,编写MMC/SD卡相关驱动主要涉及的就是Host层,其余层不用考虑。对于SDIO设备除了Host层以外,还有可能要编写MMC Card层的设备驱动。
编写Host层驱动,主要是填充mmc_host结构体相关内容和实现mmc_host_ops结构体中的函数。最后调用mmc_add_host向MMC core注册host驱动。可以参考S3C24XX的HOST驱动程序(drivers/mmc/host/s3cmci.c,s3cmci.h),上层MMC Core主要调用mmc_host_ops中的函数来实现与硬件交互。如下是mmc_host_ops结构体里面的函数:
struct mmc_host_ops {
/*使能和禁止HOST控制器*/
int (*enable)(struct mmc_host *host);
int (*disable)(struct mmc_host *host, int lazy);
/*这个是关键的函数,所有对MMC/SD的操作,包括发命令和读数据,都通过该接口来实现,所以实现该接口时要处理是命令还是数据操作,另外要考虑是否使用DMA来进行数据传输。*/
void (*request)(struct mmc_host *host, struct mmc_request *req);
/*用来设置MMC/SD的时钟,电压等操作*/
void (*set_ios)(struct mmc_host *host, struct mmc_ios *ios);
/*检查MMC/SD是否写保护了*/
int (*get_ro)(struct mmc_host *host);
/*检查mmc/sd的插入和拔出*/
int (*get_cd)(struct mmc_host *host);
void (*enable_sdio_irq)(struct mmc_host *host, int enable);
/* optional callback for HC quirks */
void (*init_card)(struct mmc_host *host, struct mmc_card *card);
};
博客
http://blog.chinaunix.net/u3/101649/showart.php?id=2232724对S3C2440的HOST驱动进行了详细的分析。
接下来说说MMC Core层。
该层主要实现了几个结构体函数指针,用来构建整个MMC设备驱动模型。它们是:
struct bus_type mmc_bus_type /*mmc总线,用来管理sd/mmc卡设备和驱动*/
struct mmc_bus_ops mmc_ops /*MMC卡总线操作函数,主要是在电源管理方面*/
struct mmc_bus_ops mmc_sd_op /*SD卡总线操作函数,主要是在电源管理方面*/
struct mmc_bus_ops mmc_sdio_ops /*SDIO总线操作函数,主要是在电源管理方面*/
struct bus_type sdio_bus_type /*SDIO另外定义了一条总线*/
core.c文件中实现了几个关键的函数,用来提供给上层MMC Card调用和对SD/MMC卡的侦测函数以及初始化。
供给上层MMC Card调用主要有:
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq);
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries);
mmc card层就是通过这些函数来操作mmc/sd卡。而这些函数最终调用的是mmc_host_ops 结构体中的request函数来进行具体的操作。
对SD/MMC卡的侦测函数以及初始化,主要实现在
void mmc_rescan(struct work_struct *work);
函数中。
host层在调用mmc_add_host时会引发该函数的调用,侦测/初始化顺序为:
先SDIO接口
/*
* First we search for SDIO...
*/
err = mmc_send_io_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sdio(host, ocr)) {
...
在SD:
/*
* ...then normal SD...
*/
err = mmc_send_app_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sd(host, ocr))
最后是MMC:
/*
* ...and finally MMC.
*/
err = mmc_send_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_mmc(host, ocr))
其中mmc_attach_xxx函数就是用来完成侦测和初始化的,选择相应的总线操作函数,并产生struct mmc_card结构体,并填充其内容,最后注册一个mmc_card(代表着一个设备),并在注册中由mmc_bus_type结构体的match和probe函数来查找到适合该设备的驱动(这个又牵涉到设备驱动模型,可以查看设备驱动模型相关内容,了解设备和驱动匹配的过程),这里将匹配到mmc card层的MMC_Block(MMC块设备驱动程序,由 struct mmc_driver代表)。在完成设备侦测和初始化以后,以后的操作就是mmc
card层中相关的设备驱动程序发出的了。
再说说MMC card层,该层主要实现具体的设备驱动程序,如MMC块设备驱动程序,通过mmc_register_driver注册。如果是SDIO就有可能是其它字符设备驱动程序了,其通过调用sdio_register_driver来注册设备驱动。
总体概括来说:
host层提供驱动相关MMC/SD/SDIO控制器的功能。
Core层提供了具体MMC/SD/SDIO设备侦测和初始化功能,以及电源管理方面的内容和通用的操作功能。
Card为实现具体的设备驱动层。
这样的分层结构在Linux设备驱动中非常常见,如I2C,SPI等都提供了这样的驱动模型。
最后简单说说SDIO相关部分。在core层注册了新的sdio_bus_type总线,并且定义了新的sdio_driver来代表sdio设备驱动,并定义了struct sdio_func来代表设备。所以在SDIO设备除了struct mmc_card来代表设备以外,还有struct sdio_func来代表具体功能设备。所以在mmc_attach_sdio函数中除了注册mmc_card以外,还注册了sdio_func。具体代码如下:
int mmc_attach_sdio(struct mmc_host *host, u32 ocr)
{
....
/*
* First add the card to the driver model...
*/
err = mmc_add_card(host->card);
if (err)
goto remove_added;
/*
* ...then the SDIO functions.
*/
for (i = 0;i < funcs;i++) {
err = sdio_add_func(host->card->sdio_func[i]);
if (err)
goto remove_added;
}
....
}
所以它除了调用mmc_bus_type结构体的match和probe函数来查找到适合该设备的驱动外,也调用sdio_bus_type结构体的match和probe函数来查找到适合该设备的驱动。
linux sd卡驱动分析
1. 硬件基础:
SD/MMC/SDIO 概念区分概要
SD (Secure Digital )与 MMC (Multimedia Card )
SD 是一种 flash memory card 的标准,也就是一般常见的 SD 记忆卡,而 MMC 则是较早的一种记忆卡标准,目前已经被 SD 标准所取代。
SDIO 是目前我们比较关心的技术,SDIO 故名思义,就是 SD 的 I/O 接口(interface )的意思,不过这样解释可能还有点抽像。更具体的说明,SD 本来是记忆卡的标准,但是现在也可以把 SD 拿来插上一些外围接口使用,这样的技术便是 SDIO 。
所以 SDIO 本身是一种相当单纯的技术,透过 SD 的 I/O 接脚来连接外部外围,并且透过 SD 上的 I/O 数据接位与这些外围传输数据,而且 SD 协会会员也推出很完整的 SDIO stack 驱动程序,使得 SDIO 外围(我们称为 SDIO 卡)的开发与应用变得相当热门。
现在已经有非常多的手机或是手持装置都支持 SDIO 的功能(SD 标准原本就是针对 mobile device 而制定),而且许多 SDIO 外围也都被开发出来,让手机外接外围更加容易,并且开发上更有弹性(不需要内建外围)。目前常见的 SDIO 外围(SDIO 卡)有:
· Wi-Fi card (无线网络卡)
· CMOS sensor card (照相模块)
· GPS card
· GSM/GPRS modem card
· Bluetooth card
· Radio/TV card (很好玩)
SDIO 的应用将是未来嵌入式系统最重要的接口技术之一,并且也会取代目前 GPIO 式的 SPI 接口。
SD/SDIO 的传输模式
SD 传输模式有以下 3 种:
· SPI mode (required )
· 1-bit mode
· 4-bit mode
SDIO 同样也支持以上 3 种传输模式。依据 SD 标准,所有的 SD (记忆卡)与 SDIO (外围)都必须支持 SPI mode ,因此 SPI mode 是「required 」。此外,早期的 MMC 卡(使用 SPI 传输)也能接到 SD 插糟(SD slot ),并且使用 SPI mode 或 1-bit mode 来读取。
SD 的 MMC Mode
SD 也能读取 MMC 内存,虽然 MMC 标准上提到,MMC 内存不见得要支持 SPI mode (但是一定要支持 1-bit mode ),但是市面上能看到的 MMC 卡其实都有支持 SPI mode 。因此,我们可以把 SD 设定成 SPI mode 的传输方式来读取 MMC 记忆卡。
SD 的 MMC Mode 就是用来读取 MMC 卡的一种传输模式。不过,SD 的 MMC Mode 虽然也是使用 SPI mode ,但其物理特性仍是有差异的:
· MMC 的 SPI mode 最大传输速率为 20 Mbit/s ;
· SD 的 SPI mode 最大传输速率为 25 Mbit/s 。
为避免混淆,有时也用 SPI/MMC mode 与 SPI/SD mode 的写法来做清楚区别。
2.MMC 子系统的基本框架结构:
很遗憾,内核没有为我们提供关于MMC 子系统的文档,在谷歌上搜索了很多,也没有找到相关文章。只能自己看代码分析了,可能有很多理解不对的地方,希望研究过这方面的朋友多邮件交流一下。
MMC 子系统的代码在kernel/driver/MMC 下,目前的MMC 子系统支持一些形式的记忆卡:SD,SDIO,MMC. 由于笔者对SDIO 的规范不是很清楚,后面的分析中不会涉及。MMC 子系统范围三个部分:
HOST 部分是针对不同主机的驱动程序,这一部是驱动程序工程师需要根据自己的特点平台来完成的。
CORE 部分: 这是整个MMC 的核心存,这部分完成了不同协议和规范的实现,并为HOST 层的驱动提供了接口函数。
CARD 部分:因为这些记忆卡都是块设备,当然需要提供块设备的驱动程序,这部分就是实现了将你的SD 卡如何实现为块设备的。
3.HOST 层分析:
HOST 层实现的就是我们针对特定主机的驱动程序,这里以mini2440 的s3cmci.c 为例子进行分析,我们先采用platform_driver_register(&s3cmci_2440_driver) 注册了一个平台设备,接下来重点关注probe 函数。在这个函数总,我们与CORE 的联系是通过下面三句实现的。首先分配一个mmc_host 结构体,注意sizeof(struct s3cmci_host) ,这样就能在mmc_host 中找到了s3cmci_host ,嵌入结构和被嵌入的结构体能够找到对方在Linux
内核代码中的常用技术了。接下来为mmc->pos 赋值, s3cmci_ops 结构实现了几个很重要的函数,待会我一一介绍。中间还对mmc 结构的很多成员进行了赋值,最后将mmc 结构加入到MMC 子系统,mmc_alloc_host ,以及mmc_add_host 的具体做了什么事情,我们在下节再分析,这三句是些MMC 层驱动必须包含的。
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
mmc->ops = &s3cmci_ops;
……………
s3cmci_ops 中包含了四个函数:
static struct mmc_host_ops s3cmci_ops = {
.request = s3cmci_request,
.set_ios = s3cmci_set_ios,
.get_ro = s3cmci_get_ro,
.get_cd = s3cmci_card_present,
};
我们从简单的开始分析 , 这些函数都会在 core 部分被调用:
s3cmci_get_ro: 这个函数通过从 GPIO 读取,来判断我们的卡是否是写保护的
s3cmci_card_present : 这个函数通过从 GPIO 读取来判断卡是否存在
s3cmci_set_ios : s3cmci_set_ios(struct mmc_host *mmc, struct mmc_ios *ios)
依据核心层传递过来的 ios ,来设置硬件 IO, 包括引脚配置,使能时钟,和配置总线带宽。
s3cmci_request : 这个 函数是最主要,也最复杂的函数,实现了命令和数据的发送和接收,
当 CORE 部分需要发送命令或者传输数据时,都会调用这个函数,并传递 mrq 请求。
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;
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
s3cmci_send_request(mmc);
}
接下来看 s3cmci_send_request(mmc) :
这个函数先判断一下请求时传输数据还是命令, 如果是数据的话:
先调用 s3cmci_setup_data 来对 S3C2410_SDIDCON 寄存器进行设置,然后设置 SDITIMER 寄存器这就设置好了总线宽度,是否使用 DMA, ,并启动了数据传输模式,并且使能了下面这些中断:
imsk = S3C2410_SDIIMSK_FIFOFAIL | S3C2410_SDIIMSK_DATACRC |
S3C2410_SDIIMSK_DATATIMEOUT | S3C2410_SDIIMSK_DATAFINISH;
解析来判断是否是采用 DMA 进行数据传输还是采用 FIFO 进行数据传输
if (host->dodma)
/ because host->dodma = 0,so we don't use it
res = s3cmci_prepare_dma(host, cmd->data);// 准备 DMA 传输,
else
res = s3cmci_prepare_pio(host, cmd->data);.// 准备 FIFO 传输
如果是命令的话: 则调用 s3cmci_send_command ()这个函数是命令发送的函数,和 datesheet 上描述的过程差不多 , 关于 SD 规范中命令的格式,请参考参考资料 1.
writel(cmd->arg, host->base + S3C2410_SDICMDARG);/* 先写参数寄存器
ccon = cmd->opcode & S3C2410_SDICMDCON_INDEX;// 确定命令种类
ccon |= S3C2410_SDICMDCON_SENDERHOST | S3C2410_SDICMDCON_CMDSTART;
/*with start 2bits*/
if (cmd->flags & MMC_RSP_PRESENT)
ccon |= S3C2410_SDICMDCON_WAITRSP;
/*wait rsp*/
if (cmd->flags & MMC_RSP_136)
ccon |= S3C2410_SDICMDCON_LONGRSP;
// 确定 respose 的种类
writel(ccon, host->base + S3C2410_SDICMDCON);
命令通道分析完了,我们分析数据通道,先分析采用 FIFO 方式传输是怎么样实现的。
先分析 s3cmci_prepare_pio(host, cmd->data)
根据 rw 来判断是读还是写
if (rw) {
do_pio_write(host);
/* Determines SDI generate an interrupt if Tx FIFO fills half*/
enable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
} else {
enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF
| S3C2410_SDIIMSK_RXFIFOLAST);
}
如果是写数据到 SD 的话,会调用 do_pio_write, 往 FIFO 中填充数据。当 64 字节的 FIFO 少于 33 字节时就会产生中断。如果是从 SD 读数据,则先使能中断,当 FIFO 多于 31 字节时时,则会调用中断服务程序,中断服务程序中将会调用 do_pio_read FIFO 的数据读出。
接下来分析 do_pio_write :
to_ptr = host->base + host->sdidata;
fifo_free(host) 用来检测 fifo 剩余空间
while ((fifo = fifo_free(host)) > 3) {
if (!host->pio_bytes) {
res = get_data_buffer(host, &host->pio_bytes,
/* If we have reached the end of the block, we have to
* write exactly the remaining number of bytes. If we
* in the middle of the block, we have to write full
* words, so round down to an even multiple of 4. */
if (fifo >= host->pio_bytes)//fifo 的空间比 pio_bytes 大,表明这是读这个块的最后一次
fifo = host->pio_bytes;
/* because the volume of FIFO can contain the remaning block*/
else
fifo -= fifo & 3;/*round down to an even multiple of 4*/
host->pio_bytes -= fifo;// 更新还剩余的没有写完的字
host->pio_count += fifo;/*chang the value of pio_bytes*/
fifo = (fifo + 3) >> 2;// 将字节数转化为字数
/*how many words fifo contain,every time we just writ one word*/
ptr = host->pio_ptr;
while (fifo--)
writel(*ptr++, to_ptr);// 写往 FIFO.
host->pio_ptr = ptr;
}
注释一:注意, MMC 核心为 mrq->data 成员分配了一个 struct scatterlist 的表,用来支持分散***,使用这种方法,这样使物理上不一致的内存页,被组装成一个连续的数组,避免了分配大的缓冲区的问题
我们看代码
if (host->pio_sgptr >= host->mrq->data->sg_len) {
dbg(host, dbg_debug, "no more buffers (%i/%i) ",
host->pio_sgptr, host->mrq->data->sg_len);
return -EBUSY;
}
sg = &host->mrq->data->sg[host->pio_sgptr];
*bytes = sg->length;// 页缓冲区中的长度
* pointer = sg_virt(sg); 将页地址映射为虚拟地址
host->pio_sgptr++; 这里表明我们的程序又完成了一次映射
这样,每一个 mmc 请求,我们只能处理 scatterlist 表中的一个页(块)。因此,完成一次完整的请求需要映射 sg_len 次
再来总结一下一个 mmc 写设备请求的过程:
在 s3cmci_prepare_pio 中我们第一次先调用 do_pio_write ,如果 FIFO 空间大于 3 ,且能够获取到 scatterlist ,则我们就开始往 FIFO 写数据,当 FIFO 空间小于 3 ,则使能 TXFIFOHALF 中断,在中断服务程序中,如果检测到 TFDET 表明又有 FIFO 空间了,则关闭 TXFIFOHALF 中断,并调用 do_pio_write 进行写。
数据流向如下: scatterlist-------->fifo---------->sdcard
一个 mmc 读设备请求的过程 数据流向如下 : sdcard --------> fifo ---------->scatterlist ,
????关于读数据的过程,中断的触发不是很清楚, s3cmci_prepare_pio 中 enable_imask(host, S3C2410_SDIIMSK_RXFIFOHALF , S3C2410_SDIIMSK_RXFIFOLAST); 但如果没从 SD 卡中读数据,怎么会引发这个中断呢?是由 S3C2410_SDIIMSK_RXFIFOLAST 引起的吗
接下来我们分析一下中断服务程序:
static irqreturn_t s3cmci_irq(int irq, void *dev_id)
该程序先获取所有的状态寄存器:
mci_csta = readl(host->base + S3C2410_SDICMDSTAT);
mci_dsta = readl(host->base + S3C2410_SDIDSTA);
mci_dcnt = readl(host->base + S3C2410_SDIDCNT);
mci_fsta = readl(host->base + S3C2410_SDIFSTA);
mci_imsk = readl(host->base + host->sdiimsk);
这些将作为中断处理的依据。
如果不是 DMA 模式,则处理数据的收发
if (!host->dodma) {
if ((host->pio_active == XFER_WRITE) &&
(mci_fsta & S3C2410_SDIFSTA_TFDET)) {
/*This bit indicates that FIFO data is available for transmit when
DatMode is data transmit mode. If DMA mode is enable, sd
host requests DMA operation.*/
disable_imask(host, S3C2410_SDIIMSK_TXFIFOHALF);
tasklet_schedule(&host->pio_tasklet);
注意我们采用 tasklet 这种延时机制来减少中断服务的时间,延时函数 pio_tasklet 中调用了 do_pio_write 和 了 do_pio_read
host->status = "pio tx";
}
if ((host->pio_active == XFER_READ) &&
(mci_fsta & S3C2410_SDIFSTA_RFDET)) {
disable_imask(host,
S3C2410_SDIIMSK_RXFIFOHALF |
S3C2410_SDIIMSK_RXFIFOLAST);
tasklet_schedule(&host->pio_tasklet);
host->status = "pio rx";
}
接下来的很多代码是对其他的一些类型中断的处理。
最后来分析 DMA 模式:这种模式下不需要 CPU 的干预。 S3C2440 的 DMA 有 4 个通道,我们选择了通道 0
static int s3cmci_prepare_dma(struct s3cmci_host *host, struct mmc_data *data)
{
int dma_len, i;
int rw = (data->flags & MMC_DATA_WRITE) ? 1 : 0;
BUG_ON((data->flags & BOTH_DIR) == BOTH_DIR);
s3cmci_dma_setup(host, rw ? S3C2410_DMASRC_MEM : S3C2410_DMASRC_HW); // 注一
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
(rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE); // 注二
if (dma_len == 0)
return -ENOMEM;
host->dma_complete = 0;
host->dmatogo = dma_len;
for (i = 0; i < dma_len; i++) {
int res;
dbg(host, dbg_dma, "enqueue %i:%u@%u ", i,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
res = s3c2410_dma_enqueue(host->dma, (void *) host,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
if (res) {
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_FLUSH);
return -EBUSY;
}
}
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START);
return 0;
}
注一 : 这个函数先调用 s3c2410_dma_devconfig 来配置 DMA 源 / 目的的意见类型和地址,注意我们这里的设备地址 host->mem->start + host->sdidata 实际上就是 SDIDATA 寄存器的地址值,如果是写 SD 卡,则为目的地址,否则为源地址。然后调用 s3c2410_dma_set_buffdone_fn(host->dma, s3cmci_dma_done_callback);
设置 dma 通道 0 的回调函数。
注二:
dma_len = dma_map_sg(mmc_dev(host->mmc), data->sg, data->sg_len,
(rw) ? DMA_TO_DEVICE : DMA_FROM_DEVICE);
这里进行分散 / ***映射( P444,LDD3 ) , 返回值是传送的 DMA 缓冲区数,可能会小于 sg_len ,也就是说 sg_len 与 dma_len 可能是不同的。
sg_dma_address(&data->sg[i]), 返回的是总线( DMA )地址
sg_dma_len(&data->sg[i])); 返回的是缓冲区的长度。
最后调用 s3c2410_dma_enqueue(host->dma, (void *) host,
sg_dma_address(&data->sg[i]),
sg_dma_len(&data->sg[i]));
对每个 DMA 缓冲区进行排队,等待处理。
s3c2410_dma_ctrl(host->dma, S3C2410_DMAOP_START); 启动 DMA
这样 DMA 缓冲区就和 scatterlist 联系起来,当写数据时, scatterlist 中的数据由于上面的映射关系会直接“拷贝”到 DMA 缓冲区,当读数据时则反之。整个过程不需要 CPU 干预,自动完成。
以上就是针对 mini2440 HOST 部分的内容。
、 CORE 层分析:
CORE 层完成了不同协议和规范的实现,并为 HOST 层的驱动提供了接口函数,在 HOST 层我们曾经调用的两个函数:
mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
mmc_add_host(mmc);
我们就从这两个函数入手,来分析 CORE 层与 HOST 层是如何交互的。
先看 mmc_alloc_host 函数:
dev_set_name(&host->class_dev, "mmc%d", host->index);
host->parent = dev;
host->class_dev.parent = dev;
host->class_dev.class = &mmc_host_class;
device_initialize(&host->class_dev);
这几句是将导致在 /SYS/CLASS/mmc_host 下出现 mmc0 目录,添加类设备,在 2.6.21 后的版本中,类设备的 class_device 已近被 device 所取代, LDD3P387 的内容有点 OUT 了
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
初始化了一个工作队列,延时函数为 mmc_rescan ,这个延时函数很重要,下午要详细分析
最后对 host 做一些默认配置,不过这些配置在 probe 函数的后面都被重置了。
分析 mmc_add_host(mmc);
device_add(&host->class_dev); 这里才真正的添加了类设备。
其中调用了 mmc_start_host
void mmc_start_host(struct mmc_host *host)
{
mmc_power_off(host);
mmc_detect_change(host, 0);
}
mmc_power_off 中对 ios 进行了设置,然后调用 mmc_set_ios(host);
host->ios.power_mode = MMC_POWER_OFF;
host->ios.bus_width = MMC_BUS_WIDTH_1;
host->ios.timing = MMC_TIMING_LEGACY;
mmc_set_ios(host);
mmc_set_ios(host) 中的关键语句 host->ops->set_ios(host, ios); 这里的 set_ios 实际上就是我们前面所提到的 .set_ios = s3cmci_set_ios,
再看 mmc_detect_change(host, 0); 最后一句是
mmc_schedule_delayed_work(&host->detect, delay);
实际上就是调用我们前面说的延时函数 mmc_rescan
mmc_power_up(host);// 这个函数实际上与前面的 mmc_power_off 类似,不过设置了启动时需要的 ios
mmc_go_idle(host);
//CMD0 , from inactive to idle
mmc_send_if_cond(host, host->ocr_avail);// 发送 SD_SEND_IF_COND ,是使用 SD2.0 卡才需要设置的命令
/*suppot for 2.0 card*/
* ...then normal SD...
*/
err = mmc_send_app_op_cond(host, 0, &ocr);
if (!err) {
if (mmc_attach_sd(host, ocr))
mmc_power_off(host);
goto out;
}
蓝 {MOD}部分是遵照 SD 卡协议的 SD 卡启动过程,包括了非激活模式、卡识别模式和数据传输模式三种模式共九种状态的转换,你需要参照相关规范来理解。可以先参考下面三章图对模式和状态,以及状态转换有个初步了解。
我们最初的 SD 卡的状态时 inactive 状态调用 mmc_go_idle(host) 后,发送命令 CMD0 是其处于 IDLE 状态。
我们详细分析一下 mmc_go_idle
memset(&cmd, 0, sizeof(struct mmc_command));
cmd.opcode = MMC_GO_IDLE_STATE; MMC_GO_IDLE_STATE 就是命令 CMD0
cmd.arg = 0; 此命令无参数
cmd.flags = MMC_RSP_SPI_R1 | MMC_RSP_NONE | MMC_CMD_BC;
err = mmc_wait_for_cmd(host, &cmd, 0);// 见注 1
mmc_delay(1);
注 1 : mmc_wait_for_cmd(host, &cmd, 0) 是用来发送命令的,我们揭开它的神秘面纱吧。
memset(&mrq, 0, sizeof(struct mmc_request));
memset(cmd->resp, 0, sizeof(cmd->resp));
cmd->retries = retries;
mrq.cmd = cmd; 将命令嵌入到一个 mmc 请求中
cmd->data = NULL;mmc 命令的 data 部分设置为 NULL, 这样表示我们要传输的是命令而不是数据
mmc_wait_for_req(host, &mrq);// 关键部分
在该函数中调用了mmc_start_request ,而这个函数调用了host->ops->request(host, mrq) ,这个request 函数就是我们在前面分析的s3cmci_request ,这样MMC 核心第二次核HOST 层握手了
我们再看看: err = mmc_send_app_op_cond(host, 0, &ocr);// 注一
if (!err) {
if (mmc_attach_sd(host, ocr))// 注二
mmc_power_off(host);
goto out;
注一:实际上是要发送 ACMD41 命令,这条命令可以用来获取 SDcard 的允许电压范围值,由于这是一条应用命令,所有发送它之前需要发送 CMD_55 命令。执行完后 card 状态变为 READY 获取的电压范围保存在 ocr 中,再调用 mmc_attach_sd(host, ocr) 看这个电压范围是否满足主机的要求,不满足,则 power_off 主机。
注二: mmc_attach_sd 完成匹配,和初始化卡的功能
host->ocr = mmc_select_voltage(host, ocr); 看是否匹配,如果匹配则做下面初始化工作
mmc_sd_init_card(host, host->ocr, NULL); 我们分析该函数
( 1 ) mmc_all_send_cid ()这个函数发生 CMD2 ,获取卡的身份信息,进入到身份状态
(2)card = mmc_alloc_card(host, &sd_type); 分配一张 SD 类型的 card 结构
(3) 接着调用 mmc_send_relative_add, 获取卡的相对地址,注意一前卡和主机通信都采用默认地址,现在有了自己的地址了,进入到 stand_by 状态
( 4 )通过发送 SEND_CSD (CMD9) 获取 CSD 寄存器的信息,包括 block 长度,卡容量等信息
(5) mmc_select_card(card) 发送 CMD7, 选中目前 RADD 地址上的卡,任何时候总线上只有一张卡被选中,进入了传输状态 ,
( 6 )调用 mmc_app_send_scr 发送命令 ACMD51 获取 SRC 寄存器的内容,进入到 SENDING-DATA 状态
在函数中还将获得的各个卡寄存器的内容解码,并保存到 cmd 结构的相应成员中。
( 7 ) if (host->ops->get_ro(host) > 0 )
mmc_card_set_readonly(card);
通过调用 get_ro(host) 函数,实际上就是 s3cmci_get_ro 函数了。 我们判断是否写保护,如果是的,将 card 状态设置为只读状态
最后再 mmc_attach_sd 里,我们将 card 结构添加进去
mmc_add_card(host->card);
dev_set_name(&card->dev, "%s:%04x", mmc_hostname(card->host), card->rca); 这里我们以 host 名 +rca 地址来命名卡我们可以看到在 /sys/devices/platform/s3c2440-sdi/mmc_host:mmc0/ 下出现 mmc0 : 0002 的目录,这个 0002 就是 rca 地址
到这里我们分析完了 MMC 的核心层。
linux-2.6.2x的mmc驱动与linux-2.6.1x的mmc驱动的区别
在linux-2.6.2x中,mmc驱动用到的block_device_operations结构已重新定义,请看:
linux-2.6.1x:
struct block_device_operations {
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
struct module *owner;
};
linux-2.6.2x
struct block_device_operations {
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
int (*ioctl) (struct inode *, struct file *, unsigned, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned, unsigned long);
long (*compat_ioctl) (struct file *, unsigned, unsigned long);
int (*direct_access) (struct block_device *, sector_t, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
};
注意到新版本的block驱动接口结构增加了gntgeo成员,使调用者可以直接调用此函数获得设备的几何结构。
工作流程:
mmc驱动主要文件包括
drivers/mmc/card/block.c
drivers/mmc/card/queue.c
drivers/mmc/core/core.c
drivers/mmc/core/host.c
drivers/mmc/core/
内核启动时,首先执行core/core.c的mmc_init,注册mmc、sd总线,以及一个host class设备。接着执行card/block.c中,申请一个块设备。
数据结构:
mmc总线操作相关函数,由于mmc卡支持多种总数据线,如SPI、SDIO、8LineMMC,而不同的总线的操作控制方式不尽相同,所以通过此结构与相应的总线回调函数相关联。
//总线操作结构
struct mmc_bus_ops {
void (*remove)(struct mmc_host *);
void (*detect)(struct mmc_host *);
int (*sysfs_add)(struct mmc_host *, struct mmc_card *card);
void (*sysfs_remove)(struct mmc_host *, struct mmc_card *card);
void (*suspend)(struct mmc_host *);
void (*resume)(struct mmc_host *);
};
// mmc卡的总线操作 core/mmc.c
static const struct mmc_bus_ops mmc_ops = {
.remove = mmc_remove,
.detect = mmc_detect,
.sysfs_add = mmc_sysfs_add,
.sysfs_remove = mmc_sysfs_remove,
.suspend = mmc_suspend,
.resume = mmc_resume,
};
// sd卡的总线操作 core/sd.c
static const struct mmc_bus_ops mmc_sd_ops = {
.remove = mmc_sd_remove,
.detect = mmc_sd_detect,
.sysfs_add = mmc_sd_sysfs_add,
.sysfs_remove = mmc_sd_sysfs_remove,
.suspend = mmc_sd_suspend,
.resume = mmc_sd_resume,
};
// sdio的总线操作 core/sdio.c
static const struct mmc_bus_ops mmc_sdio_ops = {
.remove = mmc_sdio_remove,
.detect = mmc_sdio_detect,
};
关于总线操作的函数:
.detect,驱动程序经常需要调用此函数去检测mmc卡的状态,具体实现是发送CMD13命令,并读回响应,如果响应错误,则依次调用.remove、detach_bus来移除卡及释放总线。
总体架构:
kernel启动时,先后执行mmc_init()及mmc_blk_init(),以对mmc设备及mmc块模块进行初始化。
然后在挂载mmc设备驱动时,执行驱动程序中的xx_mmc_probe(),检测host设备中挂载的sd设备。此时probe函数会创建一个host设备,然后开启一个延时任务mmc_rescan()。
驱动挂载成功后,mmc_rescan()函数被执行,然后对卡进行初始化(步骤后面详细讲述)。
假如扫描到总线上挂有有效的设备,就调用相对应的函数把设备装到系统中,mmc_attach_sdio()、mmc_attach_sd()、mmc_attach_mmc()这三个函数分别是装载sdio设备,sd卡和mmc卡的。
在 sd卡中,驱动循环发送ACMD41、CMD55给卡,读取OCR寄存器,成功后,依次发送CMD2(读CID)、CMD3(得到RCA)、CMD9(读 CSD)、CMD7(选择卡)。后面还有几个命令分别是ACMD41&CMD51,使用CMD6切换一些功能,如切换到高速模式。
经过上述步骤,已经确定当前插入的卡是一张有效、可识别的存储卡。然后调用mmc_add_card()把存储卡加到系统中。正式与系统驱动连接在一起。
卡设备加到系统中后,通知mmc块设备驱动。块设备驱动此时调用probe函数,即mmc_blk_probe()函数,mmc_blk_probe()首先分配一个新的mmc_blk_data结构变量,然后调用mmc_init_queue,初始化blk队列。然后建立一个线程 mmc_queue_thread()。
mmc_rescan:mmc_rescan()函数是在驱动装载的时候,由驱动xx_mmc_probe()调用 mmc_alloc_host()时启动的一个延时任务。 xx_mmc_probe()->mmc_alloc_host()->INIT_DELAYED_WORK(&host->detect, mmc_rescan);
当
core部分
1、取得总线
2、检查总线操作结构指针bus_ops,如果为空,则重新利用各总线对端口进行扫描,检测顺序依次为:SDIO、Normal SD、MMC。当检测到相应的卡类型后,就使用mmc_attach_bus()把相对应的总线操作与host连接起来。
void mmc_attach_bus(struct mmc_host *host, const struct mmc_bus_ops *ops)
{
...
host->bus_ops = ops;
...
}
3、初始化卡接以下流程初始化:
a、发送CMD0使卡进入IDLE状态
b、发送CMD8,检查卡是否SD2.0。SD1.1是不支持CMD8的,因此在SD2.0 Spec中提出了先发送CMD8,如响应为无效命令,则卡为SD1.1,否则就是SD2.0(请参考SD2.0 Spec)。
c、发送CMD5读取OCR寄存器。
d、发送ACMD55、CMD41,使卡进入工作状态。MMC卡并不支持ACMD55、CMD41,如果这步通过了,则证明这张卡是SD卡。
e、如果d步骤错误,则发送CMD1判断卡是否为MMC。SD卡不支持CMD1,而MMC卡支持,这就是SD和MMC类型的判断依据。
f、如果ACMD41和CMD1都不能通过,那这张卡恐怕就是无效卡了,初始化失败。