51@@linux_sd卡驱动分析
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
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及数据传输的实现
}
接下来看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 start2bits*/
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 of4.*/
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 of4*/
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);
return0;
}
注一:这个函数先调用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,
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 for2.0card*/
*...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;
}
蓝色部分是遵照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,。