Linux内核之MMC框架
Linux内核之MMC框架
背景
之前简单说了说SDIO、SD、MMC之间的区别与联系,具体可见以下链接:
https://notes.z-dd.online/2024/01/19/MMC%E5%92%8CSD%E4%B8%8ESDIO/
最近刚好看到这块,今天来看看Linux内核中关于它们的MMC框架。
软件架构
Linux内核中,SDIO、SD、MMC相关的驱动都由MMC框架来管理。其主要的软件架构如下图所示:
和一般的框架类似,可以分为横向的“从左到右”和纵向的“从上到下”两个种层次结构。
横向的“从左到右”
MMC属于一种总线驱动,所以主要包括Host、Bus、Card三类实体(从左到右),软件上也是一一对应:
host,负责驱动Host controller,提供诸如访问card的寄存器、检测card的插拔、读写card等操作方法。从设备模型的角度看,host会检测卡的插入,并向bus注册MMC card设备;
bus,是MMC bus的虚拟抽象,以标准设备模型的方式,收纳MMC card(device)以及对应的MMC driver(driver);
card,抽象具体的MMC卡,由对应的MMC driver驱动(从这个角度看,可以忽略MMC的技术细节,只需关心一个个具有特定功能的卡设备,如存储卡、WIFI卡、GPS卡等等)。
纵向的“从上到下”
MMC framework从下到上也有3个层次:
MMC core位于中间,是MMC framework的核心实现,负责抽象host、bus、card等软件实体,负责向底层提供统一、便利的编写Host controller driver的API;
MMC host controller driver位于底层,基于MMC core提供的框架,驱动具体的硬件(MMC controller);
MMC card driver位于最上面,负责驱动MMC core抽象出来的虚拟的card设备,并对接内核其它的framework(例如块设备、TTY、wireless等),实现具体的功能。
源码及工作流程
基于前面的架构,我们来简单看看MMC框架的源码及工作流程。这里以较新的内核代码(这里看的是v5.4)为例。
代码目录
内核中关于MMC框架的代码目录为:drivers/mmc/
,目录树大致为:
drivers/mmc/
├── core
│ ├── block.c
│ ├── bus.c
│ ├── core.c
│ ├── host.c
│ ├── mmc.c
│ ├── mmc_ops.c
│ ├── pwrseq.c
│ ├── sd.c
│ ├── sdio.c
│ ├── sdio_ops.c
│ ├── ... ...
│ ├── sd_ops.c
│ └── slot-gpio.h
├── host
│ ├── dw_mmc.c
│ ├── dw_mmc-rockchip.c
│ ├── mmci.c
│ ├── sdhci.c
│ ├── sunxi-mmc.c
│ ├── ... ...
│ └── wmt-sdmmc.c
├── Kconfig
└── Makefile
按照前面的架构,以前的代码是包括core、host、card这三块,较新的内核源码将通用的 card 代码(比如block.c
、sdio_uart.c
)合并到 core ,所以现在主要分为2大块:
- core:MMC core,负责抽象host、bus、card等软件实体;负责向底层提供统一、通用的编写Host controller driver的API;还包含了一些通用的 card 驱动代码;
- host:MMC host controller driver,基于MMC core提供的框架,驱动具体的硬件,主要是不同厂家的MMC controller;
工作流程
这里主要讲讲scan(添加host,检测card)的过程。
在各个厂家 host 驱动的 probe
函数中,会调用mmc_alloc_host()
,里面会初始化一个扫描的工作队列,同时probe
函数还会调用mmc_add_host()
函数,最终会调度前面初始化的扫描工作队列,开始 mmc_rescan
。
上面是厂家 host驱动probe初始化会调用 mmc_rescan
流程,当有卡插拔时也会触发 mmc_rescan
流程。
# 初始化时
xxx_probe() -- drivers/mmc/host/xxx.c
-> mmc_alloc_host() -- drivers/mmc/core/host.c
-> INIT_DELAYED_WORK(&host->detect, mmc_rescan); // 初始化扫描卡的工作队列
-> mmc_add_host() -- drivers/mmc/core/host.c
-> mmc_start_host() -- drivers/mmc/core/core.c
-> _mmc_detect_change()
-> mmc_schedule_delayed_work(&host->detect, delay); // 开始扫描工作队列
-> mmc_rescan()
# 卡插拔时
xxx_irq() -- drivers/mmc/host/xxx.c
-> mmc_detect_change() -- drivers/mmc/core/core.c
-> _mmc_detect_change()
-> mmc_schedule_delayed_work(&host->detect, delay); // 开始扫描工作队列
-> mmc_rescan()
mmc_rescan 流程:
- 首先会用不同的频率来尝试扫描mmc设备,依次为400KHz、300KHz、200KHz和100KHz,如果成功则不进行后续更低频率的尝试
- 在尝试每个频率时,会通过
mmc_rescan_try_freq
函数来具体执行rescan流程 mmc_rescan_try_freq
函数中会依次执行power_up、hw_reset以及让卡进入idle状态,接下来会依次attach SDIO、SD和MMC三种子协议的卡,成功的话就会去初始化卡,添加卡,失败就会去power_off,继续下一个频率的尝试。
部分细节代码:
//mmc_rescan:
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_rescan_try_freq:
/* Order's important: probe SDIO, then SD, then MMC */
if (!(host->caps2 & MMC_CAP2_NO_SDIO))
if (!mmc_attach_sdio(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_SD))
if (!mmc_attach_sd(host))
return 0;
if (!(host->caps2 & MMC_CAP2_NO_MMC))
if (!mmc_attach_mmc(host))
return 0;
其他具体的细节流程这里不一一展开了,感兴趣的同学可以对照追下代码。