ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux 驱动研究 —— SDIO (5)

Linux 驱动研究 —— SDIO (5) 1. 计算机体系“总线”与 Linux 驱动“总线”1.1 硬件电路上的“总线”1.2 内核驱动架构中的“总线”1.3 两者的映射关系与本质区别1.4 狭义共享总线与广义 Linux 驱动总线的区别1.4.1 计算机系统结构中对总线的严格定义1.4.2 物理接口泛化为 bus_type1.5 物理可枚举总线 与 平台总线1.5.1 热插拔总线的动态硬件枚举特性1.5.2 缺乏标准发现机制的设备与平台总线2. SDIO 设备的 VID/PID 不写进设备树2.1 根源SDIO 的可枚举总线属性2.2 设备树的职责边界与设计哲学2.3 SDIO 设备的正确匹配机制2.4 SDIO 在设备树中真正应该配置的内容3. SDIO 控制器设备树配置规范引言在前几篇中我们分析了底层物理寻址、状态机切换、寄存器解析以及“一主多子”的 SDIO 设备驱动模型。在此基础之上本篇将进入第五篇探讨 Linux 驱动模型哲学与 SDIO 设备树DTS配置规范。在第三章中我们重点关注的是主机控制器Host Controller侧研究对象SoC 内部的硬件单元如 NXP i.MX6ULL 的 eSDHC 控制器。核心内容硬件寄存器基地址reg、中断号interrupts、时钟和引脚配置。匹配机制平台总线Platform Bus使用platform_device和platform_driver完成驱动匹配。而第五篇则将视角转向SDIO 总线与外设/卡槽侧研究对象挂载在 SDIO 总线上的具体外设或卡槽属性例如板载焊接的 Wi-Fi 模块。核心内容非热插拔属性non-removable、总线宽度bus-width 4、唤醒引脚wifi-gpios以及上下电时序的 DTS 配置规范。匹配机制SDIO 总线 / MMC 总线模型。简而言之第三章解决的是“主机Host如何通过设备树驱动起来”的问题而第五篇解决的是“SDIO 总线与外设在设备树中的高级配置规范”问题。1. 计算机体系“总线”与 Linux 驱动“总线”在讨论 SDIO 设备树配置以及驱动匹配之前必须先理清一个贯穿整个 Linux 内核设备驱动模型的根本概念硬件电路上的“总线”与Linux 内核驱动架构中的软件“总线”。这两个词虽然在日常开发中经常混用但在本质上属于两个完全不同的维度。1.1 硬件电路上的“总线”在计算机体系结构和硬件设计中总线Hardware Bus指的是一组物理连接线路、传输协议和电气规范。物理形态它是 PCB 板上的铜箔走线、芯片引脚或者是 SoC 内部的 AHB、APB 总线矩阵。核心职责负责在不同的硬件组件如 CPU、内存、外设控制器、SDIO 卡之间传输电信号、时钟信号、命令与数据。典型代表I2C 总线、SPI 总线、PCIe 总线以及我们深入剖析的 SDIO 总线。在硬件层面SDIO 总线规定了 CMD、CLK 以及 D0~D3 数据线的电气特性和时序。1.2 内核驱动架构中的“总线”而在 Linux 内核源码如drivers/base/bus.c中软件总线Software Bus是一个纯粹的软件抽象概念在内核中表现为struct bus_type结构体。数据结构它不传输任何电流或电信号而是一个管理容器和媒合平台。它内部维护了两条关键链表一条挂载着该总线上发现的所有设备struct device另一条挂载着所有注册的驱动struct device_driver。核心职责实现设备与驱动的“盲盒匹配”。当一个新的设备或驱动注册到总线时总线会调用其内置的match()回调函数。如果发现设备和驱动的暗号如设备的 Device ID 或设备树中的compatible属性契合总线就会拉红线执行驱动的probe()函数将它们绑定在一起。典型代表平台总线platform_bus_type、I2C 总线类型、SPI 总线类型、以及SDIO 总线类型sdio_bus_type。1.3 两者的映射关系与本质区别理解两者的关联与鸿沟是掌握 Linux 驱动模型的关键维度硬件电路上的“总线”Linux 内核驱动中的“总线” (struct bus_type)本质属性物理实体电线、接口、时序、协议软件抽象C 语言结构体、链表、匹配算法存在空间硬件电路板与 SoC 芯片内部Linux 内核内存空间与虚拟文件系统 (sysfs)核心作用搬运物理数据与电信号管理设备与驱动的生命周期、实现解耦与匹配它们之间的桥梁软件总线struct bus_type是对硬件总线在软件层面的建模与映射。Linux 内核通过软件总线抽象出一套标准的接口使得无论底层是哪种硬件总线其上挂载的设备与驱动都能通过统一的“注册—匹配—探测”流程运转起来。1.4 狭义共享总线与广义 Linux 驱动总线的区别为了更彻底地看清软硬件视角的差异我们需要进一步将“总线”这一概念拆解为硬件架构层面的狭义共享总线与 Linux 驱动层面的广义软件总线。1.4.1 计算机系统结构中对总线的严格定义在计算机体系结构和硬件设计中“总线Bus”是一个具有严格物理约束的概念。它的狭义定义通常具备以下三个核心特征物理介质的共享性Shared Medium总线是一组由多方共享的物理信号线如数据线、地址线、控制线。电信号在同一条物理走线上广播或双向流通所有挂载在总线上的设备都通过这组公共物理通道与控制器交互。多设备挂载能力Multi-Drop / Multi-Master一条物理总线上可以挂载多个从设备Slave甚至支持多个主设备Master通过仲裁机制轮流控制总线。例如在一个 I2C 总线上可以同时挂载温度传感器、EEPROM 和时钟芯片。严格的底层协议与时序标准Standard Protocols硬件总线定义了严苛的电气规范、时钟频率、寻址方式以及仲裁机制如 I2C 的起始/停止位与 ACK 应答、SPI 的片选极性、SDIO 的命令响应包格式。任何接入该总线的硬件必须严格遵守这一套时序电平标准。1.4.2 物理接口泛化为 bus_type然而当我们把视线转移到 Linux 内核源码中时会发现内核对“总线”的定义被极大程度地泛化与抽象了。在 Linux 驱动模型中struct bus_type代表的软件总线早已超越了“多设备共享物理电线”的狭义范畴超越物理形态的纯软件抽象在 Linux 眼中总线不一定非要是多设备共享的物理线路。例如平台总线Platform Bus在硬件上根本不存在一条名为“platform”的物理总线它只是一条 SoC 内部总线架构如 AMBA/AHB/APB在软件层面的虚拟映射和抽象。将一切连接关系统一为“总线—设备—驱动”模型无论底层的物理接口是真正的多设备共享总线如 I2C、SPI、SDIO还是点对点的独立连接甚至是没有总线概念的 SoC 内部设备Linux 内核都强行将它们泛化为一种统一的bus_type架构。解耦物理传输与软件管理的哲学通过这种泛化设计Linux 实现了驱动架构的高度统一无论底层硬件怎么变内核管理设备生命周期、触发probe探测、实现驱动与设备匹配的代码逻辑是完全复用的。bus_type抹平了硬件物理形态的差异为上层驱动提供了一套标准化、面向对象的软件操作界面。1.5 物理可枚举总线 与 平台总线在前文中我们了解了软件总线对硬件的泛化抽象。但在实际的 Linux 内核设计中根据设备能否在运行时被动态发现所有类型的总线又可以清晰地划分为两大阵营物理可枚举总线与Platform 虚拟总线。1.5.1 热插拔总线的动态硬件枚举特性像SDIO、USB、PCIe这样的总线属于典型的物理可枚举总线Enumerable Bus。它们的核心特征在于硬件层面自带标准的“自我介绍”与发现机制动态探测与实时响应当外设被插入插槽时硬件层面如 SDIO 的 CD 引脚或总线电平变化会触发中断通知主机控制器启动扫描流程如前面章节剖析过的mmc_rescan。无需静态配置外设身份主机控制器在检测到物理连接后无需依靠设备树硬编码去猜测挂了什么芯片而是直接通过标准的总线命令去读取外设内部的身份寄存器例如 SDIO 的 CCCR 与 CIS、PCIe 的 Configuration Space。运行时的动态生命周期管理设备可以在系统开机后随时热插拔。内核总线核心Bus Core会在运行时动态创建或销毁对应的struct device节点并自动在总线的驱动链表中寻找匹配项完成probe。1.5.2 缺乏标准发现机制的设备与平台总线内核中存在大量不具备物理可枚举特性的设备。这类设备主要分为两类SoC 内部无标准协议的控制器Platform 虚拟总线范畴如 UART、I2C 控制器、中断控制器以及第三章讨论的usdhc1SDIO 主机控制器。它们直接挂载在 CPU 的内存映射总线上Memory-Mapped IO缺乏自动汇报身份的机制因此必须依赖平台总线与静态设备树DTS来配置其物理地址与中断号。外挂但缺乏自举能力的物理总线设备如 SPI / I2C 从设备SPI 或 I2C 属于物理总线在软件上拥有独立的总线类型如spi_bus_type、i2c_bus_type但其底层协议未规定标准的自我介绍寄存器。当 SPI Flash 等设备挂载在总线上时控制器无法通过自动识别机制获取其信息。因此这类外设的处理方式与 Platform 设备相同无法动态热插拔和自主发现必须完全依赖静态设备树DTS在对应控制器的节点下配置属性如compatible、reg片选号等再由内核解析并注册到对应的软件总线上。核心概念辨析控制器层与外设层的解耦I2C 和 SPI 在软件架构中拥有独立的软件总线如i2c_bus_type但仍需依赖平台总线与设备树进行配置。这是因为 Linux 内核将此类总线严格拆分为两个软件层级控制器层Adapte/Masterr与外设层Device。整个初始化与挂载过程遵循以下步骤第一步SoC 内部控制器的平台总线初始化在 SoC 内部I2C 或 SPI 主机控制器硬件属于 CPU 内存映射空间中的寄存器集合不具备自举枚举能力。内核需编写平台驱动platform_driver使其挂载在平台总线platform_bus_type上。该平台驱动在执行probe()时读取设备树DTS中的物理地址reg和中断号interrupts完成硬件控制器的初始化。第二步创建专用的软件总线主控制器的平台驱动probe成功后在内核中注册 I2C 适配器I2C Adapter或 SPI 主控制器SPI Master。此操作在内核中动态创建并激活对应的软件总线如i2c_bus_type或spi_bus_type。第三步通过设备树指定外设并完成绑定专用软件总线建立后内核解析设备树中该控制器节点下挂载的子节点如传感器sensor68或 SPI Flash。内核将子节点包装为设备结构体如i2c_client或spi_device并挂载到对应软件总线的设备链表上。外部从设备的驱动如i2c_driver通过总线的match机制与设备完成绑定。SPI 与 I2C 总线在 Linux 内核中的匹配机制可以划分为两个独立且衔接的层次控制器层主机侧SPI 主控制器或 I2C 适配器本身属于 SoC 内部的内存映射硬件单元缺乏自举枚举能力。它们被封装为平台设备platform_device通过平台总线Platform Bus与对应的平台驱动platform_driver完成匹配和初始化。从设备层外设侧当控制器驱动初始化成功并建立专用的软件总线后会解析设备树中的子节点将其包装为从设备如spi_device或i2c_client。这些从设备会注册到各自的专用总线上如spi_bus_type或i2c_bus_type并通过该专用总线的机制与外设驱动完成匹配。总结平台总线与专用总线的核心分工总线层级管理对象依赖方式典型代表平台总线 (Platform Bus)SoC 内部的根基与控制器本身依赖设备树硬编码指定基地址和中断eSDHC 控制器、I2C 控制器、SPI 控制器专用总线 (i2c_bus_type/spi_bus_type)外部扩展的从设备与外设依赖其父级控制器通过平台总线完成初始化后由设备树静态指定子节点温湿度传感器、EEPROM、SPI Flash2. SDIO 设备的 VID/PID 不写进设备树在嵌入式 Linux 开发中部分开发者在配置板载 SDIO 外设例如常见的 Wi-Fi 模块时容易产生一种惯性思维既然 I2C 或 SPI 从设备需要在设备树中使用compatible属性静态指定身份那么 SDIO 设备作为一种具体的芯片其厂商 IDVID和产品 IDPID也应当硬编码在设备树中。这种做法违背了 Linux 驱动模型的底层设计原则。在标准的 Linux 内核架构中绝不应该在设备树中硬编码 SDIO 设备的 VID、PID 或具体的设备身份信息。2.1 根源SDIO 的可枚举总线属性这一规范的根本原因在于 SDIO 属于物理可枚举总线Enumerable Bus。与 I2C、SPI 等缺乏自举能力的不可枚举总线不同SDIO 协议在硬件层面规定了一套完整的标准自举机制硬件自发现流程当 SDIO 主机控制器上电并完成初始化后主机会主动向总线发送标准命令如 CMD5、CMD52 等与外设进行交互。寄存器上报身份外设内部包含标准的配置寄存器空间如 CCCR、FBR 以及 CIS 结构。主机通过读取这些寄存器能够直接获取外设的制造商 IDManufacturer ID、卡 IDCard ID以及功能支持情况。因此SDIO 设备的身份是由硬件在运行时实时上报的而不是由软件在启动前静态指定的。2.2 设备树的职责边界与设计哲学设备树DTS在 Linux 内核中的核心职责是描述无法被硬件自主发现的静态硬件拓扑例如 SoC 内部的控制器、内存映射地址、中断号以及无法自举的 I2C/SPI 从设备。对于 SDIO 这种具备动态枚举能力的物理总线如果在设备树中硬编码设备的 VID 和 PID不仅多此一举还会破坏内核总线子系统的动态发现逻辑。内核的 SDIO 核心层SDIO Core的设计目标就是将总线上的动态扫描和设备注册完全自动化。2.3 SDIO 设备的正确匹配机制在标准 SDIO 驱动模型中设备与驱动的匹配完全依靠软件层面的动态协商动态枚举阶段内核的mmc_rescan线程检测到物理连接后通过 SDIO 协议读取外设的 VID 和 PID在内存中动态创建sdio_func结构体。驱动内建表匹配SDIO 驱动程序不依赖设备树中的compatible属性而是在代码内部定义一个sdio_device_id结构体数组明确声明该驱动支持哪些具体的 VID 和 PID。总线媒合SDIO 软件总线sdio_bus_type将动态枚举出的设备信息与驱动程序中的sdio_device_id表进行比对。一旦吻合立即触发驱动的probe()函数完成绑定。2.4 SDIO 在设备树中真正应该配置的内容虽然 SDIO 设备的 VID/PID 不能写入设备树但设备树在 SDIO 子系统中依然发挥着关键作用。其配置核心集中在主机控制器Host Controller侧以及卡槽板级属性上non-removable向内核声明该 SDIO 接口连接的是板载固定的外设如焊接的 Wi-Fi 芯片而不是可插拔的 SD 卡从而跳过反复检测插拔状态的逻辑。bus-width指定总线宽度例如配置为bus-width 4使用 4 位数据线传输。电源与控制引脚配置外设所需的上下电时序、复位引脚reset-gpios或唤醒引脚wifi-gpios。3. SDIO 控制器设备树配置规范在明确了 SDIO 设备的身份不由设备树决定之后设备树的核心职责转为对SDIO 主机控制器Host Controller及其板级物理接口进行正确配置。以下是一个典型的 SoC 内置 SDIO 主机控制器以 NXP i.MX6ULL 的 usdhc 控制器为例在设备树中的标准配置范例usdhc1 { pinctrl-names default, state_100mhz, state_200mhz; pinctrl-0 pinctrl_usdhc1; pinctrl-1 pinctrl_usdhc1_100mhz; pinctrl-2 pinctrl_usdhc1_200mhz; bus-width 4; non-removable; keep-power-in-suspend; vmmc-supply reg_wifi_3v3; wifi-reset-gpios gpio5 7 GPIO_ACTIVE_LOW; status okay; };核心配置属性解析pinctrl-*引脚控制配置设定控制器对应的物理引脚复用与电气属性如驱动能力、压摆率。SDIO 接口必须正确配置时钟线CLK、命令线CMD以及数据线D0~D3的底层引脚寄存器。bus-width总线宽度指定数据总线的传输位宽。配置为4表示使用 4 位数据线D0、D1、D2、D3部分高速或大容量接口支持 8 位。non-removable非热插拔属性声明当前控制器连接的是板载固定的外设如焊接在 PCB 上的 Wi-Fi/蓝牙模组而不是标准的 SD 卡插槽。内核读取该属性后会跳过周期性的硬件插拔检测轮询Card Detect直接在初始化时触发一次总线扫描。keep-power-in-suspend挂起时保持供电针对板载无线模块的电源管理策略。确保系统进入睡眠状态时不会切断 SDIO 模块的供电从而保障系统的网络连接状态或支持休眠唤醒。vmmc-supply电源稳压器关联内核 Regulator 子系统指定该 SDIO 接口所使用的外部供电电源如 3.3V 稳压器用于控制控制器的上下电时序。wifi-reset-gpios/ 板级控制引脚多数板载 SDIO 无线芯片除了标准的总线信号外还需要额外的 GPIO 来控制硬件复位或使能。此类引脚通过 DTS 传入驱动程序中在初始化流程中由软件按时序拉高或拉低。
返回列表