在这里插入图片描述

[toc]

drivers/mmc 多媒体卡(MultiMediaCard)子系统 SD/eMMC/SDIO设备驱动框架

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/mmc 子系统是为了给Linux内核提供一个统一的、可扩展的框架来支持一整类基于**多媒体卡(MultiMediaCard)**规范及其衍生协议的设备而诞生的。这些设备包括:

  • MMC (MultiMediaCard):一种早期的闪存卡标准。
  • SD (Secure Digital) Card:在MMC基础上发展而来的、目前最普及的移动存储卡,增加了安全特性和更高的性能。
  • eMMC (embedded MMC):将MMC控制器和NAND闪存封装在同一个BGA芯片中,作为一种“嵌入式”存储解决方案,被广泛用作智能手机、平板电脑和许多嵌入式设备的“硬盘”。
  • SDIO (Secure Digital Input/Output):一种扩展规范,允许SD插槽除了支持存储卡外,还能连接I/O设备,如Wi-Fi模块、蓝牙模块、GPS接收器等。

在没有这个统一框架之前,对这些设备的支持是零散的。该框架的诞生解决了以下关键问题:

  • 代码复用:MMC、SD、SDIO、eMMC协议有大量共通之处。一个统一的框架可以抽象出公共的协议处理、命令收发、数据传输逻辑,避免为每种卡和每种主机控制器都重写一遍。
  • 硬件抽象:市面上有各种各样的SD/MMC主机控制器(Host Controller),它们是SoC上负责与SD卡进行物理通信的IP核。该框架需要将这些具体硬件的差异隔离开,为上层提供一个标准接口。
  • 功能分离:需要将对“卡的功能”(是存储设备还是I/O设备)的驱动与底层的“总线协议”驱动分离开。

它的发展经历了哪些重要的里程碑或版本迭代?

  • 框架的建立:最重要的里程碑是将分散的驱动重构为一个分层的、模块化的MMC子系统。这确立了Host驱动 - Core - Card驱动的核心架构。
  • SDHCI规范的支持:SD主机控制器接口(SD Host Controller Interface)是一个标准化的硬件寄存器规范。sdhci.c驱动的出现是一个巨大的进步,因为它提供了一个通用的Host驱动,可以支持任何符合SDHCI规范的硬件控制器,大大减少了为新SoC编写Host驱动的工作量。
  • 高性能模式的支持:随着SD卡和eMMC标准的演进,框架不断增加对新功能和更高速度模式的支持,如DDR模式、HS200(200MB/s)、HS400(400MB/s)等。
  • eMMC高级功能:为eMMC增加了对TRIM/Discard(提升闪存寿命和性能)、安全擦除、分区管理等高级功能的支持。

目前该技术的社区活跃度和主流应用情况如何?

MMC子系统是Linux内核中最为成熟和活跃的核心子系统之一。由于几乎所有的移动和嵌入式设备都依赖它,因此它得到了持续的维护和功能增强。

  • 主流应用
    • 智能手机/平板电脑:eMMC曾是安卓设备内部存储的主流标准(现在正逐渐被UFS取代)。
    • 单板计算机(SBC):如树莓派,使用SD卡作为其主启动和存储设备。
    • 物联网(IoT)设备:广泛使用SDIO接口的Wi-Fi/蓝牙模块。
    • 数码相机、无人机等消费电子产品:使用SD卡作为主要存储介质。

核心原理与设计

它的核心工作原理是什么?

drivers/mmc 的核心是一个清晰的三层架构

  1. 主机控制器驱动 (Host Driver) - (drivers/mmc/host/)

    • 职责:这是最底层,直接与硬件打交道。它负责控制具体的SD/MMC控制器IP核,包括管理时钟、电源、引脚、执行DMA传输、处理硬件中断等。
    • 实现:每个Host驱动(如sdhci.c for SDHCI, dw_mmc.c for Synopsys DesignWare a core)都会实现一套标准的操作函数集 struct mmc_host_ops
    • 抽象:它将底层硬件的复杂性抽象为一个标准的MMC主机对象 (struct mmc_host),并将其注册到MMC核心。
  2. MMC核心 (Core) - (drivers/mmc/core/)

    • 职责:这是整个子系统的大脑和调度中心。它实现了MMC/SD/SDIO协议栈,负责卡的上电、初始化、识别过程,以及命令(CMD)和数据(DAT)的收发逻辑。
    • 实现:核心层代码不关心具体的Host硬件是什么样的。当它需要发送一个命令时,它会通过Host驱动注册的mmc_host_ops中的函数(如 .request(), .set_ios())来间接地控制硬件。
    • 角色:它扮演了总线驱动的角色,扫描总线上(插槽里)的设备,并为识别出的设备创建对应的逻辑设备。
  3. 设备驱动 (Card Driver) - (drivers/mmc/card/)

    • 职责:这是最上层,负责驱动卡片本身的功能。
    • 实现
      • block.c:这是一个通用的Card驱动。当MMC核心识别出一张存储卡(SD或eMMC)时,block.c会绑定到该设备,并创建一个块设备节点(如 /dev/mmcblk0),使其可以被文件系统挂载。
      • sdio.c:当MMC核心识别出是一张SDIO卡时,它会进一步解析卡上的功能(Function),并为每个功能(如WLAN, Bluetooth)创建SDIO设备。然后,相应的SDIO功能驱动(如位于drivers/net/wireless/下的Wi-Fi驱动)会绑定到这些设备上。

它的主要优势体现在哪些方面?

  • 高度模块化:三层架构使得各部分可以独立开发和维护。添加对新Host控制器的支持只需编写一个新的Host驱动;支持一种新的SDIO设备只需编写一个新的Card驱动,核心层代码保持不变。
  • 代码复用率高:协议逻辑和公共流程都集中在核心层,被所有驱动共享。
  • 标准化:紧密遵循MMC/SD/SDIO的官方规范,保证了良好的兼容性。SDHCI的通用驱动更是标准化的典范。
  • 灵活性:同一个框架无缝支持存储、I/O等多种类型的设备。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 性能瓶颈:与现代的NVMe或UFS接口相比,MMC/SD协议(即使是最高速的模式)在吞吐量、延迟和IOPS方面都存在性能上限。它是一个半双工的总线,命令和数据传输不能完全并行。
  • 协议复杂性:MMC/SD的初始化过程和命令集相当复杂,给调试带来了一定的挑战。
  • 功耗:虽然SDIO功耗不高,但相比专用的低功耗总线(如I2C/SPI),在某些极低功耗场景下可能不是最优选择。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

  • 可移动存储:SD卡是事实上的标准。所有需要可插拔、小型化存储卡的场景,如数码相机、运动相机、无人机、便携式游戏机,都依赖MMC子系统。
  • 高性价比的嵌入式系统存储:eMMC在成本、性能和封装尺寸之间取得了很好的平衡,因此在对成本敏感的中低端智能手机、平板电脑、车载信息娱乐系统、智能电视中,是内部存储的首选方案。
  • 无线模块连接:SDIO是连接Wi-Fi/蓝牙组合芯片的一种非常流行和成熟的接口,特别是在各种物联网网关和嵌入式设备中。

是否有不推荐使用该技术的场景?为什么?

  • 高性能计算与存储:在需要极高I/O性能的场景,如企业级服务器、高端PC、高端智能手机,应使用NVMe SSD或UFS闪存。这些技术基于PCIe或M-PHY,提供多通道、全双工、更高效的命令队列机制,性能远超eMMC。
  • 简单的低速外设:对于只需要传输少量控制数据的简单传感器或外设,使用SDIO是“杀鸡用牛刀”。更简单、引脚更少的总线,如I2C或SPI,是更合适的选择。

对比分析

请将其 与 其他相似技术 进行详细对比。

特性 MMC/SD/eMMC USB (Mass Storage Class) UFS (Universal Flash Storage) SATA / NVMe
主要用途 可移动存储、嵌入式内部存储、SDIO外设 通用的外部设备连接(存储、外设) 高性能嵌入式内部存储(eMMC的替代者) 高性能内部/外部存储
总线协议 并行、半双工、命令-响应式协议。 差分串行、包交换、主从协议。 差分串行、全双工、基于SCSI命令集。 差分串行、全双工、基于ATA或PCIe命令集。
性能 中等。eMMC最高约400MB/s,SD卡速度不等。延迟较高。 高。USB 3.x/4可达数GB/s。 高。UFS 3.x/4.0可达数GB/s,延迟低,支持命令队列。 非常高。SATA 6Gbps,NVMe可达数十GB/s,延迟极低。
物理接口 并行数据线+命令线+时钟线 (1/4/8位)。 串行差分对 (D+/D-)。 高速差分串行对 (M-PHY)。 高速差分串行对。
应用场景 成本敏感的移动/嵌入式设备,SD卡生态。 PC、移动设备的外设连接,U盘。 中高端智能手机、平板电脑、汽车。 PC、工作站、服务器。
内核子系统 drivers/mmc drivers/usb drivers/ufs (或 drivers/scsi) drivers/ata, drivers/pci

include/linux/mmc/mmc.h

MMC协议核心定义:命令、响应及寄存器规范

本文件是Linux内核中用于支持MMC(MultiMediaCard)和eMMC(embedded MMC)存储卡的核心协议头文件。与sdio.h专注于I/O功能不同,此文件定义了MMC存储卡相关的命令集、响应格式、寄存器(CSD, EXT_CSD)布局以及各种状态和能力标志。它将JEDEC发布的MMC物理规范中的数值和位域,转化为内核代码可以使用的符号常量和宏,是所有MMC/eMMC驱动程序与卡进行交互的基础语言。

实现原理分析

该文件的实现是典型的将硬件规范文档化的编码实践,其核心是利用C预处理器将协议的细节抽象化:

  1. 命令符号化: 每一个MMC命令(如CMD0, CMD1, …)都被#define为一个唯一的整数值,并赋予一个描述性的名称,例如MMC_GO_IDLE_STATE。这使得驱动代码的逻辑(如状态机转换)清晰易懂。
  2. 响应格式位域化: 卡片对命令的响应(特别是R1响应)是一个包含多个状态和错误标志的32位字。文件通过定义位掩码(如R1_OUT_OF_RANGE (1 << 31))和提取宏(如R1_CURRENT_STATE(x)),将解析这个状态字的过程符号化,使得错误处理和状态判断逻辑标准化。
  3. 寄存器映射: MMC卡拥有复杂的内部寄存器,如CSD(Card-Specific Data)和至关重要的EXT_CSD(Extended CSD)。该文件将EXT_CSD这个长达512字节的寄存器中的每一个字节偏移量(offset)都定义为一个宏,如EXT_CSD_BUS_WIDTH (183)。同时,寄存器内特定位或位域的含义也被定义为宏,如EXT_CSD_BUS_WIDTH_8 (2)。
  4. 内联辅助函数: 文件提供了一些简单的static inline函数,如mmc_op_multi,用于快速判断一个命令的类型。这些函数在编译时会被内联,没有函数调用的开销,提供了类型安全的便捷检查。

代码分析

MMC命令定义

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
/* 标准MMC命令 (根据JEDEC 4.1规范) */
/* Class 1: 基础命令 */
#define MMC_GO_IDLE_STATE 0 // CMD0: 使卡进入空闲状态 (复位)
#define MMC_SEND_OP_COND 1 // CMD1: 发送操作条件,用于初始化和识别卡
#define MMC_ALL_SEND_CID 2 // CMD2: 请求所有卡发送其CID(卡识别)号
#define MMC_SET_RELATIVE_ADDR 3 // CMD3: 设置卡的相对地址 (RCA)
#define MMC_SWITCH 6 // CMD6: 切换卡的功能 (例如, 速度模式, 总线宽度)
#define MMC_SELECT_CARD 7 // CMD7: 选择/取消选择一张卡
#define MMC_SEND_EXT_CSD 8 // CMD8: 读取扩展CSD寄存器 (对eMMC至关重要)
#define MMC_SEND_CSD 9 // CMD9: 读取CSD寄存器
#define MMC_STOP_TRANSMISSION 12 // CMD12: 停止多块读/写操作
#define MMC_SEND_STATUS 13 // CMD13: 获取卡的状态

/* Class 2 & 4: 块读写命令 */
#define MMC_SET_BLOCKLEN 16 // CMD16: 设置块长度
#define MMC_READ_SINGLE_BLOCK 17 // CMD17: 读单个块
#define MMC_READ_MULTIPLE_BLOCK 18 // CMD18: 读多个块
#define MMC_SET_BLOCK_COUNT 23 // CMD23: 设置后续多块传输的块数量 (可靠写入)
#define MMC_WRITE_BLOCK 24 // CMD24: 写单个块
#define MMC_WRITE_MULTIPLE_BLOCK 25 // CMD25: 写多个块

/* Class 5: 擦除命令 */
#define MMC_ERASE_GROUP_START 35 // CMD35: 定义擦除组的起始地址
#define MMC_ERASE_GROUP_END 36 // CMD36: 定义擦除组的结束地址
#define MMC_ERASE 38 // CMD38: 执行擦除

/* Class 8: 应用特定命令 */
#define MMC_APP_CMD 55 // CMD55: 指示下一条命令是应用特定命令 (SD卡用)
#define MMC_GEN_CMD 56 // CMD56: 通用命令读/写

R1响应格式与状态定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
MMC R1响应中的状态位定义 (原生模式)
*/
#define R1_OUT_OF_RANGE (1 << 31) // 参数超出范围错误
#define R1_ADDRESS_ERROR (1 << 30) // 地址错误
#define R1_BLOCK_LEN_ERROR (1 << 29) // 块长度错误
#define R1_COM_CRC_ERROR (1 << 23) // 命令CRC校验错误
#define R1_ILLEGAL_COMMAND (1 << 22) // 非法命令
#define R1_ERROR (1 << 19) // 通用错误
#define R1_STATUS(x) (x & 0xFFF9A000) // 提取R1响应中所有错误位的掩码
#define R1_CURRENT_STATE(x) ((x & 0x00001E00) >> 9) // 提取当前卡状态 (4位)
#define R1_READY_FOR_DATA (1 << 8) // 卡已准备好接收数据

/* R1_CURRENT_STATE(x) 的可能值 */
#define R1_STATE_IDLE 0 // 空闲状态
#define R1_STATE_READY 1 // 准备状态
#define R1_STATE_IDENT 2 // 识别状态
#define R1_STATE_STBY 3 // 待命状态
#define R1_STATE_TRAN 4 // 传输状态
#define R1_STATE_DATA 5 // 发送数据状态
#define R1_STATE_RCV 6 // 接收数据状态
#define R1_STATE_PRG 7 // 编程状态
#define R1_STATE_DIS 8 // 断开连接状态

扩展CSD (EXT_CSD) 寄存器关键字段定义

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
31
32
33
/*
* EXT_CSD 寄存器字段的字节偏移量定义
*/

#define EXT_CSD_BKOPS_EN 163 // 后台操作使能
#define EXT_CSD_BKOPS_START 164 // 启动后台操作
#define EXT_CSD_ERASE_GROUP_DEF 175 // 擦除组定义
#define EXT_CSD_PART_CONFIG 179 // 分区配置
#define EXT_CSD_BUS_WIDTH 183 // 总线宽度模式
#define EXT_CSD_HS_TIMING 185 // 高速时序接口
#define EXT_CSD_CARD_TYPE 196 // 卡类型 (支持的速度模式)
#define EXT_CSD_REV 192 // EXT_CSD 结构版本
#define EXT_CSD_SEC_CNT 212 // 设备扇区计数 (4字节)

/* EXT_CSD_CARD_TYPE 字段的位域定义 */
#define EXT_CSD_CARD_TYPE_HS_26 (1<<0) // 卡支持 26MHz
#define EXT_CSD_CARD_TYPE_HS_52 (1<<1) // 卡支持 52MHz
#define EXT_CSD_CARD_TYPE_DDR_52 (1<<2 | 1<<3) // 卡支持 52MHz DDR模式
#define EXT_CSD_CARD_TYPE_HS200 (1<<4 | 1<<5) // 卡支持 200MHz HS200模式
#define EXT_CSD_CARD_TYPE_HS400 (1<<6 | 1<<7) // 卡支持 200MHz DDR HS400模式

/* EXT_CSD_BUS_WIDTH 字段的值定义 */
#define EXT_CSD_BUS_WIDTH_1 0 // 1位总线模式
#define EXT_CSD_BUS_WIDTH_4 1 // 4位总线模式
#define EXT_CSD_BUS_WIDTH_8 2 // 8位总线模式
#define EXT_CSD_DDR_BUS_WIDTH_4 5 // 4位DDR总线模式
#define EXT_CSD_DDR_BUS_WIDTH_8 6 // 8位DDR总线模式

/* EXT_CSD_HS_TIMING 字段的值定义 */
#define EXT_CSD_TIMING_BC 0 // 向后兼容时序
#define EXT_CSD_TIMING_HS 1 // 高速时序 (HS)
#define EXT_CSD_TIMING_HS200 2 // HS200时序
#define EXT_CSD_TIMING_HS400 3 // HS400时序

MMC_SWITCH (CMD6) 命令相关定义

1
2
3
4
5
6
7
/*
* MMC_SWITCH (CMD6) 的 "access" 模式
*/
#define MMC_SWITCH_MODE_CMD_SET 0x00 // 改变命令集
#define MMC_SWITCH_MODE_SET_BITS 0x01 // 置位 (将'value'中为1的位设置为1)
#define MMC_SWITCH_MODE_CLEAR_BITS 0x02 // 清位 (将'value'中为1的位清零)
#define MMC_SWITCH_MODE_WRITE_BYTE 0x03 // 写字节 (将目标字节直接设置为'value')

drivers/mmc/core/card.h MMC/SD/eMMC 卡功能驱动集合

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/mmc/core/card 目录及其下的文件是为了在MMC子系统的三层架构(Host - Core - Card)中扮演最顶层——“卡功能驱动”的角色。当MMC核心层(Core)完成对物理卡片的识别和初始化后,需要有一个驱动来解释这张卡的功能,并向Linux内核的其他子系统(如块设备层、输入子系统等)提供这些功能。

card目录下的驱动解决了以下核心问题:

  • 功能实现:将一张原始的、遵循MMC/SD/eMMC协议的卡片,转换成一个内核和用户空间可以理解和使用的标准设备。
  • 块设备抽象:对于SD卡和eMMC这类存储卡,block.c驱动负责将其抽象为一个标准的块设备(如/dev/mmcblk0),使其可以被分区、格式化并挂载文件系统。
  • 设备特定功能:eMMC标准定义了许多超越简单块存储的高级功能,如分区管理(Boot partitions, RPMB partition)、TRIM/Discard命令(用于SSD性能优化)、安全擦除等。mmc/card/block.c和相关文件负责实现对这些高级功能的支持。
  • SDIO功能枚举:对于SDIO卡,sdio.c驱动扮演一个特殊的“枚举器”角色。它本身不驱动具体功能,而是负责解析SDIO卡的CIS(卡信息结构),找出卡上有多少个I/O功能,并为每个功能在sdio_bus上注册一个设备,等待真正的功能驱动来绑定。

它的发展经历了哪些重要的里程碑或版本迭代?

card目录下的驱动发展与MMC/SD/eMMC规范的演进紧密相关。

  • 基础块设备支持 (block.c):最初的核心功能是提供对MMC和SD卡的读/写/擦除操作,并将其封装成一个块设备。
  • eMMC的兴起:随着eMMC成为嵌入式系统的主流存储,block.c被大幅扩展以支持eMMC的各种新特性:
    • 高速模式支持(HS200, HS400)。
    • 分区支持,能够访问eMMC的Boot和RPMB(Replay Protected Memory Block)分区,并为它们创建独立的块设备节点。
    • TRIM/Discard和Sanitize命令的支持,这对于保持闪存性能和寿命至关重要。
  • SDIO的成熟 (sdio.c)sdio.c的完善是另一个里程碑,它与sdio_bus.c共同构成了Linux强大的SDIO支持框架,能够处理复杂的多功能combo卡。
  • SD卡新规范支持:不断增加对新SD规范(如SDHC, SDXC, SDUC)和速度等级(UHS-I, UHS-II)的支持。

目前该技术的社区活跃度和主流应用情况如何?

card目录下的代码是MMC子系统中非常活跃的部分,因为硬件规范在不断演进。

  • block.c:由于eMMC和SD卡技术仍在发展(例如,引入新的缓存或健康报告功能),block.c会持续更新以支持这些新特性。它是所有使用SD卡或eMMC存储的Linux系统的核心。
  • sdio.c:相对稳定,但随着新的SDIO规范或特性出现,也可能需要更新。

这些驱动的应用无处不在,覆盖了从树莓派(SD卡启动)到绝大多数安卓手机(eMMC作为主存储)的广阔领域。

核心原理与设计

它的核心工作原理是什么?

