2. Linux 设备模型基础:总线、设备、驱动模型,platform 总线与设备驱动
Linux 内核从 2.6 版本开始引入了统一的设备驱动模型(Device Driver Model),旨在将系统中的硬件设备、驱动程序和通信路径抽象为标准的软件对象,从而简化驱动开发、电源管理和用户空间接口。本章将深入剖析该模型的核心三要素——总线(Bus)、设备(Device)和驱动(Driver),并重点讲解嵌入式开发中最常用的 platform 总线及其驱动框架。
2.1 设备模型三要素
Linux 设备模型将硬件拓扑抽象为三个核心对象,它们在内核中通过数据结构相互关联,形成一棵以 struct device 为节点的树形结构。
| 要素 | 内核数据结构 | 职责描述 |
|---|---|---|
| 总线(Bus) | struct bus_type |
定义设备与驱动的匹配规则、热插拔操作、电源管理回调;是设备与驱动之间的桥梁。 |
| 设备(Device) | struct device |
代表一个物理或虚拟硬件实体,包含资源信息(如 I/O 地址、中断号)、电源状态、父子关系。 |
| 驱动(Driver) | struct device_driver |
封装操作硬件的具体代码,提供 probe()、remove()、suspend()、resume() 等接口。 |
核心机制:当内核注册一个新设备或新驱动时,总线会遍历其维护的设备链表和驱动链表,调用匹配函数(
match())进行配对。配对成功后,总线调用驱动的 probe() 方法,驱动开始初始化硬件并注册子设备。
下图展示了设备模型中三要素的协作关系:
2.2 总线匹配与 probe 流程
设备与驱动的匹配是设备模型的核心动作。当系统启动或热插拔事件发生时,总线按照以下步骤完成绑定:
- 设备注册:总线将新设备加入设备链表,并遍历驱动链表调用
match()。 - 驱动注册:总线将新驱动加入驱动链表,并遍历设备链表调用
match()。 - 匹配判断:
match()函数根据总线类型返回布尔值。例如 PCI 总线匹配 VID/PID,USB 匹配 VID/PID/接口类,platform 总线匹配设备树compatible属性或设备名。 - 绑定与 probe:匹配成功后,总线调用驱动的
probe()方法,驱动获得设备资源指针,开始初始化硬件。 - 解绑与 remove:设备移除或驱动卸载时,总线调用驱动的
remove()方法释放资源。
关键点:一个驱动可以支持多个设备(通过
of_match_table 或 id_table),一个设备只能绑定一个驱动。匹配失败时,设备保持“孤儿”状态,不会产生任何操作。
2.3 platform 总线:嵌入式系统的虚拟总线
在嵌入式系统中,大量设备(如 GPIO 按键、LED、I2C 控制器、SPI 控制器)并不挂载在 PCI、USB 等可枚举总线上。Linux 为此引入了 platform 总线(platform_bus_type),它是一种虚拟总线,用于管理那些 直接由 CPU 内存映射或通过板级代码描述 的设备。
| 特性 | platform 总线 | PCI / USB 总线 |
|---|---|---|
| 硬件发现方式 | 静态描述(设备树 / ACPI / 板级文件) | 自动枚举(配置空间 / 描述符) |
| 匹配依据 | compatible 属性、设备名 |
VID / PID / 类代码 |
| 资源传递 | struct resource 数组(I/O、中断、DMA) |
PCI BAR、USB endpoint |
| 典型应用 | GPIO、UART、SPI、I2C 控制器、看门狗 | 网卡、显卡、USB 外设 |
2.4 platform 设备与驱动核心数据结构
platform 体系在通用设备模型基础上增加了两层封装:
struct platform_device:继承struct device,增加struct resource *resource(资源数组)和int num_resources。struct platform_driver:继承struct device_driver,增加probe()、remove()、shutdown()等回调,以及struct of_device_id *of_match_table(设备树匹配表)。
以下是一个典型的 platform 驱动框架代码:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
/* 设备树匹配表 */
static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "vendor,my-gpio-key" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
/* probe 函数:驱动与设备匹配成功后调用 */
static int my_probe(struct platform_device *pdev)
{
struct resource *res;
int irq;
pr_info("my_driver: probe called\n");
/* 获取 I/O 内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
/* 获取中断资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 硬件初始化代码 */
return 0;
}
/* remove 函数:驱动卸载或设备移除时调用 */
static int my_remove(struct platform_device *pdev)
{
pr_info("my_driver: remove called\n");
return 0;
}
/* platform_driver 结构体 */
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "my_gpio_key",
.of_match_table = my_driver_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Expert");
MODULE_DESCRIPTION("Platform driver example for GPIO key");
注意事项:
of_match_table必须与设备树中节点的compatible属性完全一致,包括大小写和逗号。module_platform_driver()宏展开为module_init()和module_exit(),自动处理注册与注销。- 资源获取应使用
platform_get_resource()系列函数,避免直接访问pdev->resource。
2.5 设备树与 platform 设备的绑定流程
在设备树使能的内核中,platform 设备的创建和注册由内核在启动阶段自动完成。下图展示了从设备树节点到 platform 设备实例的完整链路:
2.6 关键 API 速查表
以下是在 platform 驱动开发中高频使用的内核 API:
| API 函数 | 所属头文件 | 功能说明 |
|---|---|---|
platform_get_resource() |
linux/platform_device.h |
获取指定类型和索引的资源(I/O、内存、DMA) |
platform_get_irq() |
linux/platform_device.h |
获取中断号,返回值为 int |
devm_platform_ioremap_resource() |
linux/platform_device.h |
获取 I/O 资源并建立内存映射,自动管理生命周期 |
of_match_node() |
linux/of.h |
在 of_match_table 中查找匹配的 compatible 条目 |
module_platform_driver() |
linux/platform_device.h |
宏,展开为 module_init / module_exit 注册 platform_driver |
最佳实践:推荐使用
devm_ 系列资源管理函数(如 devm_platform_ioremap_resource、devm_request_irq),它们会在驱动卸载或 probe 失败时自动释放资源,避免内存泄漏和中断残留。
2.7 本章小结
- Linux 设备模型通过 总线、设备、驱动 三个核心对象实现硬件与软件的松耦合。
- platform 总线 是嵌入式 Linux 中最常用的虚拟总线,适用于非可枚举设备。
- 设备树中的节点在系统启动时被解析为
platform_device,并通过compatible属性与platform_driver匹配。 - 驱动开发者只需实现
probe()和remove()回调,并在of_match_table中声明支持的设备。 - 掌握
platform_get_resource()和devm_系列 API 是编写健壮驱动的基础。