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

文章目录
- [drivers/mmc 多媒体卡(MultiMediaCard)子系统 SD/eMMC/SDIO设备驱动框架](#drivers/mmc 多媒体卡(MultiMediaCard)子系统 SD/eMMC/SDIO设备驱动框架)
- include/linux/mmc/mmc.h
-
- MMC协议核心定义:命令、响应及寄存器规范
-
- 实现原理分析
- 代码分析
-
- MMC命令定义
- R1响应格式与状态定义
- [扩展CSD (EXT_CSD) 寄存器关键字段定义](#扩展CSD (EXT_CSD) 寄存器关键字段定义)
- [MMC_SWITCH (CMD6) 命令相关定义](#MMC_SWITCH (CMD6) 命令相关定义)
- [drivers/mmc/core/card.h MMC/SD/eMMC 卡功能驱动集合](#drivers/mmc/core/card.h MMC/SD/eMMC 卡功能驱动集合)
-
- MMC卡状态与属性宏定义:卡片核心状态的管理接口
- drivers/mmc/core/core.c
- [drivers/mmc/core/bus.c MMC总线驱动(MMC Bus Driver) 连接MMC/SD卡设备与功能驱动的桥梁](#drivers/mmc/core/bus.c MMC总线驱动(MMC Bus Driver) 连接MMC/SD卡设备与功能驱动的桥梁)
-
- MMC总线类型定义:连接卡设备与驱动的桥梁
- [drivers/mmc/core/host.c MMC主机控制器管理(MMC Host Controller Management) 所有主机驱动的注册与管理中心](#drivers/mmc/core/host.c MMC主机控制器管理(MMC Host Controller Management) 所有主机驱动的注册与管理中心)
-
- MMC主机控制器类:sysfs中的统一视图与生命周期管理
- [drivers/mmc/core/pwrseq_simple.c mmc-pwrseq-simple 为 MMC/SD/SDIO 设备提供通用的上电复位时序(可选外部时钟 + 复位控制 + 延时)](#drivers/mmc/core/pwrseq_simple.c mmc-pwrseq-simple 为 MMC/SD/SDIO 设备提供通用的上电复位时序(可选外部时钟 + 复位控制 + 延时))
-
- 介绍
- 总结
- [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 上电时序的复位线与外部时钟门控封装](#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 上电时序的复位线与外部时钟门控封装)
-
- 作用与实现要点
- [mmc_pwrseq_simple_set_gpios_value 复位GPIO数组统一置位/清零](#mmc_pwrseq_simple_set_gpios_value 复位GPIO数组统一置位/清零)
- [mmc_pwrseq_simple_pre_power_on 上电前准备外部时钟与复位线预处理](#mmc_pwrseq_simple_pre_power_on 上电前准备外部时钟与复位线预处理)
- [mmc_pwrseq_simple_post_power_on 上电后释放复位并等待稳定时间](#mmc_pwrseq_simple_post_power_on 上电后释放复位并等待稳定时间)
- [mmc_pwrseq_simple_power_off 掉电阶段拉回复位、等待并关闭外部时钟](#mmc_pwrseq_simple_power_off 掉电阶段拉回复位、等待并关闭外部时钟)
- [mmc_pwrseq_simple_probe 解析资源能力并注册上电时序实例](#mmc_pwrseq_simple_probe 解析资源能力并注册上电时序实例)
- [mmc_pwrseq_simple_remove 注销上电时序实例](#mmc_pwrseq_simple_remove 注销上电时序实例)
- drivers/mmc/core/pwrseq_emmc.c
-
- [mmc_pwrseq_emmc_reset mmc_pwrseq_emmc_reset_nb mmc_pwrseq_emmc_probe mmc_pwrseq_emmc_remove eMMC 硬件复位脉冲与紧急重启路径的复位保证](#mmc_pwrseq_emmc_reset mmc_pwrseq_emmc_reset_nb mmc_pwrseq_emmc_probe mmc_pwrseq_emmc_remove eMMC 硬件复位脉冲与紧急重启路径的复位保证)
-
- 作用与实现要点
- [mmc_pwrseq_emmc_reset 对 eMMC 产生硬件复位脉冲](#mmc_pwrseq_emmc_reset 对 eMMC 产生硬件复位脉冲)
- [mmc_pwrseq_emmc_reset_nb 系统重启通知链中的复位处理](#mmc_pwrseq_emmc_reset_nb 系统重启通知链中的复位处理)
- [mmc_pwrseq_emmc_probe 获取复位脚并按能力注册紧急重启复位](#mmc_pwrseq_emmc_probe 获取复位脚并按能力注册紧急重启复位)
- [mmc_pwrseq_emmc_remove 注销重启回调并注销 pwrseq](#mmc_pwrseq_emmc_remove 注销重启回调并注销 pwrseq)
- drivers/mmc/core/quirks.h
-
- [mmc_fixup_of_compatible_match mmc_fixup_device 设备修正表匹配与quirk注入逻辑](#mmc_fixup_of_compatible_match mmc_fixup_device 设备修正表匹配与quirk注入逻辑)
-
- 作用与实现要点
- [mmc_fixup_of_compatible_match 设备树兼容项匹配](#mmc_fixup_of_compatible_match 设备树兼容项匹配)
- [mmc_fixup_device 按修正表筛选并触发厂商回调](#mmc_fixup_device 按修正表筛选并触发厂商回调)
- [drivers/mmc/core/block.c MMC/SD 块设备驱动(mmcblk) 将 MMC/SD/eMMC 卡抽象为块设备并承接 blk-mq 请求下发与分区/属性管理](#drivers/mmc/core/block.c MMC/SD 块设备驱动(mmcblk) 将 MMC/SD/eMMC 卡抽象为块设备并承接 blk-mq 请求下发与分区/属性管理)
-
- 介绍
- 核心原理与设计
- 使用场景
- 对比分析
-
- [请将其 与 其他相似技术 进行详细对比。](#请将其 与 其他相似技术 进行详细对比。)
- 总结
- [mmc_blk_init mmc_blk_exit 模块初始化与退出时的块设备与 RPMB 资源编排](#mmc_blk_init mmc_blk_exit 模块初始化与退出时的块设备与 RPMB 资源编排)
-
- [mmc_blk_init 模块初始化:注册 RPMB 总线/字符设备号与 mmcblk 块设备驱动](#mmc_blk_init 模块初始化:注册 RPMB 总线/字符设备号与 mmcblk 块设备驱动)
- [mmc_blk_exit 模块退出:按依赖逆序注销驱动与全局资源](#mmc_blk_exit 模块退出:按依赖逆序注销驱动与全局资源)
- [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_blk_exit __register_blkdev unregister_blkdev mmc_blk_rpmb_add mmc_blk_probe mmc_blk_remove _mmc_blk_suspend mmc_blk_shutdown MMC 块层注册与探测及电源管理收尾逻辑)
-
- 作用与实现要点
- [平台关注:单核、无 MMU、ARMv7-M(STM32H750)](#平台关注:单核、无 MMU、ARMv7-M(STM32H750))
- [mmc_blk_init 模块初始化并注册 mmcblk 与 rpmb 相关资源](#mmc_blk_init 模块初始化并注册 mmcblk 与 rpmb 相关资源)
- [mmc_blk_exit 模块退出并反注册 mmcblk 与 rpmb 相关资源](#mmc_blk_exit 模块退出并反注册 mmcblk 与 rpmb 相关资源)
- [__register_blkdev 注册块设备主设备号到全局 major 表](#__register_blkdev 注册块设备主设备号到全局 major 表)
- [unregister_blkdev 注销块设备主设备号并从全局 major 表移除](#unregister_blkdev 注销块设备主设备号并从全局 major 表移除)
- [mmc_blk_rpmb_add 为卡的 RPMB 分区注册 rpmb 设备实例](#mmc_blk_rpmb_add 为卡的 RPMB 分区注册 rpmb 设备实例)
- [mmc_blk_probe 绑定卡并初始化块层数据结构与运行时电源管理](#mmc_blk_probe 绑定卡并初始化块层数据结构与运行时电源管理)
- [mmc_blk_remove 解绑卡并回收块层资源与运行时电源管理状态](#mmc_blk_remove 解绑卡并回收块层资源与运行时电源管理状态)
- [_mmc_blk_suspend 挂起请求队列以进入休眠或关机路径](#_mmc_blk_suspend 挂起请求队列以进入休眠或关机路径)
- [mmc_blk_shutdown 关机时复用挂起逻辑停止队列](#mmc_blk_shutdown 关机时复用挂起逻辑停止队列)
- [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 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 路由RPMB帧并同步执行块层请求](#mmc_route_rpmb_frames 路由RPMB帧并同步执行块层请求)
- [mmc_blk_alloc_rpmb_part 为RPMB分区创建字符设备并挂接到父块设备](#mmc_blk_alloc_rpmb_part 为RPMB分区创建字符设备并挂接到父块设备)
- [mmc_blk_remove_rpmb_part 注销RPMB字符设备并释放device引用](#mmc_blk_remove_rpmb_part 注销RPMB字符设备并释放device引用)
- [mmc_blk_alloc_parts 扫描物理分区并按类型创建块分区或RPMB字符设备](#mmc_blk_alloc_parts 扫描物理分区并按类型创建块分区或RPMB字符设备)
- [mmc_blk_remove_req 删除主块盘并清理队列与引用](#mmc_blk_remove_req 删除主块盘并清理队列与引用)
- [mmc_blk_remove_parts 统一移除RPMB设备与普通分区块设备](#mmc_blk_remove_parts 统一移除RPMB设备与普通分区块设备)
- [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路径](#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路径)
-
- 作用与实现要点
- [平台关注:单核、无 MMU、ARMv7-M(STM32H750)](#平台关注:单核、无 MMU、ARMv7-M(STM32H750))
- [mmc_blk_ioctl_cmd 单条 MMC ioctl 命令转发到块层队列执行](#mmc_blk_ioctl_cmd 单条 MMC ioctl 命令转发到块层队列执行)
- [mmc_blk_ioctl_multi_cmd 多条 MMC ioctl 命令批量转发到块层队列执行](#mmc_blk_ioctl_multi_cmd 多条 MMC ioctl 命令批量转发到块层队列执行)
- [mmc_rpmb_ioctl RPMB 字符设备 ioctl 分发到块层实现](#mmc_rpmb_ioctl RPMB 字符设备 ioctl 分发到块层实现)
- [mmc_rpmb_ioctl_compat 32/64 位兼容 ioctl 参数指针转换](#mmc_rpmb_ioctl_compat 32/64 位兼容 ioctl 参数指针转换)
- [mmc_rpmb_chrdev_open RPMB 字符设备打开与引用计数持有](#mmc_rpmb_chrdev_open RPMB 字符设备打开与引用计数持有)
- [mmc_rpmb_chrdev_release RPMB 字符设备关闭与引用计数释放](#mmc_rpmb_chrdev_release RPMB 字符设备关闭与引用计数释放)
- [mmc_rpmb_fileops RPMB 字符设备 file_operations 回调表](#mmc_rpmb_fileops RPMB 字符设备 file_operations 回调表)
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命令定义
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)**来工作。
- 驱动注册 :在模块初始化时,
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是一个内核中的标准宏,它能根据结构体成员的地址反向计算出整个结构体的起始地址,是设备模型中实现对象间关联的关键技术。
代码分析
访问器宏
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确保在系统早期阶段执行初始化。
-
初始化流程 (
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总线。这种相反的顺序是至关重要的,可以避免因依赖关系导致的错误(例如,在仍有设备注册在总线上时就注销了总线类型)。
- 该函数在模块卸载时被调用,执行与
代码分析
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)的紧密集成。
-
自定义阻塞锁:
- 函数的核心是一个
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等),则不再需要重复上电操作,提高了效率。
代码分析
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 负责在底层硬件操作完成后,进行错误分析、状态更新,并最终通知上层调用者,而调谐管理则是在高速模式下维持总线信号完整性的重要保障。
实现原理分析
-
请求完成 (
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标志被设置,实际的调谐操作也会被推迟。这可以防止在一次连续的数据传输过程中间插入耗时的调谐操作,保证了数据流的连续性。
代码分析
调谐保持与释放
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)实现了上层逻辑与底层硬件的解耦。
-
请求的准备 (
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回调通常会唤醒一个正在等待的完成量。
- 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须 调用
代码分析
完成命令
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)中进行处理。
-
请求(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错误等)被填充。
代码分析
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总线上的设备和驱动。
其工作流程如下:
- 总线注册 :在模块初始化时,
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总线。
代码分析
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_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池,以备后续使用。
代码分析
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_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数组统一置位/清零
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失败返回-ENOMEM;devm_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_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 设备树兼容项匹配
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_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 块设备驱动
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.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帧并同步执行块层请求
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_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 命令转发到块层队列执行
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
};