4. 设备树与内核匹配
在 Linux 设备驱动模型中,设备树(Device Tree)负责描述硬件资源,内核负责把这些描述转换成可被驱动消费的设备对象。理解 of_match_table、platform_driver 以及 device_node 到 platform_device 的转换机制,是外设 Bring-up 阶段最核心的基本功。很多“驱动加载了但 probe 不执行”“设备节点存在却匹配不上”的问题,根源都在这一条链路上。
一、核心原理:从 DTB 到 platform_device
内核启动时,bootloader 把编译好的 DTB 传给内核。内核早期会解析 DTB,建立一棵 device_node 树,每个节点对应设备树中的一个设备描述。随后,内核在初始化阶段调用 of_platform_populate(),遍历这些 device_node,把符合条件的节点转换成 platform_device,并注册到 platform 总线上。
转换的关键判断是:节点是否具有 compatible 属性,以及它的父节点是否允许创建 platform 设备。通常,根节点下的子节点、以及 simple-bus 等总线节点下的子节点,都会被转换为 platform_device。转换过程中,内核会把 reg、interrupts、clocks、gpios 等属性解析成 platform_device 的 resource,供驱动在 probe 时通过 platform_get_resource() 等接口获取。
二、of_match_table 与 platform_driver 的匹配机制
驱动侧通过 platform_driver 注册到 platform 总线。匹配时,总线会依次比较 driver->of_match_table、driver->id_table 和 driver->name。其中 of_match_table 是设备树场景下最常用的匹配方式,它把驱动支持的 compatible 字符串列表告诉总线。
static const struct of_device_id mydev_of_match[] = {
{ .compatible = "vendor,mydev-v1" },
{ .compatible = "vendor,mydev-v2" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);
static struct platform_driver mydev_driver = {
.probe = mydev_probe,
.remove = mydev_remove,
.driver = {
.name = "mydev",
.of_match_table = mydev_of_match,
},
};
module_platform_driver(mydev_driver);
当 platform_device 被创建后,总线会拿它的 compatible 属性与每个已注册驱动的 of_match_table 逐项比较。一旦匹配成功,总线调用驱动的 probe(),并把 platform_device 指针传入。驱动在 probe 中通过 dev->of_node 拿到对应的 device_node,再解析私有属性。
三、device_node 到 platform_device 的转换细节
转换并不是简单地把节点“复制”成设备,而是有选择地解析。内核会为每个 platform_device 分配 resource 数组,把 reg 属性中的地址和长度转换成 IORESOURCE_MEM,把 interrupts 转换成 IORESOURCE_IRQ。对于 clocks、gpios、regulators 等,则保留在 device_node 中,由驱动在 probe 时用 of_* 系列 API 解析。
下面这张图展示了从 DTB 到驱动 probe 的完整信号与对象流转关系,重点标出了 device_node、platform_device、of_match_table 三者的位置与交互。
四、案例与场景:一个 I2C 外设的 Bring-up
假设我们要 bring-up 一颗挂在 I2C 总线上的传感器,设备树节点如下:
&i2c1 {
status = "okay";
sensor@48 {
compatible = "vendor,sensor-a1";
reg = <0x48>;
interrupt-parent = <&gpio1>;
interrupts = <12 IRQ_TYPE_EDGE_FALLING>;
vdd-supply = <&vdd_3v3>;
};
};
内核解析后,sensor@48 节点会被转换为一个 platform_device(在 I2C 子系统中实际是 i2c_client,但匹配逻辑同样依赖 of_match_table)。驱动侧声明:
static const struct of_device_id sensor_of_match[] = {
{ .compatible = "vendor,sensor-a1" },
{ }
};
MODULE_DEVICE_TABLE(of, sensor_of_match);
当总线匹配到 vendor,sensor-a1 后,调用驱动的 probe。probe 中通过 of_get_child_by_name()、of_property_read_u32() 等接口读取私有属性,通过 regulator_get() 获取电源,通过 platform_get_irq() 获取中断号。如果一切正常,传感器即可完成初始化。
五、常见误区与复盘清单
| 误区现象 | 根本原因 | 排查方法 |
|---|---|---|
| 驱动已加载,probe 不执行 | compatible 字符串与 of_match_table 不一致 | 对比 dmesg 中 platform_device 的 compatible 与驱动表项 |
| device_node 存在但无 platform_device | 父节点未启用或缺少 compatible | 检查父节点 status 与 simple-bus 属性 |
| probe 中 of_node 为 NULL | 设备不是从设备树转换而来 | 确认是否走了 of_platform_populate 路径 |
| reg 地址读取错误 | reg 属性格式或地址单元数不匹配 | 检查 #address-cells 与 #size-cells |
| 中断申请失败 | interrupt-parent 或 interrupts 配置错误 | 用 /proc/interrupts 与 dmesg 交叉验证 |
of_match_table 与 id_table。在纯设备树系统中,优先使用 of_match_table;id_table 主要用于非设备树场景或兼容旧平台。若两者同时存在,总线会按优先级依次尝试,但设备树场景下 of_match_table 是首选。
总结来说,设备树与内核匹配的本质是一条“描述—转换—匹配—执行”的链路:DTB 提供硬件描述,内核把 device_node 转换为 platform_device,platform_driver 通过 of_match_table 与设备匹配,最终触发 probe。掌握这条链路,就能在 Bring-up 中快速定位“设备不识别”“驱动不加载”“probe 不执行”等高频问题。