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)在内存中的完整表示:

设备树内存结构:device_node + property 关系图 根节点 "/" soc 节点 uart@7e201000 compatible reg interrupts 说明 • 每个 device_node 通过 parent/child/sibling 指针构成树形结构 • 每个 device_node 通过 properties 指针指向属性链表 • 属性链表中的每个 property 包含 name、length、value 和 next 指针

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 调用流程:

probe 函数中 of_* API 典型调用流程 probe 入口 of_match_device() of_iomap() of_property_read_u32() of_irq_parse_one() 注册设备成功 匹配检查 检查 compatible 是否匹配 返回 of_device_id 指针 资源获取 映射寄存器地址 读取自定义属性 解析中断号

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 函数中,典型的调用顺序为:匹配检查 → 资源映射 → 属性读取 → 中断解析 → 设备注册。