4. 内核设备树解析机制:OF API 与匹配原理

设备树(Device Tree)是嵌入式 Linux 驱动与硬件描述之间的桥梁。内核通过 Open Firmware (OF) API 在运行时解析设备树二进制(DTB),完成驱动与设备的匹配、资源获取、中断映射等关键操作。本章深入剖析 of_match_table 的匹配流程、核心 OF API 函数族以及属性读取的完整链路。

核心认知:OF API 是驱动开发者最频繁调用的内核接口之一。理解其匹配原理和属性读取机制,是编写可移植、健壮设备驱动的基石。

4.1 OF API 体系概览

OF API 位于 include/linux/of.h 和 drivers/of/ 目录下,主要分为三大类:

类别 核心函数 用途
设备节点查找 of_find_node_by_path(), of_find_node_by_name(), of_find_compatible_node() 根据路径、名称或 compatible 属性定位节点
属性读取 of_property_read_u32(), of_property_read_string(), of_property_count_elems_of_size() 从节点属性中提取标量、字符串、数组等数据
中断与资源 of_irq_get(), of_iomap(), of_address_to_resource() 解析中断控制器、内存映射地址等硬件资源
关键设计原则:OF API 采用“节点 + 属性”的扁平化模型。每个 struct device_node 代表一个设备树节点,通过 struct property 链表挂载所有属性。驱动开发者无需手动遍历 DTB 二进制结构,所有解析由内核抽象完成。
OF API 调用层次与数据流 驱动代码 板级初始化 中断控制器 OF API 核心层 of_match_table · of_property_read · of_irq_get · of_iomap DTB 扁平设备树解析引擎 fdt_unflatten_device_tree · 属性链表构建 · 中断映射 逐层封装

4.2 of_match_table 匹配原理

of_match_table 是驱动与设备树节点匹配的核心机制。每个 struct platform_driver 或 struct i2c_driver 都包含一个 of_match_table 指针,指向 struct of_device_id 数组。

4.2.1 数据结构

struct of_device_id {
    char    name[32];       // 节点名称匹配
    char    type[32];       // 节点 device_type 属性
    char    compatible[128]; // compatible 属性(最常用)
    const void *data;       // 私有数据指针
};

匹配优先级:compatible → type → name。实际开发中 90% 以上的场景只使用 compatible 字段。

4.2.2 匹配流程

  1. 内核启动阶段:of_platform_populate() 遍历设备树根节点,为每个 compatible 匹配的节点创建 struct platform_device。
  2. 驱动注册阶段:platform_driver_register() 将驱动挂入总线,触发 platform_match()。
  3. 匹配执行:of_driver_match_device() 调用 of_match_device(),遍历驱动的 of_match_table,与节点的 compatible 属性逐一比较。
  4. 回调触发:匹配成功则调用驱动的 probe() 函数,并将匹配到的 of_device_id 指针传入。
常见陷阱:compatible 字符串必须与设备树中的值完全一致(包括大小写和逗号)。例如设备树中写 "ti,am335x-uart",驱动中必须写 "ti,am335x-uart",不能省略厂商前缀。
of_match_table 匹配流程 设备树节点 compatible = "vendor,dev" 驱动 of_match_table { .compatible = "vendor,dev" } of_match_device() 字符串比较 匹配成功 → probe() 匹配失败 → 跳过 匹配顺序:compatible → type → name

4.3 of_property_read 系列函数

属性读取是驱动从设备树获取配置信息的主要手段。内核提供了一套类型安全的读取函数族,覆盖整数、字符串、布尔、数组等常见数据类型。

4.3.1 函数原型速查表

函数 读取类型 返回值
of_property_read_u32() 32 位无符号整数 0 成功,负值错误码
of_property_read_u32_array() 32 位整数数组 实际读取元素个数
of_property_read_string() 字符串 0 成功,负值错误码
of_property_read_bool() 布尔值(存在即 true) true / false
of_property_count_elems_of_size() 获取数组元素个数 元素个数或负值错误码

4.3.2 典型使用模式

static int my_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct device_node *np = dev->of_node;
    u32 val, irq;
    const char *str;
    int ret;

    // 1. 读取 u32 属性
    ret = of_property_read_u32(np, "clock-frequency", &val);
    if (ret)
        dev_err(dev, "failed to read clock-frequency\n");

    // 2. 读取字符串属性
    ret = of_property_read_string(np, "label", &str);
    if (!ret)
        dev_info(dev, "label: %s\n", str);

    // 3. 检查布尔属性(如 dma-coherent)
    if (of_property_read_bool(np, "dma-coherent"))
        dev_info(dev, "device is dma-coherent\n");

    // 4. 读取中断号(通过 OF 中断映射)
    irq = of_irq_get(np, 0);
    if (irq < 0)
        dev_err(dev, "failed to get irq\n");

    return 0;
}
最佳实践:始终检查返回值。设备树属性可能缺失或格式错误,驱动应具备优雅降级能力。对于可选属性,使用 of_property_read_bool() 或提供默认值。

4.3.3 属性读取内部流程

以 of_property_read_u32() 为例,其内部执行以下步骤:

  1. 节点查找:通过 of_find_property() 在节点的 property 链表中查找指定名称的属性。
  2. 长度校验:检查属性长度是否至少为 4 字节(u32 大小)。
  3. 字节序转换:设备树以大端(big-endian)存储,调用 of_read_number() 将大端数据转换为 CPU 本地字节序。
  4. 结果写入:将转换后的值写入用户提供的指针。
of_property_read_u32 内部执行流程 of_find_property() 查找属性链表 长度校验 ≥ 4 bytes 字节序转换 大端 → CPU 序 写入结果 *val = 转换值 长度不足 → 返回 -EINVAL 所有 of_property_read 系列函数遵循相同模式

4.4 中断资源获取:of_irq_get

中断是设备树驱动中最关键的资源之一。of_irq_get() 函数封装了从设备树节点到 Linux IRQ 号的完整映射过程。

// 函数原型
int of_irq_get(struct device_node *dev, int index);

// 使用示例
int irq_num = of_irq_get(np, 0);  // 获取第 0 个中断
if (irq_num < 0) {
    dev_err(dev, "failed to get IRQ: %d\n", irq_num);
    return irq_num;
}

ret = devm_request_irq(dev, irq_num, my_isr, 0, "my_device", dev);

of_irq_get() 内部调用 of_irq_parse_one() 解析 interrupts 属性,然后通过 irq_create_of_mapping() 将设备树中断描述符转换为内核 IRQ 号。该过程涉及中断控制器驱动(如 GIC)的 irq_domain 映射。

进阶提示:对于多中断设备,使用 platform_get_irq() 更简洁,它内部封装了 of_irq_get() 并自动处理 struct platform_device 的节点关联。

4.5 本章小结

  • OF API 是内核解析设备树的统一接口,覆盖节点查找、属性读取、中断映射三大功能。
  • of_match_table 通过 compatible 字符串匹配驱动与设备,匹配成功触发 probe()。
  • of_property_read 系列函数提供类型安全的属性读取,内部自动处理字节序转换。
  • 中断获取 通过 of_irq_get() 完成从设备树到 Linux IRQ 号的映射,是中断申请的前置步骤。