基于S3C2440的嵌入式Linux驱动——MMC/SD子系统解读(一)
扫描二维码
随时随地手机看文章
本文的内容基于如下硬件和软件平台:
目标平台:TQ2440
CPU:s3c2440
内核版本:3.12.5
基于SD规范4.10,即《SD Specifications Part 1 Physical Layer Simplified Specification Version 4.10》。
一、MMC子系统构架待写。。。
待写。。。
首先看看子系统是如何初始化的,完成哪些工作。
代码位于linux/drivers/mmc/core/core.c。
staticint__initmmc_init(void)
{
intret;
/*创建一个工作队列*/
workqueue=alloc_ordered_workqueue("kmmcd",0);
if(!workqueue)
return-ENOMEM;
/*注册mmc总线,总线提供probe方法
并直接在内部调用驱动probe方法*/
ret=mmc_register_bus();
if(ret)
gotodestroy_workqueue;
/*注册名为mmc_host的类*/
ret=mmc_register_host_class();
if(ret)
gotounregister_bus;
/*注册sdio总线,总线提供probe方法
并直接在内部调用驱动probe方法*/
ret=sdio_register_bus();
if(ret)
gotounregister_host_class;
return0;
unregister_host_class:
mmc_unregister_host_class();
unregister_bus:
mmc_unregister_bus();
destroy_workqueue:
destroy_workqueue(workqueue);
returnret;
}
代码首先注册了一个工作队列,这个工作队列将用于扫描sd卡设备。我们会在后面进行说明。
工作对类已内核线程的形式运行,可以用ps命令看到名为[kmmcd]的内核线程。
接着注册了两条名为mmc和sdio的总线,以及一个名为mmc_host的类。具体代码如下:
staticstructbus_typemmc_bus_type={
.name="mmc",
.dev_attrs=mmc_dev_attrs,
.match=mmc_bus_match,
.uevent=mmc_bus_uevent,
.probe=mmc_bus_probe,
.remove=mmc_bus_remove,
.shutdown=mmc_bus_shutdown,
.pm=&mmc_bus_pm_ops,
};
intmmc_register_bus(void)
{
returnbus_register(&mmc_bus_type);
}
staticstructclassmmc_host_class={
.name="mmc_host",
.dev_release=mmc_host_classdev_release,
};
intmmc_register_host_class(void)
{
returnclass_register(&mmc_host_class);
}
staticstructbus_typesdio_bus_type={
.name="sdio",
.dev_attrs=sdio_dev_attrs,
.match=sdio_bus_match,
.uevent=sdio_bus_uevent,
.probe=sdio_bus_probe,
.remove=sdio_bus_remove,
.pm=SDIO_PM_OPS_PTR,
};
intsdio_register_bus(void)
{
returnbus_register(&sdio_bus_type);
}
staticstructclassmmc_host_class={
.name="mmc_host",
.dev_release=mmc_host_classdev_release,
};
intmmc_register_host_class(void)
{
returnclass_register(&mmc_host_class);
}
熟悉Linux的设备驱动模型的同学对这些肯定非常熟悉。总线和类的注册只是调用了相应的接口,这些就不再赘述了。
其次,sdio总线不是我们关心的。我们只关心mmc总线。首先来看看mmc总线的match方法:
代码位于linux/drivers/mmc/core/bus.c。
/*
*ThiscurrentlymatchesanyMMCdrivertoanyMMCcard-drivers
*themselvesmakethedecisionwhethertodrivethiscardintheir
*probemethod.
*/
staticintmmc_bus_match(structdevice*dev,structdevice_driver*drv)
{
return1;
}
match返回居然直接返回了1。这表示任意的驱动都能和mmc卡设备成功匹配。
从注释中我们也能看出,驱动的probe方法将会决定驱动是否能真正的匹配这个mmc卡设备。
熟悉设备驱动模型的可能知道,随着match返回1表示匹配成功后,将会调用总线提供的probe方法。接着我们来看下mmc总线的probe方法。
代码位于linux/drivers/mmc/core/bus.c。
staticintmmc_bus_probe(structdevice*dev)
{
structmmc_driver*drv=to_mmc_driver(dev->driver);
structmmc_card*card=mmc_dev_to_card(dev);
returndrv->probe(card);
}
从这里我们可以看到在mmc的probe方法中直接调用了驱动probe方法,这也验证了刚才注释中所说的话。
从上面分析可以看出,子系统初始化代码仅仅注册了两条总线和一个类,并建立了一个工作队列。
MMC核心层要和SD卡设备进行通信,为了完成这一个工作需要将CMD或者ACMD命令通过MMC/SD控制器发送给SD卡。
那么MMC核心层如何将通信的数据包交给MMC/SD控制器,并让后者去发送呢?
MMC通过函数mmc_wait_for_req完成这个工作,我们来看下这个函数。
4.1 mmc_wait_for_req函数下列代码位于linux/drivers/mmc/core/core.c。
/**
*mmc_wait_for_req-startarequestandwaitforcompletion
*@host:MMChosttostartcommand
*@mrq:MMCrequesttostart
*
*StartanewMMCcustomcommandrequestforahost,andwait
*forthecommandtocomplete.Doesnotattempttoparsethe
*response.
*/
voidmmc_wait_for_req(structmmc_host*host,structmmc_request*mrq)
{
__mmc_start_req(host,mrq);
mmc_wait_for_req_done(host,mrq);
}
EXPORT_SYMBOL(mmc_wait_for_req);
通过注释可以发现,该函数会阻塞并等待request的完成。
该函数分两步走,第一步调用__mmc_start_req发送命令,第二部调用 mmc_wait_for_req_done等待命令完成。
分别来看下这两个函数 :
staticint__mmc_start_req(structmmc_host*host,structmmc_request*mrq)
{
/*初始化completion,并设置done方法*/
init_completion(&mrq->completion);
mrq->done=mmc_wait_done;
/*如果mmc已经被拔出,设置错误并返回错误*/
if(mmc_card_removed(host->card)){
mrq->cmd->error = -ENOMEDIUM;