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 时自动回收,减少遗漏。
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 未执行,可按以下顺序排查:
- 确认设备树节点状态为
okay,且 I2C 控制器已使能。 - 查看
/sys/bus/i2c/devices/下是否出现1-0048,若无则设备未注册成功。 - 查看
/sys/bus/i2c/drivers/下驱动目录是否绑定到该设备。 - 用
dmesg | grep -i tmp102查看 probe 返回值和错误码。 - 用
i2cdetect -y 1确认硬件地址是否应答。
对于 PCI 设备,可用 lspci -nn 查看 vendor/device ID,用 lspci -k 查看当前绑定驱动。若驱动未绑定,检查 pci_device_id 表是否包含该 ID,以及驱动是否被 blacklist。
掌握这条主线后,再看任何具体总线驱动,都可以用同一套问题去拆解:设备从哪里来?匹配依据是什么?probe 里申请了哪些资源?remove 是否对称释放?把这四点确认清楚,驱动的注册与匹配流程就不再神秘。