4. 设备树匹配机制:compatible属性、of_match_table、设备树与驱动绑定的核心流程

设备树(Device Tree)是嵌入式Linux系统中描述硬件拓扑结构的核心机制。驱动与设备之间的绑定,本质上是通过设备树节点中的compatible属性与驱动中的of_match_table进行字符串匹配完成的。本节将深入剖析这一匹配机制的核心流程与数据结构。

4.1 compatible属性:设备树的“身份证”

每个设备树节点都通过compatible属性向内核声明自己支持的驱动类型。该属性是一个字符串列表,遵循“厂商,型号”的命名规范,优先级从高到低排列。

关键规范:compatible 属性中的第一个字符串通常是最精确的型号,后续字符串为兼容型号。内核匹配时按顺序尝试,直到找到匹配的驱动。
// 设备树中 CAN 控制器节点的典型 compatible 声明
can0: can@fdc00000 {
    compatible = "microchip,mcp2515", "bosch,can-controller";
    reg = <0x0 0xfdc00000 0x0 0x1000>;
    interrupts = <0 42 4>;
    clocks = <&can_clk>;
    ...
};

上述节点中,"microchip,mcp2515"是精确型号,"bosch,can-controller"是通用兼容型号。如果内核没有为MCP2515编写专用驱动,但存在一个通用的CAN控制器驱动,后者仍可绑定该设备。

4.2 of_match_table:驱动的“准入名单”

驱动开发者通过of_match_table结构体数组,向内核声明该驱动能够支持哪些设备。每个表项是一个of_device_id结构,核心字段就是.compatible字符串。

// 驱动中 of_match_table 的定义示例
static const struct of_device_id mcp251x_of_match[] = {
    { .compatible = "microchip,mcp2515", },
    { .compatible = "microchip,mcp2517", },
    { .compatible = "microchip,mcp2518", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mcp251x_of_match);
提示:MODULE_DEVICE_TABLE 宏用于将 of_match_table 导出到模块的符号表中,使得内核在热插拔或模块加载时能够自动进行匹配,无需手动绑定。

4.3 核心匹配流程:从设备树节点到驱动 probe

当内核启动或驱动模块加载时,设备树匹配流程按以下步骤执行:

  1. 设备节点注册:内核解析设备树(DTB),为每个包含compatible属性的节点创建struct platform_device。
  2. 驱动注册:驱动调用platform_driver_register(),将of_match_table注册到内核的总线驱动模型中。
  3. 总线匹配:平台总线(platform bus)的.match函数遍历驱动的of_match_table,与设备节点的compatible属性进行字符串比较。
  4. 绑定与probe:匹配成功后,内核调用驱动的.probe()函数,并将设备节点的struct device_node指针传递给驱动。
注意:匹配顺序是“驱动优先”还是“设备优先”取决于注册顺序。通常设备节点在系统启动早期就已注册,驱动模块加载时触发匹配。如果驱动编译进内核,则在设备节点创建时立即匹配。

4.4 匹配流程可视化

下图展示了从设备树节点到驱动probe的完整匹配链路:

设备树匹配核心流程 设备树源文件(.dts) DTB 二进制文件 内核解析为 device_node 创建 platform_device 平台总线 .match() compatible 匹配? 是 probe() 否 尝试下一个驱动 匹配成功 → 驱动与设备绑定,probe() 函数获得设备树资源(reg、interrupts、clocks 等)

4.5 关键数据结构对比

设备树匹配涉及三个核心数据结构,它们的职责与关系如下表所示:

数据结构 所属层级 核心字段 作用
struct device_node 设备树层 full_name, properties 表示一个设备树节点,包含所有属性链表
struct of_device_id 驱动层 .compatible, .data 驱动支持的设备匹配表项
struct platform_driver 总线驱动层 .driver.of_match_table 驱动注册时挂载匹配表,供总线调用

4.6 匹配过程中的关键函数调用链

从驱动注册到probe调用,内核内部执行以下函数链:

platform_driver_register()
  └─> driver_register()
       └─> bus_add_driver()
            └─> driver_attach()
                 └─> bus_for_each_dev()
                      └─> __driver_attach()
                           └─> driver_match_device()
                                └─> platform_match()
                                     └─> of_match_device()
                                          └─> of_match_node()
                                               └─> __of_match_node()
                                                    └─> 字符串比较 compatible
核心函数解析:of_match_device() 遍历驱动的 of_match_table,对每个表项调用 of_match_node(),后者从设备节点的 compatible 属性中提取字符串列表,与表项进行精确字符串匹配。匹配成功则返回对应的 of_device_id 指针,驱动可通过 .data 字段获取私有数据。

4.7 实际案例:CAN驱动绑定过程

以MCP2515 CAN控制器为例,设备树节点与驱动的匹配过程如下:

  1. 设备树节点:包含 compatible = "microchip,mcp2515",以及 reg、interrupts、clocks 等资源描述。
  2. 驱动注册:mcp251x_can_driver 注册时携带 mcp251x_of_match[] 表,其中包含 "microchip,mcp2515"。
  3. 匹配触发:平台总线调用 platform_match(),通过 of_match_device() 找到匹配项。
  4. probe执行:内核调用 mcp251x_can_probe(),驱动通过 devm_platform_ioremap_resource() 获取寄存器地址,通过 platform_get_irq() 获取中断号,完成CAN控制器的初始化与注册。
// 驱动 probe 函数中获取设备树资源的典型代码
static int mcp251x_can_probe(struct platform_device *pdev)
{
    struct net_device *net;
    struct mcp251x_priv *priv;
    void __iomem *base;
    int irq, ret;

    // 获取寄存器基地址(来自 reg 属性)
    base = devm_platform_ioremap_resource(pdev, 0);
    if (IS_ERR(base))
        return PTR_ERR(base);

    // 获取中断号(来自 interrupts 属性)
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    // 获取时钟(来自 clocks 属性)
    priv->clk = devm_clk_get(&pdev->dev, NULL);
    if (IS_ERR(priv->clk))
        return PTR_ERR(priv->clk);

    // 注册 CAN 网络设备
    ret = register_candev(net);
    ...
}
最佳实践:驱动开发者应优先使用 devm_* 系列API(如 devm_platform_ioremap_resource)获取设备树资源,这些API自动管理资源生命周期,在驱动卸载或probe失败时自动释放,避免内存泄漏。

4.8 匹配失败时的回退机制

如果 of_match_table 匹配失败,平台总线还会尝试其他匹配方式:

  • ACPI匹配:在支持ACPI的平台上,尝试使用ACPI表进行匹配。
  • ID表匹配:如果驱动提供了 id_table(传统平台驱动),则进行名称匹配。
  • 名称回退:将设备节点的 name 属性与驱动名称进行字符串比较(已废弃,仅用于兼容旧驱动)。
注意:对于现代嵌入式Linux系统,强烈建议仅使用 of_match_table 进行匹配。依赖名称回退的方式不可靠,且已被内核社区标记为过时。

通过上述机制,设备树匹配实现了硬件描述与驱动代码的解耦:硬件工程师修改设备树即可适配不同外设,驱动开发者只需关注 of_match_table 的声明与 probe 函数的实现,双方通过 compatible 这一契约完成协同工作。