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 设备树语法结构图
下图展示了设备树从根节点到具体外设的层级关系,以及节点与属性的对应结构:
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() |
引用其他节点(时钟、中断控制器、电源域) |
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 设备树在功耗调优中的协同机制
设备树为驱动提供了硬件功耗参数的静态描述,驱动在初始化阶段读取这些属性,动态调整功耗策略。下图展示了从设备树到驱动协同的功耗调优数据流:
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" 以避免驱动加载和时钟使能。