3. of核心API概览:of_find_node_by_path、of_get_property、of_parse_phandle等函数族
设备树解析的核心工作由一组以 of_ 前缀命名的内核API完成,它们定义在 drivers/of/ 目录下,头文件位于 include/linux/of.h。本章聚焦于最常用的三个函数族:节点查找、属性读取和phandle解析。理解这些API的调用链和返回值,是使用printk进行调试的前提。
调试切入点: 所有 of API 在失败时通常返回
NULL 或负错误码。在调用后立即添加 printk 打印返回值,是定位设备树匹配失败的第一道防线。
3.1 节点查找:of_find_node_by_path 与 of_find_node_by_name
节点查找是设备树解析的入口。驱动通常通过设备路径或节点名获取 struct device_node 指针,后续所有属性读取都依赖该指针。
| 函数原型 | 功能说明 | 典型返回值 |
|---|---|---|
of_find_node_by_path(const char *path) |
通过绝对路径(如 "/soc/i2c@7e804000")或别名(如 "/aliases/i2c0")查找节点 |
成功返回 struct device_node *,失败返回 NULL |
of_find_node_by_name(struct device_node *from, const char *name) |
通过节点 name 属性(不含 unit-address)遍历查找 |
成功返回匹配节点,遍历完返回 NULL |
of_find_node_by_type(struct device_node *from, const char *type) |
通过 device_type 属性查找(旧式设备树兼容) |
成功返回匹配节点,失败返回 NULL |
of_find_compatible_node(struct device_node *from, const char *type, const char *compatible) |
通过 compatible 字符串查找,最常用 |
成功返回匹配节点,失败返回 NULL |
引用计数陷阱:
of_find_node_by_path 等函数会递增节点的引用计数。使用完毕后必须调用 of_node_put() 释放,否则会造成内存泄漏。调试时可通过 /sys/kernel/debug/of_node 观察引用计数异常。
// 典型调试代码片段:查找 /soc/i2c@7e804000 节点
struct device_node *np;
np = of_find_node_by_path("/soc/i2c@7e804000");
if (!np) {
printk(KERN_ERR "OF_DEBUG: Failed to find node /soc/i2c@7e804000\n");
return -ENODEV;
}
printk(KERN_INFO "OF_DEBUG: Found node %pOF\n", np); // %pOF 是内核专用打印格式
// ... 使用 np 读取属性 ...
of_node_put(np); // 释放引用
3.2 属性读取:of_get_property 与 of_property_read_* 系列
获取节点指针后,下一步是读取属性值。of_get_property 是底层函数,返回原始字节流;而 of_property_read_* 系列封装了类型转换和错误检查,推荐在驱动中使用。
| 函数 | 读取类型 | 返回格式 | 典型错误 |
|---|---|---|---|
of_get_property(np, name, &len) |
原始字节 | const void *,长度通过 len 返回 |
属性不存在返回 NULL |
of_property_read_u32(np, name, &val) |
32位整数 | 成功返回0,失败返回负错误码 | -EINVAL 属性不存在或类型不匹配 |
of_property_read_u32_array(np, name, out, sz) |
32位整数数组 | 成功返回0,失败返回负错误码 | -EOVERFLOW 数组长度不足 |
of_property_read_string(np, name, &str) |
字符串 | 成功返回0,str 指向属性值 |
-ENODATA 属性为空 |
of_property_read_bool(np, name) |
布尔值 | 属性存在返回 true,否则 false |
无错误码 |
// 属性读取调试示例
struct device_node *np;
u32 clock_freq;
const char *compat_str;
int ret;
np = of_find_node_by_path("/soc/i2c@7e804000");
if (!np) {
printk(KERN_ERR "OF_DEBUG: node not found\n");
return -ENODEV;
}
// 读取 u32 属性
ret = of_property_read_u32(np, "clock-frequency", &clock_freq);
if (ret) {
printk(KERN_WARNING "OF_DEBUG: clock-frequency read failed, ret=%d\n", ret);
} else {
printk(KERN_INFO "OF_DEBUG: clock-frequency = %u Hz\n", clock_freq);
}
// 读取字符串属性
ret = of_property_read_string(np, "compatible", &compat_str);
if (!ret) {
printk(KERN_INFO "OF_DEBUG: compatible = %s\n", compat_str);
}
// 使用底层 of_get_property 调试原始数据
int proplen;
const u32 *raw = of_get_property(np, "reg", &proplen);
if (raw) {
printk(KERN_INFO "OF_DEBUG: reg property length = %d bytes\n", proplen);
printk(KERN_INFO "OF_DEBUG: reg[0] = 0x%08x\n", be32_to_cpu(raw[0]));
}
of_node_put(np);
字节序注意: 设备树中所有多字节整数以大端(big-endian)存储。使用
of_property_read_u32 系列函数时,内核会自动转换为主机字节序;但使用 of_get_property 获取原始数据时,必须手动调用 be32_to_cpu() 转换。
3.3 phandle解析:of_parse_phandle 与 of_parse_phandle_with_args
设备树通过 phandle(指针句柄)实现节点间的引用,例如中断控制器、时钟源、GPIO控制器等。解析phandle是驱动编写中最容易出错、也最需要printk调试的环节。
| 函数 | 解析内容 | 典型使用场景 |
|---|---|---|
of_parse_phandle(np, name, index) |
返回第 index 个phandle指向的节点指针 |
单个引用,如 interrupt-parent |
of_parse_phandle_with_args(np, name, cells, index) |
返回节点指针 + 参数数组(如 #clock-cells 定义的参数) |
带参数的引用,如 clocks |
of_parse_phandle_with_fixed_args(np, name, cells, index, out_args) |
固定参数个数的phandle解析 | GPIO、DMA等固定参数引用 |
// phandle 解析调试示例:解析 clocks 属性
struct device_node *np, *clk_np;
struct of_phandle_args clk_spec;
int ret, i;
np = of_find_node_by_path("/soc/spi@7e204000");
if (!np) return -ENODEV;
// 解析第0个clock引用,带1个参数(#clock-cells = 1)
ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells", 0, &clk_spec);
if (ret) {
printk(KERN_ERR "OF_DEBUG: Failed to parse clocks[0], ret=%d\n", ret);
of_node_put(np);
return ret;
}
printk(KERN_INFO "OF_DEBUG: clocks[0] phandle node = %pOF\n", clk_spec.np);
printk(KERN_INFO "OF_DEBUG: clocks[0] args count = %d\n", clk_spec.args_count);
for (i = 0; i < clk_spec.args_count; i++) {
printk(KERN_INFO "OF_DEBUG: clocks[0] args[%d] = %u\n", i, clk_spec.args[i]);
}
// 使用完后必须释放 phandle 节点引用
of_node_put(clk_spec.np);
of_node_put(np);
3.4 常用辅助函数与调试技巧
除了上述核心API,以下辅助函数在调试中同样重要:
of_match_node(matches, np):检查节点是否匹配of_device_id表,常用于 probe 入口调试。of_alias_get_id(np, name):从/aliases节点获取别名序号,如获取i2c0中的0。of_count_phandle_with_args(np, name, cells):统计phandle引用个数,用于循环解析前的边界检查。of_node_full_name(np):返回节点的完整路径名,比直接打印np->name更准确。
printk 调试黄金法则:
1. 每次调用 of API 后立即打印返回值。
2. 使用
3. 在
4. 在
1. 每次调用 of API 后立即打印返回值。
2. 使用
%pOF 打印节点全路径,使用 %pOFp 打印 phandle 值。3. 在
of_parse_phandle_with_args 后打印 args_count 和 args 数组,确认参数个数和值正确。4. 在
of_node_put 前打印节点引用计数(通过 kref_read(&np->kref)),排查引用泄漏。
// 综合调试示例:完整解析一个带 phandle 和属性的节点
static int debug_of_parse(struct device_node *np)
{
struct of_phandle_args gpio_spec;
u32 reg_base;
int ret, i;
// 1. 打印节点基本信息
printk(KERN_INFO "OF_DEBUG: Parsing node %pOF\n", np);
// 2. 读取 reg 属性
ret = of_property_read_u32(np, "reg", ®_base);
printk(KERN_INFO "OF_DEBUG: reg read ret=%d, value=0x%x\n", ret, reg_base);
// 3. 解析 GPIO phandle
ret = of_parse_phandle_with_fixed_args(np, "gpios", "#gpio-cells", 0, &gpio_spec);
if (ret) {
printk(KERN_ERR "OF_DEBUG: gpios parse failed, ret=%d\n", ret);
return ret;
}
printk(KERN_INFO "OF_DEBUG: gpio controller = %pOF\n", gpio_spec.np);
printk(KERN_INFO "OF_DEBUG: gpio args = ");
for (i = 0; i < gpio_spec.args_count; i++)
printk("%u ", gpio_spec.args[i]);
printk("\n");
of_node_put(gpio_spec.np);
return 0;
}
3.5 API 调用链与错误传播
在实际驱动中,of API 通常形成调用链:of_find_node_by_path → of_property_read_u32 → of_parse_phandle。任何一个环节失败都会导致后续操作无法进行。下表总结了常见错误及其调试方向:
| 错误场景 | 典型返回值 | 调试方向 |
|---|---|---|
| 节点路径错误 | NULL |
检查设备树源文件中的路径拼写,使用 dtc -I dtb -O dts 反编译确认 |
| 属性名拼写错误 | -EINVAL 或 NULL |
对比设备树绑定文档,注意大小写和下划线 |
| phandle 索引越界 | -EINVAL |
使用 of_count_phandle_with_args 确认总数 |
| #*-cells 属性缺失 | -EINVAL |
检查被引用节点是否定义了 #clock-cells、#gpio-cells 等 |
| 引用计数泄漏 | 内核内存泄漏告警 | 确保每个 of_find_* 和 of_parse_phandle 都有对应的 of_node_put |
本章小结: of_find_node_by_path、of_get_property 和 of_parse_phandle 构成了设备树解析的三大支柱。掌握它们的返回值含义和错误处理模式,是后续使用 printk 进行逐层调试的基础。下一章将深入 of_find_node_by_path 的内核源码,在关键路径插入 printk 观察节点查找的完整过程。