card目录下的驱动作为**MMC总线(mmc_bus_type上的驱动程序(struct mmc_driver)**来工作。

  1. 驱动注册:在模块初始化时,block.c会调用mmc_register_driver(&mmc_block_driver)将自己注册到MMC总线上。mmc_block_driver这个结构体声明了它是一个MMC驱动,并提供了.probe.remove等回调函数。
  2. 设备匹配:当MMC核心层在插槽中发现一张卡并成功初始化后,它会将这张卡注册为一个mmc总线设备。MMC总线核心(bus.c)会尝试将这个新设备与所有已注册的mmc_driver进行匹配。mmc_block_driver的匹配规则很简单:它几乎能匹配所有类型的存储卡(SD, MMC, eMMC)。
  3. 探测(Probe):一旦匹配成功,mmc_block_driver.probe函数(mmc_block_probe)就会被调用。这是功能实现的核心所在:
    • 分配块设备:它会分配一个gendisk结构体和一个请求队列(request_queue),这是块设备层的核心数据结构。
    • 设置请求队列:它会配置请求队列,将队列的“请求处理函数”指向mmc_queue_rq。这意味着,当文件系统层发出一个读/写请求(struct bio)时,最终会由mmc_queue_rq函数来处理。
    • mmc_queue_rq的逻辑:这个函数是连接块设备层和MMC核心层的桥梁。它会将一个来自块设备层的request转换成一个或多个发往MMC核心层的mmc_request(包含CMD17/18/24/25等读写命令)。
    • 添加磁盘:最后,调用add_disk,向内核宣告一个新的块设备诞生了,此时/dev/mmcblkX设备节点就会出现。
  4. SDIO的特殊流程sdio.c的流程类似,但它的.probe函数(sdio_probe)不做块设备相关的工作。它的任务是解析CIS,然后为每个发现的功能调用sdio_add_func(),在sdio_bus上创建设备。

它的主要优势体现在哪些方面?

  • 关注点分离card驱动完美地体现了分层设计的思想。它不关心底层Host控制器是如何操作硬件的,也不关心MMC协议的握手细节。它只关注于如何将一个已初始化的mmc_card设备的功能暴露给上层。
  • 标准化接口:通过实现标准的块设备或SDIO设备接口,它使得上层应用(如文件系统、网络栈)可以无缝地使用MMC设备,而无需知道其底层技术。
  • 可扩展性:支持新的eMMC功能或SD卡类型,通常只需要修改block.c,而不会影响到核心层或主机驱动。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 单一模型block.c是一个通用的块设备驱动,它试图以一种统一的方式来处理所有存储卡。对于某些有非常特殊功能或性能特性的eMMC芯片,这种通用模型可能无法发挥其全部潜力。
  • 块设备层的开销:对于某些嵌入式场景,如果只是想对闪存进行裸读写,经过完整的块设备层(包括I/O调度器等)可能会带来不必要的开销。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

它是Linux内核中处理MMC/SD/eMMC存储和SDIO功能的唯一且标准的解决方案。

  • 挂载文件系统:当你在Linux系统中将一张SD卡格式化为ext4并挂载时,所有对该文件系统的读写操作最终都会通过VFS -> 文件系统 -> 块设备层,最终由mmc_block_driver转换成发往卡片的物理命令。
  • eMMC分区:在嵌入式设备或手机上,系统访问eMMC的不同物理分区(如userdata, system, boot)时,依赖于block.c为这些分区创建的独立块设备节点(/dev/mmcblk0p1, /dev/mmcblk0p2等)。
  • 使用SDIO Wi-Fi模块:当一个SDIO Wi-Fi模块被识别时,sdio.c负责枚举其功能,然后真正的Wi-Fi驱动会绑定到sdio_bus上的功能设备,从而提供网络服务。

是否有不推荐使用该技术的场景?为什么?

不存在不推荐使用该技术的场景。只要一个设备是标准的MMC/SD/eMMC/SDIO卡,就必须由card目录下的相应驱动来处理,这是MMC子系统设计的核心部分。

对比分析

请将其 与 其他总线的顶层功能驱动(如USB Storage, NVMe)进行详细对比。

特性 MMC Card Driver (block.c) USB Storage Driver (usb-storage) NVMe Driver (nvme/host.c)
所属总线 mmc_bus_type usb_bus_type pci_bus_type
功能抽象 将MMC/SD卡抽象为Linux块设备 将USB Mass Storage设备抽象为SCSI磁盘设备,然后再由SCSI中间层转换为块设备。 将NVMe SSD抽象为专有的**nvme_ns块设备**。
交互协议 直接构建和发送MMC协议命令(CMD17, CMD18等)。 将读/写请求转换为SCSI命令(CDBs),再封装成USB批量传输(Bulk Transfers)。 将请求转换为NVMe命令,并将其放入提交队列(Submission Queue),通过写门铃寄存器(doorbell)来通知硬件。
性能模型 命令-响应式,半双工。 命令-响应式,但USB协议本身支持一定程度的流水线。 队列化、异步模型。支持多个队列和极高的队列深度(QD),性能极高。
驱动角色 直接的块设备驱动。 协议转换驱动。它本身是一个USB接口驱动,同时又是一个SCSI低层驱动(LLD)。 全功能的块设备驱动。它实现了从PCIe交互到块设备接口的全套逻辑。
复杂性 中等。需要理解MMC读写和eMMC高级功能。 较高。需要理解USB传输和SCSI命令集。 非常高。需要理解PCIe、NVMe协议、队列管理、中断处理等。

总结drivers/mmc/core/card下的驱动与其他总线的功能驱动一样,都扮演着“翻译官”的角色,将上层子系统(如块设备层)的通用请求,翻译成底层总线和设备能听懂的特定协议。它们的实现细节和复杂性,完全由其所服务的硬件协议的特性所决定。MMC协议相对直接,而USB Storage和NVMe则分别引入了SCSI协议层和高性能队列模型,因此其驱动实现也更为复杂。

MMC卡状态与属性宏定义:卡片核心状态的管理接口

本代码片段并非可执行函数,而是一系列C预处理器宏的定义,通常位于头文件(如include/linux/mmc/card.h)中。其核心功能是为MMC/SD卡(由struct mmc_card表示)提供一个标准化的、抽象的接口,用于定义、查询和修改卡片的核心状态(如是否存在、是否只读),以及便捷地访问其关键属性(如设备名称)。

实现原理分析

这组宏的实现原理是C语言中一种常见且高效的设计模式,即利用位掩码(Bitmask)来管理一组布尔状态,并通过宏来封装其操作,以提高代码的可读性和可维护性。

  1. 位掩码(Bitmask): struct mmc_card 结构体中有一个名为 state 的整型成员。每一个独立的状态(如PRESENT, READONLY)都被定义为一个唯一的2的幂次值(例如 (1<<0), (1<<1)),这意味着在二进制表示中,每个状态都对应一个独立的位置为’1’。通过这种方式,一个32位的整型变量理论上可以同时存储32个不同的布尔状态,极大地节省了内存空间。
  2. 状态查询: 查询宏(如 mmc_card_present(c))利用按位与(&)操作符。将 state 变量与代表特定状态的位掩码相与,如果结果不为零,则说明该状态位被置位(为’1’),表示状态为真。
  3. 状态设置: 设置宏(如 mmc_card_set_present(c))利用按位或赋值(|=)操作符。将 state 变量与特定状态的位掩码相或,这会将对应的状态位置为’1’,而不会影响其他位的状态。
  4. 状态清除: 清除宏(如 mmc_card_clr_suspended(c))则使用按位与赋值(&=)和按位非(~)的组合。~MMC_STATE_SUSPENDED 会产生一个除了暂停状态位为’0’、其他所有位都为’1’的掩码。将其与 state 变量相与,就能精确地将暂停状态位清零,同时保持其他位不变。
  5. 访问器宏: 像 mmc_card_name(c)container_of 这样的宏提供了对结构体成员的便捷访问,隐藏了内部数据结构的细节,使得上层代码更加简洁。container_of 是一个内核中的标准宏,它能根据结构体成员的地址反向计算出整个结构体的起始地址,是设备模型中实现对象间关联的关键技术。

代码分析

访问器宏

1
2
3
4
5
6
// mmc_card_name(c): 获取卡的产品名称。
#define mmc_card_name(c) ((c)->cid.prod_name)
// mmc_card_id(c): 获取卡的设备名称(例如 "mmcblk0")。
#define mmc_card_id(c) (dev_name(&(c)->dev))
// mmc_dev_to_card(d): 从一个device结构体指针获取其所属的mmc_card结构体指针。
#define mmc_dev_to_card(d) container_of(d, struct mmc_card, dev)

卡状态位定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* 卡状态定义 */
// MMC_STATE_PRESENT: 卡片存在于sysfs中,表示逻辑上已插入并被识别。
#define MMC_STATE_PRESENT (1<<0)
// MMC_STATE_READONLY: 卡片是只读的(可能由硬件写保护开关或内部状态决定)。
#define MMC_STATE_READONLY (1<<1)
// MMC_STATE_BLOCKADDR: 卡片使用块寻址(用于SDHC/SDXC/SDUC等高容量卡)。
#define MMC_STATE_BLOCKADDR (1<<2)
// MMC_CARD_SDXC: 卡片是SDXC类型。
#define MMC_CARD_SDXC (1<<3)
// MMC_CARD_REMOVED: 卡片已被移除。这是一个重要的状态,用于快速失败后续的I/O操作。
#define MMC_CARD_REMOVED (1<<4)
// MMC_STATE_SUSPENDED: 卡片处于挂起状态(低功耗模式)。
#define MMC_STATE_SUSPENDED (1<<5)
// MMC_CARD_SDUC: 卡片是SDUC类型。
#define MMC_CARD_SDUC (1<<6)

状态查询宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// mmc_card_present(c): 检查卡片是否在位。
#define mmc_card_present(c) ((c)->state & MMC_STATE_PRESENT)
// mmc_card_readonly(c): 检查卡片是否为只读。
#define mmc_card_readonly(c) ((c)->state & MMC_STATE_READONLY)
// mmc_card_blockaddr(c): 检查卡片是否使用块寻址。
#define mmc_card_blockaddr(c) ((c)->state & MMC_STATE_BLOCKADDR)
// mmc_card_ext_capacity(c): 检查卡片是否为SDXC类型。
#define mmc_card_ext_capacity(c) ((c)->state & MMC_CARD_SDXC)
// mmc_card_removed(c): 检查卡片是否已被移除。
#define mmc_card_removed(c) ((c) && ((c)->state & MMC_CARD_REMOVED))
// mmc_card_suspended(c): 检查卡片是否已挂起。
#define mmc_card_suspended(c) ((c)->state & MMC_STATE_SUSPENDED)
// mmc_card_ult_capacity(c): 检查卡片是否为SDUC类型。
#define mmc_card_ult_capacity(c) ((c)->state & MMC_CARD_SDUC)

状态修改宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// mmc_card_set_present(c): 设置卡片为存在状态。
#define mmc_card_set_present(c) ((c)->state |= MMC_STATE_PRESENT)
// mmc_card_set_readonly(c): 设置卡片为只读状态。
#define mmc_card_set_readonly(c) ((c)->state |= MMC_STATE_READONLY)
// mmc_card_set_blockaddr(c): 设置卡片为块寻址模式。
#define mmc_card_set_blockaddr(c) ((c)->state |= MMC_STATE_BLOCKADDR)
// mmc_card_set_ext_capacity(c): 设置卡片为SDXC类型。
#define mmc_card_set_ext_capacity(c) ((c)->state |= MMC_CARD_SDXC)
// mmc_card_set_ult_capacity(c): 设置卡片为SDUC类型。
#define mmc_card_set_ult_capacity(c) ((c)->state |= MMC_CARD_SDUC)
// mmc_card_set_removed(c): 设置卡片为已移除状态。
#define mmc_card_set_removed(c) ((c)->state |= MMC_CARD_REMOVED)
// mmc_card_set_suspended(c): 设置卡片为已挂起状态。
#define mmc_card_set_suspended(c) ((c)->state |= MMC_STATE_SUSPENDED)
// mmc_card_clr_suspended(c): 清除卡片的挂起状态。
#define mmc_card_clr_suspended(c) ((c)->state &= ~MMC_STATE_SUSPENDED)

drivers/mmc/core/core.c

MMC/SD/SDIO核心子系统初始化与卸载

本代码片段是Linux内核MMC/SD/SDIO核心子系统的模块入口和出口。其核心功能是在内核启动时,注册和初始化MMC、SDIO总线类型以及MMC主机控制器类,为所有具体的MMC主机控制器驱动(如针对STM32H750的SDMMC外设驱动)和MMC/SD/SDIO设备驱动(如SD卡块设备驱动、SDIO WiFi驱动)提供必要的基础设施。在模块卸载时,它负责按相反的顺序清理这些注册的组件。

实现原理分析

该代码遵循标准的Linux内核模块初始化和退出模式,利用subsys_initcall确保在系统早期阶段执行初始化。

  1. 初始化流程 (mmc_init):

    • 该函数按严格的依赖顺序执行初始化步骤。首先调用mmc_register_bus(),这会向内核设备模型注册一个名为mmcbus_type。这个总线类型是连接MMC卡设备和MMC卡驱动的桥梁。
    • 成功后,调用mmc_register_host_class(),它会注册一个名为mmc_host的设备类(class)。这会在sysfs中创建/sys/class/mmc_host目录,为所有MMC主机控制器提供一个统一的视图(如mmc0, mmc1)。
    • 接着,调用sdio_register_bus()注册一个独立的、名为sdiobus_type。虽然SDIO卡通过MMC总线进行通信,但其上的功能设备(Functions)具有不同的寻址和驱动匹配逻辑,因此需要一个专门的SDIO总线来管理SDIO功能驱动和设备。
    • 函数使用了goto语句进行错误处理。如果在任何一步注册失败,程序会跳转到相应的标签,执行已经成功注册部分的反向注销操作,确保系统状态的一致性。这是一种在内核中常见的、高效的错误回滚(rollback)模式。
  2. 退出流程 (mmc_exit):

    • 该函数在模块卸载时被调用,执行与mmc_init完全相反的操作。
    • 它严格按照“后进先出”(LIFO)的原则进行清理:首先注销SDIO总线,然后是MMC主机类,最后是MMC总线。这种相反的顺序是至关重要的,可以避免因依赖关系导致的错误(例如,在仍有设备注册在总线上时就注销了总线类型)。

代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// mmc_init: MMC核心子系统的模块初始化函数。
static int __init mmc_init(void)
{
int ret;

// 注册MMC总线类型。
ret = mmc_register_bus();
if (ret)
return ret;

// 注册MMC主机控制器设备类。
ret = mmc_register_host_class();
if (ret)
goto unregister_bus; // 如果失败,跳转到unregister_bus标签进行清理。

// 注册SDIO总线类型。
ret = sdio_register_bus();
if (ret)
goto unregister_host_class; // 如果失败,跳转进行清理。

return 0; // 所有初始化成功。

// 错误处理标签,用于回滚注册操作。
unregister_host_class:
mmc_unregister_host_class(); // 注销MMC主机类。
unregister_bus:
mmc_unregister_bus(); // 注销MMC总线。
return ret; // 返回错误码。
}

// mmc_exit: MMC核心子系统的模块退出函数。
static void __exit mmc_exit(void)
{
// 以与初始化相反的顺序注销所有组件。
sdio_unregister_bus();
mmc_unregister_host_class();
mmc_unregister_bus();
}

// 使用subsys_initcall宏,确保该初始化函数在内核子系统初始化阶段被调用。
subsys_initcall(mmc_init);
// 注册模块退出函数。
module_exit(mmc_exit);

// 模块元信息。
MODULE_DESCRIPTION("MMC core driver");
MODULE_LICENSE("GPL");

MMC主机控制器独占声明:实现总线访问的互斥与电源管理

本代码片段展示了MMC核心子系统中至关重要的主机控制器锁定机制。其核心功能是通过mmc_claim_host(及其实现__mmc_claim_host)提供一个可重入的、可中断的、且与电源管理集成的互斥锁。任何需要访问MMC/SD/SDIO总线硬件的驱动程序(如SD卡块设备驱动、SDIO WiFi驱动)在执行I/O操作前都必须调用此函数。它确保了在任何时刻,只有一个执行上下文可以控制MMC主机硬件,同时保证了硬件在被访问前处于上电状态。

实现原理分析

此函数并未直接使用标准的mutex_lock,而是实现了一个更复杂的、定制化的阻塞锁。这是因为MMC主机的锁定需要满足几个特殊需求:可重入性、可被外部事件中止、以及与运行时电源管理(Runtime PM)的紧密集成。

  1. 自定义阻塞锁:

    • 函数的核心是一个while(1)循环,它在一个自旋锁(host->lock)的保护下检查锁定条件。自旋锁用于保护host->claimed标志位和host->wq等待队列等共享状态,防止在检查和修改这些状态时发生竞态。
    • 等待条件: 循环的退出条件是 !host->claimed || mmc_ctx_matches(...)。这意味着,如果主机当前未被声明(claimed为0),或者声明主机的上下文与当前请求的上下文匹配(mmc_ctx_matches返回真),那么当前任务就可以获得锁。后者实现了锁的可重入性,允许同一个任务或上下文多次声明主机而不会死锁。
    • 睡眠与唤醒: 如果条件不满足(即其他上下文已持有锁),当前任务会调用schedule()放弃CPU,进入TASK_UNINTERRUPTIBLE睡眠状态。它通过add_wait_queue将自己挂载在host->wq等待队列上。当锁的持有者释放锁时,会唤醒等待队列上的所有任务,它们将重新进入循环进行条件检查。
  2. 中止机制: abort参数允许锁的获取过程被外部事件中断。例如,当一个卡被物理拔出时,另一个内核线程可以设置abort标志。在等待循环中,每次都会检查atomic_read(abort),如果发现非零值,将立即放弃获取锁并返回,避免了在设备已不存在的情况下无限期等待。

  3. 电源管理集成:

    • host->claim_cnt是一个引用计数器,用于支持可重入锁。
    • 关键逻辑在于if (host->claim_cnt == 1)。只有在主机从未被声明(计数从0变为1)的第一次成功获取锁时,才会执行pm_runtime_get_sync(mmc_dev(host))
    • pm_runtime_get_sync会同步地唤醒并恢复设备的电源。这确保了硬件只在上层驱动真正需要访问它时才上电,并在第一次claim时完成。对于后续的重入claim(计数从1变为2等),则不再需要重复上电操作,提高了效率。

代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// mmc_claim_host: 独占性地声明一个主机(简化接口)。
// @host: 需要声明的mmc主机。
// 这是一个内联函数,作为对__mmc_claim_host的简单封装,
// 提供了最常用情况下的接口(没有特殊的上下文或中止条件)。
static inline void mmc_claim_host(struct mmc_host *host)
{
__mmc_claim_host(host, NULL, NULL);
}

// __mmc_claim_host: 独占性地声明一个主机(完整实现)。
// @host: 需要声明的mmc主机。
// @ctx: 声明主机的上下文,为NULL则使用默认上下文。
// @abort: 一个原子变量指针,用于从外部中止锁的获取过程。
// 返回值: 成功获取锁则返回0,被中止则返回非零值。
int __mmc_claim_host(struct mmc_host *host, struct mmc_ctx *ctx,
atomic_t *abort)
{
// 如果没有提供特定上下文,则使用当前任务作为上下文。
struct task_struct *task = ctx ? NULL : current;
// 声明一个等待队列节点,并将其与当前任务关联。
DECLARE_WAITQUEUE(wait, current);
unsigned long flags;
int stop;
bool pm = false; // 标志是否需要执行电源管理操作。

// 告知内核锁检查器(lockdep)此函数可能会睡眠。
might_sleep();

// 将当前任务的等待节点加入到主机的等待队列中。
add_wait_queue(&host->wq, &wait);
// 获取主机的自旋锁,并保存当前中断状态、禁用本地中断。
spin_lock_irqsave(&host->lock, flags);
while (1) {
// 将当前任务状态设置为不可中断睡眠。
set_current_state(TASK_UNINTERRUPTIBLE);
// 检查是否被外部请求中止。
stop = abort ? atomic_read(abort) : 0;
// 退出循环的条件:1.被中止;2.主机未被声明;3.是同一个上下文的可重入声明。
if (stop || !host->claimed || mmc_ctx_matches(host, ctx, task))
break;
// 条件不满足,临时释放自旋锁并允许中断。
spin_unlock_irqrestore(&host->lock, flags);
// 放弃CPU,进行任务调度,任务在此处睡眠。
schedule();
// 任务被唤醒后,重新获取自旋锁并禁用中断,再次进入循环检查条件。
spin_lock_irqsave(&host->lock, flags);
}
// 退出循环后,将任务状态恢复为运行态。
set_current_state(TASK_RUNNING);
if (!stop) {
// 成功获取锁(未被中止)。
host->claimed = 1; // 标记主机已被声明。
mmc_ctx_set_claimer(host, ctx, task); // 记录声明者。
host->claim_cnt += 1; // 递增声明计数器(支持重入)。
// 如果是第一次声明(计数从0到1),则标记需要进行PM操作。
if (host->claim_cnt == 1)
pm = true;
} else
// 如果是被中止的,唤醒等待队列中的其他任务,以防它们也需要中止。
wake_up(&host->wq);
// 释放自旋锁,并恢复之前的中断状态。
spin_unlock_irqrestore(&host->lock, flags);
// 将当前任务的等待节点从主机的等待队列中移除。
remove_wait_queue(&host->wq, &wait);

// 如果标记了需要PM操作。
if (pm)
// 同步地恢复设备的运行时电源,确保硬件已上电可用。
pm_runtime_get_sync(mmc_dev(host));

return stop; // 返回中止状态。
}
// 导出符号,使得内核其他模块可以调用此函数。
EXPORT_SYMBOL(__mmc_claim_host);

MMC请求完成与调谐管理:请求生命周期的终点与信号完整性维护

本代码片段展示了MMC子系统中的两个关键功能:mmc_request_done 函数,它是所有MMC请求处理流程的终点站;以及mmc_retune_hold/release 函数对,它们实现了一个引用计数机制来管理和推迟信号调谐(retuning)过程。mmc_request_done 负责在底层硬件操作完成后,进行错误分析、状态更新,并最终通知上层调用者,而调谐管理则是在高速模式下维持总线信号完整性的重要保障。

实现原理分析

  1. 请求完成 (mmc_request_done): 这是一个由底层主机控制器驱动(Host Driver)调用的核心回调函数。其原理如下:

    • 错误检测与响应: 函数首先检查请求中各个命令(主命令、数据、停止命令)的错误码。它特别关注CRC校验错误(-EILSEQ),因为这通常是信号完整性问题的标志。如果检测到CRC错误,并且当前操作本身不是调谐命令,它会调用mmc_retune_needed()来设置一个标志,表明在处理下一个请求之前需要进行一次信号调谐。
    • 状态清理: 它负责清理MMC核心层的状态,例如将host->ongoing_mrq指针置为NULL,表示当前没有正在进行中的请求。
    • 调试与追踪: 在请求被确认为最终完成(即没有错误、不需要重试或卡已被拔出)后,函数会打印详细的调试日志,并关闭LED指示灯,为开发者提供详尽的状态信息。
    • 异步通知: 最关键的一步是调用mrq->done(mrq)回调函数(如果存在)。这是实现异步操作的核心机制。上层(如块设备层)在提交请求时可以提供这个回调函数,当硬件操作完成时,该回调被执行,从而唤醒等待的进程或触发下一个操作,而不需要上层进行阻塞轮询。
  2. 调谐保持机制 (mmc_retune_hold/release):

    • 这是一个基于引用计数的锁机制,用于控制何时可以安全地执行信号调谐。
    • mmc_retune_hold会增加host->hold_retune计数器。这通常在一个复杂操作(如多块读写)开始时调用。
    • mmc_retune_release则减少该计数器,在操作结束时调用。
    • MMC核心在准备分发新请求时,会检查host->hold_retune计数器。只要该计数器大于0,即使retune_needed标志被设置,实际的调谐操作也会被推迟。这可以防止在一次连续的数据传输过程中间插入耗时的调谐操作,保证了数据流的连续性。

代码分析

调谐保持与释放

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
// mmc_retune_hold: 增加调谐保持计数,推迟调谐操作。
void mmc_retune_hold(struct mmc_host *host)
{
// 如果这是第一次保持(计数器为0),则设置retune_now标志。
// 这可能用于在释放后立即触发一次被挂起的调谐。
if (!host->hold_retune)
host->retune_now = 1;
// 增加保持计数器。只要此计数器>0,调谐就不会在请求之间自动执行。
host->hold_retune += 1;
}

// mmc_retune_release: 减少调谐保持计数,允许调谐操作。
void mmc_retune_release(struct mmc_host *host)
{
if (host->hold_retune)
// 减少保持计数器。
host->hold_retune -= 1;
else
// 如果计数器已经为0,则说明发生了保持/释放不匹配,这是一个内核错误。
WARN_ON(1);
}
EXPORT_SYMBOL(mmc_retune_release);

static inline void mmc_retune_needed(struct mmc_host *host)
{
if (host->can_retune)
host->need_retune = 1;
}

请求完成处理

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// mmc_request_done: 完成一个MMC请求的处理。
// @host: 完成请求的MMC主机。
// @mrq: 已完成的MMC请求。
// 描述: MMC驱动在完成对一个请求的处理后,应调用此函数。
void mmc_request_done(struct mmc_host *host, struct mmc_request *mrq)
{
struct mmc_command *cmd = mrq->cmd;
int err = cmd->error;

/* 在出现CRC错误时,标记需要重新调谐 */
if (!mmc_op_tuning(cmd->opcode) && // 如果当前命令本身不是调谐命令
!host->retune_crc_disable && // 并且主机没有禁用CRC错误触发的调谐
(err == -EILSEQ || (mrq->sbc && mrq->sbc->error == -EILSEQ) || // 检查命令、SBC、数据或停止命令是否有CRC错误
(mrq->data && mrq->data->error == -EILSEQ) ||
(mrq->stop && mrq->stop->error == -EILSEQ)))
mmc_retune_needed(host); // 设置需要调谐的标志

// 对于SPI模式,如果命令是非法的,则不再进行重试。
if (err && cmd->retries && mmc_host_is_spi(host)) {
if (cmd->resp[0] & R1_SPI_ILLEGAL_COMMAND)
cmd->retries = 0;
}

// 如果当前正在进行的请求是本请求,则将其清空。
if (host->ongoing_mrq == mrq)
host->ongoing_mrq = NULL;

// 调用内部函数完成命令,这可能会唤醒等待此特定命令完成的进程。
mmc_complete_cmd(mrq);

// 记录请求完成的跟踪点,用于调试和性能分析。
trace_mmc_request_done(host, mrq);

/* 仅在请求明确完成(无错误、不重试或卡已移除)时,才进行调试打印和关闭LED等操作 */
if (!err || !cmd->retries || mmc_card_removed(host->card)) {
mmc_should_fail_request(host, mrq);

// 如果没有正在进行的请求,则关闭LED。
if (!host->ongoing_mrq)
led_trigger_event(host->led, LED_OFF);

// 打印详细的调试信息,包括命令、响应和数据传输情况。
if (mrq->sbc) {
pr_debug("%s: req done <CMD%u>: %d: %08x %08x %08x %08x\n",
mmc_hostname(host), mrq->sbc->opcode,
mrq->sbc->error,
mrq->sbc->resp[0], mrq->sbc->resp[1],
mrq->sbc->resp[2], mrq->sbc->resp[3]);
}

pr_debug("%s: req done (CMD%u): %d: %08x %08x %08x %08x\n",
mmc_hostname(host), cmd->opcode, err,
cmd->resp[0], cmd->resp[1],
cmd->resp[2], cmd->resp[3]);

if (mrq->data) {
pr_debug("%s: %d bytes transferred: %d\n",
mmc_hostname(host),
mrq->data->bytes_xfered, mrq->data->error);
}

if (mrq->stop) {
pr_debug("%s: (CMD%u): %d: %08x %08x %08x %08x\n",
mmc_hostname(host), mrq->stop->opcode,
mrq->stop->error,
mrq->stop->resp[0], mrq->stop->resp[1],
mrq->stop->resp[2], mrq->stop->resp[3]);
}
}
/*
* 请求的发起者必须处理重试。
* 此处调用完成回调函数,通知上层调用者整个请求(mrq)已结束。
*/
if (mrq->done)
mrq->done(mrq);
}

EXPORT_SYMBOL(mmc_request_done);

MMC请求处理核心:命令的准备、分发与完成机制

本代码片段是Linux内核MMC子系统的核心部分,负责处理一个MMC/SD/SDIO请求(mmc_request,简称mrq)从提交到完成的整个生命周期。它定义了MMC核心层(总线无关的逻辑)与底层主机控制器驱动(硬件相关的实现)之间的标准交互接口。其主要功能包括:对请求进行合法性检查与准备、将请求分发给主机控制器驱动执行,以及在请求完成后进行状态更新、错误处理和完成通知。

实现原理分析

该代码的执行流程体现了典型的分层驱动模型,通过函数指针结构(mmc_host_ops)实现了上层逻辑与底层硬件的解耦。

  1. 请求的准备 (mmc_mrq_prep): 在一个请求被发送到硬件之前,此函数作为“守门员”,负责进行一系列的验证和初始化。它确保请求中的命令(mmc_command)、数据(mmc_data)等部分的内部指针和状态被正确设置。最重要的是,它会根据主机控制器(mmc_host)的能力(如最大块大小max_blk_size、最大请求大小max_req_size)来校验请求参数的合法性,防止向硬件提交无法处理的请求。

  2. 请求的启动与分发 (mmc_start_request, __mmc_start_request):

    • mmc_start_request是上层(如块设备层)发起一个请求的标准入口。它是一个协调者,负责调用准备函数mmc_mrq_prep,并设置用于同步的完成量(completion)。
    • 核心的分发任务由__mmc_start_request完成。它会处理一些协议细节,如在必要时执行mmc_retune以保证信号完整性,或为SDIO设备等待card_busy状态。
    • 最终,它通过调用host->ops->request(host, mrq),将请求的控制权移交给底层的主机控制器驱动。这个ops->request函数指针是关键的抽象层,其具体实现由硬件驱动(如针对STM32的SDMMC驱动)提供。
  3. 请求的完成 (mmc_request_done):

    • 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须调用mmc_request_done来通知核心层。
    • 此函数是请求处理的终点。它负责分析命令执行后的错误码,并根据错误类型采取相应措施,例如,在发生CRC错误(-EILSEQ)时,通过mmc_retune_needed标记需要进行重新调谐。
    • 它会更新主机的状态,例如将ongoing_mrq指针清空。
    • 最关键的一步是,如果请求中设置了done回调函数(mrq->done),它会调用该函数。这构成了异步通知机制,允许最初发起请求的调用者得知操作已完成。对于同步请求,这个done回调通常会唤醒一个正在等待的完成量。

代码分析

完成命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static inline void mmc_complete_cmd(struct mmc_request *mrq)
{
if (mrq->cap_cmd_during_tfr && !completion_done(&mrq->cmd_completion))
complete_all(&mrq->cmd_completion);
}

void mmc_command_done(struct mmc_host *host, struct mmc_request *mrq)
{
if (!mrq->cap_cmd_during_tfr)
return;

mmc_complete_cmd(mrq);

pr_debug("%s: cmd done, tfr ongoing (CMD%u)\n",
mmc_hostname(host), mrq->cmd->opcode);
}
EXPORT_SYMBOL(mmc_command_done);

请求启动与分发

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
static inline void mmc_delay(unsigned int ms)
{
if (ms <= 20)
usleep_range(ms * 1000, ms * 1250);
else
msleep(ms);
}

// __mmc_start_request: 内部函数,实际启动一个请求。
static void __mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
int err;

/* 假设主机控制器已由 mmc_claim_host 恢复 */
// 首先尝试重新调谐,如果失败则直接完成请求并返回错误。
err = mmc_retune(host);
if (err) {
mrq->cmd->error = err;
mmc_request_done(host, mrq);
return;
}

/* 对于某些SDIO读写命令,必须等待卡不处于繁忙状态 */
if (sdio_is_io_busy(mrq->cmd->opcode, mrq->cmd->arg) &&
host->ops->card_busy) {
int tries = 500; /* 最多等待约500ms */

// 调用底层驱动提供的card_busy函数检查状态。
while (host->ops->card_busy(host) && --tries)
mmc_delay(1);

// 如果等待超时,则以-EBUSY错误完成请求。
if (tries == 0) {
mrq->cmd->error = -EBUSY;
mmc_request_done(host, mrq);
return;
}
}

if (mrq->cap_cmd_during_tfr) {
host->ongoing_mrq = mrq;
// 确保重试路径也能重新初始化完成量。
reinit_completion(&mrq->cmd_completion);
}

trace_mmc_request_start(host, mrq);

if (host->cqe_on)
host->cqe_ops->cqe_off(host);

// 调用底层主机控制器驱动提供的request函数,将请求分发到硬件。
host->ops->request(host, mrq);
}

