2. 设备树语法详解:节点、属性、标签、包含与宏定义
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点和键值对属性来描述系统中所有可寻址设备。本章将深入解析设备树源文件(.dts / .dtsi)的核心语法要素,包括节点、属性、标签、包含机制以及宏定义的使用技巧。掌握这些语法是编写正确、可维护的设备树文件的基础。
2.1 节点(Node)
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或功能块。节点通过路径(path)在树中唯一标识,路径由根节点(/)开始,使用斜杠分隔。
2.1.1 节点定义格式
node-name@unit-address {
property-name = <value>;
child-node {
...
};
};
- node-name:节点名称,通常反映设备类型(如
cpu、memory、uart)。 - unit-address:可选,表示设备在总线上的地址(如寄存器基址、片选号),用于区分同名节点。
- 属性:节点内部定义的键值对,描述设备特性。
- 子节点:节点可以嵌套,形成层次结构。
2.1.2 节点命名规则
| 规则 | 说明 | 示例 |
|---|---|---|
| 通用名称 | 使用标准设备类别名,如 serial、gpio、spi |
serial@ff000000 |
| 地址后缀 | 当同一总线有多个同类设备时,必须加地址区分 | i2c@0、i2c@1 |
| 特殊节点 | / 为根节点,aliases、chosen 为系统节点 |
/ { ... }; |
device-type@address 格式,避免使用过于具体的芯片型号作为节点名,以增强设备树的可移植性。
2.2 属性(Property)
属性是节点中存储设备信息的键值对。设备树规范定义了多种标准属性,同时也允许厂商自定义属性。
2.2.1 常用标准属性
| 属性名 | 值类型 | 用途 |
|---|---|---|
compatible |
字符串列表 | 设备驱动匹配的关键标识,格式为 "厂商,型号" |
reg |
32位整数对列表 | 描述设备地址空间(基址 + 长度) |
interrupts |
32位整数列表 | 描述中断号及触发类型 |
clocks |
phandle + 整数 | 引用时钟控制器及输出索引 |
status |
字符串 | 设备状态:"okay"、"disabled"、"reserved" |
#address-cells |
32位整数 | 子节点 reg 属性中地址字段的单元数 |
#size-cells |
32位整数 | 子节点 reg 属性中长度字段的单元数 |
2.2.2 属性值编码格式
// 字符串
compatible = "vendor,device";
// 32位整数(一个或多个)
reg = <0x10000000 0x1000>;
// 64位整数(使用两个32位单元)
reg = <0x00000000 0x80000000 0x00000000 0x10000000>;
// 字节数组
local-mac-address = [00 11 22 33 44 55];
// 布尔属性(存在即为真)
interrupt-controller;
// 混合类型
interrupts = <0 59 4>;
2.3 标签(Label)
标签是附加在节点或属性上的符号名称,用于在设备树的其他位置引用该节点。标签在编译时被解析为 phandle(指针句柄),是设备树实现节点间引用的核心机制。
2.3.1 标签定义与引用
// 定义标签
uart0: serial@ff000000 {
compatible = "vendor,uart";
reg = <0xff000000 0x1000>;
interrupts = <0 33 4>;
};
// 在其他节点中引用标签
amba-bus {
uart-phandle = <&uart0>; // 通过 & 引用标签
};
2.3.2 标签的典型用途
- 驱动绑定:在
chosen节点或板级文件中引用外设节点。 - 时钟/中断/GPIO 引用:通过
&label获取控制器节点句柄。 - 覆盖(overlay):在设备树覆盖文件中通过标签定位要修改的节点。
.dtsi 文件中不能定义同名标签,否则会导致编译冲突。
2.4 包含(Include)
设备树源文件支持 #include 预处理指令,用于将公共定义、SoC 描述文件或头文件包含到当前文件中。这是实现设备树模块化和复用的关键机制。
2.4.1 包含机制的使用
// 包含 SoC 公共定义
#include "soc.dtsi"
// 包含头文件(用于宏定义)
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
// 包含板级特定配置
#include "board-common.dtsi"
2.4.2 包含文件的层次结构
| 文件类型 | 扩展名 | 内容 | 复用范围 |
|---|---|---|---|
| SoC 描述文件 | .dtsi |
CPU、内存控制器、外设总线、通用 IP 核 | 同一 SoC 系列所有板卡 |
| 板级公共文件 | .dtsi |
板级外设(如以太网 PHY、音频编解码器) | 同一参考设计的不同变体 |
| 板级最终文件 | .dts |
板级特有配置、GPIO 复用、设备使能 | 单一具体板卡 |
| 头文件 | .h |
宏定义、常量、位掩码 | 所有设备树文件 |
2.5 宏定义
设备树源文件通过 C 预处理器支持宏定义,这极大地提高了代码的可读性和可维护性。宏定义通常放在 .h 头文件中,通过 #include 引入。
2.5.1 常用宏定义示例
// gpio.h
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW 1
// irq.h
#define IRQ_TYPE_EDGE_RISING 1
#define IRQ_TYPE_EDGE_FALLING 2
#define IRQ_TYPE_LEVEL_HIGH 4
#define IRQ_TYPE_LEVEL_LOW 8
// 在 dts 中使用宏
gpio-keys {
compatible = "gpio-keys";
button@0 {
label = "Power";
gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
interrupts = <IRQ_TYPE_EDGE_FALLING>;
};
};
2.5.2 宏定义的优势
- 消除魔法数字:用有意义的名称替代裸数字,如
GPIO_ACTIVE_LOW代替0。 - 统一管理:所有板卡共享同一套宏定义,避免重复定义和笔误。
- 与内核驱动对齐:设备树中的宏定义与内核头文件中的定义保持一致,确保匹配。
dt-bindings/ 目录下内核提供的标准宏定义。对于厂商私有属性,可以在板级 .h 文件中自定义宏,但需在驱动中同步维护。
2.6 设备树语法结构总览
下图展示了设备树源文件的完整语法结构,从根节点到子节点、属性、标签以及包含关系的整体视图。
2.7 节点引用与覆盖
设备树支持通过标签引用节点,并在后续文件中覆盖或追加属性。这种机制使得 SoC 公共描述与板级特定配置可以分离,极大提高了复用性。
2.7.1 引用与覆盖语法
// soc.dtsi - SoC 公共定义
uart0: serial@ff000000 {
compatible = "vendor,uart";
reg = <0xff000000 0x1000>;
status = "disabled"; // 默认关闭
};
// board.dts - 板级文件
#include "soc.dtsi"
&uart0 {
status = "okay"; // 覆盖 status 属性
clock-frequency = <24000000>; // 追加新属性
};
2.7.2 覆盖规则
| 操作 | 语法 | 效果 |
|---|---|---|
| 覆盖属性值 | &label { prop = <new>; }; |
用新值替换原属性值 |
| 追加属性 | &label { new-prop = <val>; }; |
在节点中增加新属性 |
| 追加子节点 | &label { child { ... }; }; |
在节点下增加子节点 |
| 删除属性 | /delete-property/ prop; |
从节点中移除指定属性 |
| 删除节点 | /delete-node/ &label; |
从树中移除整个节点 |
.dtsi 文件中将所有外设节点的 status 默认设为 "disabled",然后在板级 .dts 文件中通过 &label 引用并设置为 "okay"。这样可以避免未使用的外设占用资源。
2.8 语法检查与编译
设备树源文件需要通过 dtc(Device Tree Compiler)编译为二进制 .dtb 文件。编译过程中会进行语法检查、标签解析和宏展开。
2.8.1 编译命令
# 编译 dts 为 dtb
dtc -I dts -O dtb -o board.dtb board.dts
# 反编译 dtb 为 dts(用于调试)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb
# 检查语法错误
dtc -I dts -O dtb -o /dev/null board.dts 2>&1
2.8.2 常见编译错误
- 标签重复:多个文件定义了同名标签。
- 属性值类型不匹配:如字符串属性被赋值为整数。
- 地址单元不匹配:子节点
reg的单元数与父节点#address-cells/#size-cells不一致。 - phandle 引用无效:
&label引用了不存在的标签。
dtc -O dts -o /dev/stdout board.dtb 可以查看编译后的设备树内容,确认属性值是否被正确解析。如果内核启动时设备树解析失败,可以通过 CONFIG_OF_UNITTEST 启用设备树单元测试。
2.9 本章小结
设备树语法是嵌入式 Linux 驱动开发的基础技能。节点定义硬件拓扑,属性描述设备特性,标签实现跨文件引用,包含机制促进代码复用,宏定义提升可读性。掌握这些语法要素后,开发者可以编写出结构清晰、易于维护的设备树文件,为后续的驱动开发和内核集成打下坚实基础。