MMC 子系统之sdio wifi.docx
《MMC 子系统之sdio wifi.docx》由会员分享,可在线阅读,更多相关《MMC 子系统之sdio wifi.docx(23页珍藏版)》请在冰豆网上搜索。
![MMC 子系统之sdio wifi.docx](https://file1.bdocx.com/fileroot1/2023-7/10/af4d0a45-2e10-425d-88a8-2c6c54766034/af4d0a45-2e10-425d-88a8-2c6c547660341.gif)
MMC子系统之sdiowifi
MMC子系统之sdiowifi
(1)2014-05-0117:
21:
46
分类:
LINUX
原文地址:
MMC子系统之sdiowifi
(1)作者:
书笙拓荒
为了理解sdio的wifi工作原理,学习一下linux3.0的mmc子系统;首先学习一下别人的博客,故引用文章一篇!
《MMC卡驱动分析》
最近花时间研究了一下MMC卡驱动程序,开始在网上找了很多关于MMC卡驱动的分析文章,但大都是在描述各个层,这对于初学者来讲帮助并不大,所以我就打算把自己的理解写下来,希望对大家有用。
个人觉得理解LINUX内核当中MMC/SD卡驱动程序构架是学习MMC卡驱动程序的重点,只有理解了它的基本框架或流程才能真正理解一个块设备驱动程序的写法,同时才能真正理解LINUX设备驱动模型是如何发挥作用的。
一.需要的基础知识:
1. LINUX一般设备驱动框架。
2. 块设备驱动程序的基本构架
3. LINUX设备驱动模型。
二.驱动程序分析
首先,来明确一下我们需要分析的文件。
下面的文件均来自linux-2.6.24源码,我们重点是分析驱动程序的基本构架,所以不同内核版本的差异并不是很大。
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层是不用知道的。
驱动程序层次图
好了,对这几个目录有一个大概认识以后,我们来看几个重要的数据结构:
structmmc_host用来描述卡控制器
structmmc_card用来描述卡
structmmc_driver用来描述mmc卡驱动
structmmc_host_ops用来描述卡控制器操作集,用于从主机控制器层向core层注册操作函数,从而将core层与具体的主机控制器隔离。
也就是说core要操作主机控制器,就用这个ops当中给的函数指针操作,不能直接调用具体主控制器的函数。
第一阶段:
从s3cmci_init开始往下看
staticint__inits3cmci_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:
staticstructplatform_drivers3cmci_driver_2410={
.driver.name ="s3c2410-sdi",
.probe =s3cmci_probe_2410,
.remove =s3cmci_remove,
.suspend =s3cmci_suspend,
.resume =s3cmci_resume,
};
我们到s3cmci_probe_2410函数中看,还是干脆直接看s3cmci_probe算了:
staticints3cmci_probe(structplatform_device*pdev,intis2440)//来自/host/s3cmci.c
{
structmmc_host *mmc;
structs3cmci_host *host;
intret;
……
mmc=mmc_alloc_host(sizeof(structs3cmci_host),&pdev->dev);
if(!
mmc){
ret=-ENOMEM;
gotoprobe_out;
}
……
mmc->ops =&s3cmci_ops;
……
ret=mmc_add_host(mmc);
if(ret){
dev_err(&pdev->dev,"failedtoaddmmchost.\n");
gotofree_dmabuf;
}
……
platform_set_drvdata(pdev,mmc);
return0;
……
}
这个函数很长,做的事件也很多,但我们关心的整个驱动的构架/流程,所以过滤掉一些细节的东西,只看2个最重要的函数:
mmc_alloc_host、mmc_add_host。
函数命名已经很形象了,前者是申请一个mmc_host,而后者是添加一个mmc_host。
中间还有一个操作,就是给mmc的ops 成员赋上了s3cmci_ops这个值。
申请mmc_host当然很简单,就是申请一个结构体(我们暂且这样认为,因为他里面还做的其它事情,后面会看到),而添加又是添加到哪里去呢?
看mmc_add_host函数:
intmmc_add_host(structmmc_host*host)//来自core/host.c
{
interr;
……
err=device_add(&host->class_dev);
if(err)
returnerr;
mmc_start_host(host);
return0;
}
很简单,就是增加了一个device,然后就调用mmc_start_host了,那就先跳过device_add这个动作,来看mmc_start_host:
voidmmc_start_host(structmmc_host*host)//来自/host/core.c
{
mmc_power_off(host); //掉电一下
mmc_detect_change(host,0); //?
?
?
}
看上去只有两行代码,不过浓缩才是精华,mmc_power_off(host)光看名子都知道是在干什么,先跳过,来看mmc_detect_change,那么它到底干了些什么呢?
看一下就知道了:
voidmmc_detect_change(structmmc_host*host,unsignedlongdelay) //core/core.c
{
mmc_schedule_delayed_work(&host->detect,delay);
}
staticintmmc_schedule_delayed_work(structdelayed_work*work,unsignedlongdelay)
{
returnqueue_delayed_work(workqueue,work,delay);
}
mmc_detect_change又跳了一下,最后调用了queue_delayed_work,不知道这个函数功能的去查一下〈〈LDD3〉〉和〈〈深入理解LINUX内核〉〉,这几个代码告诉我们在workqueue这个工作队列当中添加一个延迟的工作任务,而这个工作任务就是由host->detect来描述的,在随后的delay个jiffies后会有一个记录在host->detect里面的函数被执行,那么到这里s3cmci_probe这个函数算是结束了,但事情还没有完,workqueue这个工作队列还在忙,不一会儿它就会调用host->detect里面那个函数,这个函数到底是哪个函数,到底是用来干什么的呢?
好像没有看到,detect包含在host里面,那估计是在刚才那个申请的地方设置的那个函数,回过头来看一下mmc_alloc_host:
structmmc_host*mmc_alloc_host(intextra,structdevice*dev) //来自core/host.c
{
structmmc_host*host;
host=kzalloc(sizeof(structmmc_host)+extra,GFP_KERNEL);
if(!
host)
returnNULL;
INIT_DELAYED_WORK(&host->detect,mmc_rescan);
returnhost;
}
如果你看了queue_delayed_work这个函数功能介绍,相信对INIT_DELAYED_WORK也不会陌生了吧。
不废话了,来看mmc_rescan:
//来自core/host.c
voidmmc_rescan(structwork_struct*work) ////来自core/host.c
{
structmmc_host*host= container_of(work,structmmc_host,detect.work);
u32ocr;
interr;
……
/*detectanewlyinsertedcard*/
……
/*
*FirstwesearchforSDIO...
*/
err=mmc_send_io_op_cond(host,0,&ocr);
if(!
err){
if(mmc_attach_sdio(host,ocr))
mmc_power_off(host);
gotoout;
}
/*
*...thennormalSD...
*/
err=mmc_send_app_op_cond(host,0,&ocr);
if(!
err){
if(mmc_attach_sd(host,ocr))
mmc_power_off(host);
gotoout;
}
/*
*...andfinallyMMC.
*/
err=mmc_send_op_cond(host,0,&ocr);
if(!
err){
if(mmc_attach_mmc(host,ocr))
mmc_power_off(host);
gotoout;
}
mmc_release_host(host);
mmc_power_off(host);
out:
if(host->caps&MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect,HZ);
}
浏览一个这个函数,看看函数名,再看看注释,知道什么了吗?
它是在检测是不是有卡插入了卡控制器,如果有卡挺入就要采取相应的行动了。
这里要明白一点,我们平时用的SD/MMC卡就是一个卡,如果要操作它得用SD/MMC卡控制器才行,所以可以看到有structmmc_card,structmmc_host的区分。
到这里了,来回忆一下s3cmci_probe这个函数做的事情,大概就是准备一个mmc_host结构,然后添加一个主控制器设备到内核,最后又调用了一下mmc_rescan来检测是不是有卡插入了。
如果有卡插入了还好,可以去操作卡了,那如果没有卡插入呢?
mmc_rescan不是白调用了一次吗?
是啊,的确是白调用了一次。
可是卡插入时为什么PC还是能检测到呢?
看来卡检测的动作不光是在probe的最后一步做了一次,其它地方也有做。
卡插入一般都是人为地随时去插入的,像这种情况一般都是会用中断机制去提供系统有外来侵入,然后再去采取行动。
SD/MMC卡也的确是这样做的,找来找去,发现在s3cmci_probe里面注册了一个中断函数s3cmci_irq_cd(函数名的意思应该是irqcarddetect),就是这个了,看看这个函数先:
staticirqreturn_ts3cmci_irq_cd(intirq,void*dev_id) //host/s3cmci.c
{
structs3cmci_host*host=(structs3cmci_host*)dev_id;
mmc_detect_change(host->mmc,msecs_to_jiffies(500));
returnIRQ_HANDLED;
}
看到这个函数想都不用想,直接跳到mmc_rescan里面去看就行了。
前面已经知道了mmc_rescan里面就是在检测卡是不是插入了,既然卡随时插入我们都能检测到了,那就来看卡插入后都做了些什么动作吧。
第二阶段:
mmc_rescan里面既要检测sd卡,又要检测mmc卡的,我们就照着一个往下走,假定有个人插入了MMC卡,那就应该走下面这几行:
err=mmc_send_op_cond(host,0,&ocr);
if(!
err){
if(mmc_attach_mmc(host,ocr))
mmc_power_off(host);
gotoout;
}
mmc_send_op_cond这个函数据说是读了一下卡的什么值,这个值是什么意义我也不清楚,这就像检测FLASH时读FLASH的ID一样,网卡也是这样的,不用管这个值的意义了,只要知道它能标识是一个MMC卡插入就行了。
如果取这个值没有错误的话就得进mmc_attach_mmc了:
/*
*StartingpointforMMCcardinit.
*/
intmmc_attach_mmc(structmmc_host*host,u32ocr) //core/mmc.c
{
interr;
……
mmc_attach_bus_ops(host); //这个与总线的电源管理有关,暂时跳过
……
/*
*Detectandinitthecard.
*/
err=mmc_init_card(host,host->ocr,NULL);
if(err)
gotoerr;
……
mmc_release_host(host);
err=mmc_add_card(host->card);
if(err)
gotoremove_card;
return0;
remove_card:
……
err:
……
returnerr;
}
还是找几个关键函数来看mmc_init_card从函数名来看就是初始化一个card,这个card就用structmmc_card结构来描述,然后又调用mmc_add_card将卡设备添加到了内核,先来看mmc_init_card都做了些什么事情:
staticintmmc_init_card(structmmc_host*host,u32ocr,
structmmc_card*oldcard)
{
structmmc_card*card;
interr;
u32cid[4];
unsignedintmax_dtr;
……
/*
*Allocatecardstructure.
*/
card=mmc_alloc_card(host,&mmc_type);
if(IS_ERR(card)){
err=PTR_ERR(card);
gotoerr;
}
card->type=MMC_TYPE_MMC;
card->rca=1;
memcpy(card->raw_cid,cid,sizeof(card->raw_cid));
……
host->card=card;
return0;
free_card:
……
err:
……
returnerr;
}
将与硬件操作相关的全部删掉,最后对我们有用的也就这几行了mmc_alloc_card申请了一个structmmc_card结构,然后给card->type赋上MMC_TYPE_MMC,最后将card又赋给了host->card,这和具体硬件还是挺像的,因为一个主控制器一般就插一个卡,有卡时host->card有值,没有卡时host->card自己就是NULL了。
钻进mmc_alloc_card里面来看看:
/*
*AllocateandinitialiseanewMMCcardstructure.
*/
structmmc_card*mmc_alloc_card(structmmc_host*host,structdevice_type*type)
{