// mmc_start_request: 启动一个MMC请求(上层API)。
int mmc_start_request(struct mmc_host *host, struct mmc_request *mrq)
{
int err;

// ... (处理扩展地址)
if (mrq->cmd->has_ext_addr)
mmc_send_ext_addr(host, mrq->cmd->ext_addr);

// 初始化用于命令完成的完成量。
init_completion(&mrq->cmd_completion);

// 保持调谐状态。
mmc_retune_hold(host);

// 如果卡已被移除,返回错误。
if (mmc_card_removed(host->card))
return -ENOMEDIUM;

// 打印调试信息。
mmc_mrq_pr_debug(host, mrq, false);

// 确保主机已被声明(claimed)。
WARN_ON(!host->claimed);

// 准备请求,进行合法性校验。
err = mmc_mrq_prep(host, mrq);
if (err)
return err;

// ... (UHS-II 准备)
if (host->uhs2_sd_tran)
mmc_uhs2_prepare_cmd(host, mrq);

// 点亮LED,表示活动状态。
led_trigger_event(host->led, LED_FULL);
// 调用内部函数分发请求。
__mmc_start_request(host, mrq);

return 0;
}
EXPORT_SYMBOL(mmc_start_request);

请求准备与同步封装

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
// mmc_mrq_prep: 准备一个MMC请求,进行校验和初始化。
static int mmc_mrq_prep(struct mmc_host *host, struct mmc_request *mrq)
{
// ... (初始化命令、数据等结构中的error和mrq指针)
if (mrq->cmd) { //...
mrq->cmd->error = 0;
mrq->cmd->mrq = mrq;
mrq->cmd->data = mrq->data;
}
if (mrq->sbc) { //...
mrq->sbc->error = 0;
mrq->sbc->mrq = mrq;
}
if (mrq->data) {
// 校验请求的数据大小是否在主机能力范围之内。
if (mrq->data->blksz > host->max_blk_size ||
mrq->data->blocks > host->max_blk_count ||
mrq->data->blocks * mrq->data->blksz > host->max_req_size)
return -EINVAL;

// ... (校验scatterlist的总长度是否与预期相符)
for_each_sg(mrq->data->sg, sg, mrq->data->sg_len, i)
sz += sg->length;
if (sz != mrq->data->blocks * mrq->data->blksz)
return -EINVAL;

// 初始化数据传输相关的状态。
mrq->data->error = 0;
mrq->data->mrq = mrq;
if (mrq->stop) { //...
mrq->data->stop = mrq->stop;
mrq->stop->error = 0;
mrq->stop->mrq = mrq;
}
}

return 0;
}

// mmc_wait_done: 一个通用的完成回调函数,用于同步等待。
static void mmc_wait_done(struct mmc_request *mrq)
{
// 唤醒在mrq->completion上等待的进程。
complete(&mrq->completion);
}

static inline void mmc_wait_ongoing_tfr_cmd(struct mmc_host *host)
{
struct mmc_request *ongoing_mrq = READ_ONCE(host->ongoing_mrq);

/*
* If there is an ongoing transfer, wait for the command line to become
* available.
*/
if (ongoing_mrq && !completion_done(&ongoing_mrq->cmd_completion))
wait_for_completion(&ongoing_mrq->cmd_completion);
}

// __mmc_start_req: 封装了启动一个同步请求的逻辑。
static int __mmc_start_req(struct mmc_host *host, struct mmc_request *mrq)
{
int err;

// ... (等待可能正在进行的传输命令)
mmc_wait_ongoing_tfr_cmd(host);

// 初始化用于同步等待的完成量。
init_completion(&mrq->completion);
// 将请求的完成回调设置为mmc_wait_done。
mrq->done = mmc_wait_done;

// 启动请求。
err = mmc_start_request(host, mrq);
if (err) {
// 如果启动失败,则手动完成请求流程以唤醒等待者。
mrq->cmd->error = err;
mmc_complete_cmd(mrq);
complete(&mrq->completion);
}

return err;
}

MMC命令发送与阻塞等待

本代码片段定义了MMC核心子系统中用于发送命令并同步等待其完成的高级接口:mmc_wait_for_cmd 及其依赖的 mmc_wait_for_req。其核心功能是为上层驱动提供一个简化的、阻塞式的编程模型。驱动程序只需构建一个命令,调用这些函数,函数将在命令完成后才返回。这极大地简化了驱动的编写,隐藏了底层请求提交、中断处理和任务睡眠/唤醒的复杂细节。

实现原理分析

此功能的实现建立在MMC核心的异步请求机制之上,并将其封装为同步调用。它体现了清晰的层次结构:一个命令(mmc_command)被包装在一个请求(mmc_request)中进行处理。

  1. 请求(Request)与命令(Command)的抽象:

    • mmc_command (cmd): 代表一个单独的MMC命令,包含操作码、参数、期望的响应类型等。
    • mmc_request (mrq): 代表一个完整的MMC操作,它通常包含一个命令(mrq.cmd),并且可能还包含一个数据传输(mrq.data)和一个停止命令(mrq.stop)。本代码片段处理的是最简单的情况:一个只包含单个命令的请求。
  2. 同步封装 (mmc_wait_for_req):

    • 此函数首先调用__mmc_start_req(host, mrq)。这个内部函数负责将请求mrq提交给底层的主机控制器驱动,并启动硬件操作。这个调用是非阻塞的,它会立即返回。
    • 关键在于后续的 if (!mrq->cap_cmd_during_tfr) 判断。在普通命令中,这个标志位为假,因此会立即调用mmc_wait_for_req_done(host, mrq)
    • mmc_wait_for_req_done 是一个阻塞函数。它内部会使用等待队列(wait queue)机制,使当前任务进入睡眠状态,直到硬件完成命令并产生中断。中断服务程序会标记请求已完成并唤醒等待的任务。
  3. 便利的命令封装 (mmc_wait_for_cmd):

    • 这是一个更高级的便利函数,专门用于发送不涉及数据传输的单个命令(例如,SDIO CMD52)。
    • 它在函数栈上创建一个临时的mmc_request结构体mrq
    • WARN_ON(!host->claimed) 是一个重要的断言,确保上层驱动在发送命令前已经通过mmc_claim_host获取了总线锁。
    • 它将用户传入的cmd指针放入mrq.cmd中,并将cmd->data明确设置为NULL,表示这是一个没有数据阶段的请求。
    • 最后,它调用mmc_wait_for_req来同步地执行这个只包含命令的请求。
    • 函数返回cmd->error,这个字段会在底层请求处理过程中,根据命令的执行结果(如超时、CRC错误等)被填充。

代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// mmc_wait_for_req: 启动一个请求并等待其完成。
// @host: MMC主机控制器。
// @mrq: 要启动的MMC请求。
// 描述:
// 为一个主机启动一个新的MMC请求,并等待其完成。
// 对于支持 'cap_cmd_during_tfr' 的特殊请求,此函数仅启动传输,
// 调用者可以继续发送不使用数据线的命令,然后通过调用mmc_wait_for_req_done()来等待。
void mmc_wait_for_req(struct mmc_host *host, struct mmc_request *mrq)
{
// 调用内部函数 __mmc_start_req 来提交请求并启动硬件。
// 这是一个非阻塞调用。
__mmc_start_req(host, mrq);

// 如果请求不是 "传输期间可发命令" 的特殊类型。
if (!mrq->cap_cmd_during_tfr)
// 则立即调用 mmc_wait_for_req_done 来阻塞等待请求完成。
mmc_wait_for_req_done(host, mrq);
}
// 导出符号,供内核其他模块使用。
EXPORT_SYMBOL(mmc_wait_for_req);

// mmc_wait_for_cmd: 启动一个命令并等待其完成。
// @host: MMC主机控制器。
// @cmd: 要启动的MMC命令。
// @retries: 最大重试次数。
// 描述:
// 这是一个便利函数,用于同步发送单个不带数据的命令。
// 它返回命令执行期间发生的任何错误。
int mmc_wait_for_cmd(struct mmc_host *host, struct mmc_command *cmd, int retries)
{
struct mmc_request mrq = {}; // 在函数栈上创建一个MMC请求结构体。

// 警告:确保在发送命令前,主机已经被声明(加锁)。
// 这是一个重要的正确性检查。
WARN_ON(!host->claimed);

// 清空命令的响应缓冲区,以防含有旧数据。
memset(cmd->resp, 0, sizeof(cmd->resp));
// 设置命令的重试次数。
cmd->retries = retries;

// 将命令包装在请求中。
mrq.cmd = cmd;
// 明确指出此命令没有关联的数据传输。
cmd->data = NULL;

// 调用mmc_wait_for_req来同步执行这个只包含命令的请求。
mmc_wait_for_req(host, &mrq);

// 请求完成后,命令结构体中的error字段已被底层驱动填充。
// 将此错误码返回给调用者。
return cmd->error;
}
// 导出符号,供内核其他模块使用。
EXPORT_SYMBOL(mmc_wait_for_cmd);

drivers/mmc/core/bus.c MMC总线驱动(MMC Bus Driver) 连接MMC/SD卡设备与功能驱动的桥梁

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/mmc/core/bus.c 的诞生是为了在MMC子系统中实现Linux驱动模型的核心思想:将设备(Device)的发现与驱动(Driver)的绑定过程分离开来

在MMC子系统的三层架构(Host -> Core -> Card)中,MMC核心(Core)负责与卡片通信并识别其类型(是SD存储卡、eMMC还是SDIO Wi-Fi卡)。但识别出卡片后,需要一个机制来为这张卡找到并加载正确的功能驱动(例如,为存储卡加载块设备驱动,为Wi-Fi卡加载网络驱动)。

bus.c 就是这个机制的实现。它创建了一个名为 mmc 的新总线类型(Bus Type),将MMC卡抽象为挂载在这条总线上的“设备”,并将功能驱动抽象为这条总线上的“驱动”,然后负责将两者“匹配”(match)起来。这解决了以下问题:

  • 关注点分离:让MMC核心专注于协议和卡片识别,而让功能驱动(如card/block.c)专注于实现其特定功能(如块设备I/O),两者通过bus.c这个中间层解耦。
  • 标准化:为所有类型的MMC/SD/SDIO卡提供了一个统一的驱动注册和设备绑定模型,遵循了Linux设备模型的标准范式。
  • 可扩展性:当需要支持一种新型的SDIO设备时,开发者只需编写一个遵循MMC总线规范的新功能驱动,而无需改动核心代码。

它的发展经历了哪些重要的里程碑或版本迭代?

bus.c 本身是MMC子系统核心框架的一部分,其发展与MMC核心的演进紧密相连。

  • 框架确立:其最重要的里程碑是作为MMC核心的一部分被创建出来,正式将MMC子系统纳入了Linux的标准化总线模型。
  • SDIO支持的完善:随着SDIO变得越来越重要,bus.c 的逻辑需要支持更复杂的场景。一张SDIO卡可以有多个功能(Function),例如一个Wi-Fi加蓝牙的“combo”卡。MMC总线需要能正确地为每个功能创建设备,并将其与对应的功能驱动匹配。这通常涉及到与 drivers/mmc/core/sdio_bus.c 的协作,后者在MMC总线之上建立了一个专门的SDIO功能总线。
  • 电源管理集成:为mmc_bus_type添加了电源管理回调函数(.suspend, .resume),使得MMC设备可以参与到整个系统的休眠与唤醒流程中。

目前该技术的社区活跃度和主流应用情况如何?

bus.c 是MMC子系统的基石,其代码非常稳定,不会频繁变动。它的正确运行是所有使用SD卡、eMMC、SDIO设备的Linux系统的基础。社区的开发活动更多地集中在Host驱动和具体的Card功能驱动上,而bus.c作为底层的粘合剂,默默地支撑着整个体系的运转。

核心原理与设计

它的核心工作原理是什么?

bus.c 的核心工作是定义并实现 mmc_bus_type,这是一个 struct bus_type 实例,它告诉内核如何管理MMC总线上的设备和驱动。

其工作流程如下:

  1. 总线注册:在模块初始化时,bus.c 调用 bus_register(&mmc_bus_type),在内核中注册“mmc”这条总线。这会在sysfs中创建 /sys/bus/mmc 目录。
  2. 设备发现与注册(由Core触发):这个过程不由bus.c发起。当MMC核心(core.c)通过 mmc_rescan() 函数检测到一张卡并成功初始化后,它会为一个 struct mmc_card 分配内存,并调用 mmc_add_card()mmc_add_card() 会调用 device_add(),将这张卡作为一个 struct device 注册到内核,并挂在 mmc_bus_type 上。
  3. 驱动注册(由功能驱动触发):一个功能驱动,比如块设备驱动(card/block.c),会在其初始化时调用 mmc_register_driver(),将自己(一个struct mmc_driver)注册到 mmc_bus_type 上。
  4. 匹配过程(Matchmaking):每当有新的设备或新的驱动注册到 mmc 总线上时,总线核心就会尝试进行匹配。它会调用 mmc_bus_type 中指定的 .match 函数,即 mmc_bus_match()
  5. mmc_bus_match() 的逻辑:这个函数非常核心。它会比较驱动程序所支持的设备ID列表(driver->id_table)和当前设备的ID(card->cidcard->type等信息)。如果找到匹配项,函数返回true
  6. 探测(Probing):一旦 .match() 返回成功,总线核心就会调用匹配到的驱动的 .probe 函数(例如 mmc_block_probe())。.probe 函数负责执行所有功能相关的初始化,比如为存储卡创建块设备节点 /dev/mmcblkX
  7. 移除过程:当卡被拔出或驱动被卸载时,会触发相反的过程:调用驱动的 .remove 函数,然后设备和驱动会从总线上解除绑定并注销。

它的主要优势体现在哪些方面?

  • 完全遵循驱动模型:完美地将设备和驱动解耦,是Linux驱动模型的一个经典实现。
  • 自动化绑定:内核可以自动完成设备和驱动的匹配与加载,支持热插拔。
  • 清晰的层次结构:明确了MMC核心、总线和功能驱动之间的界限和交互方式。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 抽象带来的复杂性:对于初学者来说,这种通过总线进行的间接调用(device_add() -> bus_match() -> driver->probe())比直接函数调用更难跟踪和理解。
  • 非通用性bus.c 的逻辑是为MMC/SD/SDIO协议量身定做的,其匹配规则(基于CID等卡信息)对其他类型的总线没有意义。它本身没有劣势,只是其设计具有高度的特异性。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

bus.c 是MMC子系统内部的强制性组件,而不是一个可选项。它的“使用场景”就是MMC子系统工作的每一个瞬间。

  • 场景一:插入一张SD卡
    1. Host驱动检测到卡插入,通知MMC核心。
    2. MMC核心(core.c)执行卡初始化序列,识别出这是一张SD存储卡。
    3. 核心调用 mmc_add_card(),将卡注册为一个mmc总线设备。
    4. bus.cmmc_bus_match() 被调用,它发现这张SD卡与 mmc_block_driver 兼容。
    5. mmc_block_driver.probe 函数被调用,创建 /dev/mmcblk0
  • 场景二:系统启动时识别eMMC
    1. 系统启动时,eMMC的Host驱动被加载。
    2. 它调用MMC核心的扫描函数。
    3. 核心识别出焊接在主板上的eMMC芯片。
    4. 后续流程与场景一完全相同,bus.c 负责将其与块设备驱动绑定,最终创建出代表eMMC分区的设备节点,如 /dev/mmcblk1p1

是否有不推荐使用该技术的场景?为什么?

不存在不推荐使用bus.c的场景。只要一个设备使用MMC/SD/SDIO协议栈,它就必须通过bus.c所定义的mmc_bus_type来与功能驱动进行交互。绕过这个机制就等于破坏了整个MMC子系统的设计。

对比分析

请将其 与 其他总线实现(如PCI, USB)进行详细对比。

mmc/core/bus.cdrivers/pci/pci-driver.cdrivers/usb/core/driver.c目标上是完全一致的:实现一个标准化的总线模型。但它们在实现细节,特别是设备枚举和匹配方式上,反映了各自总线的物理和协议特性。

特性 MMC Bus (bus.c) PCI Bus USB Bus
总线拓扑 简单,通常是点对点(一个Host控制器一个插槽)。没有复杂的树状或网状结构。 树状层次结构。有根总线(Root Complex)、桥(Bridge)和端点设备(Endpoint)。 树状层次结构。有根集线器(Root Hub)、外部集线器(Hub)和功能设备。
设备枚举方式 软件协议驱动。由MMC核心发送一系列标准命令(如CMD2, CMD3)与卡片进行交互,卡片会回复其CID/CSD等身份信息。 硬件扫描。由内核读取标准化的PCI配置空间(Configuration Space)。每个设备都有唯一的Vendor ID和Device ID。 软件协议驱动。设备插入后,由Host Controller进行端口复位和地址分配,然后内核读取设备返回的一系列标准描述符(Device, Configuration, Interface Descriptors)。
匹配规则 (.match) 基于MMC/SD卡内部寄存器信息(如CID中的制造商ID、产品名)和卡类型(SD, MMC, SDIO)。 主要基于PCI配置空间中的Vendor ID, Device ID, Class Code等。 主要基于USB描述符中的Vendor ID, Product ID, Class/SubClass/Protocol等。
设备地址 由MMC核心在逻辑上管理,没有固定的硬件地址。 由总线号(Bus)、设备号(Device)、功能号(Function)组成的唯一硬件地址。 由总线分配的动态设备地址(1-127)。

