在嵌入式系统开发中,设备树(Device Tree)作为硬件描述的标准方式,已经成为Linux内核不可或缺的一部分。而of_property_read_u32这个看似简单的API,却是驱动开发者与设备树交互的重要桥梁。本文将带您深入Linux 4.14内核,从内存数据结构到函数调用栈,完整解析一个u32类型属性值是如何从设备树文件一步步传递到驱动程序的。
当Linux内核启动时,设备树编译器(DTC)会将.dts文件编译成.dtb二进制格式。内核在启动过程中解析这个二进制文件,在内存中构建起设备树的完整数据结构。这个过程中,两个关键数据结构起着核心作用:
struct device_node:对应设备树中的一个节点,包含以下关键成员:
c复制struct device_node {
const char *name;
const char *type;
struct property *properties; // 属性链表头指针
struct device_node *parent;
struct device_node *child;
struct device_node *sibling;
// ...
};
struct property:描述设备树节点的属性,其核心结构为:
c复制struct property {
char *name; // 属性名
int length; // 属性值长度
void *value; // 属性值指针
struct property *next; // 下一个属性
// ...
};
提示:设备树中的所有属性都以链表形式组织,
properties指针就是这个链表的头节点。理解这个链表结构对后续的属性查找过程至关重要。
下图展示了设备树节点在内存中的典型布局:
code复制+-------------------+ +-------------------+
| device_node | | property |
|-------------------| |-------------------|
| *name = "uart1" |---->| *name = "clock" |
| *type = "serial" | | length = 4 |
| *properties ------|---+ | *value = [0x3d09] |
| *parent | | | *next ------------+
+-------------------+ | +-------------------+
|
| +-------------------+
+->| property |
|-------------------|
| *name = "status" |
| length = 5 |
| *value = "okay" |
| *next = NULL |
+-------------------+
of_property_read_u32的调用过程实际上是一系列函数的精妙协作。让我们从顶层API开始,逐层深入内核:
这是驱动开发者最常接触的接口,其实现极其简洁:
c复制static inline int of_property_read_u32(const struct device_node *np,
const char *propname,
u32 *out_value)
{
return of_property_read_u32_array(np, propname, out_value, 1);
}
这个内联函数只是将单值读取转换为数组读取的特例(数组长度为1),体现了Linux内核"组合优于继承"的设计哲学。
第二层函数处理数组读取的通用情况:
c复制static inline int of_property_read_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz)
{
int ret = of_property_read_variable_u32_array(np, propname,
out_values, sz, 0);
if (ret >= 0)
return 0;
else
return ret;
}
这里有两个关键设计点:
这个函数完成了实际工作:
c复制int of_property_read_variable_u32_array(const struct device_node *np,
const char *propname,
u32 *out_values,
size_t sz_min,
size_t sz_max)
{
size_t sz, count;
const __be32 *val = of_find_property_value_of_size(np, propname,
(sz_min * sizeof(*out_values)),
(sz_max * sizeof(*out_values)),
&sz);
if (IS_ERR(val))
return PTR_ERR(val);
if (!sz_max)
sz = sz_min;
else
sz /= sizeof(*out_values);
count = sz;
while (count--)
*out_values++ = be32_to_cpup(val++);
return sz;
}
三个关键技术点值得注意:
be32_to_cpup()处理大端(BE)到CPU本地字节序的转换,因为设备树属性值总是以大端格式存储of_find_property_value_of_size确保属性值大小在期望范围内这个函数验证属性值的大小是否符合预期:
c复制static void *of_find_property_value_of_size(const struct device_node *np,
const char *propname,
u32 min, u32 max,
size_t *len)
{
struct property *prop = of_find_property(np, propname, NULL);
if (!prop)
return ERR_PTR(-EINVAL);
if (!prop->value)
return ERR_PTR(-ENODATA);
if (prop->length < min)
return ERR_PTR(-EOVERFLOW);
if (max && prop->length > max)
return ERR_PTR(-EOVERFLOW);
if (len)
*len = prop->length;
return prop->value;
}
错误处理非常全面,包括:
真正的属性查找发生在of_find_property中:
c复制struct property *of_find_property(const struct device_node *np,
const char *name,
int *lenp)
{
struct property *pp;
unsigned long flags;
raw_spin_lock_irqsave(&devtree_lock, flags);
pp = __of_find_property(np, name, lenp);
raw_spin_unlock_irqrestore(&devtree_lock, flags);
return pp;
}
这里有两个重要设计:
devtree_lock保护设备树结构的并发访问,确保在多核环境下安全__of_find_property完成实际工作这是属性查找的最底层实现:
c复制static struct property *__of_find_property(const struct device_node *np,
const char *name,
int *lenp)
{
struct property *pp;
if (!np)
return NULL;
for (pp = np->properties; pp; pp = pp->next) {
if (of_prop_cmp(pp->name, name) == 0) {
if (lenp)
*lenp = pp->length;
break;
}
}
return pp;
}
查找过程简单直接:线性遍历属性链表,直到找到名称匹配的属性。值得注意的是:
of_prop_cmp只是strcmp的宏定义,没有特殊优化设备树属性值总是以大端格式存储,而现代CPU多为小端架构,因此需要转换:
c复制#define be32_to_cpup(ptr) __be32_to_cpu(*(ptr))
static inline u32 __be32_to_cpu(__be32 val)
{
return (__force u32)swab32((__force u32)val);
}
在ARM等平台上,这个操作会被优化为单条指令(如rev)。
基于对上述实现的分析,我们可以得出一些重要的实践启示:
减少属性查找次数:
c复制// 不佳实践:多次查找同一属性
of_property_read_u32(np, "clock", &clk);
of_property_read_string(np, "clock", &clk_name);
// 优化方案:一次性读取所有需要属性
struct property *pp = of_find_property(np, "clock", NULL);
if (pp) {
// 自行解析属性值
}
属性组织建议:
完整的错误处理应该考虑所有可能的错误情况:
c复制int ret = of_property_read_u32(np, "clock-frequency", &freq);
if (ret) {
switch (-ret) {
case EINVAL:
dev_err(dev, "property does not exist\n");
break;
case ENODATA:
dev_err(dev, "property has no value\n");
break;
case EOVERFLOW:
dev_err(dev, "property size mismatch\n");
break;
default:
dev_err(dev, "unknown error %d\n", ret);
}
return ret;
}
属性命名一致性:
clock vs clocks)值格式规范:
clock-frequency单位总是Hz)下表总结了设备树属性访问的常见模式:
| 访问模式 | 适用场景 | 性能 | 代码复杂度 |
|---|---|---|---|
of_property_read_*系列函数 |
简单属性读取 | 中等 | 低 |
直接操作property结构 |
复杂或批量属性处理 | 高 | 中 |
| 缓存属性指针 | 高频访问属性 | 最高 | 高 |