4. 设备驱动模型

Linux 设备驱动模型的核心思想是“设备与驱动分离”:设备负责描述“硬件在哪里、有什么资源”,驱动负责描述“如何操作这类硬件”。总线(bus)作为中间层,负责把设备与驱动撮合到一起。当匹配成功时,内核调用驱动的 probe 回调完成初始化;当设备或驱动被移除时,调用 remove 回调完成清理。理解这条主线,就能看懂 platform、i2c、spi、PCI 四类总线的注册与匹配差异。

一、核心原理:设备、驱动、总线三者的关系

在 sysfs 中,每一条总线对应 /sys/bus/<busname>/ 目录,下面通常有 devices/ 和 drivers/ 两个子目录。设备注册后出现在 devices/,驱动注册后出现在 drivers/。总线通过自身的 match 函数判断某个 device 与某个 driver 是否匹配;匹配成功后,内核调用 driver_probe_device(),最终进入驱动的 probe 函数。

四类总线的关键差异在于“匹配依据”和“设备信息来源”:

总线类型 设备注册方式 匹配依据 典型 probe 入参
platform platform_device_register / 设备树展开 name 字符串或 of_match_table struct platform_device *
i2c i2c_new_client_device / 设备树展开 i2c_device_id 或 of_match_table struct i2c_client *, i2c_device_id *
spi spi_new_device / 设备树展开 spi_device_id 或 of_match_table struct spi_device *
PCI 由 PCI 枚举自动创建 vendor/device ID 或 class struct pci_dev *, pci_device_id *

二、注册与匹配流程

以 platform 为例,注册流程可以概括为:设备侧调用 platform_device_register() 把设备挂到 platform 总线上;驱动侧用 module_platform_driver() 注册 platform_driver。总线遍历双方,用 name 或设备树 compatible 进行匹配。匹配成功后,内核先执行 really_probe(),再调用驱动的 probe()。i2c 与 spi 的流程类似,只是设备通常由控制器枚举或设备树生成,匹配依据换成地址加 ID 表。PCI 设备由内核枚举总线时自动创建,驱动通过 pci_register_driver() 注册,匹配依据是 vendor/device ID。

/* platform 驱动注册与匹配的典型骨架 */
static int my_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    dev_info(&pdev->dev, "probe ok, base=%p\n", base);
    return 0;
}

static int my_remove(struct platform_device *pdev)
{
    dev_info(&pdev->dev, "remove\n");
    return 0;
}

static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    { }
};
MODULE_DEVICE_TABLE(of, my_of_match);

static struct platform_driver my_driver = {
    .probe  = my_probe,
    .remove = my_remove,
    .driver = {
        .name = "my-device",
        .of_match_table = my_of_match,
    },
};
module_platform_driver(my_driver);

三、probe 与 remove 机制

probe 是驱动生命周期的起点,职责通常包括:获取资源(寄存器、中断、时钟、GPIO、DMA)、初始化硬件、注册字符设备或输入设备、申请中断、创建 sysfs 属性。remove 是逆过程:释放中断、注销设备、关闭时钟、释放内存。现代驱动推荐使用 devm_* 系列接口,让资源与 struct device 绑定,remove 时自动回收,减少遗漏。

probe 可能被调用多次:设备重新绑定驱动、驱动模块重新加载、设备树 overlay 应用时都会触发。因此 probe 必须可重入、可重复执行,不能假设只运行一次。
常见误区:在 probe 中直接使用全局变量保存设备指针,导致多实例设备互相覆盖;或在 remove 中忘记释放中断,造成卸载模块后系统仍收到中断。应优先使用 devm_request_irq() 和 devm_kzalloc()。

四、案例与排障场景

假设一块开发板上有一颗 I2C 温度传感器,设备树节点如下:

&i2c1 {
    status = "okay";
    temp@48 {
        compatible = "vendor,tmp102";
        reg = <0x48>;
    };
};

驱动中定义 of_device_id 匹配 vendor,tmp102,并在 probe 中调用 i2c_smbus_read_word_data() 读取温度。若 probe 未执行,可按以下顺序排查:

  1. 确认设备树节点状态为 okay,且 I2C 控制器已使能。
  2. 查看 /sys/bus/i2c/devices/ 下是否出现 1-0048,若无则设备未注册成功。
  3. 查看 /sys/bus/i2c/drivers/ 下驱动目录是否绑定到该设备。
  4. 用 dmesg | grep -i tmp102 查看 probe 返回值和错误码。
  5. 用 i2cdetect -y 1 确认硬件地址是否应答。

对于 PCI 设备,可用 lspci -nn 查看 vendor/device ID,用 lspci -k 查看当前绑定驱动。若驱动未绑定,检查 pci_device_id 表是否包含该 ID,以及驱动是否被 blacklist。

设备驱动模型:注册 → 匹配 → probe → remove 设备侧 Device platform_device_register i2c_new_client_device spi_new_device PCI 枚举自动创建 驱动侧 Driver module_platform_driver i2c_add_driver spi_register_driver pci_register_driver 总线 Bus match() 匹配函数 name / of_match_table i2c_device_id / spi_device_id pci_device_id (vendor/device) 匹配成功 → really_probe() 调用 driver->probe(device) probe() 典型职责 • 获取寄存器 / 中断 / 时钟 / GPIO • 初始化硬件,注册字符设备 • 申请中断,创建 sysfs 属性 • 使用 devm_* 自动管理资源 remove() 典型职责 • 释放中断,注销设备节点 • 关闭时钟,释放 DMA 通道 • 回收内存,删除 sysfs 属性 • devm_* 资源由内核自动回收 设备与驱动解绑 → remove → 资源释放

掌握这条主线后,再看任何具体总线驱动,都可以用同一套问题去拆解:设备从哪里来?匹配依据是什么?probe 里申请了哪些资源?remove 是否对称释放?把这四点确认清楚,驱动的注册与匹配流程就不再神秘。