MMC总线类型定义:连接卡设备与驱动的桥梁

本代码片段定义了Linux内核中mmc总线的核心行为。它通过填充一个bus_type结构体(mmc_bus_type),实现了MMC设备(如SD卡、eMMC芯片)在内核设备模型中的探测(probe)、移除(remove)、关机(shutdown)和电源管理等关键回调函数。此外,它还负责通过uevent机制向用户空间(userspace)通告设备信息,并创建了sysfs属性文件(如type),以展示卡的类型。这部分代码是MMC核心的基础,它使得具体的MMC卡驱动(如mmc_block)能够与具体的MMC卡设备进行匹配和绑定。

实现原理分析

此代码的核心是mmc_bus_type结构体的实例化。这个结构体是Linux设备模型中用于描述一种总线的标准方式,它通过函数指针将总线级别的通用操作与设备模型核心连接起来。

  1. Probe/Remove/Shutdown 代理: mmc_bus_probemmc_bus_removemmc_bus_shutdown函数扮演了“代理”或“中间人”的角色。当设备模型核心决定要为一个MMC卡设备probe一个MMC驱动时,它会调用mmc_bus_probe。这个函数并不执行设备特定的初始化,而是简单地从通用device结构中提取出mmc_cardmmc_driver结构,然后调用mmc_driver结构中真正的probe函数指针。这种设计将总线的通用逻辑与驱动的具体实现解耦。

  2. Uevent事件生成: mmc_bus_uevent函数是实现设备自动配置的关键。当一个MMC卡被识别并注册到总线上时,内核会调用此函数来生成一个uevent事件。该函数将卡的详细信息(如类型、名称、SDIO ID等)打包成环境变量,发送给用户空间的udevdmdev等守护进程。其中最关键的一行是add_uevent_var(env, "MODALIAS=mmc:block"),它生成了一个标准的模块别名(MODALIAS)。用户空间守护进程看到这个别名后,会执行modprobe mmc:block,从而自动加载处理MMC块设备的内核模块(mmc_block.ko)。

  3. Sysfs属性: type_show函数和DEVICE_ATTR_RO(type)宏一起工作,在sysfs中为每个MMC设备创建一个名为type的只读文件。当用户通过cat /sys/bus/mmc/devices/mmc0:xxxx/type读取该文件时,type_show函数会被调用,它会根据卡的类型返回一个可读的字符串(如”SD”, “MMC”)。

  4. 总线注册: mmc_register_bus函数非常简单,它只是调用了设备模型核心提供的bus_register函数,将mmc_bus_type结构体注册到内核中,从而正式“激活”了MMC总线。

代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// type_show: sysfs属性 "type" 的读取回调函数。
// @dev: 指向设备结构体的指针。
// @attr: 指向设备属性结构体的指针。
// @buf: 用于存放输出字符串的缓冲区。
// 返回值: 成功则为写入缓冲区的字节数,失败则为负数错误码。
static ssize_t type_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct mmc_card *card = mmc_dev_to_card(dev);

// 根据卡的类型,向sysfs缓冲区输出对应的字符串。
switch (card->type) {
case MMC_TYPE_MMC:
return sysfs_emit(buf, "MMC\n");
case MMC_TYPE_SD:
return sysfs_emit(buf, "SD\n");
case MMC_TYPE_SDIO:
return sysfs_emit(buf, "SDIO\n");
case MMC_TYPE_SD_COMBO:
return sysfs_emit(buf, "SDcombo\n");
default:
return -EFAULT;
}
}
// 使用宏定义一个名为 "type" 的只读设备属性。
static DEVICE_ATTR_RO(type);

// 定义一个包含所有MMC设备属性的数组。
static struct attribute *mmc_dev_attrs[] = {
&dev_attr_type.attr,
NULL, // 数组以NULL结尾。
};
// 使用宏将属性数组定义为一个属性组。
ATTRIBUTE_GROUPS(mmc_dev);

// mmc_bus_uevent: MMC总线的uevent事件生成函数。
// @dev: 产生事件的设备。
// @env: uevent环境变量。
// 返回值: 成功则为0,失败则为负数错误码。
static int
mmc_bus_uevent(const struct device *dev, struct kobj_uevent_env *env)
{
const struct mmc_card *card = mmc_dev_to_card(dev);
const char *type;
unsigned int i;
int retval = 0;

// 根据卡类型设置字符串。
switch (card->type) {
// ... (类型转换) ...
}

// 添加 MMC_TYPE 环境变量。
if (type) {
retval = add_uevent_var(env, "MMC_TYPE=%s", type);
if (retval)
return retval;
}

// 如果是SDIO或SD Combo卡,添加SDIO相关的ID信息。
if (mmc_card_sdio(card) || mmc_card_sd_combo(card)) {
retval = add_uevent_var(env, "SDIO_ID=%04X:%04X",
card->cis.vendor, card->cis.device);
// ... (添加其他SDIO信息) ...
}

/* 纯SDIO卡不由mmc_block驱动处理,因此uevent信息到此为止。 */
if (mmc_card_sdio(card))
return 0;

// 添加 MMC_NAME 环境变量,即卡的名称。
retval = add_uevent_var(env, "MMC_NAME=%s", mmc_card_name(card));
if (retval)
return retval;

/*
* 请求加载mmc_block模块。
* 这会生成一个 MODALIAS=mmc:block 的uevent变量,
* 用户空间的udev会响应该事件并尝试加载mmc_block.ko模块。
*/
retval = add_uevent_var(env, "MODALIAS=mmc:block");

return retval;
}

// mmc_bus_probe: MMC总线的probe回调函数。
// @dev: 需要进行probe的设备。
// 返回值: 来自具体驱动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);

// 直接调用具体MMC驱动的probe函数。
return drv->probe(card);
}

// mmc_bus_remove: MMC总线的remove回调函数。
// @dev: 需要移除的设备。
static void mmc_bus_remove(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);

// 调用具体MMC驱动的remove函数。
drv->remove(card);
}

// mmc_bus_shutdown: MMC总线的shutdown回调函数。
// @dev: 需要关闭的设备。
static void mmc_bus_shutdown(struct device *dev)
{
struct mmc_driver *drv = to_mmc_driver(dev->driver);
struct mmc_card *card = mmc_dev_to_card(dev);
struct mmc_host *host = card->host;
int ret;

// 如果驱动实现了shutdown,则调用它。
if (dev->driver && drv->shutdown)
drv->shutdown(card);

// 停止MMC主机。
__mmc_stop_host(host);

// 如果主机控制器驱动实现了shutdown,则调用它。
if (host->bus_ops->shutdown) {
ret = host->bus_ops->shutdown(host);
if (ret)
pr_warn("%s: error %d during shutdown\n",
mmc_hostname(host), ret);
}
}

// 定义MMC总线的电源管理操作集。
static const struct dev_pm_ops mmc_bus_pm_ops = {
SET_RUNTIME_PM_OPS(mmc_runtime_suspend, mmc_runtime_resume, NULL)
SET_SYSTEM_SLEEP_PM_OPS(mmc_bus_suspend, mmc_bus_resume)
};

// 定义MMC总线类型结构体。
static const struct bus_type mmc_bus_type = {
.name = "mmc", // 总线名称,会出现在 /sys/bus/mmc
.dev_groups = mmc_dev_groups, // 设备的sysfs属性组
.uevent = mmc_bus_uevent, // uevent事件处理函数
.probe = mmc_bus_probe, // probe回调
.remove = mmc_bus_remove, // remove回调
.shutdown = mmc_bus_shutdown, // shutdown回调
.pm = &mmc_bus_pm_ops, // 电源管理回调
};

// mmc_register_bus: 注册MMC总线。
// 返回值: 成功则为0,失败则为负数错误码。
int mmc_register_bus(void)
{
// 调用设备模型核心函数来注册总线类型。
return bus_register(&mmc_bus_type);
}

drivers/mmc/core/host.c MMC主机控制器管理(MMC Host Controller Management) 所有主机驱动的注册与管理中心

历史与背景

这项技术是为了解决什么特定问题而诞生的?

drivers/mmc/core/host.c 是MMC子系统三层架构(Host - Core - Card)中承上启下的关键文件。它的存在是为了解决如何将种类繁多的物理MMC/SD主机控制器(Host Controller)硬件以一种标准化的方式接入到MMC核心协议层的问题。

host.c出现之前,或者说在没有这种抽象层的情况下,会遇到以下问题:

  • 缺乏统一接口:每种主机控制器(例如,由Synopsys、Cadence或特定SoC厂商设计的IP核)都有其独特的寄存器和工作方式。MMC核心协议层(core.c)如果需要直接与这些硬件交互,代码将会变得极其臃肿和混乱,充满了针对特定硬件的if/else分支。
  • 代码重复:每个主机控制器驱动都需要自己实现与MMC核心的对接逻辑,导致大量重复代码。
  • 开发困难:为一款新的主机控制器编写驱动将是一项非常复杂的工作,开发者不仅要理解硬件,还要深入了解MMC核心的内部工作机制。

host.c通过定义一个标准的“主机”抽象 (struct mmc_host) 和一套操作接口 (struct mmc_host_ops),完美地解决了这些问题。它充当了所有具体主机驱动和通用MMC核心协议层之间的适配器(Adapter)和管理中心

它的发展经历了哪些重要的里程碑或版本迭代?

作为MMC子系统的基础组件,host.c的演进与整个子系统的发展同步。

  • 框架的建立:最重要的里程碑是struct mmc_hostmmc_host_ops的定义,以及mmc_alloc_host/mmc_add_host等生命周期管理函数的创建。这确立了所有主机驱动必须遵循的开发范式。
  • 功能和能力的扩展:随着MMC/SD标准的发展,struct mmc_host中增加了越来越多的能力标志(capscaps2字段),以向核心层宣告其支持的特性,例如支持4/8位总线宽度、支持DDR模式、支持HS200/HS400高速模式、支持CMD23(SET_BLOCK_COUNT命令)等。mmc_host_ops也相应增加了新的回调函数。
  • devres集成:引入了devm_mmc_alloc_host(),利用内核的设备资源管理(devres)框架,使得主机驱动的资源管理(特别是内存分配与释放)变得更加简单和安全,减少了代码量并防止了内存泄漏。
  • 电源管理:为主机控制器自身增加了标准的电源管理支持,允许主机控制器在不使用时进入低功耗状态。

目前该技术的社区活跃度和主流应用情况如何?

host.c的代码非常稳定,是MMC子系统中不经常发生根本性改变的部分。然而,它又是绝对核心的,因为每一个MMC主机驱动(位于drivers/mmc/host/目录下)都必须依赖它提供的API来工作。社区的活跃度更多体现在基于这个框架开发新的主机驱动,或者为主机能力集添加新的特性支持。它是所有使用MMC/SD/eMMC/SDIO功能的Linux系统的基础。

核心原理与设计

它的核心工作原理是什么?

host.c的核心原理是接口与实现分离。它定义了“一个MMC主机应该是什么样子以及应该能做什么”(接口),而将“具体怎么做”(实现)的任务留给了具体的硬件驱动。

其工作流程和核心组件如下:

  1. 主机抽象 (struct mmc_host):这是描述一个MMC主机控制器的核心数据结构。它包含了主机的所有信息,如:

    • ops: 一个指向mmc_host_ops结构体的指针,包含了所有操作的回调函数。
    • caps, caps2: 描述硬件能力的位掩码。
    • ios: 描述当前总线状态(时钟、电压、总线宽度等)。
    • card: 指向当前插槽中被识别的卡的指针。
  2. 操作接口 (struct mmc_host_ops):这是一个函数指针的集合,是具体主机驱动必须实现的“合同”。关键操作包括:

    • .request(): 核心函数,MMC核心通过它向主机驱动提交一个数据传输请求(struct mmc_request)。
    • .set_ios(): MMC核心通过它来命令主机驱动改变总线状态(如调整时钟频率、设置总线宽度)。
    • .get_ro(): 用于检测卡是否处于只读状态(通过硬件引脚)。
    • .get_cd(): 用于检测卡是否存在(通过硬件引脚)。
  3. 生命周期管理host.c提供了一套标准的API来管理mmc_host对象的生命周期:

    • mmc_alloc_host(): 一个主机驱动在其.probe函数中首先调用此函数,来分配一个mmc_host结构体和驱动的私有数据区。
    • mmc_add_host(): 驱动在配置好mmc_host(特别是填充了opscaps)之后,调用此函数将主机注册到MMC核心。这是一个关键步骤,一旦调用成功,MMC核心就会接管这个主机,并立即触发一次总线扫描 (mmc_rescan) 来检测卡片。
    • mmc_remove_host(): 在驱动卸载时调用,将主机从核心中注销。
    • mmc_free_host(): 释放mmc_host结构体占用的内存。

它的主要优势体现在哪些方面?

  • 高度抽象和解耦:MMC核心协议层完全无需关心底层硬件的细节,它只与标准的mmc_host接口交互。
  • 简化驱动开发:为主机驱动开发者提供了一个清晰的、必须实现的接口(mmc_host_ops),极大地降低了开发门槛。
  • 代码复用:所有主机的公共管理逻辑都集中在host.c中,被所有驱动共享。
  • 可维护性:结构清晰,当需要为所有主机增加一个新功能时,可能只需要修改host.ccore.c,而无需改动成百上千个具体的主机驱动。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 接口的刚性mmc_host_ops定义了一套固定的接口,如果某个硬件有一些非常规的、无法通过现有opscaps描述的特殊功能,就很难优雅地将其集成进来。但这在实践中非常罕见。
  • 间接调用的复杂性:对于调试和代码跟踪来说,通过函数指针(host->ops->request())进行的间接调用比直接调用更难跟踪。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?

host.c提供的框架是为MMC/SD/eMMC主机控制器编写Linux驱动的唯一且强制的解决方案。任何想要将其硬件集成到Linux MMC子系统的芯片厂商或开发者,都必须使用它。

一个典型的主机驱动开发流程就是其使用场景的完美展示:

  1. 在驱动的.probe函数中,首先调用mmc_alloc_host()
  2. 获取到mmc_host指针后,填充其成员:
    • 设置host->ops指向驱动自己实现的静态mmc_host_ops实例。
    • 根据硬件寄存器的值,设置host->capshost->caps2来宣告硬件能力。
    • 设置host->f_min, host->f_max等频率范围。
  3. 完成所有硬件相关的初始化(如申请中断、映射寄存器)。
  4. 最后,调用mmc_add_host(),将控制器“上线”并交由MMC核心管理。

是否有不推荐使用该技术的场景?为什么?

不存在。只要设备是一个MMC、SD、eMMC或SDIO主机控制器,并且需要在Linux下工作,就必须遵循host.c定义的这套框架。

对比分析

请将其 与 其他总线的主机控制器管理框架(如USB HCD, SCSI Host)进行详细对比。

mmc/core/host.c的角色与USB子系统中的主机控制器驱动(HCD)层和SCSI子系统中的**SCSI主机模板(SCSI Host Template)**非常相似。它们的目标都是一样的:抽象底层硬件,为上层协议提供标准接口。

特性 MMC Host (host.c) USB HCD (Host Controller Driver) SCSI Host Template
核心抽象 struct mmc_host struct usb_hcd struct Scsi_Host
操作接口 struct mmc_host_ops struct hc_driver struct scsi_host_template
协议复杂度 中等。处理命令-响应、数据传输、总线状态。拓扑简单(点对点)。 高。处理复杂的USB协议,包括控制、批量、中断、同步四种传输类型,以及树状的设备拓扑(Hubs和设备)。 非常高。处理SCSI命令集(CDBs),支持复杂的命令队列、错误处理和多种设备类型(磁盘、磁带、光驱等)。
交互单元 struct mmc_request (包含命令mmc_command和数据mmc_data) struct urb (USB Request Block) struct scsi_cmnd (SCSI Command)
注册/注销 mmc_add_host / mmc_remove_host usb_add_hcd / usb_remove_hcd scsi_add_host / scsi_remove_host
主要关注点 总线时序、电压、速度模式和卡状态机。 端点(Endpoint)、带宽管理、帧调度和拓扑管理。 命令队列、LUN(逻辑单元)管理、错误恢复。

总结:这三者在设计哲学上是相通的,都采用了“框架 + 驱动实现”的模式。它们的区别主要源于所服务的总线协议的内在差异。MMC总线相对简单,所以mmc_host的抽象也相对直接;而USB和SCSI的协议和设备模型要复杂得多,因此它们的HCD和Host Template框架也更为庞大和复杂。

MMC主机控制器类:sysfs中的统一视图与生命周期管理

本代码片段定义并注册了一个名为mmc_host的设备类(Device Class)。其核心功能是在Linux的sysfs中创建/sys/class/mmc_host/目录,并为所有MMC主机控制器(如mmc0, mmc1等)提供一个统一的、与物理总线无关的视图。同时,它还定义了属于该类的所有设备的通用生命周期管理回调函数,特别是资源释放(release)和系统关机前(pre-shutdown)的处理逻辑。

实现原理分析

此代码是Linux设备模型中“类”概念的一个典型应用。struct class作为一种高层抽象,用于将功能相似的设备组织在一起。

  1. 类定义 (mmc_host_class):

    • 一个静态的struct class实例被定义,其.name字段被设置为"mmc_host",这决定了在sysfs中创建的目录名。
    • .dev_release回调: mmc_host_classdev_release函数被指定为该类的释放回调。当一个mmc_host设备(例如mmc0)的最后一个引用被释放时,设备模型核心会调用此函数。它的职责是完成最终的清理工作,包括注销唤醒源(wakeup source)、释放动态分配的主机索引号(ID),以及释放mmc_host结构体本身占用的内存。这是防止内存泄漏的关键步骤。
    • .shutdown_pre回调: mmc_host_classdev_shutdown函数被指定为系统关机前的回调。在系统关机过程中,设备模型会遍历该类的所有设备,并调用此函数。它的任务是调用__mmc_stop_host,将MMC主机控制器硬件置于一个安全、静默的状态,确保在系统断电前不会有意外的总线活动。
  2. 类注册 (mmc_register_host_class):

    • 这个函数非常简单,它只调用了设备模型提供的核心API class_register(),将mmc_host_class结构体注册到内核中。一旦注册成功,/sys/class/mmc_host目录就会被创建,并且具体的MMC主机控制器驱动就可以通过device_create()等函数将其设备注册到这个类中。
  3. 动态ID管理: mmc_host_classdev_release函数中的ida_free逻辑展示了对主机索引号的动态管理。当一个主机控制器驱动(如STM32的SDMMC驱动)注册时,它会通过ida_alloc从一个全局的ID分配器(mmc_host_ida)中获取一个唯一的索引号(如0, 1, 2…),除非设备树中通过别名(alias)指定了静态ID。在设备被释放时,此处的代码负责将动态分配的ID归还给ID池,以备后续使用。

代码分析

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// mmc_host_classdev_release: mmc_host类设备的释放回调函数。
// 当一个mmc_host设备的引用计数降为0时,此函数被调用。
// @dev: 指向被释放的设备结构体的指针。
static void mmc_host_classdev_release(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);

// 注销与此主机关联的唤醒源。
wakeup_source_unregister(host->ws);

// 检查此主机的索引号是否是通过设备树别名静态分配的。
// 如果不是(of_alias_get_id返回负值),说明是动态分配的,需要释放。
if (of_alias_get_id(host->parent->of_node, "mmc") < 0)
// 使用IDA(ID Allocator)框架释放动态分配的主机索引号。
ida_free(&mmc_host_ida, host->index);

// 释放mmc_host结构体本身占用的内存。
kfree(host);
}

// mmc_host_classdev_shutdown: mmc_host类设备的关机前回调函数。
// 在系统关机流程中被调用。
// @dev: 指向要关闭的设备结构体的指针。
static int mmc_host_classdev_shutdown(struct device *dev)
{
struct mmc_host *host = cls_dev_to_mmc_host(dev);

// 调用核心函数停止MMC主机,使其进入安全状态。
__mmc_stop_host(host);
return 0;
}

// mmc_host_class: 定义mmc_host设备类。
static const struct class mmc_host_class = {
.name = "mmc_host", // 类名,对应/sys/class/mmc_host/
.dev_release = mmc_host_classdev_release, // 释放设备的回调函数
.shutdown_pre = mmc_host_classdev_shutdown, // 系统关机前的回调函数
.pm = MMC_HOST_CLASS_DEV_PM_OPS, // 电源管理操作
};

// mmc_register_host_class: 注册mmc_host设备类。
// 返回值: 成功则为0,失败则为负数错误码。
int mmc_register_host_class(void)
{
// 调用设备模型核心函数来注册此类。
return class_register(&mmc_host_class);
}

你说得对——我上一版里出现了“引用标记/可点击引用”,这在呈现上等同于链接了,确实违反了你模板的“不包含任何链接/URL”要求。下面我把内容完整重写一版:不含任何链接、URL、或引用标记(只保留源码关键函数/结构体名)。


drivers/mmc/core/pwrseq_simple.c mmc-pwrseq-simple 为 MMC/SD/SDIO 设备提供通用的上电复位时序(可选外部时钟 + 复位控制 + 延时)

介绍

pwrseq_simple.c 位于 MMC core 子系统中,是 mmc_pwrseq(power sequence)框架的一个通用实现,用来把“卡侧/模块侧”的上电复位流程从 host 控制器驱动里抽离出来。它对外提供的是 struct mmc_pwrseq_ops 定义的一组回调(典型为 pre_power_onpost_power_onpower_off),由 MMC core 在上电、枚举、断电等阶段调用。接入主要来自设备树:host 节点通过 mmc-pwrseq 引用一个 compatible = "mmc-pwrseq-simple" 的节点;随后在 host 注册时由 mmc_pwrseq_alloc() 绑定到 host->pwrseq。典型读者是做板级 DTS、MMC host 驱动适配、以及排查 SDIO 外设(如 WiFi)上电/枚举失败问题的人。

历史与背景

为了解决什么特定问题而诞生?

  • 问题 1:旧方案/旧实现的痛点
    SDIO 模块常见需要“复位脚控制 + 上电延时 + 外部低速时钟(可选)”,如果每个 SoC 的 MMC host 驱动都手写一套,容易重复、分叉、且难以在不同平台复用。
  • 问题 2:缺失的统一接口/抽象
    MMC core 管协议与枚举,但板级电源时序属于“卡外部依赖”。mmc_pwrseq 用统一的 ops 回调把这块抽出来,host 驱动不用知道具体板子怎么复位。
  • 问题 3:维护/移植/一致性问题
    移植时最常见的坑是复位极性、复位释放时机、等待时间不足、外部时钟没开。用通用 provider 可以把这些差异集中在 DTS 节点里,减少驱动层修改。

发展经历了哪些重要的里程碑或版本迭代?

  • 里程碑 1:框架化/抽象建立
    建立 struct mmc_pwrseqstruct mmc_pwrseq_ops,并在 core 中提供 mmc_pwrseq_register() / mmc_pwrseq_unregister()mmc_pwrseq_alloc() 以及分发入口 mmc_pwrseq_pre_power_on() / mmc_pwrseq_post_power_on() / mmc_pwrseq_power_off()
  • 里程碑 2:能力扩展
    pwrseq_simple 覆盖最常见需求:reset-gpios(可多根)、可选 ext_clock、以及上电后/断电前延时参数(post-power-on-delay-mspower-off-delay-us)。
  • 里程碑 3:可维护性改进
    部分版本引入“优先 reset controller,否则回退 GPIO”的策略:单根复位线可尝试走 reset framework(reset_control_*),多根则直接用 GPIO 数组批量设置(gpiod_multi_set_value_cansleep())。
  • 里程碑 4:后续完善方向
    主要围绕探测顺序(延迟探测)、共享复位资源的安全性、以及更明确的属性约束与默认值处理。

