一、MMC简介

eMMC使用BGA封装了Nand Flash和Flash控制器,向外提供MMC标准接口,其结构图如下图所示(图来自《eMMC5.1官方标准协议》)。eMMC的出现使得手机厂商就能专注于产品开发的其它部分,并缩短向市场推出产品的时间。

对于我们来说,eMMC就是在Nand Flash上添加负责ECC、管理坏块等功能的控制器。

在内核中,使用MMC子系统统一管理MMC、SD、SDIO等设备。从MMC规范发布至今,基于不同的考量(物理尺寸、数据位宽和clock频率等),进化出了MMC、SD、microSD、SDIO、eMMC等不同的规范。其本质是一样的,这也是内核将它们统称为MMC的原因。

和MTD相同,MMC驱动也有一个单独的文件夹,位于drivers/mmc目录下,目录下的三个目录card、core、host对应MMC驱动的三个层次。

1. card:区块层,用于实现卡的块设备驱动。

2. core:核心层,抽象了卡的设备驱动的函数。

3. host:主机控制器层,依赖于不同平台的控制器操作函数。

二、MMC框架分析

为了方便分析框架,我们需要分析host目录,读者可在此目录下任意选择一个单板驱动文件进行分析,我选择的是s3cmci.c文件。

文件链接:

https://files.cnblogs.com/files/Lioker/19_emmc.zip

首先来看它的入口函数:

 static int __init s3cmci_init(void)
{
return platform_driver_register(&s3cmci_driver);
}

我们进入platform_driver的probe函数中,看看它如何初始化。

 static int __devinit s3cmci_probe(struct platform_device *pdev)
{
struct s3cmci_host *host;
struct mmc_host *mmc;
...
/* 分配mmc_host */
mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
... /* 省略阶段做的是设置s3cmci_host成员和gpio管脚 */
request_irq(host->irq_cd, s3cmci_irq_cd, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, DRIVER_NAME, host));
...
/* 设置mmc_host */
mmc->ops = &s3cmci_ops;
mmc->ocr_avail = MMC_VDD_32_33 | MMC_VDD_33_34;
#ifdef CONFIG_MMC_S3C_HW_SDIO_IRQ
mmc->caps = MMC_CAP_4_BIT_DATA | MMC_CAP_SDIO_IRQ;
#else
mmc->caps = MMC_CAP_4_BIT_DATA;
#endif
mmc->f_min = host->clk_rate / (host->clk_div * );
mmc->f_max = host->clk_rate / host->clk_div; if (host->pdata->ocr_avail)
mmc->ocr_avail = host->pdata->ocr_avail; mmc->max_blk_count = ;
mmc->max_blk_size = ;
mmc->max_req_size = * ;
mmc->max_seg_size = mmc->max_req_size; mmc->max_segs = ;
...
/* 添加mmc_host */
ret = mmc_add_host(mmc);
...
platform_set_drvdata(pdev, mmc);
...
return ret;
}

其中,

1. mmc_alloc_host()函数调用关系如下:

mmc = mmc_alloc_host(sizeof(struct s3cmci_host), &pdev->dev);
-> host = kzalloc(sizeof(struct mmc_host) + extra, GFP_KERNEL);
/* 初始化工作队列 */
-> INIT_DELAYED_WORK(&host->detect, mmc_rescan);

2. mmc_add_host()函数调用关系如下:

mmc_add_host(mmc);
-> device_add(&host->class_dev);
-> mmc_start_host(host);
-> mmc_power_off(host); /* 掉电刷新 */
-> mmc_detect_change(host, );
-> mmc_schedule_delayed_work(&host->detect, delay);
/* 在工作队列中添加一个延迟的工作任务host->detect */
-> return queue_delayed_work(workqueue, work, delay);

mmc_add_host()函数最终会调用mmc_alloc_host()初始化工作队列的mmc_rescan()函数。此函数用于检测是否有卡插入了卡控制器。

 void mmc_rescan(struct work_struct *work)
{
static const unsigned freqs[] = { , , , };
struct mmc_host *host = container_of(work, struct mmc_host, detect.work);
int i;
...
mmc_bus_get(host); /* 检测卡是否仍旧存在 */
if (host->bus_ops && host->bus_ops->detect && !host->bus_dead
&& !(host->caps & MMC_CAP_NONREMOVABLE))
host->bus_ops->detect(host); /* If the card was removed the bus will be marked
* as dead - extend the wakelock so userspace
* can respond */
if (host->bus_dead)
extend_wakelock = ; /*
* 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); /* 如果卡仍存在, stop here */
if (host->bus_ops != NULL) {
mmc_bus_put(host);
mmc_set_drv_state(e_inserted,host);//ly
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) == ){
mmc_set_drv_state(e_removed,host);
goto out;
}
mmc_claim_host(host);
for (i = ; i < ARRAY_SIZE(freqs); i++) {
if (!mmc_rescan_try_freq(host, max(freqs[i], host->f_min))) {
extend_wakelock = true;
break;
}
if (freqs[i] <= host->f_min)
break;
}
mmc_release_host(host); out:
if (extend_wakelock)
wake_lock_timeout(&host->detect_wake_lock, HZ / );
else
wake_unlock(&host->detect_wake_lock);
if (host->caps & MMC_CAP_NEEDS_POLL) {
wake_lock(&host->detect_wake_lock);
mmc_schedule_delayed_work(&host->detect, HZ);
}
}

