Linux kernel把mmc、sd以及sdio三者的驱动代码整合在一起,俗称mmc子系统。源码位于drivers/mmc下。其下有三个子目录,分别是:card、core、host,其中,card用于构建一个块设备作为上层与mmc子系统沟通的桥梁;core抽象了mmc、sd、sdio三者的通用操作;host则是各类平台上的host驱动代码,包括如TI Omap的omap_hsmmc,三星的s3cmci等。具体的SOC,会根据实际情况再次封装(比如rockchip采用新思designwave的IP),就重新封装为struct dw_mci。DesignWare是SoC/ASIC设计者最钟爱的设计IP库和验证IP库。它包括一个独立于工艺的、经验证的、可综合的虚拟微架构的元件集合,包括逻辑、算术、存储和专用元件系列,超过140个模块。

一、mmc子系统涉及总线(SD为例)

1.1 整体框架

①Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。设备树指定compatible属性与源码匹配后调用对应的probe函数来初始化对应平台的host硬件。

②Card驱动相应的driver和device挂载在mmc自己创建的虚拟总线mmc_bus_type下。该总线的结构体定义在driver/mmc/core/bus.c;因为是统一的协议,mmc_driver在内核已经写好并已注册:

1
2
3
driver/mmc/card/block.c
-->mmc_blk_init()
-->mmc_register_driver(&mmc_driver);

③SD卡中extended CSD寄存器中存储分区信息,上电检测到了SD卡,或者上电之后的热拔插操作,触发mmc_rescan查找检测到SD卡插入后,会通过读取该寄存器的值,获取mmc card的分区信息等,然后注册对应的mmc_card device,因mmc driver仅有一个,因此针对mmc bus而言其match函数直接返回1,表示匹配成功(因为是统一的协议),而不需要像spi/i2c总线的match接口那样对设备和驱动进行匹配检测;随后调用mmc_driver的probe函数(拔出卡则反之操作)。

④mmc_driver的probe函数针对该mmc card的每一个分区,均执行如下操作:

  1. 为每一个分区,创建对应的通用磁盘设备gendisk;
  2. 设置gendisk的fops为mmc_bdops;
  3. 为每一个通用磁盘设备,均创建request_queue及其处理方法;
  4. 为该通用磁盘设备创建mmc block块i/o请求机制;
  5. 将该分区的通用磁盘设备注册至块设备i/o,以便应用层可通过块设备访问该mmc card 分区;

1.2 设备-总线-驱动模型

在分析MMC子系统的设备-总线-驱动模型时,可以借助已经分析的i2c驱动模型、spi驱动模型的实现,来学习mmc子系统的驱动模型,通过与i2c驱动模型、spi驱动模型的对比,也可以加深我们对mmc子系统驱动模型的理解(从已知去学习未知,可提供我们的学习效率)。

我们知道针对复杂的设备,则需要借助控制器进行通信,而针对i2c、spi、mmc而言,均需要对应的控制器实现与该类设备的通信。而针对这三类控制器,其模块都进行了抽象。

i2c控制器抽象为i2c adapter、spi控制器抽象为spi master,而针对mmc控制器,则抽象为mmc host;而针对这三种类型设备的抽象,i2c设备抽象为i2c client、spi设备抽象为spi device、mmc device抽象为mmc card。

1.3 三类驱动模型的异同

①均为控制器创建了对应的class,用于将对应的控制器设备链接至对应的class上

②针对i2c adapter、spi master这两类而言,其驱动模型中均创建了对应的链表,将所有注册的控制器设备链接在一起,而mmc子系统并没有为mmc host创建类似的链表;

③I2c/spi设备不属于热插拔设备,因此在具体系统的板级文件或者设备树中,需要针对系统中存在的i2c/spi设备定义注册信息,从而在LINUX系统初始化时完成i2c/spi设备的注册;而针对mmc子系统其属于热插拔的,因此在系统初始化时不需要单独进行设备的注册;

④针对mmc子系统而言,针对mmc card的注册,mmc子系统提供了rescan接口,而该接口作为延迟工作队列的回调函数。针对mmc card而发起rescan扫描的时机如下:

  1. 在mmc host添加的时候,执行一次mmc card的扫描操作。
  2. 热拔插过程执行扫描。