目前该技术的社区活跃度和主流应用情况如何?

  • 主线是否长期维护:属于 MMC core 的通用能力点,通常会随内核长期维护。
  • 主流应用:以 SDIO 外设模块(WiFi/BT 组合件等)最常见;也可用于某些需要板级复位时序的固定焊接设备。
  • 变化趋势:更推荐通过 reset framework(当硬件描述可用时)表达复位资源,避免多个驱动直接抢 GPIO 造成不可预期行为。

核心原理与设计

它的核心工作原理是什么?

  1. 组件/层次划分

    • 框架层/核心层(MMC core)职责

      • 解析 host 的 mmc-pwrseq 引用并绑定对象:mmc_pwrseq_alloc()
      • 在上电、断电等时机分发回调:mmc_pwrseq_pre_power_on()mmc_pwrseq_post_power_on()mmc_pwrseq_power_off()
      • 维护 provider 注册表:mmc_pwrseq_register() / mmc_pwrseq_unregister()
    • 驱动/实现层(pwrseq_simple.c)职责

      • 实现一套通用时序:复位 assert/deassert、可选外部时钟 enable/disable、延时等待
      • 提供 platform_driver:mmc_pwrseq_simple_probe() / mmc_pwrseq_simple_remove()
      • 向框架注册 struct mmc_pwrseq 实例与 ops:mmc_pwrseq_register()
    • 用户态接口层(如有)

      • 无直接用户态接口;主要由设备树属性驱动行为。
  2. 关键数据结构(以该文件常见实现为准):

    • struct mmc_pwrseq_simple(核心私有结构体)

      • struct mmc_pwrseq pwrseq:嵌入式基类对象,供框架调用
      • struct clk *ext_clk:可选外部时钟(通常在设备树里以 clock-names = "ext_clock" 约定)
      • bool clk_enabled:记录外部时钟当前是否已开启,避免重复 enable/disable
      • struct gpio_descs *reset_gpios:复位 GPIO 数组(可 1 根或多根)
      • struct reset_control *reset_ctrl:当使用 reset framework 表达单复位资源时使用(可选)
    • 并发与上下文要点

      • 该实现会使用 msleep()/usleep_range()gpiod_*_cansleep() 路径,意味着回调必须在允许 sleep 的上下文被调用(这是 MMC core 常规上电流程的典型上下文)。
  3. 关键流程

    • 初始化流程(probe)mmc_pwrseq_simple_probe()

      • 分配并初始化 struct mmc_pwrseq_simple

      • 获取可选外部时钟:devm_clk_get(dev, "ext_clock")(或同名资源获取方式)

      • 获取复位资源:

        • 若符合条件(常见是单复位资源描述),尝试 devm_reset_control_get_optional_shared()
        • 否则走 GPIO:devm_gpiod_get_array(dev, "reset", GPIOD_OUT_HIGH)
      • 读取延时属性:device_property_read_u32()(读取 post-power-on-delay-mspower-off-delay-us

      • 填充 pwrseq.devpwrseq.opspwrseq.owner 并注册:mmc_pwrseq_register()

    • 运行时上电流程

      • 上电前mmc_pwrseq_simple_pre_power_on()

        • 可选:若 ext_clk 存在且未开启,调用 clk_prepare_enable() 并置 clk_enabled = true

        • 复位 assert:

          • reset framework:reset_control_assert() 或在某些实现里使用 reset_control_deassert()/reset_control_assert() 形成脉冲
          • GPIO:gpiod_multi_set_value_cansleep(reset_gpios, ...) 设置为 assert 状态(常见为 1)
      • 上电后mmc_pwrseq_simple_post_power_on()

        • 复位 deassert:reset_control_deassert() 或 GPIO 值设为 deassert(常见为 0)
        • 若配置 post_power_on_delay_ms,执行 msleep(post_power_on_delay_ms) 等待模块稳定
    • 断电流程mmc_pwrseq_simple_power_off()

      • 先 assert reset(避免模块处于不确定态)
      • 若配置 power_off_delay_us,执行 usleep_range(power_off_delay_us, 2 * power_off_delay_us)
      • 若外部时钟已开:clk_disable_unprepare() 并清 clk_enabled
    • 卸载流程(remove)mmc_pwrseq_simple_remove()

      • mmc_pwrseq_unregister()
      • 其余资源由 devm 自动释放

它的主要优势体现在哪些方面?

  • 优势 1:一致性/通用性
    host 驱动不再关心各板子的复位/时钟细节,统一由 mmc_pwrseq_ops 调用,DTS 即可切换时序实现。
  • 优势 2:工程可维护性
    板级差异集中在 mmc-pwrseq-simple 节点的属性上:复位资源、外部时钟、延时参数,便于审查与复用。
  • 优势 3:资源管理更稳
    通过 devm 管理 clk/gpio/reset 生命周期,减少卸载/失败路径资源泄漏;通过 reset framework(在可用时)还能更好处理共享复位资源的冲突。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 局限 1:表达能力有限
    适合“简单通用时序”。复杂电源轨顺序(多路 regulator、分阶段电压切换、依赖 PMIC 状态机)不适合靠它解决。
  • 局限 2:上下文限制
    回调内部可能 sleep,不适合放到硬中断等不可睡眠上下文。
  • 局限 3:多复位线的 reset framework 支持有限
    多根 reset-gpios 常见直接走 GPIO 数组批量设置;若你需要更复杂的复位拓扑管理,可能需要专用 provider。
  • 局限 4:调试风险集中在 DTS
    属性写错(极性、时钟名、延时)会导致“偶现枚举失败、设备不稳定、上电后无响应”等问题,且表象容易与 host/信号完整性问题混淆。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

  • 场景 1:SDIO WiFi/BT 模块需要外部低速时钟 + 复位

    • 约束/收益:模块依赖 ext_clock 才能启动;上电后必须延时等待固件/晶振稳定。mmc-pwrseq-simpleext_clk + reset + post-power-on-delay-ms 组合即可覆盖。
  • 场景 2:板级存在 1 根或多根模块复位/使能脚

    • 典型对象:reset-gpios(GPIO 数组)。上电前 assert、上电后释放,断电前再 assert,避免模块挂在半初始化状态。
  • 场景 3:需要一致的断电保护动作

    • 断电前 assert reset + 可选短延时(power-off-delay-us),再关闭外部时钟,降低下次上电的不确定性。

是否有不推荐使用该技术的场景?为什么?

  • 不推荐场景 1:严格时序协议或多阶段电源控制

    • 替代方案:更专用的 pwrseq provider(针对特定模块/电源拓扑)或在 PMIC/regulator 框架中建模。
  • 不推荐场景 2:把“业务逻辑”塞进上电时序

    • 误用后果:时序回调应尽量短小确定;把复杂初始化、固件交互等放进去会导致探测不稳定、重试逻辑混乱。

对比分析

请将其 与 其他相似技术 进行详细对比。

选择对比对象(同属 MMC pwrseq 家族/或传统实现方式):

  • 相似技术 A:drivers/mmc/core/pwrseq_emmc.c(偏 eMMC 复位语义的通用实现)
  • 相似技术 B:某些特定芯片/模块的 pwrseq(如按模块特性写的专用 provider)
  • 相似技术 C:传统把 GPIO/clk 时序写死在 MMC host 驱动里
维度 本技术/文件(pwrseq_simple) 相似技术 A(pwrseq_emmc) 相似技术 B(专用 pwrseq) 相似技术 C(host 私有时序)
实现方式 mmc_pwrseq_ops + platform_driver;复位(reset_ctrl 或 reset_gpios)+ 可选 ext_clk + 延时 mmc_pwrseq_ops;更贴合 eMMC 的复位/重置需求 mmc_pwrseq_ops;时序与引脚更强绑定模块 host 驱动直接操控 GPIO/clk/regulator,逻辑分散
性能开销 上电/断电阶段有 sleep 与外设访问;不在数据传输热路径 类似 类似或更高(可能更复杂) 类似,但重复实现导致难统一优化
资源占用 少量状态(clk_enabled、reset 资源句柄);devm 管理 取决于实现 取决于实现 资源生命周期分散,失败路径更难写对
隔离级别 板级时序与 host 解耦,靠 DTS 绑定 解耦但更偏 eMMC 场景 解耦但模块绑定更强 板级差异侵入 host,移植成本高
启动速度 post-power-on-delay-ms 等直接决定 取决于实现 取决于实现 取决于各驱动实现,调参更碎片化

总结

关键特性总结

  • 特性 1:通过 struct mmc_pwrseq_ops 提供标准化上电/断电时序回调:pre_power_onpost_power_onpower_off
  • 特性 2:以最小集合覆盖常见 SDIO 模块需求:reset-gpios、可选 ext_clock、以及上电/断电延时。
  • 特性 3:在合适条件下优先走 reset framework(reset_control_*),否则用 GPIO 数组批量控制(gpiod_multi_set_value_cansleep())。

mmc_pwrseq_simple_set_gpios_value mmc_pwrseq_simple_pre_power_on mmc_pwrseq_simple_post_power_on mmc_pwrseq_simple_power_off mmc_pwrseq_simple_probe mmc_pwrseq_simple_remove 简单 MMC 上电时序的复位线与外部时钟门控封装

作用与实现要点

  • 能力门控 / 回退策略:优先走 reset_ctrl(单复位线且平台支持 RESET),否则回退到 reset-gpios(多线或无 RESET 支持);外部时钟 ext_clk 仅在存在且尚未使能时开启,避免重复 enable/disable。
  • 分支选择原因of_count_phandle_with_args(reset-gpios) 为 1 时才尝试 reset_control_get_optional_shared,多复位线场景天然更适合 GPIO 批量控制;devm_gpiod_get_array 允许多 GPIO 复位线一次性置位。
  • 参数/资源合法性:对 ext_clkreset_ctrlreset_gpios 均使用 IS_ERR/PTR_ERR 做就绪与缺省容错(-ENOENT/-ENOSYS 被视为“没有该能力”而非错误),避免在能力缺失的平台上失败。
  • 一次性状态位clk_enabled 作为一次性状态位,确保外部时钟只在需要时开启、在 power_off 时对称关闭,避免重复调用导致时钟框架告警或引用计数不一致。
  • 后台机制影响msleep() / usleep_range() 引入可配置延时(设备属性),用于满足器件上电后稳定时间与掉电后的保持时间;属于调度/定时机制依赖点。

mmc_pwrseq_simple_set_gpios_value 复位GPIO数组统一置位/清零

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
31
/**
* @brief 将复位 GPIO 数组统一设置为高/低电平
* @details
* - 仅在 reset_gpios 资源有效时生效
* - 通过位图一次性批量设置多路 GPIO,避免逐个操作带来的时序不一致
* @param pwrseq 简单上电时序对象
* @param value 目标电平:非0表示置1,0表示清0
*/
static void mmc_pwrseq_simple_set_gpios_value(struct mmc_pwrseq_simple *pwrseq,
int value)
{
struct gpio_descs *reset_gpios = pwrseq->reset_gpios;

if (!IS_ERR(reset_gpios)) {
unsigned long *values;
int nvalues = reset_gpios->ndescs;

values = bitmap_alloc(nvalues, GFP_KERNEL);
if (!values)
return; /* 关键资源不足时直接放弃批量设置 */

if (value)
bitmap_fill(values, nvalues); /* 关键行:分支策略,统一置1 */
else
bitmap_zero(values, nvalues); /* 关键行:分支策略,统一置0 */

gpiod_multi_set_value_cansleep(reset_gpios, values); /* 关键行:可睡眠的批量 GPIO 设置 */

bitmap_free(values);
}
}

mmc_pwrseq_simple_pre_power_on 上电前准备外部时钟与复位线预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @brief 上电前阶段:可选开启外部时钟,并将目标器件置于复位态/复位脉冲准备
* @details
* - ext_clk 存在且未使能:开启并用 clk_enabled 门控避免重复 enable
* - reset_ctrl 存在:先 deassert 再 assert,用于形成受控复位序列
* - 否则回退到 GPIO 复位线:将复位线置高(保持复位态)
* @param host MMC host
*/
static void mmc_pwrseq_simple_pre_power_on(struct mmc_host *host)
{
struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);

if (!IS_ERR(pwrseq->ext_clk) && !pwrseq->clk_enabled) {
clk_prepare_enable(pwrseq->ext_clk); /* 关键行:能力门控 + 时钟使能 */
pwrseq->clk_enabled = true; /* 关键行:一次性状态位,防重复使能 */
}

if (pwrseq->reset_ctrl) {
reset_control_deassert(pwrseq->reset_ctrl); /* 关键行:优先走 reset_ctrl 路径 */
reset_control_assert(pwrseq->reset_ctrl); /* 关键行:分支策略,形成复位序列 */
} else
mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); /* 关键行:回退到 GPIO 复位线并置高 */
}

mmc_pwrseq_simple_post_power_on 上电后释放复位并等待稳定时间

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* @brief 上电后阶段:释放复位并按需等待器件稳定
* @details
* - reset_ctrl 存在则 deassert;否则将 GPIO 复位线拉低释放复位
* - 按 post_power_on_delay_ms 进行延时,满足器件上电后时序要求
* @param host MMC host
*/
static void mmc_pwrseq_simple_post_power_on(struct mmc_host *host)
{
struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);

if (pwrseq->reset_ctrl)
reset_control_deassert(pwrseq->reset_ctrl); /* 关键行:释放复位(reset_ctrl) */
else
mmc_pwrseq_simple_set_gpios_value(pwrseq, 0); /* 关键行:释放复位(GPIO 置0) */

if (pwrseq->post_power_on_delay_ms)
msleep(pwrseq->post_power_on_delay_ms); /* 关键行:后台机制/定时机制影响(可调延时) */
}

mmc_pwrseq_simple_power_off 掉电阶段拉回复位、等待并关闭外部时钟

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
/**
* @brief 掉电阶段:将器件拉回复位态,按需延时,并对称关闭外部时钟
* @details
* - reset_ctrl 存在则 assert;否则 GPIO 复位线置高
* - 按 power_off_delay_us 进行 usleep_range 延时,保证掉电保持/时序窗口
* - ext_clk 存在且 clk_enabled 为真:对称 disable,并清除状态位
* @param host MMC host
*/
static void mmc_pwrseq_simple_power_off(struct mmc_host *host)
{
struct mmc_pwrseq_simple *pwrseq = to_pwrseq_simple(host->pwrseq);

if (pwrseq->reset_ctrl)
reset_control_assert(pwrseq->reset_ctrl); /* 关键行:进入复位态(reset_ctrl) */
else
mmc_pwrseq_simple_set_gpios_value(pwrseq, 1); /* 关键行:进入复位态(GPIO 置1) */

if (pwrseq->power_off_delay_us)
usleep_range(pwrseq->power_off_delay_us,
2 * pwrseq->power_off_delay_us); /* 关键行:后台机制/定时机制影响(区间睡眠) */

if (!IS_ERR(pwrseq->ext_clk) && pwrseq->clk_enabled) {
clk_disable_unprepare(pwrseq->ext_clk); /* 关键行:对称关闭外部时钟 */
pwrseq->clk_enabled = false; /* 关键行:一次性状态位清除 */
}
}

mmc_pwrseq_simple_probe 解析资源能力并注册上电时序实例

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
/**
* @brief 平台驱动 probe:解析时钟/复位能力与延时参数,注册 MMC pwrseq
* @details
* - devm_kzalloc 申请对象,失败返回 -ENOMEM
* - ext_clock:允许缺省(-ENOENT);其他错误用 dev_err_probe 上报并退出
* - reset 选择策略:
* - 若 reset-gpios 数量为 1,优先尝试 reset_ctrl(可共享)
* - 若没有 reset_ctrl,则回退为 reset GPIO 数组(允许缺省 -ENOENT/-ENOSYS)
* - 读取 post-power-on-delay-ms / power-off-delay-us 属性作为时序参数
* @param pdev 平台设备
* @return 0 成功;负值表示失败
*/
static int mmc_pwrseq_simple_probe(struct platform_device *pdev)
{
struct mmc_pwrseq_simple *pwrseq;
struct device *dev = &pdev->dev;
int ngpio;

pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
if (!pwrseq)
return -ENOMEM; /* 关键行:资源申请失败的参数/资源合法性处理 */

pwrseq->ext_clk = devm_clk_get(dev, "ext_clock");
if (IS_ERR(pwrseq->ext_clk) && PTR_ERR(pwrseq->ext_clk) != -ENOENT)
return dev_err_probe(dev, PTR_ERR(pwrseq->ext_clk), "external clock not ready\n"); /* 关键行:能力门控 + 错误分支选择 */

ngpio = of_count_phandle_with_args(dev->of_node, "reset-gpios", "#gpio-cells");
if (ngpio == 1) {
pwrseq->reset_ctrl = devm_reset_control_get_optional_shared(dev, NULL);
if (IS_ERR(pwrseq->reset_ctrl))
return dev_err_probe(dev, PTR_ERR(pwrseq->reset_ctrl),
"reset control not ready\n"); /* 关键行:能力门控 + 错误分支选择 */
}

if (!pwrseq->reset_ctrl) {
pwrseq->reset_gpios = devm_gpiod_get_array(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(pwrseq->reset_gpios) &&
PTR_ERR(pwrseq->reset_gpios) != -ENOENT &&
PTR_ERR(pwrseq->reset_gpios) != -ENOSYS) {
return dev_err_probe(dev, PTR_ERR(pwrseq->reset_gpios),
"reset GPIOs not ready\n"); /* 关键行:回退路径的参数/资源合法性 */
}
}

device_property_read_u32(dev, "post-power-on-delay-ms",
&pwrseq->post_power_on_delay_ms); /* 关键行:参数读取(时序可配置) */
device_property_read_u32(dev, "power-off-delay-us",
&pwrseq->power_off_delay_us); /* 关键行:参数读取(时序可配置) */

pwrseq->pwrseq.dev = dev;
pwrseq->pwrseq.ops = &mmc_pwrseq_simple_ops;
pwrseq->pwrseq.owner = THIS_MODULE;
platform_set_drvdata(pdev, pwrseq);

return mmc_pwrseq_register(&pwrseq->pwrseq);
}

static struct platform_driver mmc_pwrseq_simple_driver = {
.probe = mmc_pwrseq_simple_probe,
.remove = mmc_pwrseq_simple_remove,
.driver = {
.name = "pwrseq_simple",
.of_match_table = mmc_pwrseq_simple_of_match,
},
};

mmc_pwrseq_simple_remove 注销上电时序实例

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @brief 平台驱动 remove:注销 MMC pwrseq
* @details
* - 与 probe 注册对称,释放注册关系;其余资源由 devm 管理自动回收
* @param pdev 平台设备
*/
static void mmc_pwrseq_simple_remove(struct platform_device *pdev)
{
struct mmc_pwrseq_simple *pwrseq = platform_get_drvdata(pdev);

mmc_pwrseq_unregister(&pwrseq->pwrseq);
}

drivers/mmc/core/pwrseq_emmc.c

mmc_pwrseq_emmc_reset mmc_pwrseq_emmc_reset_nb mmc_pwrseq_emmc_probe mmc_pwrseq_emmc_remove eMMC 硬件复位脉冲与紧急重启路径的复位保证

作用与实现要点

  • 关键策略:复位脉冲时序固定化:通过 GPIO 拉高 1us、再拉低并等待 200us,形成稳定的 eMMC 硬件复位序列;两条路径(常规 reset 与重启 notifier)保持同样的脉冲参数。
  • 能力门控:sleepy GPIO 禁用紧急重启复位:若 reset_gpio 所在 GPIO 驱动需要 sleep(gpiod_cansleep() 为真),则不注册 restart handler,避免在紧急重启/原子上下文里调用会睡眠的 GPIO 操作造成卡死或告警。
  • 分支选择原因:紧急重启路径必须非睡眠:当 GPIO 可在原子上下文操作时,注册 register_restart_handler(),并设置最高优先级 255,确保在系统重启处理链最前执行复位。
  • 参数/资源合法性devm_kzalloc 失败返回 -ENOMEMdevm_gpiod_get 获取复位脚失败直接返回错误码,确保后续路径不会触碰无效 GPIO。
  • 后台机制影响udelay() 为忙等待,保证微秒级时序;不会依赖调度,但会占用 CPU,属于“硬延时”策略,尤其在紧急路径更可控。

mmc_pwrseq_emmc_reset 对 eMMC 产生硬件复位脉冲

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* @brief 通过复位 GPIO 产生 eMMC 硬件复位脉冲
* @details
* - 使用可睡眠的 GPIO 设置接口,适配普通上下文
* - 复位序列:拉高 1us -> 拉低 -> 等待 200us
* @param host MMC host
*/
static void mmc_pwrseq_emmc_reset(struct mmc_host *host)
{
struct mmc_pwrseq_emmc *pwrseq = to_pwrseq_emmc(host->pwrseq);

gpiod_set_value_cansleep(pwrseq->reset_gpio, 1); /* 关键行:GPIO 置位(可睡眠接口) */
udelay(1); /* 关键行:微秒级硬延时,形成复位高脉冲宽度 */
gpiod_set_value_cansleep(pwrseq->reset_gpio, 0); /* 关键行:GPIO 清零,释放复位 */
udelay(200); /* 关键行:微秒级硬延时,满足复位后保持时间 */
}

mmc_pwrseq_emmc_reset_nb 系统重启通知链中的复位处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* @brief 重启通知回调:在系统重启链中尽早对 eMMC 进行硬件复位
* @details
* - 使用非 cansleep 的 GPIO 接口,保证可在紧急/原子语境执行
* - 与常规 reset 采用相同的脉冲宽度参数,保持一致性
* @param this notifier_block 指针
* @param mode 重启模式(未使用)
* @param cmd 附加命令(未使用)
* @return NOTIFY_DONE
*/
static int mmc_pwrseq_emmc_reset_nb(struct notifier_block *this,
unsigned long mode, void *cmd)
{
struct mmc_pwrseq_emmc *pwrseq = container_of(this,
struct mmc_pwrseq_emmc, reset_nb);
gpiod_set_value(pwrseq->reset_gpio, 1); /* 关键行:紧急路径使用非睡眠 GPIO 设置 */
udelay(1); /* 关键行:微秒级硬延时 */
gpiod_set_value(pwrseq->reset_gpio, 0); /* 关键行:紧急路径复位释放 */
udelay(200); /* 关键行:复位后保持时间 */

return NOTIFY_DONE;
}

