4. 内核中设备树的解析机制

设备树 (Device Tree) 在内核中并非直接以文本形式存在,而是被编译为二进制 .dtb 文件,并在启动时由 Bootloader 传递给内核。内核需要一套标准化的接口来解析这颗“树”,提取硬件资源(如地址、中断、GPIO、时钟等)并传递给驱动程序。这套接口就是 OF (Open Firmware) API。

对于 Platform 驱动而言,of_match_table 是连接设备树节点与驱动 Probe 函数的“桥梁”。本章将深入解析 OF API 的核心函数,并阐明 of_match_table 在驱动匹配流程中的关键作用。

4.1 OF API 核心函数族

OF API 定义在 include/linux/of.h 和 drivers/of/ 目录下,主要分为以下几类:

分类 核心函数 功能描述
节点查找 of_find_node_by_path() 通过设备树路径(如 /soc/i2c@1234)查找节点
of_find_node_by_name() 通过节点名称查找
of_find_compatible_node() 通过 compatible 属性查找
属性读取 of_property_read_u32() 读取 32 位整型属性
of_property_read_string() 读取字符串属性
of_property_read_u32_array() 读取整型数组属性
资源获取 of_iomap() 将 reg 属性中的地址范围映射到虚拟地址空间
of_irq_get() 获取中断号
of_get_named_gpio() 获取 GPIO 编号
匹配与数据 of_match_node() 根据 of_device_id 表匹配节点,返回匹配到的表项
of_device_get_match_data() 获取匹配表项中的 .data 指针,用于传递平台特定数据
最佳实践: 在 Probe 函数中,优先使用 device_property_read_*() 系列通用 API(如 device_property_read_u32()),它们内部会调用 OF API 或 ACPI API,使驱动同时支持设备树和 ACPI 两种固件接口。

4.2 of_match_table 的作用与匹配流程

of_match_table 是 struct platform_driver 中的一个成员,类型为 const struct of_device_id *。它的核心作用有两个:

  1. 驱动与设备树节点的匹配依据:内核通过遍历该表,与设备树节点的 compatible 属性进行字符串比较,决定是否调用该驱动的 Probe 函数。
  2. 携带平台特定数据:每个 of_device_id 表项包含一个 .data 字段,可以指向任意类型的私有数据(如硬件版本号、寄存器偏移表等),Probe 函数通过 of_device_get_match_data() 获取。

下面是一个典型的 of_device_id 表定义:

static const struct of_device_id my_driver_of_match[] = {
    {
        .compatible = "vendor,my-device-v1",
        .data = (void *)DEVICE_V1,
    },
    {
        .compatible = "vendor,my-device-v2",
        .data = (void *)DEVICE_V2,
    },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);

驱动注册时,内核的 Platform 总线匹配逻辑 会执行以下步骤:

匹配流程: 1. 内核遍历设备树中所有节点,为每个节点创建 struct platform_device。 2. 当新设备注册到 Platform 总线时,总线调用 platform_match()。 3. platform_match() 优先检查 of_match_table:调用 of_match_node(),将设备节点的 compatible 属性与驱动表中的每个表项比较。 4. 如果找到匹配项,返回匹配到的 of_device_id 指针,并触发 Probe 函数。 5. 如果未匹配,则回退到 id_table(传统非设备树匹配)或设备名称匹配。

4.3 设备树资源解析与 Probe 流程 SVG 图

下图展示了从设备树节点到驱动 Probe 函数的完整资源解析与匹配流程:

设备树节点 → of_match_table → Probe 流程 设备树节点 compatible = "vendor,dev-v1" reg = <0x1000 0x200> interrupts = <GIC 42> of_match_table .compatible = "vendor,dev-v1" .data = DEVICE_V1 .compatible = "vendor,dev-v2" 匹配成功 返回 of_device_id* 触发 Probe Probe 函数内部资源获取 1. of_device_get_match_data() → 获取 .data 2. of_iomap() → 映射 reg 地址 3. of_irq_get() → 获取中断号 4. of_get_named_gpio() → 获取 GPIO 关键说明 of_match_table 不仅用于匹配,还通过 .data 字段传递硬件差异数据 使同一驱动可支持多个硬件版本,无需修改代码

4.4 典型 Probe 函数中的 OF API 调用示例

以下代码展示了一个完整的 Probe 函数,演示如何使用 OF API 从设备树节点中提取资源:

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    const struct of_device_id *match;
    struct resource *res;
    void __iomem *base;
    int irq, gpio;
    u32 val;

    /* 1. 获取匹配表项中的私有数据 */
    match = of_match_device(dev->driver->of_match_table, dev);
    if (!match)
        return -ENODEV;
    priv->version = (enum hw_version)match->data;

    /* 2. 使用 of_iomap 映射寄存器地址 */
    base = of_iomap(dev->of_node, 0);
    if (!base)
        return -ENOMEM;

    /* 3. 获取中断号 */
    irq = of_irq_get(dev->of_node, 0);
    if (irq < 0)
        dev_warn(dev, "no irq specified\n");

    /* 4. 读取自定义属性 */
    if (of_property_read_u32(dev->of_node, "clock-frequency", &val) == 0)
        priv->clock_freq = val;

    /* 5. 获取 GPIO */
    gpio = of_get_named_gpio(dev->of_node, "enable-gpios", 0);
    if (gpio < 0)
        dev_dbg(dev, "no enable gpio\n");

    /* 注册设备等后续操作... */
    return 0;
}
注意事项:
  • of_iomap() 返回的虚拟地址需要在 Remove 函数中通过 iounmap() 释放。
  • of_irq_get() 返回的是 Linux 内核中断号,可直接用于 request_irq()。
  • 如果设备树中未定义某个可选属性,OF API 会返回错误,驱动应做好容错处理。

4.5 of_match_table 与传统 id_table 的对比

对比维度 of_match_table id_table (传统)
匹配依据 设备树节点的 compatible 属性 Platform 设备名称字符串
数据传递 通过 .data 字段传递任意类型指针 通过 driver_data 传递 unsigned long
适用场景 设备树使能的内核(ARM64、RISC-V、ARM32 with DT) 旧版非设备树平台或 ACPI 平台
宏声明 MODULE_DEVICE_TABLE(of, ...) MODULE_DEVICE_TABLE(platform, ...)
匹配优先级 最高,优先于 id_table 和设备名称匹配 次高,仅在 of_match_table 未匹配时使用

4.6 小结

OF API 是内核解析设备树的“手术刀”,而 of_match_table 则是驱动与设备树节点之间的“契约”。掌握以下要点即可在实际开发中熟练运用:

  • 匹配机制:compatible 属性字符串必须与 of_device_id 表中的条目完全一致。
  • 数据传递:利用 .data 字段传递硬件版本或配置信息,避免为每个硬件版本编写独立驱动。
  • 资源获取:Probe 函数中优先使用 of_iomap、of_irq_get、of_get_named_gpio 等专用 API,而非手动解析 reg 或 interrupts 属性。
  • 通用化:考虑使用 device_property_read_*() 系列 API 编写与固件接口无关的驱动。