这里大概分三个情况来描述热插拔识别流程:

  1. SOC带卡检测引脚,即mmc控制器部分带专门的中断寄存器位(MCI中断)
  2. SOC不带卡检测引脚,即用普通中断引脚做检测(即sdmmc_cd引脚)
  3. 没有专门的寄存器,也没有用cd的gpio中断,采用轮询POLL

二、mmc子系统函数调用流程(SD为例)

2.1 设备树mmc节点

关于SD卡的HOST最初始设备树节点在rk3399.dtsi

1
2
3
4
5
sdmmc: dwmmc@fe320000 {
compatible = "rockchip,rk3399-dw-mshc",
"rockchip,rk3288-dw-mshc";
............................
}

2.2 Host驱动流程

Host驱动相应的driver和device挂载在Linux内核内置的虚拟抽象总线platform_bus_type。我们设备树host device与driver匹配后调用对应的probe函数来初始化对应平台的host硬件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
drivers\mmc\host\dw_mmc-rockchip.c
dw_mci_rockchip_probe(struct platform_device *pdev)
--> dw_mci_pltfm_register(pdev, drv_data); //解析设备树信息,用来构造一个dw_mci *host;
--> dw_mci_probe(dw_mci *host); //将上面构造好的dw_mci *host结构体传参进来;
--> setup_timer(&host->cmd11_timer,dw_mci_cmd11_timer, (unsigned long)host); //设置一些定时器,定时发送一些命令
--> host->dma_ops = host->pdata->dma_ops;
--> ret = devm_request_irq(host->dev, host->irq, dw_mci_interrupt, host->irq_flags, "dw-mci", host);
//这个dw_mci_interrupt中断函数会被注册两次,一次是emmc(irq=25),一次是sd卡(irq=26),所以源码虽然只注册了一次,但是实际上注册了两次,用的都是同一个中断处理函数
//这个是mci寄存器的中断,我们的这款soc可以根据这个寄存器来判断发送,接收,SD卡热拔插的行为,都会触发这个中断,在这个中断处理函数里面再细分是什么操作触发的中断,进行处理,比如热拔插:
--> dw_mci_init_slot(dw_mci *host, i); // 定义了mmc_host *mmc;,用我们封装了一层的dw_mci *host去填充注册他
--> mmc = mmc_alloc_host(sizeof(struct dw_mci_slot), host->dev);
--> INIT_DELAYED_WORK(&host->detect, mmc_rescan); //初始化工作队列,里面有扫描SD卡的函数
--> mmc->ops = &dw_mci_ops; //mmc_host_ops类型的变量,用于定义本mmc_host的操作接口(包括与mmc card通信的接口request、卡检测相关的接口等)
--> mmc_add_host(mmc);
--> mmc_start_host(mmc_host *host);
--> mmc_schedule_delayed_work(&host->detect, delay); //上电添加mmc_host的时候,先主动执行一次mmc_rescan函数
--> dw_mci_enable_cd(host); //cd = card detect ≠ sdmmc_cd 引脚中断,主要看函数内容,像这款soc就是在里面开启mci寄存器的SD卡热拔插中断
--> temp = mci_readl(host, INTMASK); temp |= SDMMC_INT_CD; mci_writel(host, INTMASK, temp); //开启mci寄存器的SD卡热拔插中断

2.3 热拔插中断流程

假设热拔插SD卡引起了中断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
dw_mci_interrupt()
if (pending & SDMMC_INT_CD)
dw_mci_handle_cd(dw_mci *host);
--> mmc_detect_change(slot->mmc,msecs_to_jiffies(host->pdata->detect_delay_ms));
--> _mmc_detect_change(host, delay, true);
--> mmc_schedule_delayed_work(&host->detect, delay); //开启工作队列
--> mmc_rescan() //最终会调用此函数

// 重点看看mmc_rescan检测SD卡热拔插的函数,我们在设备树中的SD卡定义了 supports-sd 所以:
drivers\mmc\core\host.c
mmc_of_parse
if (of_property_read_bool(np, "supports-sd"))
host->restrict_caps |= RESTRICT_CARD_TYPE_SD;

