3. 设备树基础

设备树(Device Tree,DT)是嵌入式 Linux 系统中描述硬件拓扑与资源分配的核心数据结构。它替代了传统板级文件(board-*.c)中硬编码的硬件描述,实现了内核镜像与硬件配置的解耦。在功耗调优场景中,设备树是驱动获取电源域、时钟频率、中断唤醒能力等功耗相关参数的入口。本章将系统讲解设备树的语法结构、节点与属性定义,以及 reg、interrupts、clocks 等标准属性的使用规范。

核心认知:设备树不是编程语言,而是一种键值对描述的硬件配置表。驱动通过设备树匹配节点,读取属性值来初始化硬件资源,包括功耗相关的电压、频率、休眠策略等。

3.1 设备树语法基础

设备树源文件(.dts)采用树形层级结构,以节点(node)和属性(property)为基本单元。每个节点代表一个硬件设备或总线,属性描述该设备的配置参数。

3.1.1 节点定义与层级

节点以 节点名@地址 的形式定义,地址部分通常对应设备在总线上的基地址或片选号。节点内可嵌套子节点,形成从根节点到具体外设的完整路径。

/dts-v1/;

/ {
    model = "Embedded Power Optimized Board";
    compatible = "vendor,board-rev1";

    cpu@0 {
        device_type = "cpu";
        reg = <0x00>;
        operating-points = <
            996000  1100000
            800000  1000000
            400000   900000
        >;
    };

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        uart@10000000 {
            compatible = "vendor,uart-ip";
            reg = <0x10000000 0x1000>;
            interrupts = <0 33 4>;
            clocks = <&clk_uart 0>;
        };

        i2c@10001000 {
            compatible = "vendor,i2c-ctrl";
            reg = <0x10001000 0x1000>;
            #address-cells = <1>;
            #size-cells = <0>;

            pmic@3c {
                compatible = "vendor,pmic";
                reg = <0x3c>;
                regulators {
                    vcore: buck1 {
                        regulator-min-microvolt = <800000>;
                        regulator-max-microvolt = <1350000>;
                    };
                };
            };
        };
    };
};
功耗调优视角:上例中 cpu@0 节点的 operating-points 属性定义了不同频率对应的核心电压,驱动通过此表实现动态电压频率调整(DVFS),是 CPU 功耗优化的关键数据来源。

3.1.2 设备树语法结构图

下图展示了设备树从根节点到具体外设的层级关系,以及节点与属性的对应结构:

设备树层级结构与节点属性关系图 根节点 “/” model, compatible #address-cells, #size-cells cpu@0 device_type = "cpu" reg = <0x00> operating-points = <...> soc uart@10000000 compatible = "vendor,uart-ip" reg = <0x10000000 0x1000> interrupts = <0 33 4> clocks = <&clk_uart 0> i2c@10001000 pmic@3c vcore: buck1 regulator-min-microvolt regulator-max-microvolt = <800000> / <1350000> 设备节点 属性键值对 父子关系

3.2 节点与属性详解

3.2.1 节点命名与标签

节点形式 示例 说明
简单节点名 cpu 仅包含名称,适用于唯一设备
节点名@地址 uart@10000000 地址部分为寄存器基地址或总线片选号
带标签节点 vcore: buck1 标签 vcore 可在其他节点中通过 &vcore 引用
路径引用 &uart0 在 .dtsi 文件中通过标签引用节点进行覆盖或追加属性

3.2.2 属性类型与编码

设备树属性支持多种数据类型,驱动通过 of_property_read_* 系列 API 读取:

属性类型 DTS 表示 C 语言读取 API 典型用途
32 位整数 reg = <0x1000>; of_property_read_u32() 地址、中断号、时钟 ID
64 位整数 reg = <0x00000000 0x10000000>; of_property_read_u64() 大地址空间基址
字符串 compatible = "vendor,device"; of_property_read_string() 设备兼容性匹配
布尔值 status = "disabled"; of_device_is_available() 设备启用/禁用控制
字节数组 local-mac-address = [00 11 22 33 44 55]; of_property_read_u8_array() MAC 地址、密钥
phandle 引用 clocks = <&clk_uart 0>; of_parse_phandle() 引用其他节点(时钟、中断控制器、电源域)
功耗调优陷阱:属性中的数值单位必须与驱动预期一致。例如电压值通常以微伏(µV)为单位,频率以千赫兹(kHz)为单位。单位错误会导致 DVFS 配置失效,造成性能下降或功耗异常升高。

3.3 标准属性详解

3.3.1 reg 属性:地址空间描述

reg 属性定义设备在总线上的地址范围,由 #address-cells 和 #size-cells 决定每个地址和长度的单元数。对于功耗调优,reg 通常指向电源管理单元的寄存器基址。

// 32 位地址空间,1 个地址单元 + 1 个长度单元
#address-cells = <1>;
#size-cells = <1>;

pmu@20000000 {
    reg = <0x20000000 0x1000>;  // 基址 0x20000000,长度 0x1000
};