probe()函数所做的有以下几点:

1. 分配、设置并添加mmc_host

2. 检测卡是否插入了卡控制器

如果在probe()函数执行时,卡并没有插入呢?也就是除了probe()函数,一定会有其他函数最终调用了mmc_rescan()函数。现在我们需要重新看一遍probe()函数,它注册了一个中断函数s3cmci_irq_cd()。

 static irqreturn_t s3cmci_irq_cd(int irq, void *dev_id)
{
struct s3cmci_host *host = (struct s3cmci_host *)dev_id;
dbg(host, dbg_irq, "card detect\n"); mmc_detect_change(host->mmc, msecs_to_jiffies()); return IRQ_HANDLED;
}

之前分析过,mmc_detect_change(host->mmc, msecs_to_jiffies(500));函数最终会调用mmc_rescan()函数。

此时如果有卡插入了,会调用到mmc_rescan()函数,此函数调用关系如下:

mmc_rescan(struct work_struct *work)
-> mmc_rescan_try_freq(host, max(freqs[i], host->f_min))
-> mmc_attach_sdio(host) /* 检测卡的类型 */
-> mmc_attach_sd(host)
-> mmc_attach_mmc(host)
-> mmc_send_op_cond(host, , &ocr); /* 发送卡的ID */
-> mmc_init_card(host, host->ocr, NULL); /* 初始化mmc_card */
-> card = mmc_alloc_card(host, &mmc_type);
-> device_initialize(&card->dev);
-> card->dev.bus = &mmc_bus_type; /* 设置总线为mmc_bus_type */
-> card->type = MMC_TYPE_MMC; /* 设置card结构体 */
-> mmc_release_host(host);
-> mmc_add_card(host->card); /* 添加卡mmc_card */
-> device_add(&card->dev);
-> mmc_claim_host(host); /* 使能host */

在添加mmc_card调用device_add()函数时,mmc_bus_type总线会调用match()函数匹配设备驱动,如果匹配成功会调用总线的probe()函数或设备驱动的probe()函数。

 static int mmc_bus_match(struct device *dev, struct device_driver *drv)
{
return ; /* 匹配永远成功 */
}

probe()函数最终会调用mmc_driver的probe()函数。

 static int mmc_bus_probe(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev); return drv->probe(card);
}

在SI4的Project中搜索struct mmc_driver,发现只有block.c文件有对此结构体的定义。

现在我们来查看mmc_driver的probe()函数。

static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md, *part_md;
int err;
char cap_str[];
...
md = mmc_blk_alloc(card); err = mmc_blk_set_blksize(md, card);
...
mmc_set_drvdata(card, md);
mmc_fixup_device(card, blk_fixups);
...
if (mmc_add_disk(md))
goto out; list_for_each_entry(part_md, &md->part, part) {
if (mmc_add_disk(part_md))
goto out;
}
return ; out:
mmc_blk_remove_parts(card, md);
mmc_blk_remove_req(md);
return err;
}

其中,

1. mmc_blk_alloc()函数调用关系如下:

md = mmc_blk_alloc(card);
-> md = mmc_blk_alloc_req(card, &card->dev, size, false, NULL);
-> md->disk = alloc_disk(perdev_minors);
-> ret = mmc_init_queue(&md->queue, card, &md->lock, subname);
-> mq->queue = blk_init_queue(mmc_request, lock);
-> set_capacity(md->disk, size);

2. mmc_add_disk()函数调用关系如下:

mmc_add_disk(md)
-> add_disk(md->disk);
-> device_create_file(disk_to_dev(md->disk), &md->force_ro);

这个mmc_driver底层做的与块设备驱动相同:

1. 分配、初始化请求队列,绑定请求队列和请求函数

2. 分配、设置并添加gendisk

3. 注册块设备驱动

队列函数为mmc_blk_issue_rq(),其调用关系如下:

mmc_blk_issue_rq
-> mmc_blk_issue_secdiscard_rq(mq, req);
-> mmc_blk_issue_discard_rq(mq, req);
-> mmc_blk_issue_flush(mq, req);
-> mmc_blk_issue_rw_rq(mq, req); /* 上面四个函数选一个执行 */
-> mmc_wait_for_req(card->host, &brq.mrq);
-> mmc_start_request(host, mrq);
-> host->ops->request(host, mrq); /* s3cmci.c中host->requset = s3cmci_request */

三、MMC驱动框架总结

