12-Linux mmc system

题图:gratisography

Linux mmc system

MMC、SD、SDIO的技术本质是一样的(使用相同的总线规范,等等),都是从MMC规范演化而
来;MMC强调的是多媒体存储(MM,MultiMedia),SD强调的是安全和数据保护(S,Secure);
SDIO是从SD演化出来的,强调的是接口(IO,Input/Output),不再关注另一端的具体形态(可以
是WIFI设备、Bluetooth设备、GPS等等)。

先查看下MMC的驱动框架,如下:

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相关教程,感谢您的查阅。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容