// 64 位地址空间,2 个地址单元 + 2 个长度单元
#address-cells = <2>;
#size-cells = <2>;

dma@0x400000000 {
    reg = <0x00000004 0x00000000 0x0 0x1000>;  // 基址 0x400000000,长度 0x1000
};

3.3.2 interrupts 属性:中断资源描述

interrupts 属性描述设备的中断号、触发类型和中断域。中断控制器通过 interrupt-parent 属性关联。在功耗调优中,中断唤醒能力(如 IRQF_TRIGGER_LOW 电平唤醒)直接影响系统休眠功耗。

中断描述符格式 示例 含义
单中断控制器 interrupts = <33 4>; 中断号 33,触发类型 4(高电平触发)
多中断域 interrupts = <0 33 4>; 中断域 0,中断号 33,触发类型 4
中断触发类型编码 4 表示高电平触发 1=上升沿, 2=下降沿, 4=高电平, 8=低电平
// 中断控制器定义
gic: interrupt-controller@10400000 {
    compatible = "arm,gic-v3";
    reg = <0x10400000 0x10000>;
    interrupt-controller;
    #interrupt-cells = <3>;  // 域、中断号、触发类型
};

// 外设使用中断
wakeup-timer@30000000 {
    compatible = "vendor,wake-timer";
    reg = <0x30000000 0x100>;
    interrupt-parent = <&gic>;
    interrupts = <0 89 8>;  // 域0, 中断号89, 低电平触发(唤醒源)
};
功耗调优技巧:将外设中断配置为低电平触发(8)或下降沿触发(2),可以在系统休眠时通过电平变化唤醒 CPU,避免轮询带来的功耗开销。

3.3.3 clocks 属性:时钟资源描述

clocks 属性通过 phandle 引用时钟控制器,并携带时钟 ID 或索引。时钟频率和门控状态直接影响设备动态功耗。驱动通过 clk_prepare_enable() 和 clk_disable_unprepare() 控制时钟开关。

// 时钟控制器定义
clk_uart: clock-controller@20001000 {
    compatible = "vendor,uart-clk";
    reg = <0x20001000 0x100>;
    #clock-cells = <1>;  // 1 个时钟 ID
    clocks = <&osc 0>;   // 父时钟为外部晶振
};

// UART 设备引用时钟
uart@10000000 {
    compatible = "vendor,uart-ip";
    reg = <0x10000000 0x1000>;
    interrupts = <0 33 4>;
    clocks = <&clk_uart 0>;  // 使用 clk_uart 的 0 号时钟输出
    clock-names = "baud";       // 驱动通过名称获取时钟
};

3.3.4 其他功耗相关标准属性

属性名 类型 功耗调优用途
power-domains phandle + 参数 关联电源域控制器,实现设备级电源门控
operating-points 频率-电压对数组 定义 DVFS 工作点,驱动据此调节性能与功耗
wakeup-source 布尔值 标记设备可作为系统唤醒源,影响休眠流程
status 字符串 "okay" 启用设备,"disabled" 禁用设备以节省功耗
regulator-* 整数/布尔 调节器电压范围、电流限制,控制外设供电

3.4 设备树在功耗调优中的协同机制

设备树为驱动提供了硬件功耗参数的静态描述,驱动在初始化阶段读取这些属性,动态调整功耗策略。下图展示了从设备树到驱动协同的功耗调优数据流:

设备树 → 驱动协同功耗调优数据流 设备树节点 clocks / power-domains / OPP of_property_read 驱动 probe 阶段 解析属性,初始化资源 clk_prepare_enable 运行时功耗管理 DVFS / 时钟门控 / 电源域 系统休眠流程 suspend / resume 回调 suspend 触发 唤醒源判断 wakeup-source 属性 device_may_wakeup 功耗优化结果 动态调频降压 / 外设时钟关闭 / 选择性唤醒

3.5 设备树编译与调试

设备树源文件(.dts)通过 DTC(Device Tree Compiler)编译为二进制设备树 Blob(.dtb),内核在启动时解析 .dtb 构建设备树。调试设备树常用方法:

  • 查看当前设备树: ls /sys/firmware/devicetree/base/ 可遍历内核中的设备树节点。
  • 反编译 DTB: dtc -I dtb -O dts -o output.dts input.dtb 将二进制转换为可读的 DTS。
  • 验证语法: dtc -I dts -O dtb -o /dev/null input.dts 仅检查语法错误。
  • 驱动调试: 在驱动中添加 dev_info(dev, "clock rate: %lu\n", clk_get_rate(clk)); 确认时钟属性解析是否正确。
功耗调优检查清单:在完成设备树编写后,务必检查以下功耗相关属性是否配置正确:
1. 所有外设的 clocks 是否引用正确的时钟控制器和时钟 ID。
2. 支持 DVFS 的设备是否定义了完整的 operating-points 表。
3. 可作为唤醒源的设备是否添加了 wakeup-source 属性。
4. 不使用的设备是否设置为 status = "disabled" 以避免驱动加载和时钟使能。