1. 各个结构体作用:

struct mmc_card用于描述卡,struct mmc_driver用于描述卡驱动,sutrct mmc_host用于描述卡控制器,struct mmc_host_ops用于描述卡控制器操作函数。

2. 整体框架:

下一章  二十、网卡框架分析和虚拟网卡驱动和DM9621驱动分析

十九、eMMC驱动框架分析的更多相关文章

  1. uart驱动框架分析(二)uart_add_one_port

    作者:lizuobin (百问网论坛答疑助手) 原文: https://blog.csdn.net/lizuobin2/article/details/51801183 (所用开发板:mini2440 ...

  2. Linux USB驱动框架分析(2)【转】

    转自:http://blog.chinaunix.net/uid-23046336-id-3243543.html   看了http://blog.chinaunix.net/uid-11848011 ...

  3. Linux USB驱动框架分析 【转】

    转自:http://blog.chinaunix.net/uid-11848011-id-96188.html 初次接触与OS相关的设备驱动编写,感觉还挺有意思的,为了不至于忘掉看过的东西,笔记跟总结 ...

  4. linux驱动基础系列--linux spi驱动框架分析

    前言 主要是想对Linux 下spi驱动框架有一个整体的把控,因此会忽略某些细节,同时里面涉及到的一些驱动基础,比如平台驱动.设备模型等也不进行详细说明原理.如果有任何错误地方,请指出,谢谢! spi ...

  5. linux驱动基础系列--linux spi驱动框架分析(续)

    前言 这篇文章是对linux驱动基础系列--linux spi驱动框架分析的补充,主要是添加了最新的linux内核里设备树相关内容. spi设备树相关信息 如之前的文章里所述,控制器的device和s ...

  6. Linux USB驱动框架分析【转】

    转自:http://blog.csdn.net/jeffade/article/details/7701431 Linux USB驱动框架分析(一) 初次接触和OS相关的设备驱动编写,感觉还挺有意思的 ...

  7. 【原创】Linux PCI驱动框架分析(二)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  8. 【原创】Linux PCI驱动框架分析(三)

    背 景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本 ...

  9. camera驱动框架分析(上)【转】

    转自:https://www.cnblogs.com/rongpmcu/p/7662738.html 前言 camera驱动框架涉及到的知识点比较多,特别是camera本身的接口就有很多,有些是直接连 ...

随机推荐

  1. PostgreSQL中定时job执行(pgAgent)

    PostgreSQL中定时job执行 业务分析 近期项目需要定期清理数据库中的多余数据,即每月1号删除指定表中一年以上的数据. 初步分析这种定时job可以使用一下两种技术实现: Linux的cront ...

  2. Barman安装及备份PostgreSQL

    barman特点 零数据丢失备份.保证用户在只有一台备份服务器的情况下达到零数据丢失. 与备份服务器合作.允许备份服务器在与主服务器的流式复制不可用时,从barman获取wal文件. 可靠的监控集成. ...

  3. MFC调用CEF实现单页面单COOKIE管理《转》

    cookie简单介绍 cookie存储了网站的一些很重要的信息,如用户身份信息.常用设置.设置地理位置等等各种信息.使用cef访问网站时,如果设置了CefSettings.cache_path参数,则 ...

  4. 数据缺失值的处理 | R包 - mice

    有些情况下缺失值会零星的分布在数据当中,这时去掉所有包含缺失值的样本就不行了,直接用0去填补缺失值也不行. 所以此时就应该用拟合的方法来填补缺失值. library(mice) init = mice ...

  5. oracle/mysql经典电子书籍pdf下载

    Oracle LZ写的书,深入结合oracle设计.优化/SQL优化.应用层架构与优化.大量生产案例,敬请期待... Oracle编程艺术 深入理解数据库体系结构(第3版) 链接:https://pa ...

  6. ZingChart 隐藏数据点

    正常情况下 zingChart 的数据点会显示到图表中,但是如果数据点很多的情况下,可能会让你无法准确的预测趋势,而且也不美观 在 js 配置中添加最多允许显示的数据点,超过这个值将不显示数据点 效果 ...

  7. flutter中使用shared_preferences的存储

    添加依赖 shared_preferences: ^+ 工具类 import 'dart:async'; import 'package:shared_preferences/shared_prefe ...

  8. flutter Switch组件 On/off 用于切换一个单一状态

    import 'package:flutter/material.dart'; class SwitchDemo extends StatefulWidget { @override _SwitchD ...

  9. Java回调机制在RPC框架中的应用示例

    完整源码: https://gitee.com/shiyanjun/x-callback-demo 应用场景描述: 服务提供者在项目启动时,创建并启动一个TCP服务器,然后将自己提供的所有服务注册到注 ...

  10. 【转】Python读取PDF文档,输出内容

    Python3读取pdf文档,输出内容(txt) from urllib.request import urlopen from pdfminer.pdfinterp import PDFResour ...