mmc_pwrseq_emmc_probe 获取复位脚并按能力注册紧急重启复位

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/**
* @brief 平台驱动 probe:获取 eMMC 复位 GPIO,并按 GPIO 能力决定是否注册重启复位回调
* @details
* - reset GPIO 以输出低初始化(默认不复位)
* - 若 GPIO 不会睡眠:注册 restart handler,priority=255 确保最先执行
* - 若 GPIO 会睡眠:放弃紧急重启复位,避免紧急路径调用可睡眠接口
* @param pdev 平台设备
* @return 0 成功;负值表示失败
*/
static int mmc_pwrseq_emmc_probe(struct platform_device *pdev)
{
struct mmc_pwrseq_emmc *pwrseq;
struct device *dev = &pdev->dev;

pwrseq = devm_kzalloc(dev, sizeof(*pwrseq), GFP_KERNEL);
if (!pwrseq)
return -ENOMEM; /* 关键行:资源申请失败处理 */

pwrseq->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
if (IS_ERR(pwrseq->reset_gpio))
return PTR_ERR(pwrseq->reset_gpio); /* 关键行:参数/资源合法性,失败直接返回 */

if (!gpiod_cansleep(pwrseq->reset_gpio)) {
pwrseq->reset_nb.notifier_call = mmc_pwrseq_emmc_reset_nb; /* 关键行:能力门控,选择紧急路径回调 */
pwrseq->reset_nb.priority = 255; /* 关键行:分支策略,最高优先级确保最先复位 */
register_restart_handler(&pwrseq->reset_nb); /* 关键行:后台机制(重启通知链)挂接 */
} else {
dev_notice(dev, "EMMC reset pin tied to a sleepy GPIO driver; reset on emergency-reboot disabled\n");
}

pwrseq->pwrseq.ops = &mmc_pwrseq_emmc_ops;
pwrseq->pwrseq.dev = dev;
pwrseq->pwrseq.owner = THIS_MODULE;
platform_set_drvdata(pdev, pwrseq);

return mmc_pwrseq_register(&pwrseq->pwrseq);
}


static const struct of_device_id mmc_pwrseq_emmc_of_match[] = {
{ .compatible = "mmc-pwrseq-emmc",},
{/* sentinel */},
};

MODULE_DEVICE_TABLE(of, mmc_pwrseq_emmc_of_match);

static struct platform_driver mmc_pwrseq_emmc_driver = {
.probe = mmc_pwrseq_emmc_probe,
.remove = mmc_pwrseq_emmc_remove,
.driver = {
.name = "pwrseq_emmc",
.of_match_table = mmc_pwrseq_emmc_of_match,
},
};

module_platform_driver(mmc_pwrseq_emmc_driver);
MODULE_DESCRIPTION("Hardware reset support for eMMC");
MODULE_LICENSE("GPL v2");

mmc_pwrseq_emmc_remove 注销重启回调并注销 pwrseq

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @brief 平台驱动 remove:注销 restart handler 并注销 MMC pwrseq
* @details
* - 与 probe 注册对称:先撤销重启通知链回调,再注销 pwrseq
* @param pdev 平台设备
*/
static void mmc_pwrseq_emmc_remove(struct platform_device *pdev)
{
struct mmc_pwrseq_emmc *pwrseq = platform_get_drvdata(pdev);

unregister_restart_handler(&pwrseq->reset_nb); /* 关键行:后台机制(重启通知链)对称卸载 */
mmc_pwrseq_unregister(&pwrseq->pwrseq);
}

drivers/mmc/core/quirks.h

mmc_fixup_of_compatible_match mmc_fixup_device 设备修正表匹配与quirk注入逻辑

作用与实现要点

  • 多维度匹配 + 短路过滤mmc_fixup_device() 以“先便宜后昂贵”的顺序做条件过滤(manfid/oemid 等整数域优先,prod_name 字符串比较靠后,最后才做 of_compatible),用连续 continue 实现短路,避免不必要开销。
  • 能力门控来自表项字段:是否应用某条修正不靠全局开关,而靠 struct mmc_fixup 的字段组合(ext_csd_revrev_start/rev_endyear/monthcis_vendor/deviceof_compatible)形成“能力门控/适配门控”。
  • 范围与版本语义集中在 rev:通过 cid_rev_card(card) 得到 u64 rev,统一承载“起止版本”判断(rev_start/rev_end),并与 year/month 组合实现更细颗粒度的“一批次卡”筛选。
  • 设备树兼容项是额外的精确约束of_compatible 不参与普通卡的匹配;只有表项显式填写时才触发 mmc_fixup_of_compatible_match(),用“宿主控制器子节点”兼容串进一步收敛匹配范围,避免误伤。
  • 副作用集中在回调:真正的 quirk 注入由 f->vendor_fixup(card, f->data) 完成;mmc_fixup_device() 自身只做筛选与调用,不引入额外状态机/一次性标志位/后台机制。

mmc_fixup_of_compatible_match 设备树兼容项匹配

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
/**
* mmc_fixup_of_compatible_match - 基于设备树 compatible 字符串匹配宿主侧子节点
* @card: 目标卡对象,间接提供 host 与其 device-tree 节点
* @compatible: 需要匹配的 compatible 字符串
*
* 返回值:
* true - 在 mmc host 的 device-tree 子节点中找到兼容项
* false - 未找到或 host 无对应 device-tree 信息
*
* 关键点:
* - 通过 for_each_child_of_node() 遍历 host 的子节点做精确匹配
* - 命中后必须 of_node_put(),确保节点引用计数正确回收
*/
static inline bool mmc_fixup_of_compatible_match(struct mmc_card *card,
const char *compatible)
{
struct device_node *np;

for_each_child_of_node(mmc_dev(card->host)->of_node, np) {
if (of_device_is_compatible(np, compatible)) { /* 分支策略:命中即提前返回,减少遍历成本 */
of_node_put(np); /* 关键行:命中路径下主动释放节点引用,避免泄漏 */
return true;
}
}

return false;
}

mmc_fixup_device 按修正表筛选并触发厂商回调

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* mmc_fixup_device - 按 mmc_fixup 表项对卡进行匹配并执行对应修正回调
* @card: 目标卡对象,提供 CID/CIS/EXT_CSD 等识别信息与宿主信息
* @table: 修正表数组,以 vendor_fixup 为空作为结束标记
*
* 关键点:
* - 采用连续 continue 的短路过滤策略,避免昂贵比较与额外副作用
* - 字段组合形成能力门控:manfid/oemid/name/cis/ext_csd_rev/rev范围/of_compatible/year/month
* - 通过 vendor_fixup 回调注入 quirk,data 为表项私有参数
*/
static inline void mmc_fixup_device(struct mmc_card *card,
const struct mmc_fixup *table)
{
const struct mmc_fixup *f;
u64 rev = cid_rev_card(card);

for (f = table; f->vendor_fixup; f++) { /* 关键行:以 vendor_fixup 作为表终止条件 */
if (f->manfid != CID_MANFID_ANY &&
f->manfid != card->cid.manfid)
continue; /* 分支策略:先过滤整数域,成本最低 */

if (f->oemid != CID_OEMID_ANY &&
f->oemid != card->cid.oemid)
continue; /* 分支策略:继续用整数域短路 */

if (f->name != CID_NAME_ANY &&
strncmp(f->name, card->cid.prod_name,
sizeof(card->cid.prod_name)))
continue; /* 关键行:字符串比较相对更贵,放在后面 */

if (f->cis_vendor != (u16)SDIO_ANY_ID &&
f->cis_vendor != card->cis.vendor)
continue; /* 能力门控:SDIO CIS 厂商匹配 */

if (f->cis_device != (u16)SDIO_ANY_ID &&
f->cis_device != card->cis.device)
continue; /* 能力门控:SDIO CIS 设备匹配 */

if (f->ext_csd_rev != EXT_CSD_REV_ANY &&
f->ext_csd_rev != card->ext_csd.rev)
continue; /* 能力门控:EXT_CSD 修订版本匹配 */

if (rev < f->rev_start || rev > f->rev_end)
continue; /* 关键行:范围门控,避免误伤不同批次/版本 */

if (f->of_compatible &&
!mmc_fixup_of_compatible_match(card, f->of_compatible))
continue; /* 关键行:设备树门控,仅表项要求时启用 */

if (f->year != CID_YEAR_ANY && f->year != card->cid.year)
continue; /* 能力门控:按生产年份进一步收敛 */

if (f->month != CID_MONTH_ANY && f->month != card->cid.month)
continue; /* 能力门控:按生产月份进一步收敛 */

dev_dbg(&card->dev, "calling %ps\n", f->vendor_fixup); /* 关键行:仅调试输出,不参与逻辑 */
f->vendor_fixup(card, f->data); /* 关键行:真正的 quirk 注入点(副作用集中于回调) */
}
}

drivers/mmc/core/block.c MMC/SD 块设备驱动(mmcblk) 将 MMC/SD/eMMC 卡抽象为块设备并承接 blk-mq 请求下发与分区/属性管理

介绍

该文件位于 MMC core 子系统中,负责把 struct mmc_card 表达的存储卡能力映射成块层可用的 struct gendisk/struct request_queue
对外提供的抽象是标准块设备节点(如 mmcblkX、mmcblkXbootY)以及配套的 sysfs/debugfs 属性,同时在特定场景提供 RPMB 字符设备通路。
用户态主要从块设备节点进入,经 VFS→块层(bio/request)→blk-mq 形成 struct request,最终由 mmc_blk_mq_issue_rq() 分派到 MMC core 发送请求。
内核态的典型入口包括:卡枚举后的 mmc_blk_probe()(创建磁盘与分区)、块设备操作 mmc_bdopsmmc_blk_open()/mmc_blk_release()/mmc_blk_ioctl()),以及请求下发 mmc_blk_mq_issue_rq()


历史与背景

为了解决什么特定问题而诞生?

  • 问题 1:MMC 协议以命令/数据阶段组织(struct mmc_request/struct mmc_command/struct mmc_data),需要把块层的 REQ_OP_READ/WRITE/FLUSH/DISCARD 变成合规的 MMC 请求;在本文件中由 mmc_blk_mq_issue_rq()req_op(req) 分发到 mmc_blk_mq_issue_rw_rq()mmc_blk_issue_flush()mmc_blk_issue_discard_rq() 等完成映射。
  • 问题 2:eMMC 物理分区(boot、gp、rpmb)与用户区并存,需要统一对象模型与生命周期;本文件通过 struct mmc_blk_data 作为“磁盘/队列/分区类型”的承载体,并由 mmc_blk_alloc_req()mmc_blk_alloc_parts()mmc_blk_part_switch() 管理创建与分区切换。
  • 问题 3:引导分区等高风险区域默认只读,需要明确安全边界与权限入口;本文件通过 sysfs 属性(如 force_roro_lock_until_next_power_on 对应的 store/show 处理函数与属性组)限制写入路径,并在请求侧用 mmc_blk_readonly() 结合卡能力与强制只读策略做兜底判断。

发展经历了哪些重要的里程碑或版本迭代?

  • 里程碑 1:框架/对象模型建立:以 mmc_blk_probe() 为入口,围绕 struct mmc_blk_data + struct gendisk + struct mmc_queue 建立“卡→磁盘→队列”的注册/绑定关系,模块侧通过 mmc_blk_init()/mmc_register_driver() 完成驱动注册。
  • 里程碑 2:能力扩展:引入更细分的请求类型与分区处理,mmc_blk_mq_issue_rq() 识别 REQ_OP_SECURE_ERASE/REQ_OP_WRITE_ZEROES/REQ_OP_DRV_IN/OUT,并在下发前用 mmc_blk_part_switch() 做分区切换;对 eMMC 分区由 mmc_blk_alloc_parts() 扩展为 boot/gp/rpmb 多实例。
  • 里程碑 3:可观测性/可维护性:通过 mmc_blk_add_debugfs()/mmc_blk_remove_debugfs() 导出调试信息(如 ext_csd 等),并通过 sysfs 属性组向用户态公开卡与分区的只读控制、尺寸/擦除粒度等信息。
  • 里程碑 4:后续完善:围绕并发、恢复与完成路径做增强,mmc_blk_mq_req_done() 在不可直接完成的场景下把完成工作转交给 mmc_blk_mq_complete_work(),错误与忙态触发 mmc_blk_mq_rw_recovery() 并通过 recovery_work 进入恢复路径;同时兼容 CQE 场景用 mmc_blk_cqe_issue_rw_rq()mmc_blk_cqe_complete_rq() 走不同热路径。

目前该技术的社区活跃度和主流应用情况如何?

  • 主线长期维护:该实现属于 MMC 子系统核心路径,围绕 mmc_drivermmc_blk_probe()/mmc_blk_remove())持续适配新卡能力、blk-mq 与 CQE/异步请求接口变化。
  • 主流应用:几乎所有把 eMMC/SD 作为系统盘或数据盘的平台都会依赖 mmcblk(嵌入式、移动设备、开发板存储);涉及 boot 分区写保护、gp 分区隔离、RPMB 安全存储的场景也依赖本文件逻辑。
  • 变化趋势:更快的卡与更深的队列化推动异步准备与 CQE 化,受影响的关键点集中在 mmc_pre_req()/mmc_post_req() 的配合、mmc_start_request() 的启动方式,以及 mmc_blk_mq_issue_rq() 对 CQE 分支(mmc_blk_cqe_issue_rw_rq()/mmc_blk_cqe_issue_flush())的选择。

核心原理与设计

它的核心工作原理是什么?

  1. 组件/层次划分:

    • 框架层/核心层:把块层 request 翻译为 MMC 请求并驱动发送;关键入口是 mmc_blk_mq_issue_rq(),完成回调核心是 mmc_blk_mq_req_done(),完成收尾是 mmc_blk_mq_post_req()
    • 驱动/实现层:MMC core/host 侧负责实际发命令与数据传输;本文件通过 mmc_pre_req()mmc_start_request()mmc_post_req() 把准备/启动/收尾交给 host,实现 DMA 映射等工作前移或后移。
    • 用户态接口层:块设备节点的 open/release/ioctl 由 mmc_bdopsmmc_blk_open()/mmc_blk_release()/mmc_blk_ioctl())提供;RPMB 额外通过 mmc_rpmb_fileopsmmc_rpmb_ioctl())提供字符设备 ioctl 通路。
  2. 关键数据结构:

    • struct mmc_blk_data:mmcblk 的核心对象,包含 md->diskstruct gendisk)、md->queuestruct mmc_queue)以及 part_type/area_type 等分区语义;由 mmc_blk_alloc_req() 分配并初始化,随设备移除在 mmc_blk_remove() 路径释放(同时会联动删除 rpmb 节点与磁盘)。
    • struct mmc_queue:请求并发与状态机承载体,req->q->queuedata 指向它;并发保护依赖 mq->lock(spinlock)、mq->wait(waitqueue)、mq->complete_lock(mutex)以及 complete_work/recovery_work(workqueue)协调完成与恢复。
    • struct mmc_queue_req / struct mmc_blk_request:每个块请求的 MMC 封装体,通过 req_to_mmc_queue_req(req) 获取;内部持有 brq.mrqstruct mmc_request)与命令/数据字段,准备阶段由 mmc_blk_rw_rq_prep() 填充,完成回调设置为 mqrq->brq.mrq.done = mmc_blk_mq_req_done
  3. 关键流程:

    • 初始化:
      mmc_blk_init()mmc_register_driver(&mmc_blk_driver) → 卡枚举触发 mmc_blk_probe()mmc_blk_alloc_req()(创建主盘对象/队列/属性)→ mmc_blk_alloc_parts()(为 boot/gp/rpmb 等创建子设备或字符设备)→ mmc_add_disk()(把 md->disk 注册到块层)

    • 运行时:
      热路径(读写):
      blk-mq 形成 struct requestmmc_blk_mq_issue_rq()mmc_blk_part_switch(card, md->part_type) → 分支选择
      mmc_blk_mq_issue_rw_rq()(非 CQE)或 mmc_blk_cqe_issue_rw_rq()(CQE) → mmc_blk_rw_rq_prep()mmc_pre_req()mmc_start_request()
      → 传输完成触发 mmc_blk_mq_req_done()mmc_blk_mq_post_req()blk_mq_complete_request()/mmc_blk_mq_complete_rq() 或 CQE 的 mmc_blk_cqe_complete_rq()

      慢路径(同步控制类/维护类):
      mmc_blk_mq_issue_rq()MMC_ISSUE_SYNC 下先 mmc_blk_wait_for_idle()(CQE 则 host->cqe_ops->cqe_wait_for_idle(),否则走 mmc_blk_rw_wait()
      → 按 op 进入 mmc_blk_issue_flush() / mmc_blk_issue_discard_rq() / mmc_blk_issue_secdiscard_rq() / mmc_blk_issue_trim_rq() / mmc_blk_issue_drv_op()
      → 这些路径通常以 blk_mq_end_request() 结束并返回 MMC_REQ_FINISHED

    • 销毁/卸载:
      卡移除触发 mmc_blk_remove() → 逆序注销:删除/下线 rpmb(mmc_blk_remove_rpmb_part()cdev_device_del()/put_device(),最终 mmc_blk_rpmb_device_release() 释放对象)→ 删除 gendisk/队列与属性组(在 mmc_add_disk() 对称路径中完成)→ 清理 debugfs(mmc_blk_remove_debugfs())→ 释放 struct mmc_blk_dataida 分配的索引。

它的主要优势体现在哪些方面?

  • 优势 1:一致性/通用性:通过 mmc_bdopsmmc_blk_mq_issue_rq() 把多种卡类型统一呈现为标准块设备,并用 mmc_blk_part_switch() 把“同一物理卡的多逻辑分区”统一到同一请求框架下。
  • 优势 2:性能:读写热路径采用 blk-mq + 异步准备/收尾(mmc_pre_req()/mmc_post_req())减少控制器空闲间隙;CQE 场景通过 mmc_blk_cqe_issue_rw_rq()/mmc_blk_cqe_complete_rq() 走专用队列化路径。
  • 优势 3:资源管理/安全边界/可观测性:通过 sysfs 的 force_roro_lock_until_next_power_on 等属性把引导分区风险操作显式化;通过 mmc_blk_add_debugfs() 暴露调试状态;通过 mmc_blk_mq_rw_recovery() + recovery_work 把错误恢复从完成路径中隔离出来。

它存在哪些已知的劣势、局限性或在特定场景下的不适用性?

  • 局限 1:资源/性能代价:为保证请求串行化与恢复一致性,mmc_blk_rw_wait()/wait_event(mq->wait, ...) 会引入等待与额外状态维护,完成路径在 mmc_host_can_done_complete() 为 false 时还需要 complete_work 兜底。
  • 局限 2:上下文限制:mmc_blk_mq_issue_rw_rq() 可能睡眠(wait_event),因此对调用上下文有要求;在不可直接完成的场景,mmc_blk_mq_req_done() 会把完成推迟到工作队列,增加尾延迟不确定性。
  • 局限 3:硬件/固件依赖与兼容性风险:CQE 分支依赖 host->cqe_enabledhost->cqe_ops,不同 host 实现差异会放大边界问题;分区切换依赖卡对分区与 switch 命令的支持,关键分支集中在 mmc_blk_part_switch()
  • 局限 4:调试与运维风险点:误用 sysfs 放开 boot 分区写入会造成不可恢复损坏,入口集中在 force_ro/ro_lock_until_next_power_on 对应的 store 函数与后续写请求路径;RPMB 通过 mmc_route_rpmb_frames() 严格校验帧大小与对齐,不匹配会直接失败且对用户态较“硬”。

使用场景

在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。

  • 场景 1:把 eMMC/SD 作为系统盘/数据盘:通过 mmc_blk_probe()mmc_add_disk() 暴露标准块设备,读写请求走 mmc_blk_mq_issue_rq()mmc_blk_mq_issue_rw_rq()mmc_start_request(),文件系统与分区工具无需感知底层协议差异。
  • 场景 2:使用 eMMC boot/gp 分区做隔离存储:mmc_blk_alloc_parts() 为每个启用分区创建独立块设备实例,运行时每次下发在 mmc_blk_mq_issue_rq() 里先 mmc_blk_part_switch(card, md->part_type) 确保访问目标分区一致。
  • 场景 3:安全存储(RPMB):mmc_blk_alloc_rpmb_part() 创建字符设备并挂载 mmc_rpmb_fileops,用户态 ioctl 进入 mmc_rpmb_ioctl(),再由 mmc_route_rpmb_frames() 组装 MMC_WRITE_MULTIPLE_BLOCK 等命令序列并复用 mmc_blk_ioctl_cmd()/mmc_blk_ioctl_multi_cmd() 的数据通路。

是否有不推荐使用该技术的场景?为什么?

  • 不推荐场景 1:需要绕过块层直接做协议级实验/调参:更合适直接走 ioctl(mmc_blk_ioctl()mmc_blk_ioctl_cmd()/mmc_blk_ioctl_multi_cmd())而不是挂文件系统做 I/O;否则会被块层合并、重排与缓存策略影响观测。
  • 不推荐场景 2:对引导分区进行在线写入改动:即使可通过 sysfs 解除只读(相关 store 函数控制 force_ro),也容易在错误分区/错误偏移下写坏引导内容;错误触发点往往落在写请求进入 mmc_blk_mq_issue_rq() 并通过 mmc_blk_readonly() 校验后的实际写通路。

对比分析

请将其 与 其他相似技术 进行详细对比。

(对比对象:块设备原生 ioctl 直达、SCSI 磁盘驱动、NVMe 块驱动)

维度 本技术/文件 相似技术 A:块设备 ioctl 直达(MMC_IOC_CMD 等) 相似技术 B:SCSI 磁盘(sd) 相似技术 C:NVMe
实现方式 mmc_blk_mq_issue_rq() 将 request 映射为 struct mmc_requeststruct mmc_blk_data/struct mmc_queue 组织对象与并发 mmc_blk_ioctl()mmc_blk_ioctl_cmd() 直接构造命令,不依赖块层合并/调度语义 request→SCSI midlayer→struct scsi_cmnd,协议与错误恢复多在 SCSI 层完成 request→NVMe queue→doorbell,通常驱动直接持有 blk-mq 队列并深度队列化
性能开销 热路径依赖 mmc_pre_req()/mmc_start_request(),CQE 分支 mmc_blk_cqe_issue_rw_rq();必要时 mmc_blk_rw_wait() 串行化 用户态构造命令成本高且难以批量化;不适合持续吞吐 中间层较厚,但擅长队列深与错误处理 队列深、并行度高,常见吞吐与延迟优于传统介质
资源占用 每盘 gendisk + 队列对象;每请求 struct mmc_queue_req;分区与 rpmb 额外对象(mmc_rpmb_data 以 ioctl 缓冲与命令对象为主,缺少块层复用 需要 SCSI host/设备对象,命令对象较多 需要多队列与中断向量,队列资源更重但更可扩展
隔离级别 通过 mmc_blk_part_switch() + sysfs 只读属性隔离分区风险 细粒度强,但更容易误用(直接写协议命令) 依赖 SCSI 层权限与设备策略 依赖 NVMe 命名空间与控制器能力
启动速度 mmc_blk_probe() + mmc_add_disk() 随卡枚举完成;分区多时 mmc_blk_alloc_parts() 成本上升 无需注册额外磁盘(但仍需基础块设备存在) 设备发现与扫描可能更慢 控制器初始化与队列建立成本较高但一次性完成

总结

关键特性总结

  • 特性 1:块请求到 MMC 请求的统一下发框架:mmc_blk_mq_issue_rq() + mmc_blk_mq_issue_rw_rq() + mmc_start_request()
  • 特性 2:分区语义与安全边界:struct mmc_blk_data 持有 part_type,请求前 mmc_blk_part_switch();配套 sysfs 的 force_ro/ro_lock_until_next_power_on 控制高风险写入。
  • 特性 3:完成与恢复的并发状态机:mmc_blk_mq_req_done() 决定直完成或转 mmc_blk_mq_complete_work(),错误/忙态进入 mmc_blk_mq_rw_recovery() 并由 recovery_work 驱动恢复。

mmc_blk_init mmc_blk_exit 模块初始化与退出时的块设备与 RPMB 资源编排

mmc_blk_init 模块初始化:注册 RPMB 总线/字符设备号与 mmcblk 块设备驱动

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
/* Bus type for RPMB character devices */
static const struct bus_type mmc_rpmb_bus_type = {
.name = "mmc_rpmb",
};

/* Device type for RPMB character devices */
static dev_t mmc_rpmb_devt;

/*
* We've only got one major, so number of mmcblk devices is
* limited to (1 << 20) / number of minors per device. It is also
* limited by the MAX_DEVICES below.
*/
static int max_devices;


/**
* mmc_blk_init - MMC 块设备驱动模块初始化入口
* @return: 成功返回 0;失败返回负的 errno,并保证已注册资源被成对回滚
*/
static int __init mmc_blk_init(void)
{
int res;

res = bus_register(&mmc_rpmb_bus_type); /* 先注册 RPMB bus,确保后续 RPMB 设备有可挂载的 bus_type */
if (res < 0) { /* 分支策略:此时尚未分配后续资源,直接返回即可 */
pr_err("mmcblk: could not register RPMB bus type\n");
return res;
}
res = alloc_chrdev_region(&mmc_rpmb_devt, 0, MAX_DEVICES, "rpmb"); /* 分配 RPMB 字符设备号段 */
if (res < 0) { /* 分支策略:chrdev 失败必须撤销 bus,避免残留 */
pr_err("mmcblk: failed to allocate rpmb chrdev region\n");
goto out_bus_unreg;
}

if (perdev_minors != CONFIG_MMC_BLOCK_MINORS) /* 参数一致性提示:模块参数覆盖了 Kconfig 默认值 */
pr_info("mmcblk: using %d minors per device\n", perdev_minors);

max_devices = min(MAX_DEVICES, (1 << MINORBITS) / perdev_minors); /* 设备上限:按 minor 位宽与每设备占用 minors 折算 */

res = register_blkdev(MMC_BLOCK_MAJOR, "mmc"); /* 注册 mmc 块设备主设备号,供 gendisk/分区创建使用 */
if (res)
goto out_chrdev_unreg;

res = mmc_register_driver(&mmc_driver); /* 最后注册 mmc_driver:允许匹配并触发 mmc_blk_probe */
if (res)
goto out_blkdev_unreg;

return 0;

out_blkdev_unreg:
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); /* 回滚顺序:先撤销 blkdev,再撤销 chrdev/bus */
out_chrdev_unreg:
unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES); /* 回收 RPMB 字符设备号段 */
out_bus_unreg:
bus_unregister(&mmc_rpmb_bus_type); /* 最后撤销 RPMB bus_type */
return res;
}

