Linux platform驱动开发与设备树资源适配 - 第2节
2.1 Platform 总线的作用与优势
在 Linux 内核中,Platform 总线(也称“虚拟平台总线”)是一种用于连接那些不依赖硬件发现机制(如 PCI、USB 的枚举)的设备的软件抽象层。它主要服务于 SoC 内部集成的外设、内存映射设备以及传统总线(如 I2C、SPI)上的控制器。
2.1.1 为什么需要 Platform 总线?
传统总线(如 PCI)具备硬件枚举能力:设备插入后自动分配资源并触发驱动匹配。然而,嵌入式 SoC 中的许多设备(如 GPIO 控制器、DMA 引擎、时钟控制器)不具备这种能力。Platform 总线提供了一种统一的机制,让驱动开发者能够以标准方式注册设备、匹配驱动,并传递硬件资源(如中断号、内存地址)。
2.1.2 Platform 总线的优势
| 优势 | 说明 |
|---|---|
| 统一设备模型 | 所有设备都挂载在同一个设备树上,便于电源管理、sysfs 导出和热插拔模拟。 |
| 资源传递标准化 | 通过 struct resource 传递内存、中断、DMA 通道,无需驱动自行解析硬件寄存器。 |
| 驱动与设备解耦 | 驱动只需关注匹配表和 probe 逻辑,设备信息可从代码、设备树或 ACPI 中获取。 |
| 跨平台可移植 | 同一驱动可运行于 x86、ARM、RISC-V 等架构,只需更换设备描述文件(如 DTS)。 |
| 热插拔模拟 | 支持通过 sysfs 手动绑定/解绑驱动,便于调试和运行时重配置。 |
2.2 Platform_device 与 Platform_driver 结构体
Platform 总线的核心数据结构是 platform_device 和 platform_driver。它们分别代表“设备”和“驱动”,通过总线上的匹配机制建立连接。
2.2.1 platform_device 结构体
该结构体描述一个平台设备的硬件属性,包括名称、资源列表、设备私有数据等。通常在板级文件或设备树中定义,内核启动时动态创建。
struct platform_device {
const char *name; // 设备名称,用于驱动匹配
int id; // 设备实例 ID,-1 表示单实例
struct device dev; // 内嵌通用设备结构体
u32 num_resources; // 资源数量
struct resource *resource; // 指向资源数组的指针
const struct platform_device_id *id_entry; // 匹配到的 ID 表项
/* ... 其他字段 */
};
2.2.2 platform_driver 结构体
驱动开发者主要填充该结构体,实现 probe(探测)、remove(移除)、电源管理回调等操作。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*resume)(struct platform_device *);
struct device_driver driver; // 内嵌通用驱动结构体
const struct platform_device_id *id_table; // 匹配 ID 表
};
driver.name 或 id_table 与设备的 name 字段进行字符串匹配。匹配成功后,内核调用驱动的 probe 函数。
2.2.3 匹配流程与生命周期
下图展示了 Platform 总线如何将设备与驱动连接起来:
2.3 Platform 总线与设备树的关联
设备树(Device Tree)是描述硬件拓扑的扁平数据结构,它替代了传统的板级文件(board-*.c),成为 ARM、RISC-V 等架构的主流硬件描述方式。Platform 总线与设备树的结合,实现了“硬件描述”与“驱动代码”的彻底分离。
2.3.1 设备树节点到 platform_device 的转换
内核在启动时解析设备树(.dtb 文件),为每个 compatible 属性匹配的节点创建一个 platform_device。转换过程如下:
- 解析 DTB:内核调用
unflatten_device_tree()将二进制 DTB 展开为device_node树。 - 创建 platform_device:遍历
device_node,对每个非简单总线(如 I2C、SPI)的节点,调用of_platform_device_create()生成platform_device。 - 资源提取:从节点
reg、interrupts、dmas等属性中提取struct resource,填充到platform_device中。 - 注册到总线:将生成的
platform_device添加到 Platform 总线,触发匹配流程。
2.3.2 设备树中的典型节点示例
以下是一个 UART 控制器的设备树节点,它将被转换为一个 platform_device:
/ {
soc {
uart0: serial@10000000 {
compatible = "vendor,uart-v1";
reg = <0x10000000 0x1000>;
interrupts = <33>;
clocks = <&clk_uart>;
status = "okay";
};
};
};
| 设备树属性 | 转换后的 platform_device 字段 | 说明 |
|---|---|---|
compatible = "vendor,uart-v1" |
name 或 id_table 匹配依据 |
驱动通过 of_match_table 匹配该字符串 |
reg = <0x10000000 0x1000> |
resource[0] (IORESOURCE_MEM) |
指定寄存器基地址和范围 |
interrupts = <33> |
resource[1] (IORESOURCE_IRQ) |
指定中断号 |
clocks |
通过 clk API 获取,不直接映射为 resource | 驱动在 probe 中调用 clk_get() 获取 |
2.3.3 驱动中的设备树匹配
驱动开发者使用 of_match_table 来声明支持的设备树兼容性字符串:
static const struct of_device_id uart_of_match[] = {
{ .compatible = "vendor,uart-v1", },
{ .compatible = "vendor,uart-v2", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, uart_of_match);
static struct platform_driver uart_driver = {
.probe = uart_probe,
.remove = uart_remove,
.driver = {
.name = "vendor-uart",
.of_match_table = uart_of_match,
},
};
module_platform_driver(uart_driver);
driver.name 而未提供 of_match_table,则设备树节点必须包含 device_type = "vendor-uart" 才能匹配。现代内核推荐始终使用 of_match_table 进行设备树匹配。
2.3.4 设备树与 Platform 总线的协作关系
下图展示了从设备树源文件到驱动 probe 的完整数据流:
2.4 本章小结
- Platform 总线是 Linux 内核为“不可枚举”设备提供的虚拟总线,实现了设备与驱动的标准化匹配。
- platform_device 描述硬件资源(内存、中断),platform_driver 实现驱动逻辑,两者通过名称或 ID 表匹配。
- 设备树是硬件描述的标准语言,内核将其节点自动转换为
platform_device,驱动通过of_match_table匹配兼容性字符串。 - 三者结合实现了“一处驱动,多平台复用”的嵌入式开发最佳实践。