3. 设备树语法基础
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、总线、外设等硬件信息传递给操作系统内核。本章将深入解析设备树源文件(.dts)的核心语法要素:节点、属性、标签和路径,为后续的驱动移植打下坚实基础。
学习提示:设备树语法是嵌入式Linux驱动开发的“硬件语言”。掌握本节内容后,你将能够读懂并修改任何主流开发板的设备树文件。
3.1 节点(Node)—— 设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线或控制器。节点通过父子关系形成一棵倒置的树,根节点是 /。
3.1.1 节点定义格式
node-name {
property1 = <value>;
property2 = "string";
child-node {
...
};
};
- 节点名称:由字母、数字、连字符(
-)、下划线(_)组成,必须以字母开头。 - 节点体:用花括号
{ }包裹,内部包含属性和子节点。 - 结束符:每个节点定义后必须加分号
;。
3.1.2 节点命名规范
| 规则类别 | 说明 | 合法示例 | 非法示例 |
|---|---|---|---|
| 通用名称 | 使用小写字母、数字、连字符 | i2c-controller |
I2C_Controller |
| 地址后缀 | 总线设备节点名可包含@地址 | uart@fe001000 |
uart@0xfe001000 |
| 长度限制 | 建议不超过31个字符 | ethernet-phy@1 |
this-is-a-very-long-node-name-example |
关键规则:同一层级下的节点名称必须唯一。如果两个节点同名但地址不同(如
uart@fe001000 和 uart@fe002000),则被视为不同节点。
3.1.3 节点层次结构示例
/ {
model = "Embedded Learning Board v1.0";
compatible = "vendor,board";
cpu {
compatible = "arm,cortex-a7";
clock-frequency = <1000000000>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
uart@fe001000 {
compatible = "vendor,uart";
reg = <0xfe001000 0x1000>;
interrupts = <0 33 4>;
};
i2c@fe002000 {
compatible = "vendor,i2c";
reg = <0xfe002000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
};
};
};
3.2 属性(Property)—— 描述设备的语言
属性是附着在节点上的键值对,用于描述设备的特性、地址、中断、时钟等信息。每个属性由属性名和属性值组成。
3.2.1 属性值类型
| 值类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | "string" |
compatible = "vendor,device"; |
双引号包裹,用于设备兼容性标识 |
| 32位整数 | <value> |
clock-frequency = <50000000>; |
尖括号包裹,用于地址、频率等数值 |
| 64位整数 | <high low> |
reg = <0x0 0x80000000>; |
两个32位整数组合表示64位值 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
方括号包裹十六进制字节 |
| 字符串列表 | "str1", "str2" |
compatible = "vendor,device", "generic,device"; |
逗号分隔多个字符串 |
| 布尔值 | 仅属性名,无值 | status = "disabled"; |
存在即表示真,常用 status 属性 |
3.2.2 标准属性详解
compatible:最重要的属性,用于操作系统匹配驱动。格式为"厂商,设备型号",如"ti,am335x-uart"。reg:描述设备地址空间,格式为<基址 长度>,受#address-cells和#size-cells控制。interrupts:描述中断信息,格式依赖interrupt-parent和#interrupt-cells。status:设备状态,常用值"okay"(启用)、"disabled"(禁用)。model:设备型号描述字符串。
常见错误:忘记在属性值后加分号
; 是设备树编译错误的最常见原因。每个属性定义必须以分号结束。
3.3 标签(Label)—— 节点的别名
标签是为节点定义的别名,允许在设备树的其他位置通过 &label 引用该节点。标签极大简化了设备树的修改和扩展。
3.3.1 标签定义语法
uart1: uart@fe001000 {
compatible = "vendor,uart";
reg = <0xfe001000 0x1000>;
};
上述代码中,uart1 是标签,uart@fe001000 是节点名称。标签必须放在节点名称之前,用冒号分隔。
3.3.2 标签引用与覆盖
// 在另一个 .dtsi 文件中引用并扩展
&uart1 {
status = "okay";
clock-frequency = <115200>;
};
通过 &uart1 引用标签,可以向原节点添加新属性或覆盖已有属性。这是设备树分层设计的核心机制。
3.3.3 标签命名规范
| 规范 | 说明 | 示例 |
|---|---|---|
| 前缀标识类型 | 使用设备类型缩写作为前缀 | uart1, i2c0, spi2 |
| 避免关键字 | 不要使用设备树关键字 | 避免 status, reg, compatible |
| 全局唯一 | 整个设备树中标签必须唯一 | 不能有两个 uart1 |
3.4 路径(Path)—— 定位节点的地址
路径是从根节点到目标节点的完整路径字符串,用于在设备树中唯一标识一个节点。路径语法类似于文件系统的目录路径。
3.4.1 路径语法规则
- 绝对路径:以根节点
/开头,如/soc/i2c@fe002000/eeprom@50。 - 路径分隔符:使用斜杠
/分隔各级节点名称。 - 节点名称包含地址:如果节点名包含
@地址后缀,路径中必须包含完整名称。 - 路径引用:在
.dts文件中,可以使用&{/path/to/node}语法引用路径。
3.4.2 路径使用示例
// 路径引用示例
&{/soc/uart@fe001000} {
status = "okay";
};
// 在设备树覆盖文件中使用路径
/fragment@0 {
target-path = "/soc/i2c@fe002000";
__overlay__ {
eeprom@50 {
compatible = "atmel,24c02";
};
};
};
3.4.3 路径与标签的对比
| 特性 | 标签(Label) | 路径(Path) |
|---|---|---|
| 定义方式 | 在节点名前定义,如 uart1: |
由节点层次自动生成 |
| 唯一性 | 全局唯一,需手动保证 | 全局唯一,由树结构保证 |
| 可读性 | 简短,适合频繁引用 | 较长,但表达完整层次 |
| 使用场景 | 驱动匹配、设备树覆盖 | 设备树片段、动态加载 |
| 引用语法 | &uart1 |
&{/soc/uart@fe001000} |
最佳实践:在编写设备树时,优先使用标签引用节点,因为标签更简短且不易出错。路径引用通常用于设备树覆盖(DT Overlay)或无法预定义标签的场景。
3.5 综合示例:完整设备树片段
以下示例综合展示了节点、属性、标签和路径的协同使用:
// 基础定义文件:board.dtsi
/ {
model = "Embedded Learning Board";
compatible = "vendor,board";
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
uart0: uart@fe001000 {
compatible = "vendor,uart";
reg = <0xfe001000 0x1000>;
interrupts = <0 33 4>;
status = "disabled";
};
i2c0: i2c@fe002000 {
compatible = "vendor,i2c";
reg = <0xfe002000 0x1000>;
#address-cells = <1>;
#size-cells = <0>;
status = "disabled";
};
};
};
// 板级配置:board.dts
#include "board.dtsi"
&uart0 {
status = "okay";
clock-frequency = <115200>;
};
&i2c0 {
status = "okay";
clock-frequency = <100000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <8>;
};
};
// 路径引用示例
&{/soc/i2c@fe002000/eeprom@50} {
compatible = "atmel,24c02", "microchip,24c02";
};
3.6 常见错误与调试技巧
编译错误排查:使用
dtc -I dts -O dtb -o output.dtb input.dts 编译设备树时,常见错误包括:
- 缺少分号
;—— 每个属性和节点定义后必须加分号。 - 括号不匹配 —— 花括号
{}必须成对出现。 - 标签重复 —— 整个设备树中标签必须唯一。
- 路径错误 —— 路径中的节点名称必须与实际定义完全一致。
调试技巧:使用
dtc -I dtb -O dts -o decompiled.dts boot.dtb 反编译设备树二进制文件,可以验证编译结果是否符合预期。同时,fdtdump 工具可以快速查看设备树内容。