[Linux]学习笔记系列 --[drivers]mmc]mmc


title: mmc

categories:

  • linux
  • drivers
  • mmc
    tags:
  • linux
  • drivers
    abbrlink: bba6a469
    date: 2025-10-03 09:01:49

https://github.com/wdfk-prog/linux-study

文章目录

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命令定义
c 复制代码
/* 标准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响应格式与状态定义
c 复制代码
/*
  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) 寄存器关键字段定义
c 复制代码
/*
 * 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) 命令相关定义
c 复制代码
/*
 * 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 是一个内核中的标准宏,它能根据结构体成员的地址反向计算出整个结构体的起始地址,是设备模型中实现对象间关联的关键技术。

代码分析

访问器宏
c 复制代码
// 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)
卡状态位定义
c 复制代码
/* 卡状态定义 */
// 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)
状态查询宏
c 复制代码
// 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)
状态修改宏
c 复制代码
// 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总线。这种相反的顺序是至关重要的,可以避免因依赖关系导致的错误(例如,在仍有设备注册在总线上时就注销了总线类型)。

代码分析

c 复制代码
// 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等),则不再需要重复上电操作,提高了效率。

代码分析

c 复制代码
// 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标志被设置,实际的调谐操作也会被推迟。这可以防止在一次连续的数据传输过程中间插入耗时的调谐操作,保证了数据流的连续性。

代码分析

调谐保持与释放
c 复制代码
// 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;
}
请求完成处理
c 复制代码
// 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回调通常会唤醒一个正在等待的完成量。

代码分析

完成命令
c 复制代码
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);
请求启动与分发
c 复制代码
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);
请求准备与同步封装
c 复制代码
// 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错误等)被填充。

代码分析

c 复制代码
// 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总线。

代码分析

c 复制代码
// 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池,以备后续使用。

代码分析

c 复制代码
// 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数组统一置位/清零

c 复制代码
/**
 * @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 上电前准备外部时钟与复位线预处理

c 复制代码
/**
 * @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 上电后释放复位并等待稳定时间

c 复制代码
/**
 * @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 掉电阶段拉回复位、等待并关闭外部时钟

c 复制代码
/**
 * @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 解析资源能力并注册上电时序实例

c 复制代码
/**
 * @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 注销上电时序实例

c 复制代码
/**
 * @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 产生硬件复位脉冲

c 复制代码
/**
 * @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 系统重启通知链中的复位处理

c 复制代码
/**
 * @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 获取复位脚并按能力注册紧急重启复位

c 复制代码
/**
 * @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

c 复制代码
/**
 * @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 设备树兼容项匹配

c 复制代码
/**
 * 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 按修正表筛选并触发厂商回调

c 复制代码
/**
 * 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 块设备驱动

c 复制代码
/* 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 模块退出:按依赖逆序注销驱动与全局资源

c 复制代码
/**
 * 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 相关资源

c 复制代码
/**
 * 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 相关资源

c 复制代码
/**
 * 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 表

c 复制代码
/**
 * __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 表移除

c 复制代码
/**
 * 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 设备实例

c 复制代码
/**
 * 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 绑定卡并初始化块层数据结构与运行时电源管理

c 复制代码
/**
 * 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 解绑卡并回收块层资源与运行时电源管理状态

c 复制代码
/**
 * 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 挂起请求队列以进入休眠或关机路径

c 复制代码
/**
 * _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 关机时复用挂起逻辑停止队列

c 复制代码
/**
 * 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帧并同步执行块层请求

c 复制代码
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分区创建字符设备并挂接到父块设备

c 复制代码
/**
 * 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引用

c 复制代码
/**
 * 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字符设备

c 复制代码
/**
 * 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 删除主块盘并清理队列与引用

c 复制代码
/**
 * 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设备与普通分区块设备

c 复制代码
/**
 * 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 命令转发到块层队列执行

c 复制代码
/**
 * 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 命令批量转发到块层队列执行

c 复制代码
/**
 * 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 分发到块层实现

c 复制代码
/**
 * 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 参数指针转换

c 复制代码
/**
 * 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 字符设备打开与引用计数持有

c 复制代码
/**
 * 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 字符设备关闭与引用计数释放

c 复制代码
/**
 * 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 回调表

c 复制代码
/**
 * 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
};

相关推荐
崎岖Qiu2 小时前
【计算机网络 | 第一篇】计算机网络概述
笔记·学习·计算机网络
xian_wwq2 小时前
【学习笔记】一文读懂一次和二次调频
笔记·学习·储能·调频
嵌入小生0072 小时前
数据结构 | 常用排序算法大全及二分查找
linux·数据结构·算法·vim·排序算法·嵌入式
近津薪荼2 小时前
优选算法——滑动窗口4(找子串)
c++·学习·算法
EverydayJoy^v^2 小时前
RH134学习进程——十二.运行容器(3)
linux·容器
£漫步 云端彡2 小时前
Golang学习历程【第八篇 指针(pointer)】
javascript·学习·golang
三水不滴2 小时前
23种设计模式
经验分享·笔记·设计模式
崎岖Qiu2 小时前
【计算机网络 | 第二篇】三种交换方式和互联网的核心部分
网络·笔记·计算机网络·路由器
Warren982 小时前
一次文件上传异常的踩坑、定位与修复复盘(Spring Boot + 接口测试)
java·开发语言·spring boot·笔记·后端·python·面试