[toc]
drivers/mmc 多媒体卡(MultiMediaCard)子系统 SD/eMMC/SDIO设备驱动框架
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc 子系统是为了给Linux内核提供一个统一的、可扩展的框架来支持一整类基于**多媒体卡(MultiMediaCard)**规范及其衍生协议的设备而诞生的。这些设备包括:
- MMC (MultiMediaCard):一种早期的闪存卡标准。
- SD (Secure Digital) Card:在MMC基础上发展而来的、目前最普及的移动存储卡,增加了安全特性和更高的性能。
- eMMC (embedded MMC):将MMC控制器和NAND闪存封装在同一个BGA芯片中,作为一种“嵌入式”存储解决方案,被广泛用作智能手机、平板电脑和许多嵌入式设备的“硬盘”。
- SDIO (Secure Digital Input/Output):一种扩展规范,允许SD插槽除了支持存储卡外,还能连接I/O设备,如Wi-Fi模块、蓝牙模块、GPS接收器等。
在没有这个统一框架之前,对这些设备的支持是零散的。该框架的诞生解决了以下关键问题:
- 代码复用:MMC、SD、SDIO、eMMC协议有大量共通之处。一个统一的框架可以抽象出公共的协议处理、命令收发、数据传输逻辑,避免为每种卡和每种主机控制器都重写一遍。
- 硬件抽象:市面上有各种各样的SD/MMC主机控制器(Host Controller),它们是SoC上负责与SD卡进行物理通信的IP核。该框架需要将这些具体硬件的差异隔离开,为上层提供一个标准接口。
- 功能分离:需要将对“卡的功能”(是存储设备还是I/O设备)的驱动与底层的“总线协议”驱动分离开。
它的发展经历了哪些重要的里程碑或版本迭代?
- 框架的建立:最重要的里程碑是将分散的驱动重构为一个分层的、模块化的MMC子系统。这确立了Host驱动 - Core - Card驱动的核心架构。
- SDHCI规范的支持:SD主机控制器接口(SD Host Controller Interface)是一个标准化的硬件寄存器规范。
sdhci.c驱动的出现是一个巨大的进步,因为它提供了一个通用的Host驱动,可以支持任何符合SDHCI规范的硬件控制器,大大减少了为新SoC编写Host驱动的工作量。 - 高性能模式的支持:随着SD卡和eMMC标准的演进,框架不断增加对新功能和更高速度模式的支持,如DDR模式、HS200(200MB/s)、HS400(400MB/s)等。
- eMMC高级功能:为eMMC增加了对TRIM/Discard(提升闪存寿命和性能)、安全擦除、分区管理等高级功能的支持。
目前该技术的社区活跃度和主流应用情况如何?
MMC子系统是Linux内核中最为成熟和活跃的核心子系统之一。由于几乎所有的移动和嵌入式设备都依赖它,因此它得到了持续的维护和功能增强。
- 主流应用:
- 智能手机/平板电脑:eMMC曾是安卓设备内部存储的主流标准(现在正逐渐被UFS取代)。
- 单板计算机(SBC):如树莓派,使用SD卡作为其主启动和存储设备。
- 物联网(IoT)设备:广泛使用SDIO接口的Wi-Fi/蓝牙模块。
- 数码相机、无人机等消费电子产品:使用SD卡作为主要存储介质。
核心原理与设计
它的核心工作原理是什么?
drivers/mmc 的核心是一个清晰的三层架构:
主机控制器驱动 (Host Driver) - (
drivers/mmc/host/)- 职责:这是最底层,直接与硬件打交道。它负责控制具体的SD/MMC控制器IP核,包括管理时钟、电源、引脚、执行DMA传输、处理硬件中断等。
- 实现:每个Host驱动(如
sdhci.cfor SDHCI,dw_mmc.cfor Synopsys DesignWare a core)都会实现一套标准的操作函数集struct mmc_host_ops。 - 抽象:它将底层硬件的复杂性抽象为一个标准的MMC主机对象 (
struct mmc_host),并将其注册到MMC核心。
MMC核心 (Core) - (
drivers/mmc/core/)- 职责:这是整个子系统的大脑和调度中心。它实现了MMC/SD/SDIO协议栈,负责卡的上电、初始化、识别过程,以及命令(CMD)和数据(DAT)的收发逻辑。
- 实现:核心层代码不关心具体的Host硬件是什么样的。当它需要发送一个命令时,它会通过Host驱动注册的
mmc_host_ops中的函数(如.request(),.set_ios())来间接地控制硬件。 - 角色:它扮演了总线驱动的角色,扫描总线上(插槽里)的设备,并为识别出的设备创建对应的逻辑设备。
设备驱动 (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预处理器将协议的细节抽象化:
- 命令符号化: 每一个MMC命令(如CMD0, CMD1, …)都被
#define为一个唯一的整数值,并赋予一个描述性的名称,例如MMC_GO_IDLE_STATE。这使得驱动代码的逻辑(如状态机转换)清晰易懂。 - 响应格式位域化: 卡片对命令的响应(特别是R1响应)是一个包含多个状态和错误标志的32位字。文件通过定义位掩码(如
R1_OUT_OF_RANGE (1 << 31))和提取宏(如R1_CURRENT_STATE(x)),将解析这个状态字的过程符号化,使得错误处理和状态判断逻辑标准化。 - 寄存器映射: MMC卡拥有复杂的内部寄存器,如CSD(Card-Specific Data)和至关重要的EXT_CSD(Extended CSD)。该文件将EXT_CSD这个长达512字节的寄存器中的每一个字节偏移量(offset)都定义为一个宏,如
EXT_CSD_BUS_WIDTH(183)。同时,寄存器内特定位或位域的含义也被定义为宏,如EXT_CSD_BUS_WIDTH_8(2)。 - 内联辅助函数: 文件提供了一些简单的
static inline函数,如mmc_op_multi,用于快速判断一个命令的类型。这些函数在编译时会被内联,没有函数调用的开销,提供了类型安全的便捷检查。
代码分析
MMC命令定义
1 | /* 标准MMC命令 (根据JEDEC 4.1规范) */ |
R1响应格式与状态定义
1 | /* |
扩展CSD (EXT_CSD) 寄存器关键字段定义
1 | /* |
MMC_SWITCH (CMD6) 命令相关定义
1 | /* |
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)**来工作。
- 驱动注册:在模块初始化时,
block.c会调用mmc_register_driver(&mmc_block_driver)将自己注册到MMC总线上。mmc_block_driver这个结构体声明了它是一个MMC驱动,并提供了.probe和.remove等回调函数。 - 设备匹配:当MMC核心层在插槽中发现一张卡并成功初始化后,它会将这张卡注册为一个
mmc总线设备。MMC总线核心(bus.c)会尝试将这个新设备与所有已注册的mmc_driver进行匹配。mmc_block_driver的匹配规则很简单:它几乎能匹配所有类型的存储卡(SD, MMC, eMMC)。 - 探测(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设备节点就会出现。
- 分配块设备:它会分配一个
- 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)来管理一组布尔状态,并通过宏来封装其操作,以提高代码的可读性和可维护性。
- 位掩码(Bitmask):
struct mmc_card结构体中有一个名为state的整型成员。每一个独立的状态(如PRESENT,READONLY)都被定义为一个唯一的2的幂次值(例如(1<<0),(1<<1)),这意味着在二进制表示中,每个状态都对应一个独立的位置为’1’。通过这种方式,一个32位的整型变量理论上可以同时存储32个不同的布尔状态,极大地节省了内存空间。 - 状态查询: 查询宏(如
mmc_card_present(c))利用按位与(&)操作符。将state变量与代表特定状态的位掩码相与,如果结果不为零,则说明该状态位被置位(为’1’),表示状态为真。 - 状态设置: 设置宏(如
mmc_card_set_present(c))利用按位或赋值(|=)操作符。将state变量与特定状态的位掩码相或,这会将对应的状态位置为’1’,而不会影响其他位的状态。 - 状态清除: 清除宏(如
mmc_card_clr_suspended(c))则使用按位与赋值(&=)和按位非(~)的组合。~MMC_STATE_SUSPENDED会产生一个除了暂停状态位为’0’、其他所有位都为’1’的掩码。将其与state变量相与,就能精确地将暂停状态位清零,同时保持其他位不变。 - 访问器宏: 像
mmc_card_name(c)和container_of这样的宏提供了对结构体成员的便捷访问,隐藏了内部数据结构的细节,使得上层代码更加简洁。container_of是一个内核中的标准宏,它能根据结构体成员的地址反向计算出整个结构体的起始地址,是设备模型中实现对象间关联的关键技术。
代码分析
访问器宏
1 | // mmc_card_name(c): 获取卡的产品名称。 |
卡状态位定义
1 | /* 卡状态定义 */ |
状态查询宏
1 | // mmc_card_present(c): 检查卡片是否在位。 |
状态修改宏
1 | // mmc_card_set_present(c): 设置卡片为存在状态。 |
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确保在系统早期阶段执行初始化。
初始化流程 (
mmc_init):- 该函数按严格的依赖顺序执行初始化步骤。首先调用
mmc_register_bus(),这会向内核设备模型注册一个名为mmc的bus_type。这个总线类型是连接MMC卡设备和MMC卡驱动的桥梁。 - 成功后,调用
mmc_register_host_class(),它会注册一个名为mmc_host的设备类(class)。这会在sysfs中创建/sys/class/mmc_host目录,为所有MMC主机控制器提供一个统一的视图(如mmc0,mmc1)。 - 接着,调用
sdio_register_bus()注册一个独立的、名为sdio的bus_type。虽然SDIO卡通过MMC总线进行通信,但其上的功能设备(Functions)具有不同的寻址和驱动匹配逻辑,因此需要一个专门的SDIO总线来管理SDIO功能驱动和设备。 - 函数使用了
goto语句进行错误处理。如果在任何一步注册失败,程序会跳转到相应的标签,执行已经成功注册部分的反向注销操作,确保系统状态的一致性。这是一种在内核中常见的、高效的错误回滚(rollback)模式。
- 该函数按严格的依赖顺序执行初始化步骤。首先调用
退出流程 (
mmc_exit):- 该函数在模块卸载时被调用,执行与
mmc_init完全相反的操作。 - 它严格按照“后进先出”(LIFO)的原则进行清理:首先注销SDIO总线,然后是MMC主机类,最后是MMC总线。这种相反的顺序是至关重要的,可以避免因依赖关系导致的错误(例如,在仍有设备注册在总线上时就注销了总线类型)。
- 该函数在模块卸载时被调用,执行与
代码分析
1 | // mmc_init: MMC核心子系统的模块初始化函数。 |
MMC主机控制器独占声明:实现总线访问的互斥与电源管理
本代码片段展示了MMC核心子系统中至关重要的主机控制器锁定机制。其核心功能是通过mmc_claim_host(及其实现__mmc_claim_host)提供一个可重入的、可中断的、且与电源管理集成的互斥锁。任何需要访问MMC/SD/SDIO总线硬件的驱动程序(如SD卡块设备驱动、SDIO WiFi驱动)在执行I/O操作前都必须调用此函数。它确保了在任何时刻,只有一个执行上下文可以控制MMC主机硬件,同时保证了硬件在被访问前处于上电状态。
实现原理分析
此函数并未直接使用标准的mutex_lock,而是实现了一个更复杂的、定制化的阻塞锁。这是因为MMC主机的锁定需要满足几个特殊需求:可重入性、可被外部事件中止、以及与运行时电源管理(Runtime PM)的紧密集成。
自定义阻塞锁:
- 函数的核心是一个
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等待队列上。当锁的持有者释放锁时,会唤醒等待队列上的所有任务,它们将重新进入循环进行条件检查。
- 函数的核心是一个
中止机制:
abort参数允许锁的获取过程被外部事件中断。例如,当一个卡被物理拔出时,另一个内核线程可以设置abort标志。在等待循环中,每次都会检查atomic_read(abort),如果发现非零值,将立即放弃获取锁并返回,避免了在设备已不存在的情况下无限期等待。电源管理集成:
host->claim_cnt是一个引用计数器,用于支持可重入锁。- 关键逻辑在于
if (host->claim_cnt == 1)。只有在主机从未被声明(计数从0变为1)的第一次成功获取锁时,才会执行pm_runtime_get_sync(mmc_dev(host))。 pm_runtime_get_sync会同步地唤醒并恢复设备的电源。这确保了硬件只在上层驱动真正需要访问它时才上电,并在第一次claim时完成。对于后续的重入claim(计数从1变为2等),则不再需要重复上电操作,提高了效率。
代码分析
1 | // mmc_claim_host: 独占性地声明一个主机(简化接口)。 |
MMC请求完成与调谐管理:请求生命周期的终点与信号完整性维护
本代码片段展示了MMC子系统中的两个关键功能:mmc_request_done 函数,它是所有MMC请求处理流程的终点站;以及mmc_retune_hold/release 函数对,它们实现了一个引用计数机制来管理和推迟信号调谐(retuning)过程。mmc_request_done 负责在底层硬件操作完成后,进行错误分析、状态更新,并最终通知上层调用者,而调谐管理则是在高速模式下维持总线信号完整性的重要保障。
实现原理分析
请求完成 (
mmc_request_done): 这是一个由底层主机控制器驱动(Host Driver)调用的核心回调函数。其原理如下:- 错误检测与响应: 函数首先检查请求中各个命令(主命令、数据、停止命令)的错误码。它特别关注CRC校验错误(
-EILSEQ),因为这通常是信号完整性问题的标志。如果检测到CRC错误,并且当前操作本身不是调谐命令,它会调用mmc_retune_needed()来设置一个标志,表明在处理下一个请求之前需要进行一次信号调谐。 - 状态清理: 它负责清理MMC核心层的状态,例如将
host->ongoing_mrq指针置为NULL,表示当前没有正在进行中的请求。 - 调试与追踪: 在请求被确认为最终完成(即没有错误、不需要重试或卡已被拔出)后,函数会打印详细的调试日志,并关闭LED指示灯,为开发者提供详尽的状态信息。
- 异步通知: 最关键的一步是调用
mrq->done(mrq)回调函数(如果存在)。这是实现异步操作的核心机制。上层(如块设备层)在提交请求时可以提供这个回调函数,当硬件操作完成时,该回调被执行,从而唤醒等待的进程或触发下一个操作,而不需要上层进行阻塞轮询。
- 错误检测与响应: 函数首先检查请求中各个命令(主命令、数据、停止命令)的错误码。它特别关注CRC校验错误(
调谐保持机制 (
mmc_retune_hold/release):- 这是一个基于引用计数的锁机制,用于控制何时可以安全地执行信号调谐。
mmc_retune_hold会增加host->hold_retune计数器。这通常在一个复杂操作(如多块读写)开始时调用。mmc_retune_release则减少该计数器,在操作结束时调用。- MMC核心在准备分发新请求时,会检查
host->hold_retune计数器。只要该计数器大于0,即使retune_needed标志被设置,实际的调谐操作也会被推迟。这可以防止在一次连续的数据传输过程中间插入耗时的调谐操作,保证了数据流的连续性。
代码分析
调谐保持与释放
1 | // mmc_retune_hold: 增加调谐保持计数,推迟调谐操作。 |
请求完成处理
1 | // mmc_request_done: 完成一个MMC请求的处理。 |
MMC请求处理核心:命令的准备、分发与完成机制
本代码片段是Linux内核MMC子系统的核心部分,负责处理一个MMC/SD/SDIO请求(mmc_request,简称mrq)从提交到完成的整个生命周期。它定义了MMC核心层(总线无关的逻辑)与底层主机控制器驱动(硬件相关的实现)之间的标准交互接口。其主要功能包括:对请求进行合法性检查与准备、将请求分发给主机控制器驱动执行,以及在请求完成后进行状态更新、错误处理和完成通知。
实现原理分析
该代码的执行流程体现了典型的分层驱动模型,通过函数指针结构(mmc_host_ops)实现了上层逻辑与底层硬件的解耦。
请求的准备 (
mmc_mrq_prep): 在一个请求被发送到硬件之前,此函数作为“守门员”,负责进行一系列的验证和初始化。它确保请求中的命令(mmc_command)、数据(mmc_data)等部分的内部指针和状态被正确设置。最重要的是,它会根据主机控制器(mmc_host)的能力(如最大块大小max_blk_size、最大请求大小max_req_size)来校验请求参数的合法性,防止向硬件提交无法处理的请求。请求的启动与分发 (
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驱动)提供。
请求的完成 (
mmc_request_done):- 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须调用
mmc_request_done来通知核心层。 - 此函数是请求处理的终点。它负责分析命令执行后的错误码,并根据错误类型采取相应措施,例如,在发生CRC错误(
-EILSEQ)时,通过mmc_retune_needed标记需要进行重新调谐。 - 它会更新主机的状态,例如将
ongoing_mrq指针清空。 - 最关键的一步是,如果请求中设置了
done回调函数(mrq->done),它会调用该函数。这构成了异步通知机制,允许最初发起请求的调用者得知操作已完成。对于同步请求,这个done回调通常会唤醒一个正在等待的完成量。
- 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须调用
代码分析
完成命令
1 | static inline void mmc_complete_cmd(struct mmc_request *mrq) |
请求启动与分发
1 | static inline void mmc_delay(unsigned int ms) |
请求准备与同步封装
1 | // mmc_mrq_prep: 准备一个MMC请求,进行校验和初始化。 |
MMC命令发送与阻塞等待
本代码片段定义了MMC核心子系统中用于发送命令并同步等待其完成的高级接口:mmc_wait_for_cmd 及其依赖的 mmc_wait_for_req。其核心功能是为上层驱动提供一个简化的、阻塞式的编程模型。驱动程序只需构建一个命令,调用这些函数,函数将在命令完成后才返回。这极大地简化了驱动的编写,隐藏了底层请求提交、中断处理和任务睡眠/唤醒的复杂细节。
实现原理分析
此功能的实现建立在MMC核心的异步请求机制之上,并将其封装为同步调用。它体现了清晰的层次结构:一个命令(mmc_command)被包装在一个请求(mmc_request)中进行处理。
请求(Request)与命令(Command)的抽象:
mmc_command(cmd): 代表一个单独的MMC命令,包含操作码、参数、期望的响应类型等。mmc_request(mrq): 代表一个完整的MMC操作,它通常包含一个命令(mrq.cmd),并且可能还包含一个数据传输(mrq.data)和一个停止命令(mrq.stop)。本代码片段处理的是最简单的情况:一个只包含单个命令的请求。
同步封装 (
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)机制,使当前任务进入睡眠状态,直到硬件完成命令并产生中断。中断服务程序会标记请求已完成并唤醒等待的任务。
- 此函数首先调用
便利的命令封装 (
mmc_wait_for_cmd):- 这是一个更高级的便利函数,专门用于发送不涉及数据传输的单个命令(例如,SDIO CMD52)。
- 它在函数栈上创建一个临时的
mmc_request结构体mrq。 WARN_ON(!host->claimed)是一个重要的断言,确保上层驱动在发送命令前已经通过mmc_claim_host获取了总线锁。- 它将用户传入的
cmd指针放入mrq.cmd中,并将cmd->data明确设置为NULL,表示这是一个没有数据阶段的请求。 - 最后,它调用
mmc_wait_for_req来同步地执行这个只包含命令的请求。 - 函数返回
cmd->error,这个字段会在底层请求处理过程中,根据命令的执行结果(如超时、CRC错误等)被填充。
代码分析
1 | // mmc_wait_for_req: 启动一个请求并等待其完成。 |
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总线上的设备和驱动。
其工作流程如下:
- 总线注册:在模块初始化时,
bus.c调用bus_register(&mmc_bus_type),在内核中注册“mmc”这条总线。这会在sysfs中创建/sys/bus/mmc目录。 - 设备发现与注册(由Core触发):这个过程不由
bus.c发起。当MMC核心(core.c)通过mmc_rescan()函数检测到一张卡并成功初始化后,它会为一个struct mmc_card分配内存,并调用mmc_add_card()。mmc_add_card()会调用device_add(),将这张卡作为一个struct device注册到内核,并挂在mmc_bus_type上。 - 驱动注册(由功能驱动触发):一个功能驱动,比如块设备驱动(
card/block.c),会在其初始化时调用mmc_register_driver(),将自己(一个struct mmc_driver)注册到mmc_bus_type上。 - 匹配过程(Matchmaking):每当有新的设备或新的驱动注册到
mmc总线上时,总线核心就会尝试进行匹配。它会调用mmc_bus_type中指定的.match函数,即mmc_bus_match()。 mmc_bus_match()的逻辑:这个函数非常核心。它会比较驱动程序所支持的设备ID列表(driver->id_table)和当前设备的ID(card->cid、card->type等信息)。如果找到匹配项,函数返回true。- 探测(Probing):一旦
.match()返回成功,总线核心就会调用匹配到的驱动的.probe函数(例如mmc_block_probe())。.probe函数负责执行所有功能相关的初始化,比如为存储卡创建块设备节点/dev/mmcblkX。 - 移除过程:当卡被拔出或驱动被卸载时,会触发相反的过程:调用驱动的
.remove函数,然后设备和驱动会从总线上解除绑定并注销。
它的主要优势体现在哪些方面?
- 完全遵循驱动模型:完美地将设备和驱动解耦,是Linux驱动模型的一个经典实现。
- 自动化绑定:内核可以自动完成设备和驱动的匹配与加载,支持热插拔。
- 清晰的层次结构:明确了MMC核心、总线和功能驱动之间的界限和交互方式。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 抽象带来的复杂性:对于初学者来说,这种通过总线进行的间接调用(
device_add()->bus_match()->driver->probe())比直接函数调用更难跟踪和理解。 - 非通用性:
bus.c的逻辑是为MMC/SD/SDIO协议量身定做的,其匹配规则(基于CID等卡信息)对其他类型的总线没有意义。它本身没有劣势,只是其设计具有高度的特异性。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
bus.c 是MMC子系统内部的强制性组件,而不是一个可选项。它的“使用场景”就是MMC子系统工作的每一个瞬间。
- 场景一:插入一张SD卡
- Host驱动检测到卡插入,通知MMC核心。
- MMC核心(
core.c)执行卡初始化序列,识别出这是一张SD存储卡。 - 核心调用
mmc_add_card(),将卡注册为一个mmc总线设备。 bus.c的mmc_bus_match()被调用,它发现这张SD卡与mmc_block_driver兼容。mmc_block_driver的.probe函数被调用,创建/dev/mmcblk0。
- 场景二:系统启动时识别eMMC
- 系统启动时,eMMC的Host驱动被加载。
- 它调用MMC核心的扫描函数。
- 核心识别出焊接在主板上的eMMC芯片。
- 后续流程与场景一完全相同,
bus.c负责将其与块设备驱动绑定,最终创建出代表eMMC分区的设备节点,如/dev/mmcblk1p1。
是否有不推荐使用该技术的场景?为什么?
不存在不推荐使用bus.c的场景。只要一个设备使用MMC/SD/SDIO协议栈,它就必须通过bus.c所定义的mmc_bus_type来与功能驱动进行交互。绕过这个机制就等于破坏了整个MMC子系统的设计。
对比分析
请将其 与 其他总线实现(如PCI, USB)进行详细对比。
mmc/core/bus.c 与 drivers/pci/pci-driver.c 或 drivers/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设备模型中用于描述一种总线的标准方式,它通过函数指针将总线级别的通用操作与设备模型核心连接起来。
Probe/Remove/Shutdown 代理:
mmc_bus_probe、mmc_bus_remove和mmc_bus_shutdown函数扮演了“代理”或“中间人”的角色。当设备模型核心决定要为一个MMC卡设备probe一个MMC驱动时,它会调用mmc_bus_probe。这个函数并不执行设备特定的初始化,而是简单地从通用device结构中提取出mmc_card和mmc_driver结构,然后调用mmc_driver结构中真正的probe函数指针。这种设计将总线的通用逻辑与驱动的具体实现解耦。Uevent事件生成:
mmc_bus_uevent函数是实现设备自动配置的关键。当一个MMC卡被识别并注册到总线上时,内核会调用此函数来生成一个uevent事件。该函数将卡的详细信息(如类型、名称、SDIO ID等)打包成环境变量,发送给用户空间的udevd或mdev等守护进程。其中最关键的一行是add_uevent_var(env, "MODALIAS=mmc:block"),它生成了一个标准的模块别名(MODALIAS)。用户空间守护进程看到这个别名后,会执行modprobe mmc:block,从而自动加载处理MMC块设备的内核模块(mmc_block.ko)。Sysfs属性:
type_show函数和DEVICE_ATTR_RO(type)宏一起工作,在sysfs中为每个MMC设备创建一个名为type的只读文件。当用户通过cat /sys/bus/mmc/devices/mmc0:xxxx/type读取该文件时,type_show函数会被调用,它会根据卡的类型返回一个可读的字符串(如”SD”, “MMC”)。总线注册:
mmc_register_bus函数非常简单,它只是调用了设备模型核心提供的bus_register函数,将mmc_bus_type结构体注册到内核中,从而正式“激活”了MMC总线。
代码分析
1 | // type_show: sysfs属性 "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_host和mmc_host_ops的定义,以及mmc_alloc_host/mmc_add_host等生命周期管理函数的创建。这确立了所有主机驱动必须遵循的开发范式。 - 功能和能力的扩展:随着MMC/SD标准的发展,
struct mmc_host中增加了越来越多的能力标志(caps和caps2字段),以向核心层宣告其支持的特性,例如支持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主机应该是什么样子以及应该能做什么”(接口),而将“具体怎么做”(实现)的任务留给了具体的硬件驱动。
其工作流程和核心组件如下:
主机抽象 (
struct mmc_host):这是描述一个MMC主机控制器的核心数据结构。它包含了主机的所有信息,如:ops: 一个指向mmc_host_ops结构体的指针,包含了所有操作的回调函数。caps,caps2: 描述硬件能力的位掩码。ios: 描述当前总线状态(时钟、电压、总线宽度等)。card: 指向当前插槽中被识别的卡的指针。
操作接口 (
struct mmc_host_ops):这是一个函数指针的集合,是具体主机驱动必须实现的“合同”。关键操作包括:.request(): 核心函数,MMC核心通过它向主机驱动提交一个数据传输请求(struct mmc_request)。.set_ios(): MMC核心通过它来命令主机驱动改变总线状态(如调整时钟频率、设置总线宽度)。.get_ro(): 用于检测卡是否处于只读状态(通过硬件引脚)。.get_cd(): 用于检测卡是否存在(通过硬件引脚)。
生命周期管理:
host.c提供了一套标准的API来管理mmc_host对象的生命周期:mmc_alloc_host(): 一个主机驱动在其.probe函数中首先调用此函数,来分配一个mmc_host结构体和驱动的私有数据区。mmc_add_host(): 驱动在配置好mmc_host(特别是填充了ops和caps)之后,调用此函数将主机注册到MMC核心。这是一个关键步骤,一旦调用成功,MMC核心就会接管这个主机,并立即触发一次总线扫描 (mmc_rescan) 来检测卡片。mmc_remove_host(): 在驱动卸载时调用,将主机从核心中注销。mmc_free_host(): 释放mmc_host结构体占用的内存。
它的主要优势体现在哪些方面?
- 高度抽象和解耦:MMC核心协议层完全无需关心底层硬件的细节,它只与标准的
mmc_host接口交互。 - 简化驱动开发:为主机驱动开发者提供了一个清晰的、必须实现的接口(
mmc_host_ops),极大地降低了开发门槛。 - 代码复用:所有主机的公共管理逻辑都集中在
host.c中,被所有驱动共享。 - 可维护性:结构清晰,当需要为所有主机增加一个新功能时,可能只需要修改
host.c和core.c,而无需改动成百上千个具体的主机驱动。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 接口的刚性:
mmc_host_ops定义了一套固定的接口,如果某个硬件有一些非常规的、无法通过现有ops和caps描述的特殊功能,就很难优雅地将其集成进来。但这在实践中非常罕见。 - 间接调用的复杂性:对于调试和代码跟踪来说,通过函数指针(
host->ops->request())进行的间接调用比直接调用更难跟踪。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
host.c提供的框架是为MMC/SD/eMMC主机控制器编写Linux驱动的唯一且强制的解决方案。任何想要将其硬件集成到Linux MMC子系统的芯片厂商或开发者,都必须使用它。
一个典型的主机驱动开发流程就是其使用场景的完美展示:
- 在驱动的
.probe函数中,首先调用mmc_alloc_host()。 - 获取到
mmc_host指针后,填充其成员:- 设置
host->ops指向驱动自己实现的静态mmc_host_ops实例。 - 根据硬件寄存器的值,设置
host->caps和host->caps2来宣告硬件能力。 - 设置
host->f_min,host->f_max等频率范围。
- 设置
- 完成所有硬件相关的初始化(如申请中断、映射寄存器)。
- 最后,调用
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作为一种高层抽象,用于将功能相似的设备组织在一起。
类定义 (
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主机控制器硬件置于一个安全、静默的状态,确保在系统断电前不会有意外的总线活动。
- 一个静态的
类注册 (
mmc_register_host_class):- 这个函数非常简单,它只调用了设备模型提供的核心API
class_register(),将mmc_host_class结构体注册到内核中。一旦注册成功,/sys/class/mmc_host目录就会被创建,并且具体的MMC主机控制器驱动就可以通过device_create()等函数将其设备注册到这个类中。
- 这个函数非常简单,它只调用了设备模型提供的核心API
动态ID管理:
mmc_host_classdev_release函数中的ida_free逻辑展示了对主机索引号的动态管理。当一个主机控制器驱动(如STM32的SDMMC驱动)注册时,它会通过ida_alloc从一个全局的ID分配器(mmc_host_ida)中获取一个唯一的索引号(如0, 1, 2…),除非设备树中通过别名(alias)指定了静态ID。在设备被释放时,此处的代码负责将动态分配的ID归还给ID池,以备后续使用。
代码分析
1 | // mmc_host_classdev_release: mmc_host类设备的释放回调函数。 |
你说得对——我上一版里出现了“引用标记/可点击引用”,这在呈现上等同于链接了,确实违反了你模板的“不包含任何链接/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_on、post_power_on、power_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_pwrseq、struct 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-ms、power-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 造成不可预期行为。
核心原理与设计
它的核心工作原理是什么?
组件/层次划分:
框架层/核心层(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()
- 解析 host 的
驱动/实现层(pwrseq_simple.c)职责
- 实现一套通用时序:复位 assert/deassert、可选外部时钟 enable/disable、延时等待
- 提供 platform_driver:
mmc_pwrseq_simple_probe()/mmc_pwrseq_simple_remove() - 向框架注册
struct mmc_pwrseq实例与 ops:mmc_pwrseq_register()
用户态接口层(如有)
- 无直接用户态接口;主要由设备树属性驱动行为。
关键数据结构(以该文件常见实现为准):
struct mmc_pwrseq_simple(核心私有结构体)struct mmc_pwrseq pwrseq:嵌入式基类对象,供框架调用struct clk *ext_clk:可选外部时钟(通常在设备树里以clock-names = "ext_clock"约定)bool clk_enabled:记录外部时钟当前是否已开启,避免重复 enable/disablestruct gpio_descs *reset_gpios:复位 GPIO 数组(可 1 根或多根)struct reset_control *reset_ctrl:当使用 reset framework 表达单复位资源时使用(可选)
并发与上下文要点
- 该实现会使用
msleep()/usleep_range()和gpiod_*_cansleep()路径,意味着回调必须在允许 sleep 的上下文被调用(这是 MMC core 常规上电流程的典型上下文)。
- 该实现会使用
关键流程:
初始化流程(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-ms、power-off-delay-us)填充
pwrseq.dev、pwrseq.ops、pwrseq.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)
- reset framework:
上电后:
mmc_pwrseq_simple_post_power_on()- 复位 deassert:
reset_control_deassert()或 GPIO 值设为 deassert(常见为 0) - 若配置
post_power_on_delay_ms,执行msleep(post_power_on_delay_ms)等待模块稳定
- 复位 deassert:
断电流程:
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-simple用ext_clk + reset + post-power-on-delay-ms组合即可覆盖。
- 约束/收益:模块依赖 ext_clock 才能启动;上电后必须延时等待固件/晶振稳定。
场景 2:板级存在 1 根或多根模块复位/使能脚
- 典型对象:
reset-gpios(GPIO 数组)。上电前 assert、上电后释放,断电前再 assert,避免模块挂在半初始化状态。
- 典型对象:
场景 3:需要一致的断电保护动作
- 断电前 assert reset + 可选短延时(
power-off-delay-us),再关闭外部时钟,降低下次上电的不确定性。
- 断电前 assert reset + 可选短延时(
是否有不推荐使用该技术的场景?为什么?
不推荐场景 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_on、post_power_on、power_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_clk、reset_ctrl、reset_gpios均使用IS_ERR/PTR_ERR做就绪与缺省容错(-ENOENT/-ENOSYS被视为“没有该能力”而非错误),避免在能力缺失的平台上失败。 - 一次性状态位:
clk_enabled作为一次性状态位,确保外部时钟只在需要时开启、在 power_off 时对称关闭,避免重复调用导致时钟框架告警或引用计数不一致。 - 后台机制影响:
msleep()/usleep_range()引入可配置延时(设备属性),用于满足器件上电后稳定时间与掉电后的保持时间;属于调度/定时机制依赖点。
mmc_pwrseq_simple_set_gpios_value 复位GPIO数组统一置位/清零
1 | /** |
mmc_pwrseq_simple_pre_power_on 上电前准备外部时钟与复位线预处理
1 | /** |
mmc_pwrseq_simple_post_power_on 上电后释放复位并等待稳定时间
1 | /** |
mmc_pwrseq_simple_power_off 掉电阶段拉回复位、等待并关闭外部时钟
1 | /** |
mmc_pwrseq_simple_probe 解析资源能力并注册上电时序实例
1 | /** |
mmc_pwrseq_simple_remove 注销上电时序实例
1 | /** |
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失败返回-ENOMEM;devm_gpiod_get获取复位脚失败直接返回错误码,确保后续路径不会触碰无效 GPIO。 - 后台机制影响:
udelay()为忙等待,保证微秒级时序;不会依赖调度,但会占用 CPU,属于“硬延时”策略,尤其在紧急路径更可控。
mmc_pwrseq_emmc_reset 对 eMMC 产生硬件复位脉冲
1 | /** |
mmc_pwrseq_emmc_reset_nb 系统重启通知链中的复位处理
1 | /** |
mmc_pwrseq_emmc_probe 获取复位脚并按能力注册紧急重启复位
1 | /** |
mmc_pwrseq_emmc_remove 注销重启回调并注销 pwrseq
1 | /** |
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_rev、rev_start/rev_end、year/month、cis_vendor/device、of_compatible)形成“能力门控/适配门控”。 - 范围与版本语义集中在
rev:通过cid_rev_card(card)得到u64 rev,统一承载“起止版本”判断(rev_start/rev_end),并与year/month组合实现更细颗粒度的“一批次卡”筛选。 - 设备树兼容项是额外的精确约束:
of_compatible不参与普通卡的匹配;只有表项显式填写时才触发mmc_fixup_of_compatible_match(),用“宿主控制器子节点”兼容串进一步收敛匹配范围,避免误伤。 - 副作用集中在回调:真正的 quirk 注入由
f->vendor_fixup(card, f->data)完成;mmc_fixup_device()自身只做筛选与调用,不引入额外状态机/一次性标志位/后台机制。
mmc_fixup_of_compatible_match 设备树兼容项匹配
1 | /** |
mmc_fixup_device 按修正表筛选并触发厂商回调
1 | /** |
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_bdops(mmc_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_ro、ro_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_driver(mmc_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())的选择。
核心原理与设计
它的核心工作原理是什么?
组件/层次划分:
- 框架层/核心层:把块层 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_bdops(mmc_blk_open()/mmc_blk_release()/mmc_blk_ioctl())提供;RPMB 额外通过mmc_rpmb_fileops(mmc_rpmb_ioctl())提供字符设备 ioctl 通路。
- 框架层/核心层:把块层 request 翻译为 MMC 请求并驱动发送;关键入口是
关键数据结构:
struct mmc_blk_data:mmcblk 的核心对象,包含md->disk(struct gendisk)、md->queue(struct 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.mrq(struct mmc_request)与命令/数据字段,准备阶段由mmc_blk_rw_rq_prep()填充,完成回调设置为mqrq->brq.mrq.done = mmc_blk_mq_req_done。
关键流程:
初始化:
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 request→mmc_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_data及ida分配的索引。
它的主要优势体现在哪些方面?
- 优势 1:一致性/通用性:通过
mmc_bdops与mmc_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_ro、ro_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_enabled与host->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_request;struct mmc_blk_data/struct mmc_queue 组织对象与并发 |
mmc_blk_ioctl()→mmc_blk_ioctl_cmd() 直接构造命令,不依赖块层合并/调度语义 |
request→SCSI midlayer→struct scsi_cmnd,协议与错误恢复多在 SCSI 层完成 |
request→NVMe queue→doorbell,通常驱动直接持有 blk-mq 队列并深度队列化 |
| 性能开销 | 热路径依赖 mmc_pre_req()/mmc_start_request(),CQE 分支 mmc_blk_cqe_issue_rw_rq();必要时 mmc_blk_rw_wait() 串行化 |
用户态构造命令成本高且难以批量化;不适合持续吞吐 | 中间层较厚,但擅长队列深与错误处理 | 队列深、并行度高,常见吞吐与延迟优于传统介质 |
| 资源占用 | 每盘 gendisk + 队列对象;每请求 struct mmc_queue_req;分区与 rpmb 额外对象(mmc_rpmb_data) |
以 ioctl 缓冲与命令对象为主,缺少块层复用 | 需要 SCSI host/设备对象,命令对象较多 | 需要多队列与中断向量,队列资源更重但更可扩展 |
| 隔离级别 | 通过 mmc_blk_part_switch() + sysfs 只读属性隔离分区风险 |
细粒度强,但更容易误用(直接写协议命令) | 依赖 SCSI 层权限与设备策略 | 依赖 NVMe 命名空间与控制器能力 |
| 启动速度 | mmc_blk_probe() + mmc_add_disk() 随卡枚举完成;分区多时 mmc_blk_alloc_parts() 成本上升 |
无需注册额外磁盘(但仍需基础块设备存在) | 设备发现与扫描可能更慢 | 控制器初始化与队列建立成本较高但一次性完成 |
总结
关键特性总结
- 特性 1:块请求到 MMC 请求的统一下发框架:
mmc_blk_mq_issue_rq()+mmc_blk_mq_issue_rw_rq()+mmc_start_request()。 - 特性 2:分区语义与安全边界:
struct mmc_blk_data持有part_type,请求前mmc_blk_part_switch();配套 sysfs 的force_ro/ro_lock_until_next_power_on控制高风险写入。 - 特性 3:完成与恢复的并发状态机:
mmc_blk_mq_req_done()决定直完成或转mmc_blk_mq_complete_work(),错误/忙态进入mmc_blk_mq_rw_recovery()并由recovery_work驱动恢复。
mmc_blk_init mmc_blk_exit 模块初始化与退出时的块设备与 RPMB 资源编排
mmc_blk_init 模块初始化:注册 RPMB 总线/字符设备号与 mmcblk 块设备驱动
1 | /* Bus type for RPMB character devices */ |
mmc_blk_exit 模块退出:按依赖逆序注销驱动与全局资源
1 | /** |
mmc_blk_init mmc_blk_exit __register_blkdev unregister_blkdev mmc_blk_rpmb_add mmc_blk_probe mmc_blk_remove _mmc_blk_suspend mmc_blk_shutdown MMC 块层注册与探测及电源管理收尾逻辑
作用与实现要点
- 初始化/回滚策略:
mmc_blk_init()按“总线类型→字符设备号→块主设备号→MMC 驱动”顺序注册;失败用goto分段回滚,严格按相反顺序释放,避免“注册成功但未完整可用”的中间态遗留。 - 主设备号注册的并发与唯一性:
__register_blkdev()用major_names_lock(互斥)串行化注册/注销窗口,再用major_names_spinlock(自旋)保护哈希桶链表更新,保证同一major不会被并发重复占用;unregister_blkdev()额外校验name匹配,否则触发WARN_ON。 - 能力门控与资源创建:
mmc_blk_probe()首先用CCC_BLOCK_READ做能力门控(不支持块读直接拒绝绑定);随后创建每卡独立的complete_wq(高优先级、可回收、按 CPU 分配),避免完成路径与通用系统 workqueue 互相干扰。 - 运行时电源管理的分支选择:
mmc_blk_probe()配置 autosuspend 并仅对“非 SD-combo”启用 runtime PM,把 SD-combo 的策略决策留给后续 SDIO 初始化序列;mmc_blk_remove()对称地在退出前pm_runtime_get_sync()确保设备活跃,再视卡类型决定是否pm_runtime_disable(),最后pm_runtime_put_noidle()收尾。 - 分区状态一致性修正:
mmc_blk_remove()在移除请求队列前检查md->part_curr != md->part_type,必要时用mmc_claim_host()/mmc_release_host()包住mmc_blk_part_switch(),把卡的“当前硬件分区选择”拉回驱动期望值,减少退出时的状态漂移。 - RPMB 设备注册的数据生命周期:
mmc_blk_rpmb_add()用本地栈上的cid[4]作为descr.dev_id输入;RPMB 核心在注册时会复制该dev_id,因此不会悬挂指针,但要求dev_id_len与输入一致。
平台关注:单核、无 MMU、ARMv7-M(STM32H750)
- 本段逻辑与单核/无MMU无直接差异,依赖底层 ops 与系统定时机制。
- 需要额外关注的仅是“机制落地”的可用性:
alloc_workqueue()/runtime PM/autosuspend 都依赖内核线程与定时器;在裁剪较重的平台配置下可能被弱化或替换为简化实现,但代码层的并发语义(互斥/自旋/状态位)与错误回滚结构不随单核而消失。
mmc_blk_init 模块初始化并注册 mmcblk 与 rpmb 相关资源
1 | /** |
mmc_blk_exit 模块退出并反注册 mmcblk 与 rpmb 相关资源
1 | /** |
__register_blkdev 注册块设备主设备号到全局 major 表
1 | /** |
unregister_blkdev 注销块设备主设备号并从全局 major 表移除
1 | /** |
mmc_blk_rpmb_add 为卡的 RPMB 分区注册 rpmb 设备实例
1 | /** |
mmc_blk_probe 绑定卡并初始化块层数据结构与运行时电源管理
1 | /** |
mmc_blk_remove 解绑卡并回收块层资源与运行时电源管理状态
1 | /** |
_mmc_blk_suspend 挂起请求队列以进入休眠或关机路径
1 | /** |
mmc_blk_shutdown 关机时复用挂起逻辑停止队列
1 | /** |
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.card做IS_ERR()门控,再用req_len/resp_len与RPMB_FRAME_SIZE、CHECK_SIZE_NEQ()、CHECK_SIZE_ALIGNED()做合法性校验;之后以rpmb_frame.req_resp(经be16_to_cpu())驱动switch分支,决定write与读写路径。 - 命令序列策略固定化:
mmc_route_rpmb_frames()用cmd_count = write ? 3 : 2固化“写三段/读两段”的请求编排,并通过alloc_idata()+set_idata()将每段映射为struct mmc_blk_ioc_data,最终把“多段IOCTL-RPMB”封装进一次blk_execute_rq()同步执行。 - 能力门控与类型门控:
mmc_blk_alloc_parts()以mmc_card_mmc(card)作为能力门控(非 MMC 卡直接跳过),再按card->part[idx].area_type & MMC_BLK_DATA_AREA_RPMB选择 RPMB 字符设备路径mmc_blk_alloc_rpmb_part(),否则走普通分区mmc_blk_alloc_part()。 - 设备生命周期与引用关系:
mmc_blk_alloc_rpmb_part()用ida_alloc_max()分配 RPMB 字符设备 minor(mmc_rpmb_ida),并通过device_initialize()、dev_set_drvdata()、cdev_device_add()将struct mmc_rpmb_data挂到设备模型;失败路径用ida_free()/put_device()/mmc_blk_get()引用配对,避免泄漏。mmc_blk_remove_rpmb_part()、mmc_blk_remove_req()、mmc_blk_remove_parts()负责逆向回收与链表摘除(list_for_each_safe()+list_del())。
mmc_route_rpmb_frames 路由RPMB帧并同步执行块层请求
1 | static void set_idata(struct mmc_blk_ioc_data *idata, u32 opcode, |
mmc_blk_alloc_rpmb_part 为RPMB分区创建字符设备并挂接到父块设备
1 | /** |
mmc_blk_remove_rpmb_part 注销RPMB字符设备并释放device引用
1 | /** |
mmc_blk_alloc_parts 扫描物理分区并按类型创建块分区或RPMB字符设备
1 | /** |
mmc_blk_remove_req 删除主块盘并清理队列与引用
1 | /** |
mmc_blk_remove_parts 统一移除RPMB设备与普通分区块设备
1 | /** |
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_OUT或REQ_OP_DRV_IN,并在 multi 场景直接以第 0 条命令的write_flag决定整批请求方向(隐含约束:同一批 ioctl 不应混合方向)。 - 能力/路径门控:通过
rpmb指针是否为 NULL,选择MMC_DRV_OP_IOCTL_RPMB或MMC_DRV_OP_IOCTL,把同一套“块层驱动操作”机制复用于 RPMB 与非 RPMB 两类路径。 - 错误优先级策略:
drv_op_result预置为-EIO,确保下层未显式写回结果时有确定失败码;返回值上优先返回ioc_err(硬件/下层执行结果),仅在其为 0 时才返回用户拷贝等软件层错误err。 - 资源生命周期与一次性载荷:单命令用栈上
idatas[1]传递“指针数组”,多命令用kcalloc的idata**;执行后统一释放请求与idata/buf,multi 在构造失败时通过缩短n实现“只回收已成功分配的项”。
平台关注:单核、无 MMU、ARMv7-M(STM32H750)
- 这段代码的核心语义强依赖 Linux 用户态/内核态隔离与块层 blk-mq 基础设施:
__user指针、copy_from_user/copy_to_user、blk_mq_alloc_request/blk_execute_rq以及下层 MMC host 的并发/中断完成路径。在无 MMU场景中,用户拷贝语义与地址合法性边界会弱化或需要替代实现,否则“参数校验/越界防护”这一层的安全模型会发生变化。 - 即使是单核,请求执行仍可能跨越:进程上下文(ioctl 发起)↔ 块层/驱动线程 ↔ 中断回调(请求完成/唤醒)。因此锁、等待队列与状态位的意义仍在:该 ioctl 路径会睡眠等待,必须运行在可睡眠上下文。
- ARMv7-M(STM32H750)常见带 DCache,且 MMC 可能 DMA:
idata->buf参与数据通路时,缓存一致性与对齐/不可缓存区的管理通常由更底层的 DMA/host 驱动承担;在无 MMU 体系下更需要明确“缓冲区可 DMA、可 cache maintenance”的约束,否则会出现读写不一致或数据污染。
mmc_blk_ioctl_cmd 单条 MMC ioctl 命令转发到块层队列执行
1 | /** |
mmc_blk_ioctl_multi_cmd 多条 MMC ioctl 命令批量转发到块层队列执行
1 | /** |
mmc_rpmb_ioctl RPMB 字符设备 ioctl 分发到块层实现
1 | /** |
mmc_rpmb_ioctl_compat 32/64 位兼容 ioctl 参数指针转换
1 | /** |
mmc_rpmb_chrdev_open RPMB 字符设备打开与引用计数持有
1 | /** |
mmc_rpmb_chrdev_release RPMB 字符设备关闭与引用计数释放
1 | /** |
mmc_rpmb_fileops RPMB 字符设备 file_operations 回调表
1 | /** |










