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 二进制结构,所有解析由内核抽象完成。
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 匹配流程
- 内核启动阶段:
of_platform_populate()遍历设备树根节点,为每个 compatible 匹配的节点创建struct platform_device。 - 驱动注册阶段:
platform_driver_register()将驱动挂入总线,触发platform_match()。 - 匹配执行:
of_driver_match_device()调用of_match_device(),遍历驱动的of_match_table,与节点的compatible属性逐一比较。 - 回调触发:匹配成功则调用驱动的
probe()函数,并将匹配到的of_device_id指针传入。
常见陷阱:compatible 字符串必须与设备树中的值完全一致(包括大小写和逗号)。例如设备树中写
"ti,am335x-uart",驱动中必须写 "ti,am335x-uart",不能省略厂商前缀。
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() 为例,其内部执行以下步骤:
- 节点查找:通过
of_find_property()在节点的 property 链表中查找指定名称的属性。 - 长度校验:检查属性长度是否至少为 4 字节(u32 大小)。
- 字节序转换:设备树以大端(big-endian)存储,调用
of_read_number()将大端数据转换为 CPU 本地字节序。 - 结果写入:将转换后的值写入用户提供的指针。
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 号的映射,是中断申请的前置步骤。