mmc_blk_exit 模块退出:按依赖逆序注销驱动与全局资源

1
2
3
4
5
6
7
8
9
10
11
12
/**
* mmc_blk_exit - MMC 块设备驱动模块退出入口
*
* 退出路径按依赖逆序执行:先阻止新设备匹配/探测,再回收块设备与 RPMB 相关全局资源。
*/
static void __exit mmc_blk_exit(void)
{
mmc_unregister_driver(&mmc_driver); /* 分支策略:先注销 mmc_driver,避免并发/后续 probe 继续创建块设备实例 */
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc"); /* 回收块设备主设备号 */
unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES); /* 回收 RPMB 字符设备号段 */
bus_unregister(&mmc_rpmb_bus_type); /* 最后注销 RPMB bus_type */
}

mmc_blk_init mmc_blk_exit __register_blkdev unregister_blkdev mmc_blk_rpmb_add mmc_blk_probe mmc_blk_remove _mmc_blk_suspend mmc_blk_shutdown MMC 块层注册与探测及电源管理收尾逻辑

作用与实现要点

  • 初始化/回滚策略mmc_blk_init()按“总线类型→字符设备号→块主设备号→MMC 驱动”顺序注册;失败用 goto 分段回滚,严格按相反顺序释放,避免“注册成功但未完整可用”的中间态遗留。
  • 主设备号注册的并发与唯一性__register_blkdev()major_names_lock(互斥)串行化注册/注销窗口,再用 major_names_spinlock(自旋)保护哈希桶链表更新,保证同一 major 不会被并发重复占用;unregister_blkdev()额外校验 name 匹配,否则触发 WARN_ON
  • 能力门控与资源创建mmc_blk_probe()首先用 CCC_BLOCK_READ 做能力门控(不支持块读直接拒绝绑定);随后创建每卡独立的 complete_wq(高优先级、可回收、按 CPU 分配),避免完成路径与通用系统 workqueue 互相干扰。
  • 运行时电源管理的分支选择mmc_blk_probe()配置 autosuspend 并仅对“非 SD-combo”启用 runtime PM,把 SD-combo 的策略决策留给后续 SDIO 初始化序列;mmc_blk_remove()对称地在退出前 pm_runtime_get_sync()确保设备活跃,再视卡类型决定是否 pm_runtime_disable(),最后 pm_runtime_put_noidle()收尾。
  • 分区状态一致性修正mmc_blk_remove()在移除请求队列前检查 md->part_curr != md->part_type,必要时用 mmc_claim_host()/mmc_release_host()包住 mmc_blk_part_switch(),把卡的“当前硬件分区选择”拉回驱动期望值,减少退出时的状态漂移。
  • RPMB 设备注册的数据生命周期mmc_blk_rpmb_add()用本地栈上的 cid[4] 作为 descr.dev_id 输入;RPMB 核心在注册时会复制该 dev_id,因此不会悬挂指针,但要求 dev_id_len 与输入一致。

平台关注:单核、无 MMU、ARMv7-M(STM32H750)

  • 本段逻辑与单核/无MMU无直接差异,依赖底层 ops 与系统定时机制。
  • 需要额外关注的仅是“机制落地”的可用性:alloc_workqueue()/runtime PM/autosuspend 都依赖内核线程与定时器;在裁剪较重的平台配置下可能被弱化或替换为简化实现,但代码层的并发语义(互斥/自旋/状态位)与错误回滚结构不随单核而消失。

mmc_blk_init 模块初始化并注册 mmcblk 与 rpmb 相关资源

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
/**
* mmc_blk_init - MMC 块设备驱动模块初始化
* @return: 0 表示成功;负值表示失败(并已尽力按阶段回滚已注册资源)
*
* 关键点:
* - 分阶段注册:RPMB bus -> RPMB chrdev 区间 -> mmc 块主设备号 -> mmc driver
* - 失败通过 goto 反向回滚,避免残留半初始化状态
*/
static int __init mmc_blk_init(void)
{
int res;

res = bus_register(&mmc_rpmb_bus_type); /* 注册 RPMB bus 类型,失败则直接返回 */
if (res < 0) {
pr_err("mmcblk: could not register RPMB bus type\n");
return res;
}
res = alloc_chrdev_region(&mmc_rpmb_devt, 0, MAX_DEVICES, "rpmb"); /* 分配 RPMB 字符设备号区间 */
if (res < 0) {
pr_err("mmcblk: failed to allocate rpmb chrdev region\n");
goto out_bus_unreg; /* 回滚到 bus_unregister */
}

if (perdev_minors != CONFIG_MMC_BLOCK_MINORS)
pr_info("mmcblk: using %d minors per device\n", perdev_minors); /* 配置差异提示 */

max_devices = min(MAX_DEVICES, (1 << MINORBITS) / perdev_minors); /* 次设备号空间约束下的最大设备数 */

res = register_blkdev(MMC_BLOCK_MAJOR, "mmc"); /* 注册 mmc 块主设备号 */
if (res)
goto out_chrdev_unreg; /* 回滚 chrdev 区间 */

res = mmc_register_driver(&mmc_driver); /* 注册 mmc_driver:probe/remove/shutdown 回调入口 */
if (res)
goto out_blkdev_unreg; /* 回滚 blkdev 注册 */

return 0;

out_blkdev_unreg:
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
out_chrdev_unreg:
unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES);
out_bus_unreg:
bus_unregister(&mmc_rpmb_bus_type);
return res;
}

mmc_blk_exit 模块退出并反注册 mmcblk 与 rpmb 相关资源

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* mmc_blk_exit - MMC 块设备驱动模块退出
*
* 关键点:
* - 严格按初始化的逆序反注册:driver -> blkdev -> chrdev -> bus
*/
static void __exit mmc_blk_exit(void)
{
mmc_unregister_driver(&mmc_driver); /* 先断开与 mmc core 的绑定,避免并发 probe/remove */
unregister_blkdev(MMC_BLOCK_MAJOR, "mmc");
unregister_chrdev_region(mmc_rpmb_devt, MAX_DEVICES);
bus_unregister(&mmc_rpmb_bus_type);
}

__register_blkdev 注册块设备主设备号到全局 major 表

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/**
* __register_blkdev - 注册块设备主设备号及名称
* @major: 申请的主设备号;为 0 表示自动分配
* @name: 设备名(要求全局唯一)
* @probe: 旧式自动加载探测回调(可能为空)
* @return: major!=0 时成功返回 0;major==0 时成功返回分配到的主设备号;失败返回负 errno
*
* 关键点:
* - major==0 时从 major_names[] 中寻找空位分配(临时策略)
* - major 范围校验:>= BLKDEV_MAJOR_MAX 直接拒绝
* - major_names_lock + major_names_spinlock 组合保证并发一致性与桶链表更新安全
*/
int __register_blkdev(unsigned int major, const char *name,
void (*probe)(dev_t devt))
{
struct blk_major_name **n, *p;
int index, ret = 0;

mutex_lock(&major_names_lock); /* 串行化注册/注销窗口 */

if (major == 0) { /* 自动分配:寻找空闲 major */
for (index = ARRAY_SIZE(major_names)-1; index > 0; index--) {
if (major_names[index] == NULL)
break;
}

if (index == 0) { /* 没有空闲 major */
printk("%s: failed to get major for %s\n",
__func__, name);
ret = -EBUSY;
goto out;
}
major = index;
ret = major; /* major==0 场景下:成功返回分配到的主设备号 */
}

if (major >= BLKDEV_MAJOR_MAX) { /* 参数合法性:越界拒绝 */
pr_err("%s: major requested (%u) is greater than the maximum (%u) for %s\n",
__func__, major, BLKDEV_MAJOR_MAX-1, name);

ret = -EINVAL;
goto out;
}

p = kmalloc(sizeof(struct blk_major_name), GFP_KERNEL);
if (p == NULL) { /* 内存不足 */
ret = -ENOMEM;
goto out;
}

p->major = major;
#ifdef CONFIG_BLOCK_LEGACY_AUTOLOAD
p->probe = probe;
#endif
strscpy(p->name, name, sizeof(p->name));
p->next = NULL;
index = major_to_index(major);

spin_lock(&major_names_spinlock); /* 保护哈希桶链表 */
for (n = &major_names[index]; *n; n = &(*n)->next) {
if ((*n)->major == major) /* 冲突:该 major 已被占用 */
break;
}
if (!*n)
*n = p; /* 成功挂入哈希桶 */
else
ret = -EBUSY;
spin_unlock(&major_names_spinlock);

if (ret < 0) {
printk("register_blkdev: cannot get major %u for %s\n",
major, name);
kfree(p);
}
out:
mutex_unlock(&major_names_lock);
return ret;
}
EXPORT_SYMBOL(__register_blkdev);

unregister_blkdev 注销块设备主设备号并从全局 major 表移除

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
31
32
33
34
35
36
/**
* unregister_blkdev - 注销块设备主设备号
* @major: 要注销的主设备号
* @name: 设备名(必须与注册时一致)
*
* 关键点:
* - 同样使用 major_names_lock + major_names_spinlock 保护删除路径
* - name 不匹配则 WARN_ON,防止误删他人注册项
*/
void unregister_blkdev(unsigned int major, const char *name)
{
struct blk_major_name **n;
struct blk_major_name *p = NULL;
int index = major_to_index(major);

mutex_lock(&major_names_lock);
spin_lock(&major_names_spinlock);
for (n = &major_names[index]; *n; n = &(*n)->next)
if ((*n)->major == major)
break;
if (!*n || strcmp((*n)->name, name)) { /* 参数一致性校验 */
WARN_ON(1);
} else {
p = *n;
*n = p->next; /* 从桶链表摘除 */
}
spin_unlock(&major_names_spinlock);
mutex_unlock(&major_names_lock);
kfree(p);
}

int __register_blkdev(unsigned int major, const char *name,
void (*probe)(dev_t devt));
#define register_blkdev(major, name) \
__register_blkdev(major, name, NULL)
void unregister_blkdev(unsigned int major, const char *name);

mmc_blk_rpmb_add 为卡的 RPMB 分区注册 rpmb 设备实例

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
31
32
33
34
35
36
37
38
39
/**
* mmc_blk_rpmb_add - 注册该卡对应的 RPMB 设备
* @card: mmc 卡对象
*
* 关键点:
* - 依据 enhanced_rpmb_supported 选择可靠写入计数(能力门控)
* - 将 raw_cid 转为 CPU 端序的固定表示,作为 dev_id 输入
* - 遍历 md->rpmbs 为每个 rpmb 子设备调用 rpmb_dev_register()
*/
static void mmc_blk_rpmb_add(struct mmc_card *card)
{
struct mmc_blk_data *md = dev_get_drvdata(&card->dev);
struct mmc_rpmb_data *rpmb;
struct rpmb_dev *rdev;
unsigned int n;
u32 cid[4];
struct rpmb_descr descr = {
.type = RPMB_TYPE_EMMC,
.route_frames = mmc_route_rpmb_frames,
.reliable_wr_count = card->ext_csd.enhanced_rpmb_supported ?
2 : 32, /* 能力门控:增强 RPMB 支持时缩小可靠写入窗口 */
.capacity = card->ext_csd.raw_rpmb_size_mult,
.dev_id = (void *)cid,
.dev_id_len = sizeof(cid),
};

for (n = 0; n < 4; n++)
cid[n] = be32_to_cpu((__force __be32)card->raw_cid[n]); /* 端序规范化:提供稳定的 dev_id 输入 */

list_for_each_entry(rpmb, &md->rpmbs, node) {
rdev = rpmb_dev_register(&rpmb->dev, &descr);
if (IS_ERR(rdev)) { /* 分支策略:单个 rpmb 注册失败不影响其他实例 */
pr_warn("%s: could not register RPMB device\n",
dev_name(&rpmb->dev));
continue;
}
rpmb->rdev = rdev; /* 绑定注册结果,供后续路由/注销使用 */
}
}

mmc_blk_probe 绑定卡并初始化块层数据结构与运行时电源管理

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* mmc_blk_probe - mmcblk 驱动探测入口
* @card: mmc 卡对象
* @return: 0 成功;负 errno 失败
*
* 关键点:
* - CCC_BLOCK_READ 能力门控:不支持块读则拒绝绑定
* - 为该卡创建独立 complete_wq,完成路径使用高优先级/可回收/按 CPU 分配的 worker
* - runtime PM:统一配置 autosuspend,但对 SD-combo 不在此处启用 runtime PM
*/
static int mmc_blk_probe(struct mmc_card *card)
{
struct mmc_blk_data *md;
int ret = 0;

if (!(card->csd.cmdclass & CCC_BLOCK_READ)) /* 能力门控:缺少块读能力直接返回 */
return -ENODEV;

mmc_fixup_device(card, mmc_blk_fixups); /* 分支/策略入口:按卡 quirks 做修正 */

card->complete_wq = alloc_workqueue("mmc_complete",
WQ_MEM_RECLAIM | WQ_HIGHPRI | WQ_PERCPU,
0); /* worker:完成路径专用队列 */
if (!card->complete_wq) {
pr_err("Failed to create mmc completion workqueue");
return -ENOMEM;
}

md = mmc_blk_alloc(card);
if (IS_ERR(md)) { /* 参数/返回值合法性:ERR_PTR 传递失败原因 */
ret = PTR_ERR(md);
goto out_free; /* 分支策略:统一在 out_free 销毁 workqueue */
}

ret = mmc_blk_alloc_parts(card, md);
if (ret)
goto out; /* 分支策略:parts 失败需回滚 parts/req */

mmc_blk_add_debugfs(card, md); /* 后台机制:debugfs 可带来额外访问入口 */

pm_runtime_set_autosuspend_delay(&card->dev, 3000); /* 定时机制:autosuspend 延迟 */
pm_runtime_use_autosuspend(&card->dev);

if (!mmc_card_sd_combo(card)) { /* 分支策略:SD-combo 的 runtime PM 决策延后到 SDIO 流程 */
pm_runtime_set_active(&card->dev);
pm_runtime_enable(&card->dev);
}

mmc_blk_rpmb_add(card);

return 0;

out:
mmc_blk_remove_parts(card, md);
mmc_blk_remove_req(md);
out_free:
destroy_workqueue(card->complete_wq); /* worker:失败路径销毁该卡专用 workqueue */
return ret;
}

static SIMPLE_DEV_PM_OPS(mmc_blk_pm_ops, mmc_blk_suspend, mmc_blk_resume);

static struct mmc_driver mmc_driver = {
.drv = {
.name = "mmcblk",
.pm = &mmc_blk_pm_ops,
},
.probe = mmc_blk_probe,
.remove = mmc_blk_remove,
.shutdown = mmc_blk_shutdown,
};

mmc_blk_remove 解绑卡并回收块层资源与运行时电源管理状态

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
/**
* mmc_blk_remove - mmcblk 驱动移除入口
* @card: mmc 卡对象
*
* 关键点:
* - 先撤 debugfs/parts,避免用户态入口继续触达
* - pm_runtime_get_sync 确保设备处于可访问状态后再做分区切换修正
* - 分区状态不一致时通过 claim_host 串行化切换
* - 最后移除请求队列并销毁 complete_wq,避免 worker 使用已释放资源
*/
static void mmc_blk_remove(struct mmc_card *card)
{
struct mmc_blk_data *md = dev_get_drvdata(&card->dev);

mmc_blk_remove_debugfs(card, md);
mmc_blk_remove_parts(card, md);

pm_runtime_get_sync(&card->dev); /* 状态门控:保证设备活跃以完成后续硬件交互 */
if (md->part_curr != md->part_type) { /* 分支策略:仅在状态不一致时才切换 */
mmc_claim_host(card->host); /* 锁:host 级串行化,避免并发命令 */
mmc_blk_part_switch(card, md->part_type);
mmc_release_host(card->host);
}
if (!mmc_card_sd_combo(card))
pm_runtime_disable(&card->dev); /* 分支策略:与 probe 的 enable 条件对称 */
pm_runtime_put_noidle(&card->dev);

mmc_blk_remove_req(md);
destroy_workqueue(card->complete_wq); /* worker:移除末尾销毁,避免悬空任务 */
}

_mmc_blk_suspend 挂起请求队列以进入休眠或关机路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* _mmc_blk_suspend - 挂起 mmcblk 及其分区的请求队列
* @card: mmc 卡对象
* @return: 当前实现恒为 0
*
* 关键点:
* - 对 md 主队列与每个分区队列分别调用 mmc_queue_suspend()
* - md 为空时直接跳过(防御性判断)
*/
static int _mmc_blk_suspend(struct mmc_card *card)
{
struct mmc_blk_data *part_md;
struct mmc_blk_data *md = dev_get_drvdata(&card->dev);

if (md) { /* 参数/指针合法性 */
mmc_queue_suspend(&md->queue);
list_for_each_entry(part_md, &md->part, part) {
mmc_queue_suspend(&part_md->queue);
}
}
return 0;
}

mmc_blk_shutdown 关机时复用挂起逻辑停止队列

1
2
3
4
5
6
7
8
9
10
11
/**
* mmc_blk_shutdown - 关机回调
* @card: mmc 卡对象
*
* 关键点:
* - 直接复用 _mmc_blk_suspend(),统一关机与系统休眠前的队列停摆策略
*/
static void mmc_blk_shutdown(struct mmc_card *card)
{
_mmc_blk_suspend(card);
}

mmc_route_rpmb_frames mmc_blk_alloc_rpmb_part mmc_blk_remove_rpmb_part mmc_blk_alloc_parts mmc_blk_remove_req mmc_blk_remove_parts RPMB请求路由与分区设备生命周期管理

作用与实现要点

  • 参数校验与分支选择mmc_route_rpmb_frames() 先对 md->queue.cardIS_ERR() 门控,再用 req_len/resp_lenRPMB_FRAME_SIZECHECK_SIZE_NEQ()CHECK_SIZE_ALIGNED() 做合法性校验;之后以 rpmb_frame.req_resp(经 be16_to_cpu())驱动 switch 分支,决定 write 与读写路径。
  • 命令序列策略固定化mmc_route_rpmb_frames()cmd_count = write ? 3 : 2 固化“写三段/读两段”的请求编排,并通过 alloc_idata() + set_idata() 将每段映射为 struct mmc_blk_ioc_data,最终把“多段IOCTL-RPMB”封装进一次 blk_execute_rq() 同步执行。
  • 能力门控与类型门控mmc_blk_alloc_parts()mmc_card_mmc(card) 作为能力门控(非 MMC 卡直接跳过),再按 card->part[idx].area_type & MMC_BLK_DATA_AREA_RPMB 选择 RPMB 字符设备路径 mmc_blk_alloc_rpmb_part(),否则走普通分区 mmc_blk_alloc_part()
  • 设备生命周期与引用关系mmc_blk_alloc_rpmb_part()ida_alloc_max() 分配 RPMB 字符设备 minor(mmc_rpmb_ida),并通过 device_initialize()dev_set_drvdata()cdev_device_add()struct mmc_rpmb_data 挂到设备模型;失败路径用 ida_free()/put_device()/mmc_blk_get() 引用配对,避免泄漏。mmc_blk_remove_rpmb_part()mmc_blk_remove_req()mmc_blk_remove_parts() 负责逆向回收与链表摘除(list_for_each_safe() + list_del())。

