[toc]
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,deviceID进行比较。 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功能驱动(如
brcmfmacfor Broadcom Wi-Fi,mwifiexfor 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的核心实现。 |










