[toc]
drivers/mmc 多媒体卡(MultiMediaCard)子系统 SD/eMMC/SDIO设备驱动框架
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc
子系统是为了给Linux内核提供一个统一的、可扩展的框架来支持一整类基于**多媒体卡(MultiMediaCard)**规范及其衍生协议的设备而诞生的。这些设备包括:
- MMC (MultiMediaCard):一种早期的闪存卡标准。
- SD (Secure Digital) Card:在MMC基础上发展而来的、目前最普及的移动存储卡,增加了安全特性和更高的性能。
- eMMC (embedded MMC):将MMC控制器和NAND闪存封装在同一个BGA芯片中,作为一种“嵌入式”存储解决方案,被广泛用作智能手机、平板电脑和许多嵌入式设备的“硬盘”。
- SDIO (Secure Digital Input/Output):一种扩展规范,允许SD插槽除了支持存储卡外,还能连接I/O设备,如Wi-Fi模块、蓝牙模块、GPS接收器等。
在没有这个统一框架之前,对这些设备的支持是零散的。该框架的诞生解决了以下关键问题:
- 代码复用:MMC、SD、SDIO、eMMC协议有大量共通之处。一个统一的框架可以抽象出公共的协议处理、命令收发、数据传输逻辑,避免为每种卡和每种主机控制器都重写一遍。
- 硬件抽象:市面上有各种各样的SD/MMC主机控制器(Host Controller),它们是SoC上负责与SD卡进行物理通信的IP核。该框架需要将这些具体硬件的差异隔离开,为上层提供一个标准接口。
- 功能分离:需要将对“卡的功能”(是存储设备还是I/O设备)的驱动与底层的“总线协议”驱动分离开。
它的发展经历了哪些重要的里程碑或版本迭代?
- 框架的建立:最重要的里程碑是将分散的驱动重构为一个分层的、模块化的MMC子系统。这确立了Host驱动 - Core - Card驱动的核心架构。
- SDHCI规范的支持:SD主机控制器接口(SD Host Controller Interface)是一个标准化的硬件寄存器规范。
sdhci.c
驱动的出现是一个巨大的进步,因为它提供了一个通用的Host驱动,可以支持任何符合SDHCI规范的硬件控制器,大大减少了为新SoC编写Host驱动的工作量。 - 高性能模式的支持:随着SD卡和eMMC标准的演进,框架不断增加对新功能和更高速度模式的支持,如DDR模式、HS200(200MB/s)、HS400(400MB/s)等。
- eMMC高级功能:为eMMC增加了对TRIM/Discard(提升闪存寿命和性能)、安全擦除、分区管理等高级功能的支持。
目前该技术的社区活跃度和主流应用情况如何?
MMC子系统是Linux内核中最为成熟和活跃的核心子系统之一。由于几乎所有的移动和嵌入式设备都依赖它,因此它得到了持续的维护和功能增强。
- 主流应用:
- 智能手机/平板电脑:eMMC曾是安卓设备内部存储的主流标准(现在正逐渐被UFS取代)。
- 单板计算机(SBC):如树莓派,使用SD卡作为其主启动和存储设备。
- 物联网(IoT)设备:广泛使用SDIO接口的Wi-Fi/蓝牙模块。
- 数码相机、无人机等消费电子产品:使用SD卡作为主要存储介质。
核心原理与设计
它的核心工作原理是什么?
drivers/mmc
的核心是一个清晰的三层架构:
主机控制器驱动 (Host Driver) - (
drivers/mmc/host/
)- 职责:这是最底层,直接与硬件打交道。它负责控制具体的SD/MMC控制器IP核,包括管理时钟、电源、引脚、执行DMA传输、处理硬件中断等。
- 实现:每个Host驱动(如
sdhci.c
for SDHCI,dw_mmc.c
for Synopsys DesignWare a core)都会实现一套标准的操作函数集struct mmc_host_ops
。 - 抽象:它将底层硬件的复杂性抽象为一个标准的MMC主机对象 (
struct mmc_host
),并将其注册到MMC核心。
MMC核心 (Core) - (
drivers/mmc/core/
)- 职责:这是整个子系统的大脑和调度中心。它实现了MMC/SD/SDIO协议栈,负责卡的上电、初始化、识别过程,以及命令(CMD)和数据(DAT)的收发逻辑。
- 实现:核心层代码不关心具体的Host硬件是什么样的。当它需要发送一个命令时,它会通过Host驱动注册的
mmc_host_ops
中的函数(如.request()
,.set_ios()
)来间接地控制硬件。 - 角色:它扮演了总线驱动的角色,扫描总线上(插槽里)的设备,并为识别出的设备创建对应的逻辑设备。
设备驱动 (Card Driver) - (
drivers/mmc/card/
)- 职责:这是最上层,负责驱动卡片本身的功能。
- 实现:
block.c
:这是一个通用的Card驱动。当MMC核心识别出一张存储卡(SD或eMMC)时,block.c
会绑定到该设备,并创建一个块设备节点(如/dev/mmcblk0
),使其可以被文件系统挂载。sdio.c
:当MMC核心识别出是一张SDIO卡时,它会进一步解析卡上的功能(Function),并为每个功能(如WLAN, Bluetooth)创建SDIO设备。然后,相应的SDIO功能驱动(如位于drivers/net/wireless/
下的Wi-Fi驱动)会绑定到这些设备上。
它的主要优势体现在哪些方面?
- 高度模块化:三层架构使得各部分可以独立开发和维护。添加对新Host控制器的支持只需编写一个新的Host驱动;支持一种新的SDIO设备只需编写一个新的Card驱动,核心层代码保持不变。
- 代码复用率高:协议逻辑和公共流程都集中在核心层,被所有驱动共享。
- 标准化:紧密遵循MMC/SD/SDIO的官方规范,保证了良好的兼容性。SDHCI的通用驱动更是标准化的典范。
- 灵活性:同一个框架无缝支持存储、I/O等多种类型的设备。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 性能瓶颈:与现代的NVMe或UFS接口相比,MMC/SD协议(即使是最高速的模式)在吞吐量、延迟和IOPS方面都存在性能上限。它是一个半双工的总线,命令和数据传输不能完全并行。
- 协议复杂性:MMC/SD的初始化过程和命令集相当复杂,给调试带来了一定的挑战。
- 功耗:虽然SDIO功耗不高,但相比专用的低功耗总线(如I2C/SPI),在某些极低功耗场景下可能不是最优选择。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
- 可移动存储:SD卡是事实上的标准。所有需要可插拔、小型化存储卡的场景,如数码相机、运动相机、无人机、便携式游戏机,都依赖MMC子系统。
- 高性价比的嵌入式系统存储:eMMC在成本、性能和封装尺寸之间取得了很好的平衡,因此在对成本敏感的中低端智能手机、平板电脑、车载信息娱乐系统、智能电视中,是内部存储的首选方案。
- 无线模块连接:SDIO是连接Wi-Fi/蓝牙组合芯片的一种非常流行和成熟的接口,特别是在各种物联网网关和嵌入式设备中。
是否有不推荐使用该技术的场景?为什么?
- 高性能计算与存储:在需要极高I/O性能的场景,如企业级服务器、高端PC、高端智能手机,应使用NVMe SSD或UFS闪存。这些技术基于PCIe或M-PHY,提供多通道、全双工、更高效的命令队列机制,性能远超eMMC。
- 简单的低速外设:对于只需要传输少量控制数据的简单传感器或外设,使用SDIO是“杀鸡用牛刀”。更简单、引脚更少的总线,如I2C或SPI,是更合适的选择。
对比分析
请将其 与 其他相似技术 进行详细对比。
特性 | MMC/SD/eMMC | USB (Mass Storage Class) | UFS (Universal Flash Storage) | SATA / NVMe |
---|---|---|---|---|
主要用途 | 可移动存储、嵌入式内部存储、SDIO外设 | 通用的外部设备连接(存储、外设) | 高性能嵌入式内部存储(eMMC的替代者) | 高性能内部/外部存储 |
总线协议 | 并行、半双工、命令-响应式协议。 | 差分串行、包交换、主从协议。 | 差分串行、全双工、基于SCSI命令集。 | 差分串行、全双工、基于ATA或PCIe命令集。 |
性能 | 中等。eMMC最高约400MB/s,SD卡速度不等。延迟较高。 | 高。USB 3.x/4可达数GB/s。 | 高。UFS 3.x/4.0可达数GB/s,延迟低,支持命令队列。 | 非常高。SATA 6Gbps,NVMe可达数十GB/s,延迟极低。 |
物理接口 | 并行数据线+命令线+时钟线 (1/4/8位)。 | 串行差分对 (D+/D-)。 | 高速差分串行对 (M-PHY)。 | 高速差分串行对。 |
应用场景 | 成本敏感的移动/嵌入式设备,SD卡生态。 | PC、移动设备的外设连接,U盘。 | 中高端智能手机、平板电脑、汽车。 | PC、工作站、服务器。 |
内核子系统 | drivers/mmc |
drivers/usb |
drivers/ufs (或 drivers/scsi ) |
drivers/ata , drivers/pci |
include/linux/mmc/mmc.h
MMC协议核心定义:命令、响应及寄存器规范
本文件是Linux内核中用于支持MMC(MultiMediaCard)和eMMC(embedded MMC)存储卡的核心协议头文件。与sdio.h
专注于I/O功能不同,此文件定义了MMC存储卡相关的命令集、响应格式、寄存器(CSD, EXT_CSD)布局以及各种状态和能力标志。它将JEDEC发布的MMC物理规范中的数值和位域,转化为内核代码可以使用的符号常量和宏,是所有MMC/eMMC驱动程序与卡进行交互的基础语言。
实现原理分析
该文件的实现是典型的将硬件规范文档化的编码实践,其核心是利用C预处理器将协议的细节抽象化:
- 命令符号化: 每一个MMC命令(如CMD0, CMD1, …)都被
#define
为一个唯一的整数值,并赋予一个描述性的名称,例如MMC_GO_IDLE_STATE
。这使得驱动代码的逻辑(如状态机转换)清晰易懂。 - 响应格式位域化: 卡片对命令的响应(特别是R1响应)是一个包含多个状态和错误标志的32位字。文件通过定义位掩码(如
R1_OUT_OF_RANGE (1 << 31)
)和提取宏(如R1_CURRENT_STATE(x)
),将解析这个状态字的过程符号化,使得错误处理和状态判断逻辑标准化。 - 寄存器映射: MMC卡拥有复杂的内部寄存器,如CSD(Card-Specific Data)和至关重要的EXT_CSD(Extended CSD)。该文件将EXT_CSD这个长达512字节的寄存器中的每一个字节偏移量(offset)都定义为一个宏,如
EXT_CSD_BUS_WIDTH
(183)。同时,寄存器内特定位或位域的含义也被定义为宏,如EXT_CSD_BUS_WIDTH_8
(2)。 - 内联辅助函数: 文件提供了一些简单的
static inline
函数,如mmc_op_multi
,用于快速判断一个命令的类型。这些函数在编译时会被内联,没有函数调用的开销,提供了类型安全的便捷检查。
代码分析
MMC命令定义
1 | /* 标准MMC命令 (根据JEDEC 4.1规范) */ |
R1响应格式与状态定义
1 | /* |
扩展CSD (EXT_CSD) 寄存器关键字段定义
1 | /* |
MMC_SWITCH (CMD6) 命令相关定义
1 | /* |
drivers/mmc/core/card.h MMC/SD/eMMC 卡功能驱动集合
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc/core/card
目录及其下的文件是为了在MMC子系统的三层架构(Host - Core - Card)中扮演最顶层——“卡功能驱动”的角色。当MMC核心层(Core)完成对物理卡片的识别和初始化后,需要有一个驱动来解释这张卡的功能,并向Linux内核的其他子系统(如块设备层、输入子系统等)提供这些功能。
card
目录下的驱动解决了以下核心问题:
- 功能实现:将一张原始的、遵循MMC/SD/eMMC协议的卡片,转换成一个内核和用户空间可以理解和使用的标准设备。
- 块设备抽象:对于SD卡和eMMC这类存储卡,
block.c
驱动负责将其抽象为一个标准的块设备(如/dev/mmcblk0
),使其可以被分区、格式化并挂载文件系统。 - 设备特定功能:eMMC标准定义了许多超越简单块存储的高级功能,如分区管理(Boot partitions, RPMB partition)、TRIM/Discard命令(用于SSD性能优化)、安全擦除等。
mmc/card/block.c
和相关文件负责实现对这些高级功能的支持。 - SDIO功能枚举:对于SDIO卡,
sdio.c
驱动扮演一个特殊的“枚举器”角色。它本身不驱动具体功能,而是负责解析SDIO卡的CIS(卡信息结构),找出卡上有多少个I/O功能,并为每个功能在sdio_bus
上注册一个设备,等待真正的功能驱动来绑定。
它的发展经历了哪些重要的里程碑或版本迭代?
card
目录下的驱动发展与MMC/SD/eMMC规范的演进紧密相关。
- 基础块设备支持 (
block.c
):最初的核心功能是提供对MMC和SD卡的读/写/擦除操作,并将其封装成一个块设备。 - eMMC的兴起:随着eMMC成为嵌入式系统的主流存储,
block.c
被大幅扩展以支持eMMC的各种新特性:- 高速模式支持(HS200, HS400)。
- 分区支持,能够访问eMMC的Boot和RPMB(Replay Protected Memory Block)分区,并为它们创建独立的块设备节点。
- TRIM/Discard和Sanitize命令的支持,这对于保持闪存性能和寿命至关重要。
- SDIO的成熟 (
sdio.c
):sdio.c
的完善是另一个里程碑,它与sdio_bus.c
共同构成了Linux强大的SDIO支持框架,能够处理复杂的多功能combo卡。 - SD卡新规范支持:不断增加对新SD规范(如SDHC, SDXC, SDUC)和速度等级(UHS-I, UHS-II)的支持。
目前该技术的社区活跃度和主流应用情况如何?
card
目录下的代码是MMC子系统中非常活跃的部分,因为硬件规范在不断演进。
block.c
:由于eMMC和SD卡技术仍在发展(例如,引入新的缓存或健康报告功能),block.c
会持续更新以支持这些新特性。它是所有使用SD卡或eMMC存储的Linux系统的核心。sdio.c
:相对稳定,但随着新的SDIO规范或特性出现,也可能需要更新。
这些驱动的应用无处不在,覆盖了从树莓派(SD卡启动)到绝大多数安卓手机(eMMC作为主存储)的广阔领域。
核心原理与设计
它的核心工作原理是什么?
card
目录下的驱动作为**MMC总线(mmc_bus_type
)上的驱动程序(struct mmc_driver
)**来工作。
- 驱动注册:在模块初始化时,
block.c
会调用mmc_register_driver(&mmc_block_driver)
将自己注册到MMC总线上。mmc_block_driver
这个结构体声明了它是一个MMC驱动,并提供了.probe
和.remove
等回调函数。 - 设备匹配:当MMC核心层在插槽中发现一张卡并成功初始化后,它会将这张卡注册为一个
mmc
总线设备。MMC总线核心(bus.c
)会尝试将这个新设备与所有已注册的mmc_driver
进行匹配。mmc_block_driver
的匹配规则很简单:它几乎能匹配所有类型的存储卡(SD, MMC, eMMC)。 - 探测(Probe):一旦匹配成功,
mmc_block_driver
的.probe
函数(mmc_block_probe
)就会被调用。这是功能实现的核心所在:- 分配块设备:它会分配一个
gendisk
结构体和一个请求队列(request_queue
),这是块设备层的核心数据结构。 - 设置请求队列:它会配置请求队列,将队列的“请求处理函数”指向
mmc_queue_rq
。这意味着,当文件系统层发出一个读/写请求(struct bio
)时,最终会由mmc_queue_rq
函数来处理。 mmc_queue_rq
的逻辑:这个函数是连接块设备层和MMC核心层的桥梁。它会将一个来自块设备层的request
转换成一个或多个发往MMC核心层的mmc_request
(包含CMD17/18/24/25等读写命令)。- 添加磁盘:最后,调用
add_disk
,向内核宣告一个新的块设备诞生了,此时/dev/mmcblkX
设备节点就会出现。
- 分配块设备:它会分配一个
- SDIO的特殊流程:
sdio.c
的流程类似,但它的.probe
函数(sdio_probe
)不做块设备相关的工作。它的任务是解析CIS,然后为每个发现的功能调用sdio_add_func()
,在sdio_bus
上创建设备。
它的主要优势体现在哪些方面?
- 关注点分离:
card
驱动完美地体现了分层设计的思想。它不关心底层Host控制器是如何操作硬件的,也不关心MMC协议的握手细节。它只关注于如何将一个已初始化的mmc_card
设备的功能暴露给上层。 - 标准化接口:通过实现标准的块设备或SDIO设备接口,它使得上层应用(如文件系统、网络栈)可以无缝地使用MMC设备,而无需知道其底层技术。
- 可扩展性:支持新的eMMC功能或SD卡类型,通常只需要修改
block.c
,而不会影响到核心层或主机驱动。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 单一模型:
block.c
是一个通用的块设备驱动,它试图以一种统一的方式来处理所有存储卡。对于某些有非常特殊功能或性能特性的eMMC芯片,这种通用模型可能无法发挥其全部潜力。 - 块设备层的开销:对于某些嵌入式场景,如果只是想对闪存进行裸读写,经过完整的块设备层(包括I/O调度器等)可能会带来不必要的开销。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
它是Linux内核中处理MMC/SD/eMMC存储和SDIO功能的唯一且标准的解决方案。
- 挂载文件系统:当你在Linux系统中将一张SD卡格式化为ext4并挂载时,所有对该文件系统的读写操作最终都会通过VFS -> 文件系统 -> 块设备层,最终由
mmc_block_driver
转换成发往卡片的物理命令。 - eMMC分区:在嵌入式设备或手机上,系统访问eMMC的不同物理分区(如userdata, system, boot)时,依赖于
block.c
为这些分区创建的独立块设备节点(/dev/mmcblk0p1
,/dev/mmcblk0p2
等)。 - 使用SDIO Wi-Fi模块:当一个SDIO Wi-Fi模块被识别时,
sdio.c
负责枚举其功能,然后真正的Wi-Fi驱动会绑定到sdio_bus
上的功能设备,从而提供网络服务。
是否有不推荐使用该技术的场景?为什么?
不存在不推荐使用该技术的场景。只要一个设备是标准的MMC/SD/eMMC/SDIO卡,就必须由card
目录下的相应驱动来处理,这是MMC子系统设计的核心部分。
对比分析
请将其 与 其他总线的顶层功能驱动(如USB Storage, NVMe)进行详细对比。
特性 | MMC Card Driver (block.c ) |
USB Storage Driver (usb-storage ) |
NVMe Driver (nvme/host.c ) |
---|---|---|---|
所属总线 | mmc_bus_type |
usb_bus_type |
pci_bus_type |
功能抽象 | 将MMC/SD卡抽象为Linux块设备。 | 将USB Mass Storage设备抽象为SCSI磁盘设备,然后再由SCSI中间层转换为块设备。 | 将NVMe SSD抽象为专有的**nvme_ns 块设备**。 |
交互协议 | 直接构建和发送MMC协议命令(CMD17, CMD18等)。 | 将读/写请求转换为SCSI命令(CDBs),再封装成USB批量传输(Bulk Transfers)。 | 将请求转换为NVMe命令,并将其放入提交队列(Submission Queue),通过写门铃寄存器(doorbell)来通知硬件。 |
性能模型 | 命令-响应式,半双工。 | 命令-响应式,但USB协议本身支持一定程度的流水线。 | 队列化、异步模型。支持多个队列和极高的队列深度(QD),性能极高。 |
驱动角色 | 直接的块设备驱动。 | 协议转换驱动。它本身是一个USB接口驱动,同时又是一个SCSI低层驱动(LLD)。 | 全功能的块设备驱动。它实现了从PCIe交互到块设备接口的全套逻辑。 |
复杂性 | 中等。需要理解MMC读写和eMMC高级功能。 | 较高。需要理解USB传输和SCSI命令集。 | 非常高。需要理解PCIe、NVMe协议、队列管理、中断处理等。 |
总结:drivers/mmc/core/card
下的驱动与其他总线的功能驱动一样,都扮演着“翻译官”的角色,将上层子系统(如块设备层)的通用请求,翻译成底层总线和设备能听懂的特定协议。它们的实现细节和复杂性,完全由其所服务的硬件协议的特性所决定。MMC协议相对直接,而USB Storage和NVMe则分别引入了SCSI协议层和高性能队列模型,因此其驱动实现也更为复杂。
MMC卡状态与属性宏定义:卡片核心状态的管理接口
本代码片段并非可执行函数,而是一系列C预处理器宏的定义,通常位于头文件(如include/linux/mmc/card.h
)中。其核心功能是为MMC/SD卡(由struct mmc_card
表示)提供一个标准化的、抽象的接口,用于定义、查询和修改卡片的核心状态(如是否存在、是否只读),以及便捷地访问其关键属性(如设备名称)。
实现原理分析
这组宏的实现原理是C语言中一种常见且高效的设计模式,即利用位掩码(Bitmask)来管理一组布尔状态,并通过宏来封装其操作,以提高代码的可读性和可维护性。
- 位掩码(Bitmask):
struct mmc_card
结构体中有一个名为state
的整型成员。每一个独立的状态(如PRESENT
,READONLY
)都被定义为一个唯一的2的幂次值(例如(1<<0)
,(1<<1)
),这意味着在二进制表示中,每个状态都对应一个独立的位置为’1’。通过这种方式,一个32位的整型变量理论上可以同时存储32个不同的布尔状态,极大地节省了内存空间。 - 状态查询: 查询宏(如
mmc_card_present(c)
)利用按位与(&
)操作符。将state
变量与代表特定状态的位掩码相与,如果结果不为零,则说明该状态位被置位(为’1’),表示状态为真。 - 状态设置: 设置宏(如
mmc_card_set_present(c)
)利用按位或赋值(|=
)操作符。将state
变量与特定状态的位掩码相或,这会将对应的状态位置为’1’,而不会影响其他位的状态。 - 状态清除: 清除宏(如
mmc_card_clr_suspended(c)
)则使用按位与赋值(&=
)和按位非(~
)的组合。~MMC_STATE_SUSPENDED
会产生一个除了暂停状态位为’0’、其他所有位都为’1’的掩码。将其与state
变量相与,就能精确地将暂停状态位清零,同时保持其他位不变。 - 访问器宏: 像
mmc_card_name(c)
和container_of
这样的宏提供了对结构体成员的便捷访问,隐藏了内部数据结构的细节,使得上层代码更加简洁。container_of
是一个内核中的标准宏,它能根据结构体成员的地址反向计算出整个结构体的起始地址,是设备模型中实现对象间关联的关键技术。
代码分析
访问器宏
1 | // mmc_card_name(c): 获取卡的产品名称。 |
卡状态位定义
1 | /* 卡状态定义 */ |
状态查询宏
1 | // mmc_card_present(c): 检查卡片是否在位。 |
状态修改宏
1 | // mmc_card_set_present(c): 设置卡片为存在状态。 |
drivers/mmc/core/core.c
MMC/SD/SDIO核心子系统初始化与卸载
本代码片段是Linux内核MMC/SD/SDIO核心子系统的模块入口和出口。其核心功能是在内核启动时,注册和初始化MMC、SDIO总线类型以及MMC主机控制器类,为所有具体的MMC主机控制器驱动(如针对STM32H750的SDMMC外设驱动)和MMC/SD/SDIO设备驱动(如SD卡块设备驱动、SDIO WiFi驱动)提供必要的基础设施。在模块卸载时,它负责按相反的顺序清理这些注册的组件。
实现原理分析
该代码遵循标准的Linux内核模块初始化和退出模式,利用subsys_initcall
确保在系统早期阶段执行初始化。
初始化流程 (
mmc_init
):- 该函数按严格的依赖顺序执行初始化步骤。首先调用
mmc_register_bus()
,这会向内核设备模型注册一个名为mmc
的bus_type
。这个总线类型是连接MMC卡设备和MMC卡驱动的桥梁。 - 成功后,调用
mmc_register_host_class()
,它会注册一个名为mmc_host
的设备类(class
)。这会在sysfs中创建/sys/class/mmc_host
目录,为所有MMC主机控制器提供一个统一的视图(如mmc0
,mmc1
)。 - 接着,调用
sdio_register_bus()
注册一个独立的、名为sdio
的bus_type
。虽然SDIO卡通过MMC总线进行通信,但其上的功能设备(Functions)具有不同的寻址和驱动匹配逻辑,因此需要一个专门的SDIO总线来管理SDIO功能驱动和设备。 - 函数使用了
goto
语句进行错误处理。如果在任何一步注册失败,程序会跳转到相应的标签,执行已经成功注册部分的反向注销操作,确保系统状态的一致性。这是一种在内核中常见的、高效的错误回滚(rollback)模式。
- 该函数按严格的依赖顺序执行初始化步骤。首先调用
退出流程 (
mmc_exit
):- 该函数在模块卸载时被调用,执行与
mmc_init
完全相反的操作。 - 它严格按照“后进先出”(LIFO)的原则进行清理:首先注销SDIO总线,然后是MMC主机类,最后是MMC总线。这种相反的顺序是至关重要的,可以避免因依赖关系导致的错误(例如,在仍有设备注册在总线上时就注销了总线类型)。
- 该函数在模块卸载时被调用,执行与
代码分析
1 | // mmc_init: MMC核心子系统的模块初始化函数。 |
MMC主机控制器独占声明:实现总线访问的互斥与电源管理
本代码片段展示了MMC核心子系统中至关重要的主机控制器锁定机制。其核心功能是通过mmc_claim_host
(及其实现__mmc_claim_host
)提供一个可重入的、可中断的、且与电源管理集成的互斥锁。任何需要访问MMC/SD/SDIO总线硬件的驱动程序(如SD卡块设备驱动、SDIO WiFi驱动)在执行I/O操作前都必须调用此函数。它确保了在任何时刻,只有一个执行上下文可以控制MMC主机硬件,同时保证了硬件在被访问前处于上电状态。
实现原理分析
此函数并未直接使用标准的mutex_lock
,而是实现了一个更复杂的、定制化的阻塞锁。这是因为MMC主机的锁定需要满足几个特殊需求:可重入性、可被外部事件中止、以及与运行时电源管理(Runtime PM)的紧密集成。
自定义阻塞锁:
- 函数的核心是一个
while(1)
循环,它在一个自旋锁(host->lock
)的保护下检查锁定条件。自旋锁用于保护host->claimed
标志位和host->wq
等待队列等共享状态,防止在检查和修改这些状态时发生竞态。 - 等待条件: 循环的退出条件是
!host->claimed || mmc_ctx_matches(...)
。这意味着,如果主机当前未被声明(claimed
为0),或者声明主机的上下文与当前请求的上下文匹配(mmc_ctx_matches
返回真),那么当前任务就可以获得锁。后者实现了锁的可重入性,允许同一个任务或上下文多次声明主机而不会死锁。 - 睡眠与唤醒: 如果条件不满足(即其他上下文已持有锁),当前任务会调用
schedule()
放弃CPU,进入TASK_UNINTERRUPTIBLE
睡眠状态。它通过add_wait_queue
将自己挂载在host->wq
等待队列上。当锁的持有者释放锁时,会唤醒等待队列上的所有任务,它们将重新进入循环进行条件检查。
- 函数的核心是一个
中止机制:
abort
参数允许锁的获取过程被外部事件中断。例如,当一个卡被物理拔出时,另一个内核线程可以设置abort
标志。在等待循环中,每次都会检查atomic_read(abort)
,如果发现非零值,将立即放弃获取锁并返回,避免了在设备已不存在的情况下无限期等待。电源管理集成:
host->claim_cnt
是一个引用计数器,用于支持可重入锁。- 关键逻辑在于
if (host->claim_cnt == 1)
。只有在主机从未被声明(计数从0变为1)的第一次成功获取锁时,才会执行pm_runtime_get_sync(mmc_dev(host))
。 pm_runtime_get_sync
会同步地唤醒并恢复设备的电源。这确保了硬件只在上层驱动真正需要访问它时才上电,并在第一次claim时完成。对于后续的重入claim(计数从1变为2等),则不再需要重复上电操作,提高了效率。
代码分析
1 | // mmc_claim_host: 独占性地声明一个主机(简化接口)。 |
MMC请求完成与调谐管理:请求生命周期的终点与信号完整性维护
本代码片段展示了MMC子系统中的两个关键功能:mmc_request_done
函数,它是所有MMC请求处理流程的终点站;以及mmc_retune_hold
/release
函数对,它们实现了一个引用计数机制来管理和推迟信号调谐(retuning)过程。mmc_request_done
负责在底层硬件操作完成后,进行错误分析、状态更新,并最终通知上层调用者,而调谐管理则是在高速模式下维持总线信号完整性的重要保障。
实现原理分析
请求完成 (
mmc_request_done
): 这是一个由底层主机控制器驱动(Host Driver)调用的核心回调函数。其原理如下:- 错误检测与响应: 函数首先检查请求中各个命令(主命令、数据、停止命令)的错误码。它特别关注CRC校验错误(
-EILSEQ
),因为这通常是信号完整性问题的标志。如果检测到CRC错误,并且当前操作本身不是调谐命令,它会调用mmc_retune_needed()
来设置一个标志,表明在处理下一个请求之前需要进行一次信号调谐。 - 状态清理: 它负责清理MMC核心层的状态,例如将
host->ongoing_mrq
指针置为NULL,表示当前没有正在进行中的请求。 - 调试与追踪: 在请求被确认为最终完成(即没有错误、不需要重试或卡已被拔出)后,函数会打印详细的调试日志,并关闭LED指示灯,为开发者提供详尽的状态信息。
- 异步通知: 最关键的一步是调用
mrq->done(mrq)
回调函数(如果存在)。这是实现异步操作的核心机制。上层(如块设备层)在提交请求时可以提供这个回调函数,当硬件操作完成时,该回调被执行,从而唤醒等待的进程或触发下一个操作,而不需要上层进行阻塞轮询。
- 错误检测与响应: 函数首先检查请求中各个命令(主命令、数据、停止命令)的错误码。它特别关注CRC校验错误(
调谐保持机制 (
mmc_retune_hold
/release
):- 这是一个基于引用计数的锁机制,用于控制何时可以安全地执行信号调谐。
mmc_retune_hold
会增加host->hold_retune
计数器。这通常在一个复杂操作(如多块读写)开始时调用。mmc_retune_release
则减少该计数器,在操作结束时调用。- MMC核心在准备分发新请求时,会检查
host->hold_retune
计数器。只要该计数器大于0,即使retune_needed
标志被设置,实际的调谐操作也会被推迟。这可以防止在一次连续的数据传输过程中间插入耗时的调谐操作,保证了数据流的连续性。
代码分析
调谐保持与释放
1 | // mmc_retune_hold: 增加调谐保持计数,推迟调谐操作。 |
请求完成处理
1 | // mmc_request_done: 完成一个MMC请求的处理。 |
MMC请求处理核心:命令的准备、分发与完成机制
本代码片段是Linux内核MMC子系统的核心部分,负责处理一个MMC/SD/SDIO请求(mmc_request
,简称mrq
)从提交到完成的整个生命周期。它定义了MMC核心层(总线无关的逻辑)与底层主机控制器驱动(硬件相关的实现)之间的标准交互接口。其主要功能包括:对请求进行合法性检查与准备、将请求分发给主机控制器驱动执行,以及在请求完成后进行状态更新、错误处理和完成通知。
实现原理分析
该代码的执行流程体现了典型的分层驱动模型,通过函数指针结构(mmc_host_ops
)实现了上层逻辑与底层硬件的解耦。
请求的准备 (
mmc_mrq_prep
): 在一个请求被发送到硬件之前,此函数作为“守门员”,负责进行一系列的验证和初始化。它确保请求中的命令(mmc_command
)、数据(mmc_data
)等部分的内部指针和状态被正确设置。最重要的是,它会根据主机控制器(mmc_host
)的能力(如最大块大小max_blk_size
、最大请求大小max_req_size
)来校验请求参数的合法性,防止向硬件提交无法处理的请求。请求的启动与分发 (
mmc_start_request
,__mmc_start_request
):mmc_start_request
是上层(如块设备层)发起一个请求的标准入口。它是一个协调者,负责调用准备函数mmc_mrq_prep
,并设置用于同步的完成量(completion)。- 核心的分发任务由
__mmc_start_request
完成。它会处理一些协议细节,如在必要时执行mmc_retune
以保证信号完整性,或为SDIO设备等待card_busy
状态。 - 最终,它通过调用
host->ops->request(host, mrq)
,将请求的控制权移交给底层的主机控制器驱动。这个ops->request
函数指针是关键的抽象层,其具体实现由硬件驱动(如针对STM32的SDMMC驱动)提供。
请求的完成 (
mmc_request_done
):- 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须调用
mmc_request_done
来通知核心层。 - 此函数是请求处理的终点。它负责分析命令执行后的错误码,并根据错误类型采取相应措施,例如,在发生CRC错误(
-EILSEQ
)时,通过mmc_retune_needed
标记需要进行重新调谐。 - 它会更新主机的状态,例如将
ongoing_mrq
指针清空。 - 最关键的一步是,如果请求中设置了
done
回调函数(mrq->done
),它会调用该函数。这构成了异步通知机制,允许最初发起请求的调用者得知操作已完成。对于同步请求,这个done
回调通常会唤醒一个正在等待的完成量。
- 这是一个回调函数。当主机控制器驱动完成了硬件操作(无论是成功、失败还是超时),它必须调用
代码分析
完成命令
1 | static inline void mmc_complete_cmd(struct mmc_request *mrq) |
请求启动与分发
1 | static inline void mmc_delay(unsigned int ms) |
请求准备与同步封装
1 | // mmc_mrq_prep: 准备一个MMC请求,进行校验和初始化。 |
MMC命令发送与阻塞等待
本代码片段定义了MMC核心子系统中用于发送命令并同步等待其完成的高级接口:mmc_wait_for_cmd
及其依赖的 mmc_wait_for_req
。其核心功能是为上层驱动提供一个简化的、阻塞式的编程模型。驱动程序只需构建一个命令,调用这些函数,函数将在命令完成后才返回。这极大地简化了驱动的编写,隐藏了底层请求提交、中断处理和任务睡眠/唤醒的复杂细节。
实现原理分析
此功能的实现建立在MMC核心的异步请求机制之上,并将其封装为同步调用。它体现了清晰的层次结构:一个命令(mmc_command
)被包装在一个请求(mmc_request
)中进行处理。
请求(Request)与命令(Command)的抽象:
mmc_command
(cmd
): 代表一个单独的MMC命令,包含操作码、参数、期望的响应类型等。mmc_request
(mrq
): 代表一个完整的MMC操作,它通常包含一个命令(mrq.cmd
),并且可能还包含一个数据传输(mrq.data
)和一个停止命令(mrq.stop
)。本代码片段处理的是最简单的情况:一个只包含单个命令的请求。
同步封装 (
mmc_wait_for_req
):- 此函数首先调用
__mmc_start_req(host, mrq)
。这个内部函数负责将请求mrq
提交给底层的主机控制器驱动,并启动硬件操作。这个调用是非阻塞的,它会立即返回。 - 关键在于后续的
if (!mrq->cap_cmd_during_tfr)
判断。在普通命令中,这个标志位为假,因此会立即调用mmc_wait_for_req_done(host, mrq)
。 mmc_wait_for_req_done
是一个阻塞函数。它内部会使用等待队列(wait queue)机制,使当前任务进入睡眠状态,直到硬件完成命令并产生中断。中断服务程序会标记请求已完成并唤醒等待的任务。
- 此函数首先调用
便利的命令封装 (
mmc_wait_for_cmd
):- 这是一个更高级的便利函数,专门用于发送不涉及数据传输的单个命令(例如,SDIO CMD52)。
- 它在函数栈上创建一个临时的
mmc_request
结构体mrq
。 WARN_ON(!host->claimed)
是一个重要的断言,确保上层驱动在发送命令前已经通过mmc_claim_host
获取了总线锁。- 它将用户传入的
cmd
指针放入mrq.cmd
中,并将cmd->data
明确设置为NULL
,表示这是一个没有数据阶段的请求。 - 最后,它调用
mmc_wait_for_req
来同步地执行这个只包含命令的请求。 - 函数返回
cmd->error
,这个字段会在底层请求处理过程中,根据命令的执行结果(如超时、CRC错误等)被填充。
代码分析
1 | // mmc_wait_for_req: 启动一个请求并等待其完成。 |
drivers/mmc/core/bus.c MMC总线驱动(MMC Bus Driver) 连接MMC/SD卡设备与功能驱动的桥梁
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc/core/bus.c
的诞生是为了在MMC子系统中实现Linux驱动模型的核心思想:将设备(Device)的发现与驱动(Driver)的绑定过程分离开来。
在MMC子系统的三层架构(Host -> Core -> Card)中,MMC核心(Core)负责与卡片通信并识别其类型(是SD存储卡、eMMC还是SDIO Wi-Fi卡)。但识别出卡片后,需要一个机制来为这张卡找到并加载正确的功能驱动(例如,为存储卡加载块设备驱动,为Wi-Fi卡加载网络驱动)。
bus.c
就是这个机制的实现。它创建了一个名为 mmc
的新总线类型(Bus Type),将MMC卡抽象为挂载在这条总线上的“设备”,并将功能驱动抽象为这条总线上的“驱动”,然后负责将两者“匹配”(match)起来。这解决了以下问题:
- 关注点分离:让MMC核心专注于协议和卡片识别,而让功能驱动(如
card/block.c
)专注于实现其特定功能(如块设备I/O),两者通过bus.c
这个中间层解耦。 - 标准化:为所有类型的MMC/SD/SDIO卡提供了一个统一的驱动注册和设备绑定模型,遵循了Linux设备模型的标准范式。
- 可扩展性:当需要支持一种新型的SDIO设备时,开发者只需编写一个遵循MMC总线规范的新功能驱动,而无需改动核心代码。
它的发展经历了哪些重要的里程碑或版本迭代?
bus.c
本身是MMC子系统核心框架的一部分,其发展与MMC核心的演进紧密相连。
- 框架确立:其最重要的里程碑是作为MMC核心的一部分被创建出来,正式将MMC子系统纳入了Linux的标准化总线模型。
- SDIO支持的完善:随着SDIO变得越来越重要,
bus.c
的逻辑需要支持更复杂的场景。一张SDIO卡可以有多个功能(Function),例如一个Wi-Fi加蓝牙的“combo”卡。MMC总线需要能正确地为每个功能创建设备,并将其与对应的功能驱动匹配。这通常涉及到与drivers/mmc/core/sdio_bus.c
的协作,后者在MMC总线之上建立了一个专门的SDIO功能总线。 - 电源管理集成:为
mmc_bus_type
添加了电源管理回调函数(.suspend
,.resume
),使得MMC设备可以参与到整个系统的休眠与唤醒流程中。
目前该技术的社区活跃度和主流应用情况如何?
bus.c
是MMC子系统的基石,其代码非常稳定,不会频繁变动。它的正确运行是所有使用SD卡、eMMC、SDIO设备的Linux系统的基础。社区的开发活动更多地集中在Host驱动和具体的Card功能驱动上,而bus.c
作为底层的粘合剂,默默地支撑着整个体系的运转。
核心原理与设计
它的核心工作原理是什么?
bus.c
的核心工作是定义并实现 mmc_bus_type
,这是一个 struct bus_type
实例,它告诉内核如何管理MMC总线上的设备和驱动。
其工作流程如下:
- 总线注册:在模块初始化时,
bus.c
调用bus_register(&mmc_bus_type)
,在内核中注册“mmc”这条总线。这会在sysfs中创建/sys/bus/mmc
目录。 - 设备发现与注册(由Core触发):这个过程不由
bus.c
发起。当MMC核心(core.c
)通过mmc_rescan()
函数检测到一张卡并成功初始化后,它会为一个struct mmc_card
分配内存,并调用mmc_add_card()
。mmc_add_card()
会调用device_add()
,将这张卡作为一个struct device
注册到内核,并挂在mmc_bus_type
上。 - 驱动注册(由功能驱动触发):一个功能驱动,比如块设备驱动(
card/block.c
),会在其初始化时调用mmc_register_driver()
,将自己(一个struct mmc_driver
)注册到mmc_bus_type
上。 - 匹配过程(Matchmaking):每当有新的设备或新的驱动注册到
mmc
总线上时,总线核心就会尝试进行匹配。它会调用mmc_bus_type
中指定的.match
函数,即mmc_bus_match()
。 mmc_bus_match()
的逻辑:这个函数非常核心。它会比较驱动程序所支持的设备ID列表(driver->id_table
)和当前设备的ID(card->cid
、card->type
等信息)。如果找到匹配项,函数返回true
。- 探测(Probing):一旦
.match()
返回成功,总线核心就会调用匹配到的驱动的.probe
函数(例如mmc_block_probe()
)。.probe
函数负责执行所有功能相关的初始化,比如为存储卡创建块设备节点/dev/mmcblkX
。 - 移除过程:当卡被拔出或驱动被卸载时,会触发相反的过程:调用驱动的
.remove
函数,然后设备和驱动会从总线上解除绑定并注销。
它的主要优势体现在哪些方面?
- 完全遵循驱动模型:完美地将设备和驱动解耦,是Linux驱动模型的一个经典实现。
- 自动化绑定:内核可以自动完成设备和驱动的匹配与加载,支持热插拔。
- 清晰的层次结构:明确了MMC核心、总线和功能驱动之间的界限和交互方式。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 抽象带来的复杂性:对于初学者来说,这种通过总线进行的间接调用(
device_add()
->bus_match()
->driver->probe()
)比直接函数调用更难跟踪和理解。 - 非通用性:
bus.c
的逻辑是为MMC/SD/SDIO协议量身定做的,其匹配规则(基于CID等卡信息)对其他类型的总线没有意义。它本身没有劣势,只是其设计具有高度的特异性。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?请举例说明。
bus.c
是MMC子系统内部的强制性组件,而不是一个可选项。它的“使用场景”就是MMC子系统工作的每一个瞬间。
- 场景一:插入一张SD卡
- Host驱动检测到卡插入,通知MMC核心。
- MMC核心(
core.c
)执行卡初始化序列,识别出这是一张SD存储卡。 - 核心调用
mmc_add_card()
,将卡注册为一个mmc
总线设备。 bus.c
的mmc_bus_match()
被调用,它发现这张SD卡与mmc_block_driver
兼容。mmc_block_driver
的.probe
函数被调用,创建/dev/mmcblk0
。
- 场景二:系统启动时识别eMMC
- 系统启动时,eMMC的Host驱动被加载。
- 它调用MMC核心的扫描函数。
- 核心识别出焊接在主板上的eMMC芯片。
- 后续流程与场景一完全相同,
bus.c
负责将其与块设备驱动绑定,最终创建出代表eMMC分区的设备节点,如/dev/mmcblk1p1
。
是否有不推荐使用该技术的场景?为什么?
不存在不推荐使用bus.c
的场景。只要一个设备使用MMC/SD/SDIO协议栈,它就必须通过bus.c
所定义的mmc_bus_type
来与功能驱动进行交互。绕过这个机制就等于破坏了整个MMC子系统的设计。
对比分析
请将其 与 其他总线实现(如PCI, USB)进行详细对比。
mmc/core/bus.c
与 drivers/pci/pci-driver.c
或 drivers/usb/core/driver.c
在目标上是完全一致的:实现一个标准化的总线模型。但它们在实现细节,特别是设备枚举和匹配方式上,反映了各自总线的物理和协议特性。
特性 | MMC Bus (bus.c ) |
PCI Bus | USB Bus |
---|---|---|---|
总线拓扑 | 简单,通常是点对点(一个Host控制器一个插槽)。没有复杂的树状或网状结构。 | 树状层次结构。有根总线(Root Complex)、桥(Bridge)和端点设备(Endpoint)。 | 树状层次结构。有根集线器(Root Hub)、外部集线器(Hub)和功能设备。 |
设备枚举方式 | 软件协议驱动。由MMC核心发送一系列标准命令(如CMD2, CMD3)与卡片进行交互,卡片会回复其CID/CSD等身份信息。 | 硬件扫描。由内核读取标准化的PCI配置空间(Configuration Space)。每个设备都有唯一的Vendor ID和Device ID。 | 软件协议驱动。设备插入后,由Host Controller进行端口复位和地址分配,然后内核读取设备返回的一系列标准描述符(Device, Configuration, Interface Descriptors)。 |
匹配规则 (.match ) |
基于MMC/SD卡内部寄存器信息(如CID中的制造商ID、产品名)和卡类型(SD, MMC, SDIO)。 | 主要基于PCI配置空间中的Vendor ID, Device ID, Class Code等。 | 主要基于USB描述符中的Vendor ID, Product ID, Class/SubClass/Protocol等。 |
设备地址 | 由MMC核心在逻辑上管理,没有固定的硬件地址。 | 由总线号(Bus)、设备号(Device)、功能号(Function)组成的唯一硬件地址。 | 由总线分配的动态设备地址(1-127)。 |
MMC总线类型定义:连接卡设备与驱动的桥梁
本代码片段定义了Linux内核中mmc
总线的核心行为。它通过填充一个bus_type
结构体(mmc_bus_type
),实现了MMC设备(如SD卡、eMMC芯片)在内核设备模型中的探测(probe)、移除(remove)、关机(shutdown)和电源管理等关键回调函数。此外,它还负责通过uevent机制向用户空间(userspace)通告设备信息,并创建了sysfs属性文件(如type
),以展示卡的类型。这部分代码是MMC核心的基础,它使得具体的MMC卡驱动(如mmc_block
)能够与具体的MMC卡设备进行匹配和绑定。
实现原理分析
此代码的核心是mmc_bus_type
结构体的实例化。这个结构体是Linux设备模型中用于描述一种总线的标准方式,它通过函数指针将总线级别的通用操作与设备模型核心连接起来。
Probe/Remove/Shutdown 代理:
mmc_bus_probe
、mmc_bus_remove
和mmc_bus_shutdown
函数扮演了“代理”或“中间人”的角色。当设备模型核心决定要为一个MMC卡设备probe
一个MMC驱动时,它会调用mmc_bus_probe
。这个函数并不执行设备特定的初始化,而是简单地从通用device
结构中提取出mmc_card
和mmc_driver
结构,然后调用mmc_driver
结构中真正的probe
函数指针。这种设计将总线的通用逻辑与驱动的具体实现解耦。Uevent事件生成:
mmc_bus_uevent
函数是实现设备自动配置的关键。当一个MMC卡被识别并注册到总线上时,内核会调用此函数来生成一个uevent事件。该函数将卡的详细信息(如类型、名称、SDIO ID等)打包成环境变量,发送给用户空间的udevd
或mdev
等守护进程。其中最关键的一行是add_uevent_var(env, "MODALIAS=mmc:block")
,它生成了一个标准的模块别名(MODALIAS)。用户空间守护进程看到这个别名后,会执行modprobe mmc:block
,从而自动加载处理MMC块设备的内核模块(mmc_block.ko
)。Sysfs属性:
type_show
函数和DEVICE_ATTR_RO(type)
宏一起工作,在sysfs中为每个MMC设备创建一个名为type
的只读文件。当用户通过cat /sys/bus/mmc/devices/mmc0:xxxx/type
读取该文件时,type_show
函数会被调用,它会根据卡的类型返回一个可读的字符串(如”SD”, “MMC”)。总线注册:
mmc_register_bus
函数非常简单,它只是调用了设备模型核心提供的bus_register
函数,将mmc_bus_type
结构体注册到内核中,从而正式“激活”了MMC总线。
代码分析
1 | // type_show: sysfs属性 "type" 的读取回调函数。 |
drivers/mmc/core/host.c MMC主机控制器管理(MMC Host Controller Management) 所有主机驱动的注册与管理中心
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc/core/host.c
是MMC子系统三层架构(Host - Core - Card)中承上启下的关键文件。它的存在是为了解决如何将种类繁多的物理MMC/SD主机控制器(Host Controller)硬件以一种标准化的方式接入到MMC核心协议层的问题。
在host.c
出现之前,或者说在没有这种抽象层的情况下,会遇到以下问题:
- 缺乏统一接口:每种主机控制器(例如,由Synopsys、Cadence或特定SoC厂商设计的IP核)都有其独特的寄存器和工作方式。MMC核心协议层(
core.c
)如果需要直接与这些硬件交互,代码将会变得极其臃肿和混乱,充满了针对特定硬件的if/else
分支。 - 代码重复:每个主机控制器驱动都需要自己实现与MMC核心的对接逻辑,导致大量重复代码。
- 开发困难:为一款新的主机控制器编写驱动将是一项非常复杂的工作,开发者不仅要理解硬件,还要深入了解MMC核心的内部工作机制。
host.c
通过定义一个标准的“主机”抽象 (struct mmc_host
) 和一套操作接口 (struct mmc_host_ops
),完美地解决了这些问题。它充当了所有具体主机驱动和通用MMC核心协议层之间的适配器(Adapter)和管理中心。
它的发展经历了哪些重要的里程碑或版本迭代?
作为MMC子系统的基础组件,host.c
的演进与整个子系统的发展同步。
- 框架的建立:最重要的里程碑是
struct mmc_host
和mmc_host_ops
的定义,以及mmc_alloc_host
/mmc_add_host
等生命周期管理函数的创建。这确立了所有主机驱动必须遵循的开发范式。 - 功能和能力的扩展:随着MMC/SD标准的发展,
struct mmc_host
中增加了越来越多的能力标志(caps
和caps2
字段),以向核心层宣告其支持的特性,例如支持4/8位总线宽度、支持DDR模式、支持HS200/HS400高速模式、支持CMD23(SET_BLOCK_COUNT命令)等。mmc_host_ops
也相应增加了新的回调函数。 - devres集成:引入了
devm_mmc_alloc_host()
,利用内核的设备资源管理(devres)框架,使得主机驱动的资源管理(特别是内存分配与释放)变得更加简单和安全,减少了代码量并防止了内存泄漏。 - 电源管理:为主机控制器自身增加了标准的电源管理支持,允许主机控制器在不使用时进入低功耗状态。
目前该技术的社区活跃度和主流应用情况如何?
host.c
的代码非常稳定,是MMC子系统中不经常发生根本性改变的部分。然而,它又是绝对核心的,因为每一个MMC主机驱动(位于drivers/mmc/host/
目录下)都必须依赖它提供的API来工作。社区的活跃度更多体现在基于这个框架开发新的主机驱动,或者为主机能力集添加新的特性支持。它是所有使用MMC/SD/eMMC/SDIO功能的Linux系统的基础。
核心原理与设计
它的核心工作原理是什么?
host.c
的核心原理是接口与实现分离。它定义了“一个MMC主机应该是什么样子以及应该能做什么”(接口),而将“具体怎么做”(实现)的任务留给了具体的硬件驱动。
其工作流程和核心组件如下:
主机抽象 (
struct mmc_host
):这是描述一个MMC主机控制器的核心数据结构。它包含了主机的所有信息,如:ops
: 一个指向mmc_host_ops
结构体的指针,包含了所有操作的回调函数。caps
,caps2
: 描述硬件能力的位掩码。ios
: 描述当前总线状态(时钟、电压、总线宽度等)。card
: 指向当前插槽中被识别的卡的指针。
操作接口 (
struct mmc_host_ops
):这是一个函数指针的集合,是具体主机驱动必须实现的“合同”。关键操作包括:.request()
: 核心函数,MMC核心通过它向主机驱动提交一个数据传输请求(struct mmc_request
)。.set_ios()
: MMC核心通过它来命令主机驱动改变总线状态(如调整时钟频率、设置总线宽度)。.get_ro()
: 用于检测卡是否处于只读状态(通过硬件引脚)。.get_cd()
: 用于检测卡是否存在(通过硬件引脚)。
生命周期管理:
host.c
提供了一套标准的API来管理mmc_host
对象的生命周期:mmc_alloc_host()
: 一个主机驱动在其.probe
函数中首先调用此函数,来分配一个mmc_host
结构体和驱动的私有数据区。mmc_add_host()
: 驱动在配置好mmc_host
(特别是填充了ops
和caps
)之后,调用此函数将主机注册到MMC核心。这是一个关键步骤,一旦调用成功,MMC核心就会接管这个主机,并立即触发一次总线扫描 (mmc_rescan
) 来检测卡片。mmc_remove_host()
: 在驱动卸载时调用,将主机从核心中注销。mmc_free_host()
: 释放mmc_host
结构体占用的内存。
它的主要优势体现在哪些方面?
- 高度抽象和解耦:MMC核心协议层完全无需关心底层硬件的细节,它只与标准的
mmc_host
接口交互。 - 简化驱动开发:为主机驱动开发者提供了一个清晰的、必须实现的接口(
mmc_host_ops
),极大地降低了开发门槛。 - 代码复用:所有主机的公共管理逻辑都集中在
host.c
中,被所有驱动共享。 - 可维护性:结构清晰,当需要为所有主机增加一个新功能时,可能只需要修改
host.c
和core.c
,而无需改动成百上千个具体的主机驱动。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 接口的刚性:
mmc_host_ops
定义了一套固定的接口,如果某个硬件有一些非常规的、无法通过现有ops
和caps
描述的特殊功能,就很难优雅地将其集成进来。但这在实践中非常罕见。 - 间接调用的复杂性:对于调试和代码跟踪来说,通过函数指针(
host->ops->request()
)进行的间接调用比直接调用更难跟踪。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
host.c
提供的框架是为MMC/SD/eMMC主机控制器编写Linux驱动的唯一且强制的解决方案。任何想要将其硬件集成到Linux MMC子系统的芯片厂商或开发者,都必须使用它。
一个典型的主机驱动开发流程就是其使用场景的完美展示:
- 在驱动的
.probe
函数中,首先调用mmc_alloc_host()
。 - 获取到
mmc_host
指针后,填充其成员:- 设置
host->ops
指向驱动自己实现的静态mmc_host_ops
实例。 - 根据硬件寄存器的值,设置
host->caps
和host->caps2
来宣告硬件能力。 - 设置
host->f_min
,host->f_max
等频率范围。
- 设置
- 完成所有硬件相关的初始化(如申请中断、映射寄存器)。
- 最后,调用
mmc_add_host()
,将控制器“上线”并交由MMC核心管理。
是否有不推荐使用该技术的场景?为什么?
不存在。只要设备是一个MMC、SD、eMMC或SDIO主机控制器,并且需要在Linux下工作,就必须遵循host.c
定义的这套框架。
对比分析
请将其 与 其他总线的主机控制器管理框架(如USB HCD, SCSI Host)进行详细对比。
mmc/core/host.c
的角色与USB子系统中的主机控制器驱动(HCD)层和SCSI子系统中的**SCSI主机模板(SCSI Host Template)**非常相似。它们的目标都是一样的:抽象底层硬件,为上层协议提供标准接口。
特性 | MMC Host (host.c ) |
USB HCD (Host Controller Driver) | SCSI Host Template |
---|---|---|---|
核心抽象 | struct mmc_host |
struct usb_hcd |
struct Scsi_Host |
操作接口 | struct mmc_host_ops |
struct hc_driver |
struct scsi_host_template |
协议复杂度 | 中等。处理命令-响应、数据传输、总线状态。拓扑简单(点对点)。 | 高。处理复杂的USB协议,包括控制、批量、中断、同步四种传输类型,以及树状的设备拓扑(Hubs和设备)。 | 非常高。处理SCSI命令集(CDBs),支持复杂的命令队列、错误处理和多种设备类型(磁盘、磁带、光驱等)。 |
交互单元 | struct mmc_request (包含命令mmc_command 和数据mmc_data ) |
struct urb (USB Request Block) |
struct scsi_cmnd (SCSI Command) |
注册/注销 | mmc_add_host / mmc_remove_host |
usb_add_hcd / usb_remove_hcd |
scsi_add_host / scsi_remove_host |
主要关注点 | 总线时序、电压、速度模式和卡状态机。 | 端点(Endpoint)、带宽管理、帧调度和拓扑管理。 | 命令队列、LUN(逻辑单元)管理、错误恢复。 |
总结:这三者在设计哲学上是相通的,都采用了“框架 + 驱动实现”的模式。它们的区别主要源于所服务的总线协议的内在差异。MMC总线相对简单,所以mmc_host
的抽象也相对直接;而USB和SCSI的协议和设备模型要复杂得多,因此它们的HCD和Host Template框架也更为庞大和复杂。
MMC主机控制器类:sysfs中的统一视图与生命周期管理
本代码片段定义并注册了一个名为mmc_host
的设备类(Device Class)。其核心功能是在Linux的sysfs中创建/sys/class/mmc_host/
目录,并为所有MMC主机控制器(如mmc0
, mmc1
等)提供一个统一的、与物理总线无关的视图。同时,它还定义了属于该类的所有设备的通用生命周期管理回调函数,特别是资源释放(release)和系统关机前(pre-shutdown)的处理逻辑。
实现原理分析
此代码是Linux设备模型中“类”概念的一个典型应用。struct class
作为一种高层抽象,用于将功能相似的设备组织在一起。
类定义 (
mmc_host_class
):- 一个静态的
struct class
实例被定义,其.name
字段被设置为"mmc_host"
,这决定了在sysfs中创建的目录名。 .dev_release
回调:mmc_host_classdev_release
函数被指定为该类的释放回调。当一个mmc_host
设备(例如mmc0
)的最后一个引用被释放时,设备模型核心会调用此函数。它的职责是完成最终的清理工作,包括注销唤醒源(wakeup source)、释放动态分配的主机索引号(ID),以及释放mmc_host
结构体本身占用的内存。这是防止内存泄漏的关键步骤。.shutdown_pre
回调:mmc_host_classdev_shutdown
函数被指定为系统关机前的回调。在系统关机过程中,设备模型会遍历该类的所有设备,并调用此函数。它的任务是调用__mmc_stop_host
,将MMC主机控制器硬件置于一个安全、静默的状态,确保在系统断电前不会有意外的总线活动。
- 一个静态的
类注册 (
mmc_register_host_class
):- 这个函数非常简单,它只调用了设备模型提供的核心API
class_register()
,将mmc_host_class
结构体注册到内核中。一旦注册成功,/sys/class/mmc_host
目录就会被创建,并且具体的MMC主机控制器驱动就可以通过device_create()
等函数将其设备注册到这个类中。
- 这个函数非常简单,它只调用了设备模型提供的核心API
动态ID管理:
mmc_host_classdev_release
函数中的ida_free
逻辑展示了对主机索引号的动态管理。当一个主机控制器驱动(如STM32的SDMMC驱动)注册时,它会通过ida_alloc
从一个全局的ID分配器(mmc_host_ida
)中获取一个唯一的索引号(如0, 1, 2…),除非设备树中通过别名(alias)指定了静态ID。在设备被释放时,此处的代码负责将动态分配的ID归还给ID池,以备后续使用。
代码分析
1 | // mmc_host_classdev_release: mmc_host类设备的释放回调函数。 |
sd
drivers/mmc/core/sd_ops.c
1 | int mmc_send_ext_addr(struct mmc_host *host, u32 addr) |
drivers/mmc/core/sd_uhs2.c SD UHS-II总线管理(SD UHS-II Bus Management) 实现UHS-II卡初始化与信号调整
历史与背景
这项技术是为了解决什么特定问题而诞生的?
这项技术是为了支持 SD UHS-II(Ultra High Speed II) 存储卡标准而诞生的。随着专业相机(4K/8K视频录制、RAW格式高速连拍)和高性能计算设备对存储速度要求的不断提升,传统的SD/UHS-I总线(最高104 MB/s)已成为严重的性能瓶颈。UHS-II标准的出现旨在解决此问题,它引入了一套全新的物理接口和协议,以实现更高的数据传输速率。
drivers/mmc/core/sd_uhs2.c
文件中的代码专门用于解决支持UHS-II所带来的新挑战:
- 全新的物理接口:UHS-II卡在传统SD卡引脚的基础上增加了第二排引脚,用于低电压差分信号(LVDS),以实现数百MB/s的高速传输。内核需要新的逻辑来管理和切换到这个新接口。
- 复杂的初始化序列:与传统SD卡简单的命令响应初始化方式不同,UHS-II卡的发现和初始化需要一个独特的、基于特定信号模式的握手过程。
- 专用寄存器访问:UHS-II卡的功能、速度模式和配置信息存储在一组扩展功能寄存器中,访问这些寄存器需要一套特殊的命令序列。
- 信号训练(Training):在如此高的频率下工作,为了保证信号的完整性和可靠的数据传输,主机控制器必须在切换到高速模式之前执行一个“训练”或“调谐(Tuning)”过程,以校准时钟和数据信号的相位。
它的发展经历了哪些重要的里程碑或版本迭代?
UHS-II标准本身是SD卡协会发布的一个重大技术飞跃。在Linux内核中,对其支持是逐步演进的:
- 初期:内核的MMC/SD子系统只能将UHS-II卡识别为普通的UHS-I或更低速的卡,并以降级模式运行。
- 引入专有逻辑:为了支持UHS-II的原生高速模式,社区将与UHS-II相关的、高度专业化的代码从通用的SD卡处理逻辑(
sd.c
)中分离出来,创建了sd_uhs2.c
。这一步是关键的里程碑,它实现了UHS-II卡的发现、握手和初始化流程。 - 完善与优化:后续的版本迭代主要集中在修复特定主机控制器或UHS-II卡上的兼容性问题,优化训练序列的可靠性,以及添加对UHS-III等更新标准的支持(如果它们共享相似的机制)。
目前该技术的社区活跃度和主流应用情况如何?
sd_uhs2.c
是Linux内核MMC子系统中的一个核心且稳定的部分,由该子系统的维护者持续维护。这项技术是任何需要支持UHS-II高速读写的现代Linux系统的必备功能。
- 主流应用:广泛应用于带有高性能SD读卡器的高端笔记本电脑、USB外置读卡器、以及需要高速数据采集的嵌入式系统(例如,专业无人机、高端便携式录音设备等)。
核心原理与设计
它的核心工作原理是什么?
sd_uhs2.c
中的代码并不在内核启动时独立运行,而是在通用的SD卡初始化流程中,当检测到可能是UHS-II卡时被调用。其工作流程如下:
- 发现(Discovery):在标准的SD卡初始化流程中(位于
sd.c
),当主机发送ACMD41
后,UHS-II卡会通过保持CMD
和DAT0-3
线路为高电平来发出一个特殊信号。MMC核心检测到这个信号后,会中止传统流程,转而调用sd_uhs2_start_discovery()
函数。 - 握手与初始化:该函数会请求底层的主机控制器驱动执行UHS-II的物理层发现序列。这是一个精确的时序操作,成功后,卡和主机就建立了一个基本的LVDS通信链路。
- 读取扩展寄存器:一旦链路建立,
sd_uhs2.c
中的代码(如sd_uhs2_read_ext_regs
)会使用UHS-II专用的命令来读取卡的扩展功能寄存器。这些寄存器描述了卡支持的速度模式、总线宽度(Lane数)、全双工/半双工能力等关键信息。 - 模式选择:驱动会比较主机和卡共同支持的最佳模式,并通过
sd_uhs2_write_ext_regs
将选择的模式(例如,FD156 - 全双工156MB/s)写入卡的寄存器中。 - 训练(Training):在正式切换到所选的高速模式之前,必须执行信号训练。
sd_uhs2_execute_tuning()
函数会请求主机控制器进入训练模式,硬件会发送特定的数据模式,并根据接收到的响应来调整内部的采样点,以确保数据能被无误地读取。 - 切换总线模式:训练成功后,驱动会最后一次配置主机控制器,使其正式进入UHS-II LVDS模式进行数据传输。至此,
sd_uhs2.c
的任务完成,后续的数据传输由MMC核心和主机控制器驱动接管。
它的主要优势体现在哪些方面?
- 极致性能:解锁了UHS-II卡的全部性能潜力,使数据传输速率从UHS-I的约100MB/s提升至300MB/s甚至更高。
- 模块化设计:将复杂的UHS-II专用逻辑封装在一个独立的文件中,避免了对通用SD/MMC代码的侵入,使得代码结构更清晰,更易于维护。
- 遵循标准:严格按照SD卡协会发布的官方规范实现,保证了与标准UHS-II卡和主机的兼容性。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 高度依赖主机驱动:
sd_uhs2.c
本身只实现了协议层的逻辑。所有关键操作(如发现序列、训练)最终都必须由底层的主机控制器驱动程序来实现。如果主机驱动没有实现UHS-II所需的回调函数,或者实现有误,那么UHS-II模式将无法启用。 - 调试困难:UHS-II的初始化过程涉及物理层的精确信号时序,一旦出现问题(如训练失败),很难单纯通过软件日志来判断是卡的问题、主机的问题,还是电路板信号完整性的问题。
- 物理层敏感:由于工作频率极高,UHS-II对PCB板的布线质量和信号完整性要求非常苛刻,硬件设计上的瑕疵很容易导致软件层面上的功能不稳定。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
这是支持UHS-II原生速度的唯一标准解决方案。
- 专业媒体工作站:用于为摄影师、摄像师和视频剪辑师提供服务的Linux工作站或笔记本电脑,需要快速地从UHS-II卡中导入大量的RAW照片和高码率视频素材。
- 高性能外置读卡器:基于Linux的嵌入式系统(例如,一个通过USB-C连接的高速多功能读卡器)需要使用此驱动来支持UHS-II卡。
- 数据记录设备:在科学研究或工业监控领域,需要将高速传感器数据实时写入可移动存储介质的设备。
是否有不推荐使用该技术的场景?为什么?
不存在不推荐使用该技术的场景,只有硬件不支持的场景。如果一个系统的硬件(主机控制器和卡槽)支持UHS-II,那么启用sd_uhs2.c
中的逻辑是发挥硬件性能的必要条件。如果硬件不支持,那么这部分代码自然不会被执行,系统会自动回退到UHS-I或更旧的模式。
对比分析
请将其 与 其他相似技术 进行详细对比。
最直接的对比对象是传统的SD/UHS-I协议,其逻辑主要实现在drivers/mmc/core/sd.c
中。
特性 | SD UHS-II (sd_uhs2.c 驱动) |
传统 SD / UHS-I (sd.c 驱动) |
---|---|---|
物理接口 | 两排引脚,使用低电压差分信号(LVDS)进行高速传输。 | 单排引脚,使用单端CMOS信号。 |
初始化方式 | 复杂的物理层握手和发现序列,访问专用的UHS-II寄存器。 | 基于命令-响应的协议,访问标准的SD卡寄存器(CSD, CID等)。 |
总线模式 | 全双工或半双工,使用1或2对差分信号线(Lane)。 | 仅半双工,使用1位或4位数据线。 |
信号训练 | 强制要求,在进入高速模式前必须执行复杂的训练序列。 | 对SDR104等高速模式推荐执行,但过程相对简单(使用CMD19 )。 |
理论最高速率 | FD312模式下可达312 MB/s。 | SDR104模式下最高104 MB/s。 |
实现复杂性 | 高。协议层和物理层的交互非常紧密,对主机驱动要求高。 | 中等。是经过多年发展的成熟技术,逻辑相对直接。 |
向后兼容性 | 完全兼容。UHS-II卡和主机可以无缝回退到UHS-I或更低模式运行。 | N/A |
sdio
include/linux/mmc/sdio.h
SDIO协议核心定义:命令、响应及寄存器映射
本文件是Linux内核中用于支持SDIO(Secure Digital Input/Output)卡的核心头文件。它不包含可执行代码,而是完全由C预处理器宏(#define
)构成。其核心功能是将SDIO物理规范中定义的命令码、寄存器地址、响应格式以及寄存器内的位域(bit fields)等数值,翻译成一组具有明确语义的符号常量。这为上层的SDIO核心逻辑和底层的主机控制器驱动提供了一个标准、可读且与具体数值无关的编程接口,是内核中所有SDIO相关操作的基础。
实现原理分析
该文件的实现原理是典型的“规范到代码”的直接映射,是硬件编程中的一种基础实践。
- 符号化常量: 文件使用
#define
将SDIO规范中定义的魔法数字(Magic Numbers)替换为人类可读的名称。例如,将SDIO的“读写直接命令”的操作码52
定义为SD_IO_RW_DIRECT
。这样做极大地提高了代码的可读性和可维护性,当规范更新时,只需修改一处定义即可。 - 位域抽象: SDIO的寄存器和命令参数通常由多个位域组成。该文件通过位移(
<<
)、掩码(&
)和位或(|
)操作,为这些位域提供了符号化的定义。例如,R5_COM_CRC_ERROR
被定义为(1 << 15)
,代码中可以直接使用这个名称来检查R5响应中的CRC错误位,而无需关心它在16位响应中的具体位置是第15位。 - 结构化组织: 文件内容按照SDIO规范的逻辑层次进行组织,依次定义了命令(Commands)、响应格式(Responses)、公共控制寄存器(CCCR - Card Common Control Registers)和功能基本寄存器(FBR - Function Basic Registers)。这种结构使得开发者可以轻松地找到与规范特定章节对应的定义。
代码分析
SDIO命令定义
1 | /* SDIO commands type argument response */ |
R4与R5响应格式定义
1 | // R4_18V_PRESENT: R4响应中的位24,表示卡接受1.8V信号电压。 |
卡公共控制寄存器 (CCCR) 地址与位域定义
1 | /* |
功能基本寄存器 (FBR) 地址与位域定义
1 | /* |
drivers/mmc/core/sdio_bus.c SDIO总线驱动(SDIO Bus Driver) 为SDIO多功能卡提供独立的设备与驱动绑定模型
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc/core/sdio_bus.c
的诞生是为了解决一个在drivers/mmc/core/bus.c
所建立的主MMC总线模型之上更为精细的问题:如何管理一张物理卡片上的多个独立I/O功能(Functions)。
标准的MMC总线(mmc_bus
)将一整张卡(如SD存储卡)视为一个单一的设备。但这对于SDIO(Secure Digital Input/Output)卡来说是不够的。一张SDIO卡本身不是一个功能设备,而是一个承载多个功能设备的容器。例如,一张典型的“combo”卡可能包含一个Wi-Fi功能和一个蓝牙功能。
如果只使用mmc_bus
,我们就无法做到:
- 将一个Wi-Fi驱动(如
brcmfmac
)只绑定到Wi-Fi功能上。 - 同时将一个蓝牙驱动(如
btbcm
)绑定到蓝牙功能上。
sdio_bus.c
通过创建另一个、更细粒度的**sdio_bus_type
总线**,专门用来管理这些独立的SDIO功能,解决了这个问题。它允许内核将一张物理卡上的每个功能(Function 1, Function 2, …)都看作是一个独立的、可以与特定功能驱动绑定的逻辑设备。
它的发展经历了哪些重要的里程碑或版本迭代?
sdio_bus.c
作为SDIO支持的核心部分,其发展与SDIO规范在Linux中的实现同步进行。
- 基础框架的建立:最重要的里程碑是创建了
sdio_bus_type
,并定义了SDIO功能设备(struct sdio_func
)和SDIO功能驱动(struct sdio_driver
)这两个核心结构。这为SDIO的多功能特性建立了标准的驱动模型。 - CIS解析:完善了对卡信息结构(Card Information Structure, CIS)的解析。CIS是SDIO卡内部的一块存储区域,它像一张“说明书”,描述了这张卡有哪些功能、每个功能的ID(Vendor/Device ID)以及其他属性。
sdio_bus.c
依赖这些信息来创建和识别sdio_func
设备。 - 中断处理:SDIO设备通过DAT1线来产生中断。
sdio_bus.c
参与管理SDIO中断的注册和分发,确保中断能被正确地路由到声明它的那个功能驱动。 - 电源管理集成:为SDIO功能设备增加了电源管理支持,允许系统在不使用某个功能(如蓝牙)时,可以单独将其置于低功耗状态,而保持其他功能(如Wi-Fi)的运行。
目前该技术的社区活跃度和主流应用情况如何?
sdio_bus.c
是MMC子系统中非常成熟和稳定的基础组件。其本身的代码改动很少,但它是所有SDIO功能驱动赖以生存的土壤。社区的活跃度主要体现在不断有新的SDIO功能驱动(特别是各种Wi-Fi和蓝牙芯片的驱动)被开发出来,并注册到sdio_bus
上。
它被广泛应用于:
- 物联网(IoT)设备和开发板。
- 单板计算机(SBC),如早期的树莓派。
- 一些特定的工业和嵌入式设备中,SDIO因其相对简单的硬件接口而被用作连接无线模块的方案。
核心原理与设计
它的核心工作原理是什么?
sdio_bus.c
的工作原理是在主mmc_bus
之上建立一个二级总线。
工作流程(层次递进):
第一层:MMC总线发现SDIO卡
- MMC核心(
core.c
)扫描插槽,发现了一张卡,并通过初始化序列识别出其类型为SDIO卡。 - 主MMC总线(
bus.c
)将这张整卡与一个通用的SDIO卡驱动(drivers/mmc/core/sdio.c
)进行绑定。
- MMC核心(
通用SDIO卡驱动进行功能枚举
sdio.c
驱动的.probe
函数被调用后,它的核心任务不是驱动某个具体功能,而是对这张SDIO卡进行“功能枚举”。- 它会读取卡的CIS信息,了解这张卡总共有多少个I/O功能(例如,Function 1是Wi-Fi,Function 2是蓝牙)。
第二层:SDIO总线注册功能设备
- 对于枚举出的每一个功能,
sdio.c
会调用sdio_add_func()
函数。 sdio_add_func()
是sdio_bus.c
提供的核心API。它会创建一个struct sdio_func
来代表这个单一功能,并将其作为一个设备注册到sdio_bus_type
总线上。
- 对于枚举出的每一个功能,
SDIO功能驱动与设备匹配
- 一个具体的SDIO功能驱动(例如,
drivers/net/wireless/broadcom/brcmfmac/sdio.c
)会通过sdio_register_driver()
将自己注册到sdio_bus
上。它会提供一个设备ID表,声明它支持哪些厂商和设备。 - 当新的
sdio_func
设备被注册时,sdio_bus
的.match
函数会比较设备的ID(从CIS中读取)和驱动支持的ID表。 - 如果匹配成功,该功能驱动的
.probe
函数就会被调用,从而真正地初始化和驱动这个Wi-Fi或蓝牙功能。
- 一个具体的SDIO功能驱动(例如,
它的主要优势体现在哪些方面?
- 精细的粒度:它将一个物理上的多功能设备,成功地解构成多个逻辑上独立的设备,使得驱动模型可以一一对应。
- 高度模块化:Wi-Fi驱动和蓝牙驱动可以完全独立开发,它们都面向标准的
sdio_func
接口,彼此之间没有任何依赖,即使它们物理上在同一块芯片上。 - 遵循标准:完全遵循了Linux设备模型的层次化思想,结构清晰。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
- 性能非最优:SDIO总线协议本身相较于现代的PCIe或USB,其带宽和延迟性能都较差,这限制了其上设备(特别是高性能Wi-Fi)的性能上限。这不是
sdio_bus.c
代码的劣势,而是其服务的协议本身的局限性。 - 协议复杂性:SDIO的初始化、中断和多功能协商机制比简单的存储卡要复杂,这给驱动开发和调试带来了一定的挑战。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
它是Linux内核中处理SDIO卡的唯一且标准的解决方案。
- Wi-Fi/蓝牙 Combo卡:这是最经典的使用场景。一张SDIO Combo卡插入后,
sdio_bus
会创建两个sdio_func
设备。一个与Wi-Fi驱动匹配,在系统中生成一个wlan0
网络接口;另一个与蓝牙驱动匹配,生成一个hci0
蓝牙设备。 - 独立的SDIO外设:例如SDIO接口的GPS模块、FM收音机模块、NFC模块等。每种模块都有其对应的功能驱动,通过
sdio_bus
进行绑定。
是否有不推荐使用该技术的场景?为什么?
此技术是高度特化的。
- 不用于存储卡:对于纯粹的SD/eMMC存储卡,应该使用主
mmc_bus
和card/block.c
驱动,sdio_bus
完全不参与。 - 不用于其他总线设备:它只服务于SDIO协议,不能用于USB、PCIe等其他类型的设备。
对比分析
请将其 与 其他支持多功能设备的总线进行详细对比。
sdio_bus
与USB总线是进行对比的绝佳范例,因为两者都原生支持复杂的、一个物理设备包含多个逻辑功能的场景。
特性 | SDIO Bus (sdio_bus.c ) |
USB Bus |
---|---|---|
物理层 | 基于SD卡的并行总线(CMD, CLK, DAT0-3)。 | 高速差分串行总线(D+/D-)。 |
逻辑设备抽象 | 功能 (Function),由struct sdio_func 表示。 |
接口 (Interface),由struct usb_interface 表示。一个设备可以有多个配置,每个配置可以有多个接口。 |
设备枚举方式 | 读取**CIS (Card Information Structure)**元组链来发现功能及其属性。 | 读取一系列标准化的描述符 (Descriptors),如设备、配置、接口、端点描述符。 |
驱动匹配依据 | SDIO Vendor ID, Device ID (定义在SDIO规范中)。 | USB Vendor ID, Product ID, 以及更通用的Class/SubClass/Protocol码。 |
驱动绑定对象 | struct sdio_driver 绑定到 struct sdio_func 。 |
struct usb_driver 绑定到 struct usb_interface 。 |
架构总结 | 在mmc_bus 之上的二级总线,专门处理多功能I/O。 |
一个统一的总线,其协议原生支持复杂的树状拓扑和多接口设备。 |
结论:sdio_bus.c
和USB总线核心在设计思想上殊途同归:它们都为多功能设备提供了一个“容器(设备)与内容(功能/接口)”分离的驱动模型。其根本区别源于底层物理总线和协议规范的巨大差异,但它们都成功地将复杂的物理设备分解为简单的、可独立驱动的逻辑单元,这是现代操作系统驱动框架的共同特征。
SDIO总线类型实现:SDIO功能设备与驱动的匹配与管理
本代码片段完整地定义了Linux内核中sdio
总线的行为。其核心功能是为SDIO卡上的功能(Function,如WiFi、蓝牙、GPS等)提供一个独立的总线类型。它实现了SDIO功能设备与相应驱动程序的匹配逻辑、生命周期管理(probe/remove)、sysfs属性导出以及uevent事件生成,从而允许用户空间自动加载驱动模块。这是所有SDIO设备能在Linux下正常工作的基础。
实现原理分析
与mmc
总线类似,sdio
总线也通过实例化一个bus_type
结构体(sdio_bus_type
)来融入Linux设备模型,但其行为针对SDIO协议的特点进行了定制。
Sysfs属性自动化: 代码使用了
sdio_config_attr
和sdio_info_attr
两个宏来批量生成sysfs属性文件。这些宏极大地简化了代码,为每个SDIO功能设备在sysfs中自动创建了如class
,vendor
,device
,revision
等只读文件。其中,modalias
属性尤为重要,它以标准格式(sdio:cXXvYYYYdZZZZ
)输出了设备的ID信息,是udev
进行模块自动加载的依据。设备与驱动的匹配 (
sdio_bus_match
): 这是总线的核心逻辑之一。当一个新SDIO功能设备或新SDIO驱动被注册时,设备模型核心会调用sdio_bus_match
。- 该函数会获取驱动程序中定义的
id_table
,这是一个sdio_device_id
数组。 - 它会遍历这个ID表,并使用
sdio_match_one
函数将表中的每一项与SDIO功能设备的class
,vendor
,device
ID进行比较。 id_table
支持SDIO_ANY_ID
作为通配符,增加了匹配的灵活性。- 只要找到一个匹配的ID,
sdio_bus_match
就返回1,表示匹配成功,设备模型将继续调用sdio_bus_probe
。
- 该函数会获取驱动程序中定义的
Uevent事件生成 (
sdio_bus_uevent
): 当一个SDIO功能设备注册后,此函数被调用。它将SDIO功能的详细ID信息(SDIO_CLASS
,SDIO_ID
等)打包成环境变量,并通过uevent机制发送到用户空间。其中最关键的是MODALIAS
变量,其格式与sysfs中的modalias
属性完全一致。用户空间的udev
守护进程会捕获此事件,并根据MODALIAS
的值自动执行modprobe
命令,加载正确的内核驱动模块。Probe/Remove流程:
sdio_bus_probe
: 在匹配成功后被调用。它首先进行了一些必要的准备工作,包括处理电源管理(通过pm_runtime_get_sync
确保设备上电)和设置一个默认的块大小。然后,它才调用具体SDIO驱动自己的probe
函数,将控制权移交给驱动。该函数有完善的错误处理机制,如果在任何步骤失败,都会回滚之前的操作(如释放电源管理引用)。sdio_bus_remove
: 在设备移除或驱动卸载时被调用。它同样首先确保设备处于上电状态,以便驱动可以安全地访问硬件寄存器进行清理。然后调用具体驱动的remove
函数,并执行与probe
相反的清理操作,如释放电源管理引用。
代码分析
Sysfs属性定义
1 | /* 定义一个宏,用于快速生成读取SDIO配置字段的sysfs属性 */ |
设备与驱动匹配逻辑
1 | // sdio_match_one: 比较单个SDIO设备ID与一个SDIO功能设备。 |
总线回调函数 (Uevent, Probe, Remove)
1 | // sdio_bus_uevent: SDIO总线的uevent事件生成函数。 |
总线类型定义与注册
1 | // 定义SDIO总线的电源管理操作集。 |
drivers/mmc/core/sdio_io.c SDIO I/O操作(SDIO I/O Operations) SDIO功能驱动的底层通信API
历史与背景
这项技术是为了解决什么特定问题而诞生的?
drivers/mmc/core/sdio_io.c
的存在是为了给SDIO功能驱动(Function Drivers)提供一个稳定、标准且抽象的I/O接口。当一个Wi-Fi或蓝牙驱动需要与物理SDIO芯片通信时,它不应该关心如何手动构建复杂的SDIO命令(如CMD52、CMD53),也不应该直接操作MMC核心层的数据结构。
sdio_io.c
解决了以下核心问题:
- 抽象协议复杂性:SDIO协议使用特定的命令(CMD52用于读写单个字节,CMD53用于读写数据块)来进行I/O操作。这些命令的参数和格式都比较复杂。
sdio_io.c
将这些复杂的命令封装成简单直观的API,如sdio_readb()
(读字节)或sdio_memcpy_toio()
(写数据块)。 - 代码复用:如果没有这个文件,那么每一个SDIO功能驱动(如
brcmfmac
for Broadcom Wi-Fi,mwifiex
for Marvell Wi-Fi等)都需要自己实现一遍发送CMD52和CMD53的逻辑。sdio_io.c
将这些公共的、重复性的代码提取出来,形成一个共享库。 - 提供原子操作和总线仲裁:SDIO总线是共享资源,尤其是在一张卡上有多个功能(如Wi-Fi和蓝牙)时。
sdio_io.c
与总线锁定机制(sdio_claim_host
)紧密配合,确保一个功能驱动在执行一系列I/O操作时不会被另一个功能驱动打断,保证了操作的原子性。
简而言之,sdio_io.c
是连接上层SDIO功能驱动和下层MMC核心协议层之间的关键API实现层。
它的发展经历了哪些重要的里程碑或版本迭代?
sdio_io.c
作为SDIO子系统最基础的部分,其本身相对稳定。它的发展主要体现在功能的增强和性能的优化上:
- 基础API的建立:定义了一整套核心的I/O函数,包括单字节、双字节、四字节的读写,以及块数据的读写。
- 性能优化:针对
CMD53
的块模式(Block Mode)和字节模式(Byte Mode)进行了优化。对于支持块模式的卡和主机,使用块模式可以显著提高数据吞吐率。 - 错误处理的完善:增强了对各种I/O错误的检测和处理,向上层驱动返回更明确的错误码。
- 异步I/O的讨论:虽然当前主流API是同步阻塞的,但社区曾讨论过为SDIO引入异步I/O接口,以适应更高性能的需求,但这会显著增加驱动模型的复杂性。
目前该技术的社区活跃度和主流应用情况如何?
sdio_io.c
是SDIO驱动栈的基石,代码非常成熟和稳定。它的“活跃度”体现在它被所有的SDIO功能驱动持续不断地调用。任何对SDIO的性能调优或bug修复都可能触及此文件。它是所有使用SDIO接口的无线模块、GPS模块等设备在Linux下正常工作的底层保障。
核心原理与设计
它的核心工作原理是什么?
sdio_io.c
的核心原理是将高级的、面向功能的I/O请求转换成低级的、面向总线协议的MMC请求。
工作流程:
- API调用:一个SDIO功能驱动(例如Wi-Fi驱动)想要读取芯片上的一个寄存器,它会调用
sdio_readb(func, addr, &err_ret)
。 - 总线锁定:在功能驱动中,所有对
sdio_io.c
中函数的调用都必须被sdio_claim_host(func)
和sdio_release_host(func)
包围。这确保了在执行I/O期间,该功能独占MMC主机控制器。 - 命令构建:
sdio_readb()
函数内部会:- 创建一个
struct mmc_command
结构体。 - 设置其操作码
opcode
为SD_IO_RW_DIRECT
(CMD52)。 - 根据函数参数(读/写方向、功能编号、寄存器地址、数据)填充
mmc_command
的arg
字段。
- 创建一个
- 请求提交:该函数会调用
mmc_wait_for_cmd()
,将构建好的命令传递给MMC核心层。 - 核心层处理:MMC核心层(
core.c
)接收到这个命令后,通过host.c
定义的接口将其发送给具体的主机控制器驱动,由硬件执行。 - 结果返回:命令执行完毕后,结果(读取到的数据或错误码)会沿着调用链返回,最终
sdio_readb()
将读取到的字节返回给上层驱动。
对于块数据传输(如sdio_memcpy_toio
),流程类似,但构建的是一个包含SD_IO_RW_EXTENDED
(CMD53)命令和关联数据(struct mmc_data
)的struct mmc_request
,并通过mmc_wait_for_req()
提交。
它的主要优势体現在哪些方面?
- 简单易用:为驱动开发者提供了非常直观的API,隐藏了所有底层协议的细节。
- 健壮性:集中的实现确保了对SDIO命令的构建和错误处理是正确和一致的。
- 解耦:将功能驱动与MMC核心彻底分离开,功能驱动只需关心自己的设备逻辑,而无需了解MMC总线的工作方式。
它存在哪些已知的劣劣势、局限性或在特定场景下的不适用性?
- 同步阻塞模型:
sdio_io.c
提供的大部分API都是同步的,即调用会一直阻塞直到I/O操作完成。在高IOPS(每秒I/O操作次数)的场景下,这可能会成为性能瓶颈,因为驱动无法在等待I/O时执行其他任务。 - 粗粒度锁定:
sdio_claim_host()
的锁定粒度是整个MMC主机。如果一个功能(如Wi-Fi)正在进行一次耗时较长的块传输,另一个功能(如蓝牙)的紧急、低延迟的I/O请求也必须等待,可能会影响其实时性。这是SDIO协议本身的特性,而非sdio_io.c
的缺陷。
使用场景
在哪些具体的业务或技术场景下,它是首选解决方案?
它是SDIO功能驱动进行I/O操作的唯一且强制的解决方案。
- 读写控制寄存器:Wi-Fi驱动在初始化时,需要通过
sdio_writeb()
/sdio_writel()
向芯片写入一系列配置值来启动固件、设置MAC地址等。 - 收发数据:Wi-Fi驱动通过
sdio_memcpy_toio()
将一个网络数据包(sk_buff
)写入芯片的发送FIFO;通过sdio_memcpy_fromio()
从接收FIFO中读取数据包。 - 中断处理:在中断处理函数中,驱动通常会调用
sdio_readb()
来读取中断状态寄存器,以确定中断发生的原因。
是否有不推荐使用该技术的场景?为什么?
不存在。任何在Linux内核中编写SDIO功能驱动的开发者都必须使用sdio_io.c
提供的API。绕过它直接与MMC核心交互是违反驱动模型设计的,会导致代码不可移植且极不稳定。
对比分析
请将其 与 其他总线的底层I/O实现进行详细对比。
sdio_io.c
的实现可以与USB、PCIe等总线的底层I/O机制进行对比,以突显其设计特点。
特性 | SDIO I/O (sdio_io.c ) |
USB I/O (Core & HCD) | PCI/PCIe I/O (MMIO) |
---|---|---|---|
访问模型 | 命令-响应模型。所有I/O都通过发送特定命令(CMD52/53)来完成。 | 请求块模型。通过提交异步的USB请求块(URB)来描述传输,支持四种传输类型(控制、批量、中断、同步)。 | 内存映射模型。设备寄存器被映射到CPU的物理地址空间,驱动像读写内存一样通过ioread/iowrite 系列函数来访问。 |
抽象层次 | 中等。API封装了命令,但开发者仍需了解寄存器地址和数据格式。 | 高。URB模型完全抽象了底层的USB事务和包。 | 低。驱动直接与“内存地址”交互,非常接近硬件。 |
同步/异步 | 主要是同步阻塞。 | 主要是异步非阻塞(通过完成回调)。 | 同步阻塞。ioread/iowrite 是阻塞操作。 |
并发控制 | 粗粒度总线锁 (sdio_claim_host )。 |
细粒度端点队列。由主机控制器硬件和驱动软件调度多个端点的并发传输。 | 细粒度寄存器锁。驱动开发者需要使用自旋锁等机制来保护对共享寄存器或数据结构的并发访问。 |
数据单位 | 字节、字、数据块(可以是字节流或固定大小的块)。 | 端点(Endpoint),每个端点有其固定的传输类型和最大包大小。 | 字节、字、双字、四字等,与CPU的内存访问粒度一致。 |
总结:sdio_io.c
提供了一个介于高层抽象(如USB URB)和底层直接访问(如PCIe MMIO)之间的I/O模型。它通过命令封装提供了必要的抽象,简化了驱动开发,同时其同步模型和粗粒度锁也反映了SDIO总线本身相对简单的设计。
SDIO驱动核心API:总线访问与参数配置
本代码片段提供了SDIO设备驱动程序与硬件交互所必需的三个核心API函数:sdio_claim_host
、sdio_release_host
和sdio_set_block_size
。它们共同构成了SDIO驱动进行I/O操作的基础:claim
/release
函数用于管理对底层物理总线的独占访问权,而set_block_size
则用于配置数据传输的基本单元(块大小),这是优化性能和满足设备要求的关键步骤。
实现原理分析
这段代码体现了Linux内核驱动模型中的分层设计思想,将SDIO协议的特定逻辑建立在通用的MMC/SD总线核心之上。
总线访问的封装与委托:
sdio_claim_host
和sdio_release_host
是典型的封装函数。SDIO协议是MMC/SD协议的扩展,它们共享同一条物理总线和同一个主机控制器。为了避免并发访问冲突(例如,文件系统正在读写SD卡内存部分,而SDIO驱动同时要访问WLAN功能),必须对主机控制器进行锁定。这两个函数将SDIO驱动开发者关心的“声明/释放SDIO功能总线”这一高级概念,直接委托给底层的MMC核心函数mmc_claim_host
和mmc_release_host
来执行。它们通过func->card->host
这个指针链找到了代表物理控制器的mmc_host
结构体,从而实现了逻辑上的分层和代码上的复用。通过CMD52进行寄存器配置:
sdio_set_block_size
函数展示了如何通过SDIO协议配置功能参数。SDIO标准定义了一组通用的寄存器空间,称为功能基本寄存器(Function Basic Registers, FBR)。- 该函数的目标是向指定功能(
func->num
)的FBR中写入16位的块大小值。 - 它没有使用复杂的块传输命令,而是调用了
mmc_io_rw_direct
。这个底层函数会生成SDIO的CMD52命令,这是一种专门用于读/写单个字节I/O寄存器的简单命令。 - 函数分两次调用
mmc_io_rw_direct
,第一次写入块大小的低8位(LSB),第二次写入高8位(MSB),从而完成了对16位寄存器的设置。 - 函数还包含了健全性检查(块大小不能超过主机能力)和默认值计算逻辑(在不指定大小时,选择一个对主机和设备都最优的尺寸,但不超过512字节)。
- 该函数的目标是向指定功能(
代码分析
1 | // sdio_claim_host: 为一个指定的SDIO功能设备独占性地声明总线。 |
drivers/mmc/core/sdio_ops.h
1 | static inline bool sdio_is_io_busy(u32 opcode, u32 arg) |
drivers/mmc/core/sdio_ops.c
SDIO直接I/O命令(CMD52)的实现
本代码片段是Linux内核MMC/SDIO子系统中执行底层I/O操作的核心。它实现了mmc_io_rw_direct
函数,该函数的功能是构建并发送SDIO协议中定义的CMD52命令。CMD52是一种基础命令,用于对SDIO卡上特定功能(Function)的I/O寄存器空间进行单字节的读或写操作。这是所有更高级SDIO驱动(如WiFi、蓝牙)配置和控制硬件的基础。
实现原理分析
该函数的实现遵循SDIO协议规范,将一个高级的读/写请求精确地转换为一个32位的命令参数,并通过底层的主机控制器驱动发送出去。
参数到命令的转换 (
mmc_io_rw_direct_host
):- 命令结构体: 函数首先创建一个
mmc_command
结构体,这是向底层主机驱动传递命令请求的标准格式。 - 操作码:
cmd.opcode
被设置为SD_IO_RW_DIRECT
,其标准值为52。 - 参数构建: CMD52的所有信息都编码在32位的
cmd.arg
中。该函数按照SDIO规范逐位构建此参数:Bit 31
(方向):write ? 0x80000000 : 0
,写操作时置1,读操作时为0。Bits 28-30
(功能号):fn << 28
,指定要操作的功能(0-7)。Bit 27
(读后写标志):(write && out) ? 0x08000000 : 0
,用于写操作,表示写完后是否需要把寄存器的值读回来。Bits 9-25
(寄存器地址):addr << 9
,要访问的17位寄存器地址。Bits 0-7
(写数据):in
,对于写操作,这里是待写入的数据字节。
- 响应类型:
cmd.flags
被设置为MMC_RSP_R5
,表示期望卡返回一个R5类型的响应。R5是SDIO命令特有的响应格式。
- 命令结构体: 函数首先创建一个
命令执行与等待:
mmc_wait_for_cmd(host, &cmd, 0)
是将构建好的命令发送出去的关键。它会调用注册在mmc_host
中的操作函数集(host->ops->request
),将命令传递给具体的主机控制器驱动(如STM32的SDMMC驱动)。此函数会阻塞等待,直到命令完成(或超时)。响应解析:
- 命令完成后,硬件返回的R5响应被存放在
cmd.resp[0]
中。 - 代码会解析R5响应中的状态位,如
R5_ERROR
(通用错误)、R5_FUNCTION_NUMBER
(功能号错误)、R5_OUT_OF_RANGE
(地址越界),以判断操作是否成功。 - 数据提取: 对于读操作,R5响应的低8位(
cmd.resp[0] & 0xFF
)包含了从卡上读取到的数据字节。这个字节被赋给调用者提供的*out
指针。
- 命令完成后,硬件返回的R5响应被存放在
封装 (
mmc_io_rw_direct
): 这是一个简单的内联包裹函数,它从mmc_card
结构体中提取出mmc_host
,然后调用核心实现函数mmc_io_rw_direct_host
,为上层驱动提供了更方便的接口。
代码分析
1 | // mmc_io_rw_direct_host: 执行SDIO CMD52的核心实现。 |