1. Platform驱动概述
在Linux内核驱动模型中,Platform驱动(Platform Driver)是一种不依赖具体硬件总线(如PCI、USB、I2C、SPI)的虚拟总线驱动框架。它通过内核提供的“Platform总线”将设备与驱动进行匹配,是嵌入式Linux系统中最常用的驱动类型之一。
1.1 什么是Platform驱动
Platform驱动是Linux内核中一种基于虚拟总线的驱动模型。其核心思想是:将系统中无法挂载到物理总线上的设备(如片上外设、内存映射设备、中断控制器等)抽象为“Platform设备”,并通过统一的Platform总线完成设备与驱动的绑定。
关键特征: Platform驱动不依赖硬件枚举机制(如PCI的VID/PID),而是通过名称匹配或设备树兼容字符串完成驱动与设备的绑定。
| 对比项 | Platform驱动 | PCI/USB驱动 |
|---|---|---|
| 总线类型 | 虚拟Platform总线 | 物理硬件总线 |
| 设备发现方式 | 设备树/静态注册 | 硬件枚举(VID/PID) |
| 典型应用场景 | SoC内部外设、内存映射设备 | 扩展卡、外接设备 |
| 资源描述 | 设备树节点或platform_data | PCI配置空间/USB描述符 |
1.2 为什么需要Platform驱动
嵌入式系统中存在大量非总线可枚举的硬件设备,例如:
- SoC内部外设: GPIO控制器、定时器、看门狗、RTC、DMA控制器等
- 内存映射设备: 直接映射到CPU地址空间的FPGA逻辑、显示控制器等
- 中断控制器: GIC(通用中断控制器)、级联中断控制器等
- 时钟与电源管理单元: 时钟发生器、PLL、电源域控制器等
这些设备不具备PCI/USB那样的硬件枚举能力,无法通过VID/PID自动发现。Platform驱动提供了以下核心价值:
| 需求 | Platform驱动的解决方案 |
|---|---|
| 统一驱动模型 | 将非总线设备纳入Linux设备驱动模型(LDM),复用driver/core框架 |
| 资源解耦 | 通过设备树或platform_data将硬件资源与驱动代码分离,提高可移植性 |
| 电源管理 | 提供标准的suspend/resume回调,支持系统级电源管理 |
| 热插拔模拟 | 支持设备动态注册/注销,便于模块化开发和调试 |
| 多实例支持 | 同一驱动可绑定多个同类型设备实例,通过设备ID区分 |
实践建议: 在嵌入式Linux开发中,90%以上的设备驱动都可以基于Platform驱动框架实现。即使设备挂载在I2C/SPI总线上,其控制器本身通常也是Platform设备。
1.3 Platform驱动与设备树的关联
设备树(Device Tree)是Platform驱动获取硬件资源描述的核心数据源。在引入设备树之前,Platform驱动依赖C语言结构体(platform_data)传递硬件信息,这种方式存在以下问题:
- 硬编码: 硬件资源(地址、中断号)写在C代码中,修改需重新编译内核
- 单板耦合: 不同硬件平台需要维护不同的板级文件(board-*.c)
- 维护困难: 内核版本升级时,板级文件需要同步修改
设备树解决了上述问题,其与Platform驱动的协作流程如下:
- 设备树描述硬件: 在.dts文件中定义设备节点,包含reg(地址)、interrupts(中断)、clocks(时钟)等属性
- 内核解析设备树: 启动时,内核将设备树二进制(DTB)解析为device_node结构体链表
- Platform设备创建: 内核为每个匹配的device_node创建platform_device实例
- 驱动匹配与Probe: Platform驱动通过of_match_table与设备树节点的compatible属性匹配,匹配成功后调用probe函数
- 资源获取: 在probe函数中,驱动通过platform_get_resource、of_property_read_*等API获取设备树中的资源
常见误区: Platform驱动并不必须依赖设备树。在没有设备树的旧内核中,Platform驱动可以通过platform_data结构体传递资源。但在现代嵌入式Linux(ARM64、RISC-V等架构)中,设备树已成为标准配置,建议所有新开发都基于设备树实现。
1.4 设备树中的Platform设备节点示例
以下是一个典型的设备树节点定义,描述了一个UART控制器:
/* 设备树节点:描述UART控制器硬件资源 */
uart0: serial@10000000 {
compatible = "vendor,uart-ip-v1", "simple-bus";
reg = <0x10000000 0x1000>; /* 寄存器基地址 + 范围 */
interrupts = <0 33 4>; /* 中断号:GIC SPI 33, 上升沿触发 */
clocks = <&clk_uart0>; /* 时钟句柄 */
clock-names = "uart_clk"; /* 时钟名称 */
pinctrl-names = "default"; /* 引脚复用状态 */
pinctrl-0 = <&uart0_pins>;
status = "okay"; /* 设备使能 */
};
对应的Platform驱动probe函数中,资源获取方式如下:
static int uart_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
/* 1. 获取寄存器地址资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
/* 2. 获取中断号 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 3. 获取时钟资源(设备树属性) */
clk = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(clk))
return PTR_ERR(clk);
/* 4. 注册字符设备或tty驱动 */
// ... 初始化逻辑 ...
return 0;
}
/* 匹配表:与设备树compatible属性对应 */
static const struct of_device_id uart_of_match[] = {
{ .compatible = "vendor,uart-ip-v1", },
{ /* 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);
核心要点: 设备树中的
compatible属性与驱动中的of_match_table是Platform驱动与设备树之间的匹配桥梁。驱动通过platform_get_resource、devm_clk_get、device_property_read_*等API从设备树节点中获取硬件资源,实现驱动代码与硬件描述的完全解耦。
1.5 本章小结
| 知识点 | 核心内容 |
|---|---|
| Platform驱动定义 | 基于虚拟Platform总线的驱动模型,用于非总线可枚举设备 |
| 存在意义 | 统一驱动模型、资源解耦、电源管理、多实例支持 |
| 与设备树关联 | 设备树提供硬件资源描述,驱动通过of_match_table匹配并获取资源 |
| 核心API | platform_get_resource、devm_ioremap_resource、platform_get_irq、devm_clk_get |