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 表
匹配流程: 当设备注册到总线时,总线遍历已注册的驱动列表,调用 match() 函数;若匹配成功,则调用驱动的 probe() 方法,将设备与驱动绑定。驱动卸载或设备移除时,调用 remove() 解绑。

4.2 设备、总线、驱动的内核对象关系

下图展示了设备驱动模型中三要素在内核中的层次关系,以及它们如何通过 kobject 挂接到 sysfs 文件系统:

设备驱动模型三要素内核对象关系 bus_type (总线) devices 链表 drivers 链表 device (实例1) device (实例2) driver (实例1) ... match() → probe() 绑定
注意: 设备与驱动通过总线匹配后,驱动中的 probe() 函数会获得一个 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 的完整匹配流程:

platform 驱动匹配与 probe 流程 ① 注册 platform_driver ② 注册 platform_device ③ 总线 match() ④ 调用 driver 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 属性比对。
最佳实践: 新平台优先使用设备树描述硬件,避免编写板级 C 文件注册 platform_device。设备树使驱动与硬件描述完全分离,同一驱动可无修改支持不同板卡。

4.5 本章小结

Linux 设备驱动模型通过设备、总线、驱动的三层抽象,实现了硬件与驱动的解耦,极大提升了代码复用性。platform 驱动框架作为嵌入式 SoC 内部设备的标配方案,是每个嵌入式 Linux 开发者必须掌握的核心技能。理解 probe/remove 生命周期、资源获取 API 以及设备树匹配机制,是后续编写 GPIO、I2C、SPI 等具体设备驱动的基础。