1. Platform驱动概述
Platform驱动(平台驱动)是Linux内核中用于管理非可枚举、非热插拔设备的核心机制。在嵌入式系统中,大量设备(如UART、I2C控制器、SPI控制器、GPIO控制器)直接集成在SoC内部,无法通过PCIe、USB等总线自动发现。Platform驱动模型为这类设备提供了统一的总线抽象、资源管理和驱动绑定框架。
核心认知: Platform驱动并非操作物理总线,而是内核软件层的一种“虚拟总线”模型,用于将设备与驱动进行匹配,并传递硬件资源(内存地址、中断号、DMA通道等)。
1.1 Platform总线模型
Platform总线是Linux设备驱动模型(LDDM)中的一条虚拟总线,注册为 platform_bus_type。其核心数据结构与匹配流程如下:
| 组件 | 数据结构 | 职责 |
|---|---|---|
| 总线 | struct bus_type platform_bus_type |
维护设备链表、驱动链表,执行匹配与探测 |
| 设备 | struct platform_device |
描述硬件设备实例,包含资源数组(IORESOURCE_MEM、IORESOURCE_IRQ等) |
| 驱动 | struct platform_driver |
实现 probe()、remove() 回调,绑定设备后驱动硬件 |
| 匹配方式 | of_match_table / id_table |
设备树匹配(主流)或传统ID表匹配 |
匹配优先级: 内核首先尝试设备树(DT)匹配,即比较驱动
of_match_table 中的 compatible 字符串与设备节点中的 compatible 属性;若失败,则回退到 id_table 名称匹配。
下图展示了Platform总线模型中设备、驱动与总线之间的三角关系及匹配流程:
1.2 Platform驱动与设备树的关系
在现代化嵌入式Linux开发中,设备树(Device Tree,DT)已成为描述硬件信息的标准方式。Platform驱动通过设备树获取硬件资源,实现了“驱动代码与硬件配置解耦”。
设备树中的每个节点对应一个 platform_device,驱动通过 of_match_table 声明自己支持的设备。关键映射关系如下:
| 设备树属性 | Platform驱动获取方式 | 说明 |
|---|---|---|
compatible = "vendor,device" |
of_match_table 匹配 |
驱动与设备绑定的核心标识 |
reg = <0x10000000 0x1000> |
platform_get_resource(pdev, IORESOURCE_MEM, 0) |
获取内存基地址与范围 |
interrupts = <0 31 4> |
platform_get_resource(pdev, IORESOURCE_IRQ, 0) |
获取中断号与触发类型 |
dmas = <&dma0 2> |
dma_request_slave_channel(dev, "rx") |
获取DMA通道(通过DMA引擎API) |
clocks = <&clk 5> |
clk_get(dev, NULL) |
获取时钟句柄 |
常见陷阱: 设备树中的
reg 属性顺序必须与驱动中 platform_get_resource 的索引一一对应。如果设备树定义了多个 reg 区域,驱动必须按索引0、1、2…依次获取,否则将访问到错误的地址空间。
下图展示了设备树节点如何映射到Platform驱动中的资源结构:
1.3 驱动开发基本流程
开发一个完整的Platform驱动通常遵循以下步骤。以UART控制器驱动为例:
- 定义设备树节点:在dts文件中添加硬件描述,包括compatible、reg、interrupts、dmas等属性。
- 实现驱动入口与出口:使用
module_platform_driver()宏简化注册。 - 实现probe函数:获取资源、映射寄存器、注册中断、初始化DMA、注册字符设备或框架接口。
- 实现remove函数:反向释放probe中申请的所有资源。
- 实现操作函数集:根据设备类型实现
file_operations、uart_ops等回调。 - 编译与测试:将驱动编译为模块或内置,加载后验证设备节点创建与功能。
最佳实践: 始终在
probe() 中使用 devm_* 系列API(如 devm_ioremap_resource、devm_request_irq),这些API会在设备注销时自动释放资源,避免内存泄漏和中断残留。
下面是一个典型的Platform驱动框架代码:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
static int my_driver_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *base;
int irq;
/* 获取内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
/* 获取中断资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 注册中断 */
devm_request_irq(&pdev->dev, irq, my_isr, 0,
dev_name(&pdev->dev), dev);
dev_info(&pdev->dev, "driver probed successfully\n");
return 0;
}
static int my_driver_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "driver removed\n");
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "vendor,my-device" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_device",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Platform driver example");
下表总结了驱动开发各阶段的关键动作与注意事项:
| 阶段 | 关键动作 | 常见错误 |
|---|---|---|
| 设备树编写 | 确保compatible字符串与驱动一致;reg地址范围正确 | 地址重叠、大小写不匹配、缺少中断父节点 |
| probe实现 | 使用devm_* API;检查所有资源获取返回值 | 忘记检查IS_ERR()、资源索引越界 |
| remove实现 | 释放所有非devm资源;注销设备接口 | 遗漏释放、顺序错误导致内核崩溃 |
| 匹配表声明 | 使用MODULE_DEVICE_TABLE导出;末尾加sentinel | 缺少终止项、未导出导致模块无法自动加载 |
调试技巧: 在
probe() 中打印 pdev->dev.of_node->full_name 可以确认设备树节点是否被正确匹配。使用 ls /sys/bus/platform/devices/ 查看所有已注册的platform设备。