4. 设备树在内核中的表示:struct device_node、struct property、of_* API函数族
设备树(Device Tree)是描述硬件拓扑和资源配置的静态数据结构。当内核启动时,Bootloader 将设备树二进制(DTB)传递给内核,内核通过 unflatten_device_tree() 将其解析为一棵由 struct device_node 和 struct property 构成的树状内存结构。Platform 驱动正是通过这棵“设备树内存树”来获取硬件信息,并与设备节点匹配。
4.1 核心数据结构:struct device_node
struct device_node 是设备树中每一个节点(node)在内核中的抽象。它包含了节点的名称、类型、父节点、子节点链表、属性链表以及指向已绑定设备的指针。
struct device_node {
const char *name; // 节点名,不含 unit-address
const char *type; // device_type 属性值,通常为 "device"
phandle phandle; // 节点的 phandle(唯一标识)
const char *full_name; // 完整路径名,如 "/soc/i2c@7e804000"
struct property *properties; // 属性链表头
struct property *deadprops; // 已废弃属性链表
struct device_node *parent; // 父节点
struct device_node *child; // 第一个子节点
struct device_node *sibling; // 兄弟节点
struct kobject kobj; // sysfs 接口
unsigned long _flags; // 状态标志
void *data; // 驱动私有数据
struct device_node *devtree_node; // 保留
};
关键理解: 每个设备树节点(如
i2c@7e804000)在内存中就是一个 device_node 实例。驱动通过 of_match_node() 或 of_device_is_compatible() 遍历这棵树来匹配设备。
4.2 属性数据结构:struct property
每个 device_node 携带一个或多个 struct property,用于存储节点中的属性名和值(如 compatible、reg、interrupts)。
struct property {
char *name; // 属性名,如 "compatible"
int length; // 属性值的字节长度
void *value; // 属性值的起始地址
struct property *next; // 指向下一个属性
unsigned long _flags;
unsigned int unique_id;
struct bin_attribute attr; // sysfs 属性
};
| 属性名 | 典型值示例 | 说明 |
|---|---|---|
compatible |
"brcm,bcm2835-i2c" |
驱动匹配的关键字符串 |
reg |
<0x7e804000 0x1000> |
基地址和地址范围 |
interrupts |
<0 2 4> |
中断号、触发类型 |
status |
"okay" 或 "disabled" |
设备启用状态 |
4.3 设备树内存结构全景图
下图展示了一个典型设备树节点(如 UART)在内存中的完整表示:
4.4 of_* API 函数族
内核提供了一套以 of_ 前缀命名的 API,用于从 device_node 和 property 中读取设备树信息。这些 API 是 Platform 驱动开发中最常用的工具。
4.4.1 节点查找与遍历
| 函数原型 | 功能说明 |
|---|---|
of_find_node_by_name() |
根据节点名查找节点 |
of_find_node_by_type() |
根据 device_type 查找节点 |
of_find_node_by_path() |
根据完整路径查找节点 |
of_find_compatible_node() |
根据 compatible 字符串查找节点 |
of_get_parent() |
获取当前节点的父节点 |
of_get_next_child() |
遍历当前节点的子节点 |
4.4.2 属性读取
| 函数原型 | 功能说明 |
|---|---|
of_get_property() |
获取指定属性的值指针 |
of_property_read_u32() |
读取 32 位整型属性 |
of_property_read_u64() |
读取 64 位整型属性 |
of_property_read_string() |
读取字符串属性 |
of_property_read_u32_array() |
读取整型数组属性 |
of_n_addr_cells() |
获取地址单元数(#address-cells) |
of_n_size_cells() |
获取大小单元数(#size-cells) |
4.4.3 匹配与转换
| 函数原型 | 功能说明 |
|---|---|
of_match_node() |
将设备节点与 of_device_id 匹配表进行匹配 |
of_device_is_compatible() |
检查节点是否包含指定的 compatible 字符串 |
of_device_is_available() |
检查节点 status 是否为 "okay" |
of_iomap() |
将 reg 属性中的地址范围映射到虚拟地址空间 |
of_irq_parse_one() |
解析节点中断信息 |
实战要点: 在 probe 函数中,通常使用
of_match_device() 获取匹配的 of_device_id,然后通过 of_iomap() 映射寄存器地址,通过 of_property_read_u32() 读取自定义属性。这些 API 返回错误码时,驱动应返回 -EINVAL 或 -ENODEV。
4.5 of_* API 调用流程
下图展示了一个 Platform 驱动 probe 函数中典型的 of_* API 调用流程:
4.6 实战代码示例:在 probe 中使用 of_* API
static int my_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource *res;
void __iomem *base;
u32 my_param;
int irq, ret;
// 1. 检查设备节点是否可用
if (!of_device_is_available(np)) {
dev_err(dev, "device is disabled\n");
return -ENODEV;
}
// 2. 读取自定义属性
ret = of_property_read_u32(np, "my,param", &my_param);
if (ret) {
dev_err(dev, "failed to read my,param\n");
return -EINVAL;
}
// 3. 映射寄存器地址
base = of_iomap(np, 0);
if (!base) {
dev_err(dev, "failed to iomap\n");
return -ENOMEM;
}
// 4. 解析中断
irq = of_irq_parse_one(np, 0, &res);
if (irq < 0) {
dev_err(dev, "failed to parse irq\n");
iounmap(base);
return irq;
}
// 5. 注册设备(略)
dev_info(dev, "probe success: param=%d, irq=%d\n", my_param, irq);
return 0;
}
常见错误:
- 忘记检查
of_device_is_available(),导致驱动在status = "disabled"的节点上仍尝试 probe。 of_iomap()返回 NULL 后未释放已分配的资源,造成内存泄漏。- 使用
of_get_property()后未检查返回值是否为 NULL,直接解引用导致内核崩溃。
4.7 本章小结
- struct device_node 是设备树节点在内核中的核心表示,通过 parent/child/sibling 指针构成树形结构。
- struct property 存储节点属性,通过链表挂接到 device_node 上。
- of_* API 函数族 提供了节点查找、属性读取、匹配转换等完整操作接口,是 Platform 驱动开发的基础工具。
- 在 probe 函数中,典型的调用顺序为:匹配检查 → 资源映射 → 属性读取 → 中断解析 → 设备注册。