4. Linux 设备驱动模型:设备、总线、驱动、platform驱动框架
Linux 内核从 2.6 版本开始引入了统一的设备驱动模型(Device Driver Model),旨在解决嵌入式系统中设备种类繁多、总线类型各异、驱动与设备耦合过紧的问题。该模型将硬件资源抽象为 设备(Device)、总线(Bus)、驱动(Driver) 三个核心实体,并通过内核对象(kobject)与 sysfs 文件系统将它们组织成层次化的拓扑结构。
4.1 设备驱动模型三要素
下表总结了设备、总线、驱动在内核中的数据结构、职责与匹配方式:
| 要素 | 内核数据结构 | 核心职责 | 匹配依据 |
|---|---|---|---|
| 设备 (Device) | struct device |
描述硬件实体:基地址、中断号、时钟、DMA 通道等资源 | 设备名、设备树节点 compatible 属性、ACPI ID |
| 总线 (Bus) | struct bus_type |
管理设备与驱动的注册、匹配、热插拔;提供 sysfs 视图 | 总线类型(platform、I2C、SPI、PCI 等) |
| 驱动 (Driver) | struct device_driver |
实现 probe()/remove() 回调,完成硬件初始化与控制逻辑 | 驱动支持的 compatible 列表或 ID 表 |
4.2 设备、总线、驱动的内核对象关系
下图展示了设备驱动模型中三要素在内核中的层次关系,以及它们如何通过 kobject 挂接到 sysfs 文件系统:
struct device 指针,从中提取硬件资源(如 platform_get_resource())。切勿在 probe() 中硬编码地址或中断号,否则将失去设备模型带来的可移植性优势。
4.3 platform 驱动框架
在嵌入式系统中,大量设备(如 GPIO 控制器、UART、I2C 适配器、LCD 控制器)并不挂接在传统的 PCI、USB 等可枚举总线上,而是直接集成在 SoC 内部,通过内存映射寄存器访问。Linux 为此引入了 platform 总线(虚拟总线),对应的驱动框架称为 platform 驱动。
4.3.1 platform 总线特点
- 虚拟总线: 不依赖物理总线枚举,设备信息通过板级代码或设备树静态描述。
- 自动匹配: 内核根据设备名或设备树 compatible 属性自动匹配驱动。
- 资源管理: 提供
platform_get_resource()系列 API,统一获取 IO 内存、中断、DMA 等资源。 - 电源管理: 支持系统休眠/唤醒时的回调,便于嵌入式低功耗设计。
4.3.2 platform 驱动核心数据结构
编写 platform 驱动主要涉及以下两个结构体:
| 结构体 | 关键字段 | 说明 |
|---|---|---|
struct platform_driver |
probe、remove、shutdown、suspend、resume、driver |
驱动核心,注册到 platform 总线;driver.name 用于匹配设备 |
struct platform_device |
name、id、num_resources、resource、dev |
描述设备实例,通常由板级代码或设备树生成 |
4.3.3 platform 驱动匹配流程
下图展示了从设备注册到驱动 probe 的完整匹配流程:
4.3.4 最小 platform 驱动代码示例
以下是一个完整的 platform 驱动模板,演示了 probe/remove 回调、资源获取与模块注册:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/io.h>
#include <linux/err.h>
static int my_platform_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *reg_base;
int irq;
pr_info("my_platform: probe called for device %s\n", pdev->name);
/* 获取 IO 内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "failed to get MEM resource\n");
return -ENXIO;
}
/* 请求并映射 IO 内存 */
reg_base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(reg_base))
return PTR_ERR(reg_base);
/* 获取中断资源 */
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "failed to get IRQ resource\n");
return irq;
}
/* 在此处进行硬件初始化、注册中断、创建设备文件等操作 */
pr_info("my_platform: reg_base=0x%p, irq=%d\n", reg_base, irq);
return 0;
}
static int my_platform_remove(struct platform_device *pdev)
{
pr_info("my_platform: remove called for device %s\n", pdev->name);
/* 清理工作:注销中断、释放内存等 */
return 0;
}
/* 匹配表:用于设备树或 ID 匹配 */
static const struct of_device_id my_platform_of_match[] = {
{ .compatible = "vendor,my-device" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);
static struct platform_driver my_platform_driver = {
.probe = my_platform_probe,
.remove = my_platform_remove,
.driver = {
.name = "my_platform",
.of_match_table = my_platform_of_match,
},
};
module_platform_driver(my_platform_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Embedded Developer");
MODULE_DESCRIPTION("Minimal Platform Driver Example");
module_platform_driver()宏展开为 module_init/module_exit,自动注册/注销驱动。devm_ioremap_resource()是 managed 版本,自动处理内存释放,避免资源泄漏。of_match_table用于设备树匹配,驱动名driver.name用于板级代码匹配。- probe() 返回 0 表示成功,返回负值表示失败,内核不会绑定该设备。
4.4 设备树与 platform 设备
在设备树(Device Tree)流行的嵌入式平台(ARM、RISC-V、龙芯等),platform 设备通常由设备树节点自动生成。内核在启动时解析 DTB,为每个 compatible 匹配的节点创建 struct platform_device,并注册到 platform 总线。
设备树节点示例:
/ {
my_device: my-device@f0000000 {
compatible = "vendor,my-device";
reg = <0x0 0xf0000000 0x0 0x1000>;
interrupts = <0 42 4>;
status = "okay";
};
};
内核解析该节点后,自动生成:
- name: 取节点名 "my-device" 或 "my_device"(取决于内核版本)。
- resource: 根据 reg 属性生成 IORESOURCE_MEM 资源;根据 interrupts 生成 IORESOURCE_IRQ 资源。
- 匹配: 驱动中的 of_match_table 与节点 compatible 属性比对。
4.5 本章小结
Linux 设备驱动模型通过设备、总线、驱动的三层抽象,实现了硬件与驱动的解耦,极大提升了代码复用性。platform 驱动框架作为嵌入式 SoC 内部设备的标配方案,是每个嵌入式 Linux 开发者必须掌握的核心技能。理解 probe/remove 生命周期、资源获取 API 以及设备树匹配机制,是后续编写 GPIO、I2C、SPI 等具体设备驱动的基础。