4、设备树在Linux内核中的表示:struct device_node、struct property、OF API简介
设备树源文件(.dts)经过编译生成二进制.dtb后,由引导加载程序传递给内核。内核在启动早期对.dtb进行解析,将其中的节点、属性和值展开为一套标准的内核数据结构。这套数据结构是后续所有设备驱动进行资源查询、匹配和获取的基础。本章深入剖析设备树在内核中的核心表示:struct device_node、struct property,以及操作它们的OF(Open Firmware)API。
4.1 核心数据结构:struct device_node
每一个设备树节点({ }包围的实体)在内核中都被抽象为一个struct device_node实例。该结构体定义在include/linux/of.h中,是驱动开发者最常接触的设备树对象。
| 成员 | 类型 | 说明 |
|---|---|---|
name |
const char * |
节点名称,取自node-name部分,不含@unit-address |
type |
const char * |
设备类型,通常对应device_type属性值(PowerPC常用,ARM较少) |
phandle |
phandle |
节点的phandle值,用于节点间引用 |
full_name |
const char * |
从根到当前节点的完整路径名,如/soc/i2c@7e205000 |
properties |
struct property * |
指向该节点属性链表的头指针 |
parent |
struct device_node * |
指向父节点 |
child |
struct device_node * |
指向第一个子节点 |
sibling |
struct device_node * |
指向下一个兄弟节点 |
_flags |
unsigned long |
节点状态标志(如OF_DYNAMIC、OF_POPULATED) |
data |
void * |
平台私有数据指针,驱动可附加自定义数据 |
关键理解: device_node通过parent、child、sibling三个指针将整个设备树组织成一棵多叉树。驱动遍历设备树时,实际上就是在遍历这个指针网络。
4.2 属性表示:struct property
每个节点下的属性(key = value;)由struct property描述。属性是驱动获取硬件参数(寄存器地址、中断号、GPIO等)的直接来源。
| 成员 | 类型 | 说明 |
|---|---|---|
name |
char * |
属性名称,如compatible、reg、interrupts |
length |
int |
属性值的字节长度 |
value |
void * |
指向属性值的原始二进制数据 |
next |
struct property * |
指向下一个属性,形成单链表 |
注意: value指向的是原始字节流。对于reg、interrupts等数值型属性,数据采用大端(Big-Endian)字节序存储。驱动使用OF API读取时,API会自动完成字节序转换。
4.3 设备树在内核中的整体结构
下图展示了device_node与property如何共同构成内核中的设备树视图:
4.4 OF API 分类与核心函数
OF API是驱动与设备树数据结构交互的唯一接口。根据功能,可将其分为五大类:
| 分类 | 核心函数 | 功能描述 |
|---|---|---|
| 节点查找 | of_find_node_by_name()of_find_node_by_type()of_find_compatible_node()of_find_node_by_phandle() |
根据名称、类型、compatible或phandle遍历查找节点 |
| 属性读取 | of_property_read_u32()of_property_read_u64()of_property_read_string()of_property_read_u32_array() |
从节点属性中读取整型、字符串、数组等数据 |
| 资源获取 | of_iomap()of_address_to_resource()irq_of_parse_and_map() |
将reg、interrupts属性转换为内核资源 |
| 匹配检查 | of_match_node()of_device_is_compatible() |
检查节点是否与of_device_id匹配表匹配 |
| 遍历迭代 | for_each_child_of_node()for_each_property_of_node() |
宏封装,方便遍历子节点或属性链表 |
4.5 典型使用模式:从节点读取资源
以下代码片段展示了一个platform驱动probe函数中,如何使用OF API从设备节点获取内存地址和中断号:
static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
struct resource *res;
void __iomem *base;
int irq, ret;
u32 val;
/* 1. 读取reg属性,获取IO内存资源 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(dev, "failed to get MEM resource\n");
return -ENXIO;
}
base = devm_ioremap_resource(dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
/* 2. 读取interrupts属性,获取中断号 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
/* 3. 直接读取自定义属性 */
ret = of_property_read_u32(np, "my-device,clock-frequency", &val);
if (ret)
dev_warn(dev, "clock-frequency not found, use default\n");
/* 4. 检查compatible是否包含特定字符串 */
if (of_device_is_compatible(np, "my-device,rev2"))
dev_info(dev, "Running on rev2 hardware\n");
return 0;
}
常见陷阱:
- 不要直接访问
device_node->properties链表:应始终使用OF API,避免破坏内核内部结构。 - 注意引用计数:
of_find_node_by_*()系列函数会增加节点引用计数,使用完毕后需调用of_node_put()释放。 - platform_get_resource() vs of_iomap():在platform驱动中,优先使用
platform_get_resource(),它内部已封装OF解析逻辑;直接使用of_iomap()会绕过platform资源管理。
4.6 设备树节点与platform_device的关联
在内核启动后期,of_platform_populate()函数遍历设备树中所有compatible属性不为空的节点,为每个节点创建一个struct platform_device。该platform_device的dev.of_node指针指向对应的device_node。这就是驱动probe函数中pdev->dev.of_node的来源。
核心关系链:
.dts节点 → device_node → platform_device.dev.of_node → OF API → 驱动获取资源