Linux mmc system
MMC、SD、SDIO的技术本质是一样的(使用相同的总线规范,等等),都是从MMC规范演化而
来;MMC强调的是多媒体存储(MM,MultiMedia),SD强调的是安全和数据保护(S,Secure);
SDIO是从SD演化出来的,强调的是接口(IO,Input/Output),不再关注另一端的具体形态(可以
是WIFI设备、Bluetooth设备、GPS等等)。
先查看下MMC的驱动框架,如下:
在Linux中MMC/SD卡的记忆体都当作块设备。MMC/SD设备驱动代码在/drivers/mmc/分别有card、
core和host三个文件夹,分析了这么多种驱动,MMC的代码结构是最简单的。
- card层 要把操作的数据以块设备的处理方式写到记忆体上或从记忆体上读取;
- core层 则是将数据以何种格式,何种方式在 MMC/SD主机控制器与MMC/SD卡的记忆体(即块设备)之间进行传递,这种格式、方式被称之为规范或协议,
- host层 该层的代码就是要动手实现的具体MMC/SD设备驱动了,包括RAM芯片中的SDI控制器(支持对MMC/SD卡的控制,俗称MMC/SD主机控制器)和SDI控制器与MMC/SD卡的硬件接口电路。
与其他的设备驱动一样,mmc的驱动也分为控制器驱动和设备器驱动,但是不同的是mmc的设备器驱动是统一的,在/drivers/mmc/card/里面已经实现了,不需要我们进行实现;mmc控制器驱动使用的也是platform总线来进行连接的,在arcm下面进行platform_device
的添加,在/drivers/mmc/host/下面进行platform_driver
的注册。
对于这platform_device
这边就不进行说明了,直接看platform_driver
的注册,根据不同的平台在/drivers/mmc/host/下有对应的文件,这边以sdhci-s3c.c为例:
static struct platform_driver sdhci_s3c_driver = {
.probe = sdhci_s3c_probe,
.remove = __devexit_p(sdhci_s3c_remove),
.driver = {
.owner = THIS_MODULE,
.name = "s3c-sdhci",
.pm = SDHCI_S3C_PMOPS,
},
};
static int __init sdhci_s3c_init(void)
{
return platform_driver_register(&sdhci_s3c_driver);
}
static void __exit sdhci_s3c_exit(void)
{
platform_driver_unregister(&sdhci_s3c_driver);
}
module_init(sdhci_s3c_init);
module_exit(sdhci_s3c_exit);
一样通过platform_driver_register
进行注册,然后就进入probe函数。
probe最重要的作用是host的注册,那么首先必须构造出一个host,这个host就是通过mmc_alloc_host
函数来构造出来的,初始化host的时钟,设置host的gpio等等其他参数初始化,最后通过mmc_add_host
函数来注册host,下面看下这两个函数的具体内容。
1.mmc_alloc_host
mmc_alloc_host()
位于/drivers/mmc/core/host.c
struct mmc_host *mmc_alloc_host(int extra, struct device *dev)
{
int err;
struct mmc_host *host;
if (!idr_pre_get(&mmc_host_idr, GFP_KERNEL))
return NULL;
host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
if (!host)
return NULL;
spin_lock(&mmc_host_lock);
err = idr_get_new(&mmc_host_idr, host, &host->index);
spin_unlock(&mmc_host_lock);
if (err)
goto free;
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);
mmc_host_clk_init(host);
spin_lock_init(&host->lock);
init_waitqueue_head(&host->wq);
INIT_DELAYED_WORK(&host->detect, mmc_rescan);
INIT_DELAYED_WORK_DEFERRABLE(&host->disable, mmc_host_deeper_disable);
#ifdef CONFIG_PM
host->pm_notify.notifier_call = mmc_pm_notify;
#endif
/*
* By default, hosts do not support SGIO or large requests.
* They have to set these according to their abilities.
*/
host->max_segs = 1;
host->max_seg_size = PAGE_CACHE_SIZE;
host->max_req_size = PAGE_CACHE_SIZE;
host->max_blk_size = 512;
host->max_blk_count = PAGE_CACHE_SIZE / 512;
return host;
free:
kfree(host);
return NULL;
}
mmc_alloc_host
的内容相对比较简单,如下:
- 14行是内核的高效搜索树,将host->index与host结构相关联,方便以后查找。
- 24-27行主要是初始化host->class_dev,这个日后会通过device_register注册进系统。
- 35行前面已经在这个等待队列上花了不少笔墨,主要是同步对host资源的竞争的。
- 45-50行这些个都是设置host的一些属性的,是与block.c中请求队列的设置相对应的。
2.mmc_add_host
mmc_add_host()
位于/drivers/mmc/core/host.c。
它的功能就是通过device_add函数将设备注册进linux设备模型,最终的结果就是在sys/bus/platform/devices目录下能见到s3c.sdhci.1,s3c.sdhci.2等设备节点。
int mmc_add_host(struct mmc_host *host)
{
int err;
WARN_ON((host->caps & MMC_CAP_SDIO_IRQ) &&
!host->ops->enable_sdio_irq);
err = device_add(&host->class_dev);
if (err)
return err;
led_trigger_register_simple(dev_name(&host->class_dev), &host->led);
#ifdef CONFIG_DEBUG_FS
mmc_add_host_debugfs(host);
#endif
mmc_start_host(host);
register_pm_notifier(&host->pm_notify);
return 0;
}
调用了mmc_start_host()
位于/drivers/mmc/core/core.c
void mmc_start_host(struct mmc_host *host)
{
mmc_power_off(host);
mmc_detect_change(host, 0);
}
调用了mmc_detect_change()
位于/drivers/mmc/core/core.c
void mmc_detect_change(struct mmc_host *host, unsigned long delay)
{
#ifdef CONFIG_MMC_DEBUG
unsigned long flags;
spin_lock_irqsave(&host->lock, flags);
WARN_ON(host->removed);
spin_unlock_irqrestore(&host->lock, flags);
#endif
mmc_schedule_delayed_work(&host->detect, delay);
}
这边调用了mmc_schedule_delayed_work(&host->detect, delay);
这句话,与mmc_alloc_host()
函数里面的INIT_DELAYED_WORK(&host->detect, mmc_rescan);
是相呼应的,一个从创建一个运行,所以会调用INIT_DELAYED_WORK
里面的mmc_rescan
函数,位于/drivers/mmc/core/core.c
该函数就是扫描卡是否插入的程序入口,如下:
void mmc_rescan(struct work_struct *work)
{
static const unsigned freqs[] = { 400000, 300000, 200000, 100000 };
struct mmc_host *host =
container_of(work, struct mmc_host, detect.work);
int i;
if (host->rescan_disable)
return;
mmc_bus_get(host);
/*
* if there is a _removable_ card registered, check whether it is
* still present
*/
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
&& !(host->caps & MMC_CAP_NONREMOVABLE))
host->bus_ops->detect(host);
/*
* Let mmc_bus_put() free the bus/bus_ops if we've found that
* the card is no longer present.
*/
mmc_bus_put(host);
mmc_bus_get(host);
/* if there still is a card present, stop here */
if (host->bus_ops != NULL) {
mmc_bus_put(host);
goto out;
}
/*
* Only we can add a new handler, so it's safe to
* release the lock here.
*/
mmc_bus_put(host);
if (host->ops->get_cd && host->ops->get_cd(host) == 0)
goto out;
mmc_claim_host(host);
for (i = 0; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min)))
break;
if (freqs[i] <= host->f_min)
break;
}
mmc_release_host(host);
out:
if (host->caps & MMC_CAP_NEEDS_POLL)
mmc_schedule_delayed_work(&host->detect, HZ);
}
插入SD卡,mmc_rescan
扫描SD总线上是否存在SD卡,具体的实现方法就是通过向SD卡上电,看是否能成功,调用了mmc_rescan_try_freq()
函数,位于/drivers/mmc/core/core.c
static int mmc_rescan_try_freq(struct mmc_host *host, unsigned freq)
{
host->f_init = freq;
#ifdef CONFIG_MMC_DEBUG
pr_info("%s: %s: trying to init card at %u Hz\n",
mmc_hostname(host), __func__, host->f_init);
#endif
mmc_power_up(host);
/*
* Some eMMCs (with VCCQ always on) may not be reset after power up, so
* do a hardware reset if possible.
*/
mmc_hw_reset_for_init(host);
/*
* sdio_reset sends CMD52 to reset card. Since we do not know
* if the card is being re-initialized, just send it. CMD52
* should be ignored by SD/eMMC cards.
*/
sdio_reset(host);
mmc_go_idle(host);
mmc_send_if_cond(host, host->ocr_avail);
/* Order's important: probe SDIO, then SD, then MMC */
if (!mmc_attach_sdio(host))
return 0;
if (!mmc_attach_sd(host))
return 0;
if (!mmc_attach_mmc(host))
return 0;
mmc_power_off(host);
return -EIO;
}
里面对MMC进行上点,复位等操作,然后就调用sdio/sd/mmc对应的mmc_attach
函数来绑定:
-
mmc_attach_sdio
位于/drivers/mmc/core/sdio.c中; -
mmc_attach_sd
位于/drivers/mmc/core/sd.c中; -
mmc_attach_mmc
位于/drivers/mmc/core/mmc.c中;
attach里面实现sdio/sd/mmc的的内容有点多,不过也很直观,最终结果就是实现host的注册。对于这一部分的内容可以查看xieweihua2012的博客,里面对mmc_attach_sdio
有比较详细的分析,其他两个类似。
卡设备加到系统中后,通知mmc块设备驱动。块设备驱动此时调用probe函数,即mmc_blk_probe()
函数,mmc_blk_probe()
首先分配一个新的mmc_blk_data
结构变量,然后调用mmc_init_queue
,初始化blk队列,然后建立一个线程mmc_queue_thread()
然后就可以进行传输命令和数据了。
更多内容可以查看zqixiao_09的博客,里面有很多细节的知识。
Linux mmc system的分析就到这边,有感悟时会持续会更新。
注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。