3. 总线、设备与驱动

Linux设备模型的核心,是把“总线”“设备”“驱动”三者拆成独立对象,再通过统一的关系网把它们连接起来。理解这三者的结构关系、匹配机制和probe流程,是排查“驱动不加载”“设备不识别”“probe不执行”“初始化顺序错乱”等问题的基础。本节围绕bus_type、device、device_driver三个核心结构展开,重点说明它们如何注册、如何匹配、如何触发probe,以及初始化顺序中常见的坑。

一、三个核心结构的关系

在Linux设备模型中,bus_type表示一类总线,例如platform、PCI、I2C、SPI、USB等。它定义了这类总线上设备与驱动如何匹配、如何探测、如何移除。device表示一个具体设备实例,通常由设备树、ACPI、板级文件或枚举机制创建。device_driver表示一个驱动,负责声明自己能支持哪些设备,并在匹配成功后执行初始化。

三者不是简单的包含关系,而是通过kobject、kset、device_private、driver_private等机制挂接到sysfs和内核对象体系中。总线是“规则制定者”,设备是“被管理的对象”,驱动是“操作设备的代码”。总线负责把设备和驱动撮合到一起,匹配成功后调用驱动的probe函数。

核心关系可以概括为:
bus_type 提供 match 规则和 probe 调度;
device 提供资源、属性、父节点和总线归属;
device_driver 提供 id_table、of_match_table、probe/remove 等回调;
三者通过 bus_register()、device_register()、driver_register() 进入设备模型。
bus_type / device / device_driver 关系与匹配 probe 流程 bus_type match() / uevent() probe() / remove() devices_kset / drivers_kset device struct device 资源 / 属性 / 父节点 bus / driver 指针 device_driver id_table / of_match_table probe() / remove() name / owner / bus probe 执行 资源申请 / 寄存器映射 中断注册 / 字符设备 失败返回负 errno driver_register() 挂入总线 device_register() 挂入总线 bus 归属 match 成功 bus_type.match() 比较 id / of / acpi 匹配成功 → really_probe() 绑定 device 与 driver,再调用 probe

二、匹配机制:总线如何撮合设备与驱动

设备和驱动注册到总线后,总线会尝试匹配。匹配的核心入口是bus_type.match()。不同总线的匹配策略不同:platform总线通常比较driver->name与device->name,或使用of_match_table、acpi_match_table;I2C总线比较i2c_device_id或设备树compatible;PCI总线比较vendor/device ID;USB总线比较idVendor/idProduct等。

匹配成功后,总线会调用really_probe(),把device和device_driver绑定起来,然后调用驱动的probe()。如果probe返回0,表示驱动成功接管设备;如果返回负值,绑定会回滚,设备可能进入deferred probe列表,等待依赖满足后重试。

// 典型platform驱动匹配表
static const struct of_device_id my_of_match[] = {
    { .compatible = "vendor,my-device" },
    { /* sentinel */ }
};
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流程与常见失败点

probe是驱动真正初始化设备的阶段。它通常完成:使能时钟、复位设备、映射寄存器、申请中断、注册字符设备或网络设备、初始化工作队列等。probe失败最常见的原因包括:资源未就绪、时钟未使能、GPIO被占用、中断号无效、依赖驱动未加载、设备树节点缺失或属性写错。

如果probe返回-EPROBE_DEFER,内核会把设备加入延迟探测列表,等依赖驱动注册后再试。这个机制解决了“驱动加载顺序不确定”的问题,但如果依赖永远不满足,设备就会一直不 probe,表现为“设备存在但驱动没起来”。

常见误区:把probe不执行简单归因于“驱动没编译进内核”。实际上更常见的是:设备树compatible与驱动of_match_table不一致、驱动被编译成模块但未加载、总线匹配失败、probe返回-EPROBE_DEFER后依赖未满足、设备被父节点状态disabled屏蔽。

四、初始化顺序问题与排查清单

Linux启动时,总线、设备、驱动的注册顺序并不完全固定。设备可能先注册,驱动后注册;也可能驱动先注册,设备后注册。总线会在双方都到位后触发匹配。真正容易出问题的是“依赖关系”:驱动A的probe依赖驱动B提供的服务,但B还没probe。此时应使用-EPROBE_DEFER,而不是硬编码初始化顺序。

现象 可能原因 排查命令/手段
驱动已加载但probe不执行 compatible不匹配、id_table不匹配、总线match失败 ls /sys/bus/platform/drivers/、dmesg | grep -i probe
probe返回-EPROBE_DEFER反复重试 依赖的时钟、GPIO、regulator、PHY未就绪 cat /sys/kernel/debug/devices_deferred
设备节点存在但无驱动绑定 设备树status=disabled、驱动未编入、模块未加载 ls /sys/bus/platform/devices/、modprobe
probe成功但设备不可用 资源申请失败、寄存器映射错误、中断未注册 dmesg、cat /proc/interrupts、devmem
系统启动卡死或崩溃 probe中睡眠、死锁、空指针、重复注册 earlyprintk、ftrace、pstore

排查时建议按“总线→设备→驱动→匹配→probe”的顺序逐层确认:先看总线是否注册,再看设备和驱动是否出现在sysfs,然后看match是否成功,最后看probe日志和返回值。对于延迟探测问题,重点检查/sys/kernel/debug/devices_deferred和驱动依赖链。只有把结构关系、匹配机制和probe流程串起来,才能快速定位设备模型中的初始化顺序问题。