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() 等接口获取。

核心链路:DTB → device_node 树 → of_platform_populate() → platform_device → platform_bus → 驱动匹配 → probe()

二、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 三者的位置与交互。

DTB 二进制 bootloader 传入 device_node 树 compatible / reg interrupts / clocks of_platform_populate() 遍历节点 解析 resource platform_device resource 数组 dev.of_node 指针 platform_bus 设备与驱动匹配 platform_driver of_match_table probe / remove probe() 执行 解析私有属性 申请资源 / 注册设备 解析 遍历 创建 注册 注册 匹配成功 → 调用 probe 匹配依据:compatible 字符串逐项比较 of_match_table 优先于 id_table 与 driver.name DTB / 节点 platform_device platform_driver probe 执行

四、案例与场景:一个 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 交叉验证
Bring-up 时建议先确认三件事:设备树节点是否被内核解析(/proc/device-tree 下是否存在)、platform_device 是否创建(/sys/bus/platform/devices/ 下是否有对应条目)、驱动是否注册(/sys/bus/platform/drivers/ 下是否有驱动目录)。三者齐备,probe 才会被触发。
不要混淆 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 不执行”等高频问题。