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_framework.gif

和一般的框架类似,可以分为横向的“从左到右”和纵向的“从上到下”两个种层次结构。

横向的“从左到右”

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.csdio_uart.c)合并到 core ,所以现在主要分为2大块:

  • core:MMC core,负责抽象host、bus、card等软件实体;负责向底层提供统一、通用的编写Host controller driver的API;还包含了一些通用的 card 驱动代码;
  • host:MMC host controller driver,基于MMC core提供的框架,驱动具体的硬件,主要是不同厂家的MMC controller;

工作流程

mmc_workflow.gif

这里主要讲讲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 流程:

  1. 首先会用不同的频率来尝试扫描mmc设备,依次为400KHz、300KHz、200KHz和100KHz,如果成功则不进行后续更低频率的尝试
  2. 在尝试每个频率时,会通过mmc_rescan_try_freq函数来具体执行rescan流程
  3. 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;

其他具体的细节流程这里不一一展开了,感兴趣的同学可以对照追下代码。

参考