2. 设备树语法入门:节点、属性、标签与包含文件
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给操作系统内核。在嵌入式Linux开发中,设备树源文件(.dts / .dtsi)是连接硬件与驱动的核心桥梁。本节将系统讲解设备树的基本语法要素:节点、属性、标签和包含文件。
2.1 设备树的基本结构
设备树从根节点 / 开始,向下展开为多级子节点,每个节点代表一个硬件设备或总线。节点内通过属性描述设备的寄存器地址、中断号、时钟频率、GPIO引脚等参数。
2.2 节点(Node)
节点是设备树的基本组成单元,每个节点对应一个硬件设备或总线。节点可以嵌套,形成父子层次关系。
2.2.1 节点定义语法
node-name@unit-address {
property1 = <value>;
property2 = "string";
child-node {
...
};
};
| 语法元素 | 说明 | 示例 |
|---|---|---|
| node-name | 节点名称,描述设备类型 | uart, i2c, gpio |
| @unit-address | 设备地址(寄存器基址或总线地址) | @ff010000, @50 |
| property | 属性,描述设备特性 | reg, interrupts |
| child-node | 子节点,嵌套定义 | eeprom, port |
2.2.2 节点地址与命名规范
- 节点名:使用小写字母、数字和连字符,如
serial,mmc-host。 - 地址部分:十六进制,无
0x前缀,如@ff020000。 - 同一层级:节点名+地址必须唯一,如
i2c@ff020000和i2c@ff030000。 - 特殊节点:
aliases、chosen、memory为系统保留节点名。
提示:节点地址部分不是必须的。当设备在总线上有唯一地址(如I2C从设备地址)或寄存器基址时,才需要添加
@unit-address。根节点和纯容器节点通常不带地址。
2.3 属性(Property)
属性是节点的数据载体,用于描述设备的硬件特性。设备树规范定义了多种属性类型。
2.3.1 属性值类型
| 类型 | 语法 | 示例 | 说明 |
|---|---|---|---|
| 空属性 | property; |
status = "disabled"; |
布尔标志,存在即为真 |
| 32位整数 | <value> |
<0x1000> |
单个或多个整数,空格分隔 |
| 64位整数 | <high low> |
<0x0 0x10000000> |
高32位 + 低32位 |
| 字符串 | "string" |
"okay" |
双引号包裹 |
| 字节数组 | [hex bytes] |
[00 01 02] |
十六进制字节,空格分隔 |
| 混合类型 | <...> "..." [...] |
<0x10> "irq" |
多种类型组合 |
2.3.2 常用标准属性
/ {
model = "Embedded Device Board v1.0"; // 字符串:板级型号
compatible = "vendor,board"; // 字符串:兼容性匹配
#address-cells = <1>; // 整数:地址宽度(32位)
#size-cells = <1>; // 整数:大小宽度(32位)
memory@80000000 {
device_type = "memory"; // 字符串:设备类型
reg = <0x80000000 0x40000000>; // 整数对:基址 + 大小
};
chosen {
bootargs = "console=ttyS0,115200"; // 字符串:内核启动参数
stdout-path = &uart0; // 引用:标准输出设备
};
};
2.3.3 属性使用注意事项
- compatible:驱动匹配的关键属性,格式为
"厂商,设备型号",如"fsl,imx6q-uart"。 - reg:描述设备地址空间,格式为
<基址1 大小1 基址2 大小2 ...>。 - status:设备状态,可选
"okay"、"disabled"、"reserved"。 - #address-cells / #size-cells:定义子节点
reg属性的地址和大小字段宽度。
注意:
#address-cells 和 #size-cells 必须在父节点中定义,影响所有子节点的 reg 属性解析。若未定义,默认值为 <2> 和 <1>。
2.4 标签(Label)
标签是设备树中用于引用节点的别名机制。通过标签,可以在设备树的其他位置方便地引用或修改节点。
2.4.1 标签定义与引用
// 定义标签:在节点名前加标签名 + 冒号
uart0: serial@ff010000 {
compatible = "vendor,uart";
reg = <0xff010000 0x1000>;
interrupts = <0 33 4>;
};
// 引用标签:使用 & 符号
&uart0 {
status = "okay";
clock-frequency = <115200>;
};
2.4.2 标签的作用
| 用途 | 说明 | 示例 |
|---|---|---|
| 节点引用 | 在 .dtsi 中定义节点,在 .dts 中通过标签追加属性 |
&i2c1 { status = "okay"; }; |
| 路径别名 | 在 aliases 节点中建立短名称 |
aliases { serial0 = &uart0; }; |
| 中断/时钟引用 | 在 interrupt-parent 或 clocks 属性中引用 |
interrupt-parent = &gic; |
| 覆盖(Override) | 在板级文件中修改 SoC 级节点的属性 | &gpio1 { pinctrl-0 = &pinctrl_gpio1; }; |
2.4.3 标签命名规范
- 使用小写字母、数字和下划线,如
uart0,i2c_bus1。 - 标签名应具有描述性,便于识别设备类型和实例。
- 避免使用关键字(如
status,reg)作为标签名。 - 同一设备树中标签必须唯一。
2.5 包含文件(.dtsi)
.dtsi 文件是设备树的包含文件,类似于 C 语言的 .h 头文件。它用于定义 SoC 级通用硬件描述,被多个板级 .dts 文件复用。
2.5.1 包含机制
// soc.dtsi — SoC 级通用定义
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
/ {
compatible = "vendor,soc";
cpu: cpu@0 {
compatible = "arm,cortex-a7";
device_type = "cpu";
reg = <0x0>;
};
gic: interrupt-controller@ffc01000 {
compatible = "arm,gic-400";
reg = <0xffc01000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
uart0: serial@ff010000 {
compatible = "vendor,uart";
reg = <0xff010000 0x1000>;
interrupts = <0 33 4>;
status = "disabled";
};
};
// board.dts — 板级文件
#include "soc.dtsi"
/ {
model = "My Board v2.0";
compatible = "vendor,myboard";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>;
};
};
&uart0 {
status = "okay";
pinctrl-0 = <&pinctrl_uart0>;
};
2.5.2 .dtsi 与 .dts 的分工
| 文件类型 | 内容定位 | 典型内容 | 复用范围 |
|---|---|---|---|
| .dtsi | SoC 级通用定义 | CPU、中断控制器、外设控制器、时钟、GPIO 控制器 | 同一 SoC 的所有板级文件 |
| .dts | 板级具体配置 | 内存大小、外设使能、引脚复用、设备实例 | 单块电路板 |
2.5.3 包含文件的使用原则
- 分层设计:SoC 级定义放在
.dtsi,板级差异放在.dts。 - 标签覆盖:在
.dts中通过&label修改.dtsi中定义的节点属性。 - 头文件包含:使用
#include <dt-bindings/...>引入宏定义,如 GPIO 号、中断类型。 - 避免重复:不要在
.dts中重新定义.dtsi已定义的节点,应使用标签引用。
最佳实践:设备树文件组织应遵循“SoC 通用 → 系列通用 → 板级具体”的三层结构。例如:
soc.dtsi → soc-family.dtsi → board.dts。这样可最大化复用,减少冗余。
2.6 完整示例:GPIO 控制器与引脚配置
以下是一个包含 GPIO 控制器和 pinctrl 配置的完整设备树片段,综合运用了节点、属性、标签和包含文件。
// gpio-controller.dtsi
#include <dt-bindings/gpio/gpio.h>
/ {
soc {
gpio0: gpio@ff020000 {
compatible = "vendor,gpio-controller";
reg = <0xff020000 0x1000>;
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
};
pinctrl: pinctrl@ff030000 {
compatible = "vendor,pinctrl";
reg = <0xff030000 0x1000>;
pinctrl-0 = <&pinctrl_default>;
pinctrl_uart0: uart0-pins {
function = "uart0";
pins = "GPIO0_A0", "GPIO0_A1";
bias-pull-up;
};
pinctrl_i2c0: i2c0-pins {
function = "i2c0";
pins = "GPIO0_B0", "GPIO0_B1";
drive-strength = <4>;
};
};
};
};
// board.dts
#include "gpio-controller.dtsi"
/ {
model = "GPIO Demo Board";
compatible = "vendor,gpio-demo";
leds {
compatible = "gpio-leds";
led-red {
label = "red";
gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
};
};
};
&uart0 {
pinctrl-0 = <&pinctrl_uart0>;
pinctrl-names = "default";
status = "okay";
};
关键点:
gpio-controller 和 #gpio-cells 属性声明该节点为 GPIO 控制器,#gpio-cells = <2> 表示引用时需要两个参数(GPIO 编号 + 标志)。pinctrl-0 属性将引脚复用配置绑定到外设节点。
2.7 常见错误与调试
| 错误类型 | 现象 | 原因 | 解决方法 |
|---|---|---|---|
| 节点重复 | 编译报错 "duplicate node name" | 同一层级出现同名节点 | 检查节点名+地址是否唯一 |
| 标签未定义 | 编译报错 "label not found" | 引用了未定义的标签 | 确认标签在 .dtsi 中已定义 |
| 属性类型错误 | 内核解析失败或驱动不匹配 | 属性值类型与驱动期望不符 | 对照驱动文档检查属性类型 |
| reg 地址越界 | 设备无法访问或系统崩溃 | 寄存器地址超出物理地址范围 | 核对芯片手册中的地址映射 |
| 包含文件路径错误 | 编译报错 "file not found" | #include 路径不正确 |
使用相对路径或 -I 指定搜索目录 |
掌握节点、属性、标签和包含文件这四大语法要素,是编写和理解设备树的基础。在实际开发中,建议从 SoC 厂商提供的 .dtsi 文件入手,通过标签覆盖的方式逐步添加板级配置,避免从头编写复杂的设备树结构。