mmc_route_rpmb_frames 路由RPMB帧并同步执行块层请求

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
static void set_idata(struct mmc_blk_ioc_data *idata, u32 opcode,
int write_flag, u8 *buf, unsigned int buf_bytes)
{
/*
* The size of an RPMB frame must match what's expected by the
* hardware.
*/
static_assert(!CHECK_SIZE_NEQ(512), "RPMB frame size must be 512 bytes");

idata->ic.opcode = opcode;
idata->ic.flags = MMC_RSP_R1 | MMC_CMD_ADTC;
idata->ic.write_flag = write_flag;
idata->ic.blksz = RPMB_FRAME_SIZE;
idata->ic.blocks = buf_bytes / idata->ic.blksz;
idata->buf = buf;
idata->buf_bytes = buf_bytes;
}
/**
* mmc_route_rpmb_frames - 将RPMB请求/响应帧封装为块层驱动私有请求并同步执行
* @dev: RPMB字符设备对应的device,用于取回struct mmc_rpmb_data
* @req: 请求帧缓冲区(一个或多个struct rpmb_frame)
* @req_len: 请求长度(字节)
* @resp: 响应帧缓冲区(一个或多个struct rpmb_frame)
* @resp_len: 响应长度(字节)
*
* 关键点:
* - 以rpmb_frame.req_resp确定请求类型(be16_to_cpu),并做长度/对齐校验
* - 写类操作固定三段命令:写请求帧 -> 写RESULT_READ请求帧 -> 读响应帧
* - 读类操作固定两段命令:写请求帧 -> 读响应帧
* - 通过struct mmc_queue_req的drv_op/drv_op_data把多段IOCTL交给块层执行
*/
static int mmc_route_rpmb_frames(struct device *dev, u8 *req,
unsigned int req_len, u8 *resp,
unsigned int resp_len)
{
struct rpmb_frame *frm = (struct rpmb_frame *)req;
struct mmc_rpmb_data *rpmb = dev_get_drvdata(dev);
struct mmc_blk_data *md = rpmb->md;
struct mmc_blk_ioc_data **idata;
struct mmc_queue_req *mq_rq;
unsigned int cmd_count;
struct request *rq;
u16 req_type;
bool write;
int ret;

if (IS_ERR(md->queue.card))
return PTR_ERR(md->queue.card); /* 参数合法性:卡对象不可用直接失败 */

if (req_len < RPMB_FRAME_SIZE)
return -EINVAL; /* 参数合法性:最小帧尺寸门槛 */

req_type = be16_to_cpu(frm->req_resp); /* 分支策略:以req_resp选择读写路径 */
switch (req_type) {
case RPMB_PROGRAM_KEY:
if (CHECK_SIZE_NEQ(req_len) || CHECK_SIZE_NEQ(resp_len))
return -EINVAL; /* 参数合法性:单帧固定尺寸 */
write = true;
break;
case RPMB_GET_WRITE_COUNTER:
if (CHECK_SIZE_NEQ(req_len) || CHECK_SIZE_NEQ(resp_len))
return -EINVAL; /* 参数合法性:单帧固定尺寸 */
write = false;
break;
case RPMB_WRITE_DATA:
if (!CHECK_SIZE_ALIGNED(req_len) || CHECK_SIZE_NEQ(resp_len))
return -EINVAL; /* 参数合法性:请求可多帧对齐,响应单帧 */
write = true;
break;
case RPMB_READ_DATA:
if (CHECK_SIZE_NEQ(req_len) || !CHECK_SIZE_ALIGNED(resp_len))
return -EINVAL; /* 参数合法性:请求单帧,响应可多帧对齐 */
write = false;
break;
default:
return -EINVAL; /* 分支策略:未知类型直接拒绝 */
}

cmd_count = write ? 3 : 2; /* 分支策略:写三段/读两段 */

idata = alloc_idata(rpmb, cmd_count);
if (!idata)
return -ENOMEM; /* 参数与资源:为多段命令分配承载结构 */

if (write) {
struct rpmb_frame *resp_frm = (struct rpmb_frame *)resp;

set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK,
1 | MMC_CMD23_ARG_REL_WR, req, req_len); /* 分支策略:可靠写路径携带REL_WR参数 */

memset(resp_frm, 0, RPMB_FRAME_SIZE);
resp_frm->req_resp = cpu_to_be16(RPMB_RESULT_READ); /* 分支策略:构造RESULT_READ请求帧 */
set_idata(idata[1], MMC_WRITE_MULTIPLE_BLOCK, 1, resp,
resp_len);

set_idata(idata[2], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len);
} else {
set_idata(idata[0], MMC_WRITE_MULTIPLE_BLOCK, 1, req, req_len);

set_idata(idata[1], MMC_READ_MULTIPLE_BLOCK, 0, resp, resp_len);
}

rq = blk_mq_alloc_request(md->queue.queue, REQ_OP_DRV_OUT, 0);
if (IS_ERR(rq)) {
ret = PTR_ERR(rq);
goto out; /* 分支策略:块层request分配失败走统一回收 */
}

mq_rq = req_to_mmc_queue_req(rq);
mq_rq->drv_op = MMC_DRV_OP_IOCTL_RPMB; /* 能力门控:驱动私有操作类型为RPMB IOCTL */
mq_rq->drv_op_result = -EIO; /* 参数合法性:默认失败,等待底层覆写 */
mq_rq->drv_op_data = idata; /* 关键行:把多段idata交给底层执行 */
mq_rq->ioc_count = cmd_count; /* 关键行:约束底层消费段数 */
blk_execute_rq(rq, false); /* 后台机制影响:同步执行,依赖调度/等待完成 */
ret = req_to_mmc_queue_req(rq)->drv_op_result; /* 关键行:取回底层执行结果 */

blk_mq_free_request(rq);

out:
free_idata(idata, cmd_count);
return ret;
}

mmc_blk_alloc_rpmb_part 为RPMB分区创建字符设备并挂接到父块设备

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* mmc_blk_alloc_rpmb_part - 为RPMB区域分配并注册字符设备节点
* @card: mmc_card,用于命名、父设备关联
* @md: 父mmc_blk_data,用于引用计数与rpmb列表挂接
* @part_index: 分区索引/配置值
* @size: 以512字节扇区为单位的容量
* @subname: 分区子名(可为空)
*
* 关键点:
* - ida_alloc_max分配唯一minor(mmc_rpmb_ida),失败需ida_free回收
* - device_initialize + cdev_device_add完成字符设备注册
* - mmc_blk_get对父gendisk持引用,失败路径必须put_device配对释放
*/
static int mmc_blk_alloc_rpmb_part(struct mmc_card *card,
struct mmc_blk_data *md,
unsigned int part_index,
sector_t size,
const char *subname)
{
int devidx, ret;
char rpmb_name[DISK_NAME_LEN];
char cap_str[10];
struct mmc_rpmb_data *rpmb;

devidx = ida_alloc_max(&mmc_rpmb_ida, max_devices - 1, GFP_KERNEL); /* 参数合法性:minor分配失败直接返回错误码 */
if (devidx < 0)
return devidx;

rpmb = kzalloc(sizeof(*rpmb), GFP_KERNEL);
if (!rpmb) {
ida_free(&mmc_rpmb_ida, devidx); /* 关键行:失败路径回收minor */
return -ENOMEM;
}

snprintf(rpmb_name, sizeof(rpmb_name),
"mmcblk%u%s", card->host->index, subname ? subname : "");

rpmb->id = devidx;
rpmb->part_index = part_index;
rpmb->dev.init_name = rpmb_name;
rpmb->dev.bus = &mmc_rpmb_bus_type;
rpmb->dev.devt = MKDEV(MAJOR(mmc_rpmb_devt), rpmb->id);
rpmb->dev.parent = &card->dev;
rpmb->dev.release = mmc_blk_rpmb_device_release;
device_initialize(&rpmb->dev); /* 关键行:初始化device对象,后续以put_device驱动释放 */
dev_set_drvdata(&rpmb->dev, rpmb); /* 关键行:建立device->rpmb私有数据关联 */
mmc_blk_get(md->disk); /* 关键行:对父块设备持引用,确保rpmb存在期间父不释放 */
rpmb->md = md;

cdev_init(&rpmb->chrdev, &mmc_rpmb_fileops);
rpmb->chrdev.owner = THIS_MODULE;
ret = cdev_device_add(&rpmb->chrdev, &rpmb->dev);
if (ret) {
pr_err("%s: could not add character device\n", rpmb_name);
goto out_put_device; /* 分支策略:统一走put_device触发release链路 */
}

list_add(&rpmb->node, &md->rpmbs); /* 关键行:挂到父设备rpmb链表,供统一回收 */

string_get_size((u64)size, 512, STRING_UNITS_2,
cap_str, sizeof(cap_str));

pr_info("%s: %s %s %s, chardev (%d:%d)\n",
rpmb_name, mmc_card_id(card), mmc_card_name(card), cap_str,
MAJOR(mmc_rpmb_devt), rpmb->id);

return 0;

out_put_device:
put_device(&rpmb->dev); /* 关键行:触发release,内部需配对mmc_blk_get/ida分配等 */
return ret;
}

mmc_blk_remove_rpmb_part 注销RPMB字符设备并释放device引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* mmc_blk_remove_rpmb_part - 反注册RPMB字符设备并释放对应device
* @rpmb: RPMB设备实例
*
* 关键点:
* - cdev_device_del撤销字符设备与device绑定
* - put_device触发release链路完成最终回收
*/
static void mmc_blk_remove_rpmb_part(struct mmc_rpmb_data *rpmb)

{
cdev_device_del(&rpmb->chrdev, &rpmb->dev); /* 关键行:注销字符设备 */
put_device(&rpmb->dev); /* 关键行:引用归零后由release回收资源 */
}

mmc_blk_alloc_parts 扫描物理分区并按类型创建块分区或RPMB字符设备

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
31
32
33
34
35
36
37
38
39
/**
* mmc_blk_alloc_parts - 为eMMC物理分区创建对应设备节点
* @card: 目标卡
* @md: 主mmc块设备数据
*
* 关键点:
* - mmc_card_mmc作为能力门控:仅eMMC才有EXT_CSD物理分区语义
* - area_type含RPMB则走mmc_blk_alloc_rpmb_part,否则走mmc_blk_alloc_part
* - 遇到任意创建失败立即返回,避免部分创建后的不一致扩散
*/
static int mmc_blk_alloc_parts(struct mmc_card *card, struct mmc_blk_data *md)
{
int idx, ret;

if (!mmc_card_mmc(card))
return 0; /* 能力门控:非MMC卡不创建物理分区派生设备 */

for (idx = 0; idx < card->nr_parts; idx++) {
if (card->part[idx].area_type & MMC_BLK_DATA_AREA_RPMB) {
ret = mmc_blk_alloc_rpmb_part(card, md,
card->part[idx].part_cfg,
card->part[idx].size >> 9,
card->part[idx].name);
if (ret)
return ret; /* 分支策略:失败即返回,交由上层统一清理 */
} else if (card->part[idx].size) {
ret = mmc_blk_alloc_part(card, md,
card->part[idx].part_cfg,
card->part[idx].size >> 9,
card->part[idx].force_ro,
card->part[idx].name,
card->part[idx].area_type);
if (ret)
return ret; /* 分支策略:失败即返回,避免半初始化状态 */
}
}

return 0;
}

mmc_blk_remove_req 删除主块盘并清理队列与引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* mmc_blk_remove_req - 移除gendisk并清理mmc队列资源
* @md: mmc块设备数据
*
* 关键点:
* - del_gendisk停止对外提供块设备入口
* - mmc_cleanup_queue释放队列资源并阻止新请求进入
* - mmc_blk_put释放对md的kref引用
*/
static void mmc_blk_remove_req(struct mmc_blk_data *md)
{
del_gendisk(md->disk); /* 关键行:对外下线块设备 */
mmc_cleanup_queue(&md->queue); /* 关键行:清理队列,阻止新请求接入 */
mmc_blk_put(md); /* 关键行:引用计数归还,可能触发最终释放 */
}

mmc_blk_remove_parts 统一移除RPMB设备与普通分区块设备

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
/**
* mmc_blk_remove_parts - 按链表遍历并移除派生分区设备
* @card: 目标卡(当前函数未直接使用,语义上表示所属card域)
* @md: 主mmc块设备数据,持有rpmbs与part链表
*
* 关键点:
* - list_for_each_safe + list_del确保遍历过程中删除节点安全
* - RPMB链表调用mmc_blk_remove_rpmb_part,普通分区链表调用mmc_blk_remove_req
*/
static void mmc_blk_remove_parts(struct mmc_card *card,
struct mmc_blk_data *md)
{
struct list_head *pos, *q;
struct mmc_blk_data *part_md;
struct mmc_rpmb_data *rpmb;

list_for_each_safe(pos, q, &md->rpmbs) {
rpmb = list_entry(pos, struct mmc_rpmb_data, node);
list_del(pos); /* 关键行:先摘链表,再做释放,避免重复遍历/二次释放 */
mmc_blk_remove_rpmb_part(rpmb);
}

list_for_each_safe(pos, q, &md->part) {
part_md = list_entry(pos, struct mmc_blk_data, part);
list_del(pos); /* 关键行:先摘链表,再做释放,避免重复遍历/二次释放 */
mmc_blk_remove_req(part_md);
}
}

mmc_blk_ioctl_cmd mmc_blk_ioctl_multi_cmd mmc_rpmb_ioctl mmc_rpmb_ioctl_compat mmc_rpmb_chrdev_open mmc_rpmb_chrdev_release mmc_rpmb_fileops 块层请求队列转发与RPMB字符设备ioctl路径

作用与实现要点

  • 请求转发策略:把用户态 ioctl 组包后的命令,封装成 REQ_OP_DRV_{IN,OUT} 的驱动私有请求,交给块层队列同步执行(blk_execute_rq),避免绕过块层的队列化/互斥/电源管理路径。
  • 方向分支选择:以 write_flag 决定 REQ_OP_DRV_OUTREQ_OP_DRV_IN,并在 multi 场景直接以第 0 条命令的 write_flag 决定整批请求方向(隐含约束:同一批 ioctl 不应混合方向)。
  • 能力/路径门控:通过 rpmb 指针是否为 NULL,选择 MMC_DRV_OP_IOCTL_RPMBMMC_DRV_OP_IOCTL,把同一套“块层驱动操作”机制复用于 RPMB 与非 RPMB 两类路径。
  • 错误优先级策略drv_op_result 预置为 -EIO,确保下层未显式写回结果时有确定失败码;返回值上优先返回 ioc_err(硬件/下层执行结果),仅在其为 0 时才返回用户拷贝等软件层错误 err
  • 资源生命周期与一次性载荷:单命令用栈上 idatas[1] 传递“指针数组”,多命令用 kcallocidata**;执行后统一释放请求与 idata/buf,multi 在构造失败时通过缩短 n 实现“只回收已成功分配的项”。

平台关注:单核、无 MMU、ARMv7-M(STM32H750)

  • 这段代码的核心语义强依赖 Linux 用户态/内核态隔离与块层 blk-mq 基础设施__user 指针、copy_from_user/copy_to_userblk_mq_alloc_request/blk_execute_rq 以及下层 MMC host 的并发/中断完成路径。在无 MMU场景中,用户拷贝语义与地址合法性边界会弱化或需要替代实现,否则“参数校验/越界防护”这一层的安全模型会发生变化。
  • 即使是单核,请求执行仍可能跨越:进程上下文(ioctl 发起)↔ 块层/驱动线程 ↔ 中断回调(请求完成/唤醒)。因此锁、等待队列与状态位的意义仍在:该 ioctl 路径会睡眠等待,必须运行在可睡眠上下文。
  • ARMv7-M(STM32H750)常见带 DCache,且 MMC 可能 DMA:idata->buf 参与数据通路时,缓存一致性与对齐/不可缓存区的管理通常由更底层的 DMA/host 驱动承担;在无 MMU 体系下更需要明确“缓冲区可 DMA、可 cache maintenance”的约束,否则会出现读写不一致或数据污染。

mmc_blk_ioctl_cmd 单条 MMC ioctl 命令转发到块层队列执行

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* mmc_blk_ioctl_cmd - 将单条 MMC ioctl 命令封装为块层驱动私有请求并同步执行
* @md: mmc block 设备上下文
* @ic_ptr: 用户态 ioctl 命令结构指针
* @rpmb: RPMB 上下文,非 RPMB 路径为 NULL
*
* Return: 优先返回下层执行结果 drv_op_result;若下层成功则返回用户态拷贝错误码
*/
static int mmc_blk_ioctl_cmd(struct mmc_blk_data *md,
struct mmc_ioc_cmd __user *ic_ptr,
struct mmc_rpmb_data *rpmb)
{
struct mmc_blk_ioc_data *idata;
struct mmc_blk_ioc_data *idatas[1];
struct mmc_queue *mq;
struct mmc_card *card;
int err = 0, ioc_err = 0;
struct request *req;

idata = mmc_blk_ioctl_copy_from_user(ic_ptr);
if (IS_ERR(idata))
return PTR_ERR(idata);

idata->rpmb = rpmb; /* 路径门控:是否走 RPMB ioctl */

card = md->queue.card;
if (IS_ERR(card)) { /* 参数/状态合法性:card 指针异常 */
err = PTR_ERR(card);
goto cmd_done;
}

mq = &md->queue;
req = blk_mq_alloc_request(mq->queue,
idata->ic.write_flag ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN, 0); /* 分支策略:按 write_flag 选择 IN/OUT */
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto cmd_done;
}

idatas[0] = idata;
req_to_mmc_queue_req(req)->drv_op =
rpmb ? MMC_DRV_OP_IOCTL_RPMB : MMC_DRV_OP_IOCTL; /* 路径门控:RPMB 与普通 ioctl */
req_to_mmc_queue_req(req)->drv_op_result = -EIO; /* 默认失败码:防止下层未回填结果 */
req_to_mmc_queue_req(req)->drv_op_data = idatas;
req_to_mmc_queue_req(req)->ioc_count = 1;

blk_execute_rq(req, false); /* 同步执行:依赖块层/驱动完成路径 */
ioc_err = req_to_mmc_queue_req(req)->drv_op_result;

err = mmc_blk_ioctl_copy_to_user(ic_ptr, idata); /* 参数合法性/可访问性:用户态写回 */
blk_mq_free_request(req);

cmd_done:
kfree(idata->buf);
kfree(idata);
return ioc_err ? ioc_err : err; /* 返回策略:优先下层执行错误,其次用户态拷贝错误 */
}

mmc_blk_ioctl_multi_cmd 多条 MMC ioctl 命令批量转发到块层队列执行

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/**
* mmc_blk_ioctl_multi_cmd - 将多条 MMC ioctl 命令批量封装为一个块层驱动私有请求并同步执行
* @md: mmc block 设备上下文
* @user: 用户态 multi-cmd 结构指针
* @rpmb: RPMB 上下文,非 RPMB 路径为 NULL
*
* Return: 优先返回下层执行结果 drv_op_result;若下层成功则返回用户态拷贝错误码
*/
static int mmc_blk_ioctl_multi_cmd(struct mmc_blk_data *md,
struct mmc_ioc_multi_cmd __user *user,
struct mmc_rpmb_data *rpmb)
{
struct mmc_blk_ioc_data **idata = NULL;
struct mmc_ioc_cmd __user *cmds = user->cmds;
struct mmc_card *card;
struct mmc_queue *mq;
int err = 0, ioc_err = 0;
__u64 num_of_cmds;
unsigned int i, n;
struct request *req;

if (copy_from_user(&num_of_cmds, &user->num_of_cmds,
sizeof(num_of_cmds))) /* 参数合法性:用户态可读性 */
return -EFAULT;

if (!num_of_cmds) /* 分支策略:空批次直接返回成功 */
return 0;

if (num_of_cmds > MMC_IOC_MAX_CMDS) /* 参数校验:上限门控 */
return -EINVAL;

n = num_of_cmds;
idata = kcalloc(n, sizeof(*idata), GFP_KERNEL);
if (!idata)
return -ENOMEM;

for (i = 0; i < n; i++) {
idata[i] = mmc_blk_ioctl_copy_from_user(&cmds[i]);
if (IS_ERR(idata[i])) { /* 参数合法性:逐条构造失败 */
err = PTR_ERR(idata[i]);
n = i; /* 一次性回收边界:只回收已成功分配的项 */
goto cmd_err;
}
idata[i]->rpmb = rpmb; /* 路径门控:是否走 RPMB ioctl */
}

card = md->queue.card;
if (IS_ERR(card)) { /* 参数/状态合法性:card 指针异常 */
err = PTR_ERR(card);
goto cmd_err;
}

mq = &md->queue;
req = blk_mq_alloc_request(mq->queue,
idata[0]->ic.write_flag ? REQ_OP_DRV_OUT : REQ_OP_DRV_IN, 0); /* 分支策略:整批按第 0 条命令方向 */
if (IS_ERR(req)) {
err = PTR_ERR(req);
goto cmd_err;
}

req_to_mmc_queue_req(req)->drv_op =
rpmb ? MMC_DRV_OP_IOCTL_RPMB : MMC_DRV_OP_IOCTL; /* 路径门控:RPMB 与普通 ioctl */
req_to_mmc_queue_req(req)->drv_op_result = -EIO; /* 默认失败码:防止下层未回填结果 */
req_to_mmc_queue_req(req)->drv_op_data = idata;
req_to_mmc_queue_req(req)->ioc_count = n; /* 一次性批量计数:下层据此遍历 */
blk_execute_rq(req, false); /* 同步执行:依赖块层/驱动完成路径 */
ioc_err = req_to_mmc_queue_req(req)->drv_op_result;

for (i = 0; i < n && !err; i++) /* 写回策略:逐条写回,遇到首个用户态错误即停止 */
err = mmc_blk_ioctl_copy_to_user(&cmds[i], idata[i]);

blk_mq_free_request(req);

cmd_err:
for (i = 0; i < n; i++) {
kfree(idata[i]->buf);
kfree(idata[i]);
}
kfree(idata);
return ioc_err ? ioc_err : err; /* 返回策略:优先下层执行错误,其次用户态拷贝错误 */
}

mmc_rpmb_ioctl RPMB 字符设备 ioctl 分发到块层实现

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
31
32
/**
* mmc_rpmb_ioctl - RPMB 字符设备 ioctl 入口,按命令号分发到块设备 ioctl 执行路径
* @filp: 字符设备文件
* @cmd: ioctl 命令号
* @arg: 用户态参数指针
*
* Return: 分发目标函数返回值;未知命令返回 -EINVAL
*/
static long mmc_rpmb_ioctl(struct file *filp, unsigned int cmd,
unsigned long arg)
{
struct mmc_rpmb_data *rpmb = filp->private_data;
int ret;

switch (cmd) { /* 分支策略:仅支持单命令与多命令两类 ioctl */
case MMC_IOC_CMD:
ret = mmc_blk_ioctl_cmd(rpmb->md,
(struct mmc_ioc_cmd __user *)arg,
rpmb);
break;
case MMC_IOC_MULTI_CMD:
ret = mmc_blk_ioctl_multi_cmd(rpmb->md,
(struct mmc_ioc_multi_cmd __user *)arg,
rpmb);
break;
default:
ret = -EINVAL;
break;
}

return ret;
}

mmc_rpmb_ioctl_compat 32/64 位兼容 ioctl 参数指针转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* mmc_rpmb_ioctl_compat - compat_ioctl 入口,将 compat 指针扩展后复用 mmc_rpmb_ioctl
* @filp: 字符设备文件
* @cmd: ioctl 命令号
* @arg: compat 用户态参数
*
* Return: mmc_rpmb_ioctl 的返回值
*/
#ifdef CONFIG_COMPAT
static long mmc_rpmb_ioctl_compat(struct file *filp, unsigned int cmd,
unsigned long arg)
{
return mmc_rpmb_ioctl(filp, cmd, (unsigned long)compat_ptr(arg)); /* 参数合法性:compat 指针扩展 */
}
#endif

mmc_rpmb_chrdev_open RPMB 字符设备打开与引用计数持有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* mmc_rpmb_chrdev_open - RPMB 字符设备 open:绑定私有数据并提升设备引用计数
* @inode: inode
* @filp: 文件对象
*
* Return: nonseekable_open 的返回值
*/
static int mmc_rpmb_chrdev_open(struct inode *inode, struct file *filp)
{
struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev,
struct mmc_rpmb_data, chrdev);

get_device(&rpmb->dev); /* 生命周期门控:避免 open 期间设备被释放 */
filp->private_data = rpmb;

return nonseekable_open(inode, filp);
}

mmc_rpmb_chrdev_release RPMB 字符设备关闭与引用计数释放

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* mmc_rpmb_chrdev_release - RPMB 字符设备 release:释放设备引用计数
* @inode: inode
* @filp: 文件对象
*
* Return: 0
*/
static int mmc_rpmb_chrdev_release(struct inode *inode, struct file *filp)
{
struct mmc_rpmb_data *rpmb = container_of(inode->i_cdev,
struct mmc_rpmb_data, chrdev);

put_device(&rpmb->dev); /* 生命周期门控:与 open 的 get_device 配对 */

return 0;
}

mmc_rpmb_fileops RPMB 字符设备 file_operations 回调表

1
2
3
4
5
6
7
8
9
10
11
12
/**
* mmc_rpmb_fileops - RPMB 字符设备操作集:open/release/ioctl 绑定
*/
static const struct file_operations mmc_rpmb_fileops = {
.release = mmc_rpmb_chrdev_release,
.open = mmc_rpmb_chrdev_open,
.owner = THIS_MODULE,
.unlocked_ioctl = mmc_rpmb_ioctl, /* 关键路径:ioctl 主入口 */
#ifdef CONFIG_COMPAT
.compat_ioctl = mmc_rpmb_ioctl_compat, /* 关键路径:compat ioctl 入口 */
#endif
};