mmc_rescan()
if (host->bus_ops && !host->bus_dead && !(host->caps & MMC_CAP_NONREMOVABLE)) //上电第一次调用这个函数时候,还没有设置host->bus_ops,所以会执行后面的语句来设置host->bus_ops
host->bus_ops->detect(host);
--> mmc_rescan_try_freq(host, max(freqs[i], host->f_min)) //以各种频率给host发,选择最合适的频率
if ((host->restrict_caps & RESTRICT_CARD_TYPE_SD) && !mmc_attach_sd(host)) return 0; 所以就会调用mmc_attach_sd
--> mmc_attach_sd(host)
--> mmc_attach_bus(host, &mmc_sd_ops);
--> host->bus_ops = ops; //这里的ops就是上面的mmc_ops参数
--> mmc_sd_init_card(host, rocr, NULL);
--> card = mmc_alloc_card(host, &sd_type);
--> mmc_add_card(host->card);
--> device_add(&card->dev);
--> bus_add_device(dev); //Add the device to its bus's list of devices, 会给mmc_bus注册一个devices,会和系统自动注册的mmc_driver匹配,调用它的probe函数
......
--> mmc_driver->probe
--> mmc_blk_probe() //如上分析的,会读取SD卡的寄存器获取分区信息,根据分区信息注册block等。

放一张图中好看些:

2.1mmc子系统函数调用流程1

三、mmc_host相关数据结构

3.1 数据结构关联

3.1数据结构关联

3.2 mmc_host

3.2mmc_host

3.3 mmc_card

mmc_card

mmc_card结构体变量的定义如下,主要内容如下:

  1. host完成mmc_card与mmc_host的绑定;
  2. dev完成将该mmc_card与系统中的设备、总线、驱动的关联(即完成与系统设备驱动总线的关联与绑定操作);
  3. 卡类型与卡状态记录;
  4. 卡相关的寄存器信息记录(主要从mmc card中读取),包括cid、csd、scr、ssr等内容;
  5. Mmc card的分区信息,主要完成块设备的创建(由mmc driver实现);
  6. 若为sdio设备,则有cccr、cis等(关于sdio的部分,请参考sdio协议相关的文档);

3.4 mmc_ios

mmc_ios

该结构体主要定义mmc总线相关的参数,主要包括时钟频率、电源、总线模式、电源状态、总线带宽、支持的信号电压值等等这些参数的设置可通过mmc_host_ops->set_ios实现。

3.5 mmc_host_ops

mmc_host_ops

该数据结构定义了mmc_host的操作方法,主要包括如下:

  1. 若mmc_host支持enable、disable,则需要实现这两个接口(可参考mmc_controller的用户手册);
  2. pre_req、request、post_req主要定义了mmc controller访问mmc card的方法,mmc controller驱动至少应完成request接口的定义;
  3. set_ios接口主要用于设置mmc_controller总线相关的参数,mmc_host需要完成该接口的定义;
  4. get_ro用于获取mmc card 的读写权限,若mmc controller提供检测mmc card读写权限的功能,则需要提供该接口;
  5. get_cd为卡是否在位的检测接口(该接口非必须),可查看mmc controller的用户手册,确定是否支持;
  6. init_card为mmc card初始化接口,主要是对mmc card/mmc controller一些特定的初始化参数,若没有特殊对待项,该接口不需要处理;
  7. 剩下几个接口大多数驱动也没有实现,这几个接口可参考具体的mmc协议,大多数的mmc host驱动基本上不需要实现;

3.6 mmc_bus_ops

mmc_bus_ops

该结构主要是针对mmc card的sleep/awake的操作,因mmc card也需要进行sleep/awake等接口,这些接口类似于设备驱动的电源管理(suspend/resume)相关的接口。该结构体中的成员,不需要我们进行实现,mmc子系统已经完成,mmc子系统根据mmc各协议版本针对sleep/awake的支持情况,实现对应的操作:

  1. 若为mmc 4.3及以上的协议,则支持sleep/awake接口;

  2. remove接口实现mmc card的移除操作,主要是解除mmc card与mmc host的绑定,并将mmc card从mmc 总线上移除;

  3. detect/alive主要是卡在位检测以及卡是否移除检测;

  4. 针对suspend/resume接口,针对mmc 不同版本的协议,其功能有所不同:

    4.1. 若card支持poweroff notify机制,则进入poweroff状态;
    4.2. 若card支持sleep状态,则进入sleep状态,针对mmc rev>=1.3的情况;
    4.3. card不支持以上两种状态,则向card发送deselect命令;

  5. power_restore接口,即为mmc_power_restore接口,该接口目前即调用mmc_init_card,根据mmc 协议,完成mmc card的初始化操作。

参考这位博主:LINUX MMC子系统分析之一 概述jerry_chg的博客-CSDN博客linux mmc子系统