2. 设备树源文件语法:节点、属性、标签、引用、包含文件的基本规则
设备树源文件(.dts)是描述硬件拓扑结构的文本文件,其语法简洁但严谨。掌握节点、属性、标签、引用和包含文件的基本规则,是编写和调试设备树的基石。本节将逐一拆解这些核心语法元素,并通过实例和图表帮助你快速建立直观理解。
2.1 节点(Node):硬件拓扑的基本单元
节点是设备树中最基本的构建块,用于表示一个硬件设备、总线、控制器或功能单元。每个节点都有一个节点名,并可以包含若干属性和子节点,形成树状层次结构。
节点命名规则:节点名通常为
device-name@unit-address 格式。device-name 是通用设备类型(如 uart、i2c、gpio),unit-address 是设备在总线上的地址(如寄存器基址、I2C 从机地址),用于区分同一级别的多个同类型设备。
// 简单节点示例
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
cpu@0 {
device_type = "cpu";
compatible = "arm,cortex-a8";
reg = <0x0>;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB RAM
};
uart0: serial@44e09000 {
compatible = "ti,am3352-uart", "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "okay";
};
};
上述代码中,根节点 / 包含三个子节点:cpu@0、memory@80000000 和 uart0: serial@44e09000。每个节点通过 compatible 属性与内核驱动匹配,通过 reg 属性描述地址空间。
2.2 属性(Property):描述设备特征的关键值
属性是附着在节点上的键值对,用于描述设备的硬件特征、配置参数和连接关系。属性值可以是字符串、整数、字节序列、字符串列表或整数数组。
| 属性类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | property = "value"; |
model = "TI AM335x BeagleBone Black"; |
单个字符串值 |
| 字符串列表 | property = "str1", "str2"; |
compatible = "ti,am3352-uart", "ti,omap3-uart"; |
多个字符串,用逗号分隔 |
| 32 位整数 | property = <value>; |
interrupts = <72>; |
尖括号包裹单个或多个整数 |
| 整数数组 | property = <val1 val2>; |
reg = <0x44e09000 0x2000>; |
地址 + 长度对,或任意整数序列 |
| 字节序列 | property = [hex bytes]; |
local-mac-address = [00 11 22 33 44 55]; |
方括号包裹十六进制字节 |
| 布尔值 | property;(无值) |
status = "disabled"; 或 status = "okay"; |
存在即表示 true,通常用字符串模拟 |
关键属性速记:
compatible — 驱动匹配的核心属性,格式为 "厂商,设备型号"。reg — 描述设备地址空间,格式为 <基址 长度>。interrupts — 指定中断号,依赖 interrupt-parent 属性。status — 设备状态,常用 "okay" 或 "disabled"。
2.3 标签(Label):节点的别名与引用入口
标签是附加在节点名之前的标识符,用于在其他位置引用该节点。标签以冒号 : 与节点名分隔,格式为 label: node-name@address。
// 定义标签
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
};
// 在其他位置通过 &label 引用
&uart0 {
pinctrl-0 = <&uart0_pins>;
status = "okay";
};
标签的主要用途:
- 简化引用:避免重复书写冗长的节点路径(如
/ocp/serial@44e09000)。 - 覆盖属性:在
.dtsi文件中定义默认值,在.dts文件中通过标签引用并覆盖。 - 连接关系:在
pinctrl、clocks、gpios等属性中引用其他节点。
注意:标签在整个设备树中必须唯一。重复定义标签会导致编译错误。标签名通常使用小写字母、数字和下划线,以
& 符号引用。
2.4 引用(Reference):节点间的连接与覆盖
引用机制允许在一个节点中通过 &label 或 &{/path} 语法指向另一个节点,实现属性覆盖、连接关系描述或 phandle 引用。
| 引用方式 | 语法 | 使用场景 |
|---|---|---|
| 标签引用 | &label { ... }; |
覆盖或扩展已定义节点的属性 |
| 路径引用 | &{/soc/uart@44e09000} { ... }; |
当节点没有标签时,使用完整路径 |
| phandle 引用 | clocks = <&clk_mcu>; |
在属性值中引用其他节点作为句柄 |
// 引用覆盖示例
// 在 .dtsi 文件中定义默认值
uart0: serial@44e09000 {
compatible = "ti,am3352-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
status = "disabled"; // 默认关闭
};
// 在 .dts 文件中通过标签引用并启用
&uart0 {
status = "okay"; // 覆盖 status 属性
pinctrl-0 = <&uart0_pins>;
};
// phandle 引用示例
&uart0 {
clocks = <&clk_mcu>; // 引用 clk_mcu 节点作为时钟源
};
2.5 包含文件(Include):模块化与复用
设备树源文件支持 #include 预处理指令,用于引入头文件(.h)或其他设备树片段文件(.dtsi)。这是实现设备树模块化和复用的关键机制。
// am335x-boneblack.dts 文件
#include "am33xx.dtsi" // 引入 SoC 通用定义
#include "am335x-bone-common.dtsi" // 引入板级通用定义
#include // 引入 GPIO 宏定义
#include // 引入 pinmux 宏定义
/ {
model = "TI AM335x BeagleBone Black";
compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";
// 板级特有节点
leds {
pinctrl-0 = <&user_leds_pins>;
compatible = "gpio-leds";
led2 {
label = "beaglebone:green:usr0";
gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
};
};
.dtsi 文件通常包含 SoC 通用外设定义、时钟、中断控制器等基础设施,而 .dts 文件则专注于板级特有配置。这种分层设计使得同一 SoC 的不同板卡可以共享大量代码。
| 文件类型 | 扩展名 | 用途 | 包含关系 |
|---|---|---|---|
| 设备树源文件 | .dts |
板级或系统级设备树描述,通常包含板卡特有配置 | #include 一个或多个 .dtsi |
| 设备树包含文件 | .dtsi |
SoC 通用定义或板级通用定义,可被多个 .dts 复用 |
可嵌套 #include 其他 .dtsi 或头文件 |
| 设备树头文件 | .h |
定义宏常量、GPIO 标志、pinmux 配置等 | 被 .dts 或 .dtsi 包含 |
最佳实践:
• 将 SoC 通用外设(如 UART、I2C、SPI、GPIO 控制器)定义在
• 在板级
• 使用
• 将 SoC 通用外设(如 UART、I2C、SPI、GPIO 控制器)定义在
.dtsi 文件中,并默认设置 status = "disabled"。• 在板级
.dts 文件中通过标签引用启用所需外设,并配置 pinmux、时钟等板级参数。• 使用
#include 引入 dt-bindings/ 目录下的头文件,避免硬编码魔法数字。
2.6 语法综合示例与编译验证
以下是一个完整的 .dts 文件示例,综合展示了节点、属性、标签、引用和包含文件的使用:
// my-board.dts
/dts-v1/;
#include "soc.dtsi"
#include
/ {
model = "My Custom Board";
compatible = "my,board", "my,soc";
chosen {
stdout-path = &uart0;
};
leds {
compatible = "gpio-leds";
led-red {
label = "red:status";
gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
};
};
};
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
&i2c0 {
status = "okay";
clock-frequency = <100000>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
pagesize = <16>;
};
};
使用 dtc 编译验证:
# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o my-board.dtb my-board.dts
# 反编译 .dtb 验证结构
dtc -I dtb -O dts -o my-board-decompiled.dts my-board.dtb
# 检查语法错误(仅检查,不输出文件)
dtc -I dts -O dtb my-board.dts > /dev/null 2>&1 && echo "Syntax OK" || echo "Syntax Error"
常见编译错误:
•
•
•
•
•
ERROR (duplicate_label) — 标签重复定义。•
ERROR (phandle_references) — 引用的节点不存在或 phandle 无效。•
WARNING (unit_address_vs_reg) — 节点地址与 reg 属性不匹配。•
ERROR (missing_interrupt_parent) — 节点声明了 interrupts 但未指定 interrupt-parent。