3. DTS 语法基础:节点、属性、值的定义规则与基本格式
设备树源文件(.dts)是一种人类可读的、用于描述硬件拓扑与配置的树形结构语言。其核心由三个基本元素构成:节点(Node)、属性(Property) 和 值(Value)。理解这三者的定义规则与基本格式,是编写正确 DTS 文件的基石。
核心概念速览: 节点是硬件设备或总线在树中的“位置”;属性是描述该设备特性的“键值对”;值则是属性对应的具体数据。三者组合,形成对硬件的完整描述。
3.1 节点(Node)的定义规则与格式
节点是设备树的骨架,代表一个独立的硬件单元(如 CPU、I2C 控制器、GPIO 引脚)或一个逻辑分组(如 chosen、aliases)。
3.1.1 节点定义基本语法
[label:] node-name[@unit-address] {
[property-definitions]
[child-node-definitions]
};
- node-name:节点名称,必须唯一标识节点类型,如
cpu、memory、i2c。名称应使用小写字母、数字和连字符。 - @unit-address:可选,通常为设备在父总线上的地址(如寄存器基址、I2C 从机地址)。用于区分同一父节点下同名的多个子节点。
- label::可选标签,用于在 DTS 文件的其他位置引用该节点(如通过
&label进行覆盖或引用)。 - 花括号
{ }:包含该节点的所有属性和子节点定义。 - 分号
;:节点定义结束的标记。
示例: 定义一个 I2C 控制器节点,其基地址为 0x1000,并包含一个子节点(从设备)。
i2c@1000 {
compatible = "vendor,i2c-controller";
reg = <0x1000 0x100>;
#address-cells = <1>;
#size-cells = <0>;
eeprom@50 {
compatible = "atmel,24c02";
reg = <0x50>;
};
};
3.1.2 节点命名规范与常见误区
| 规范项 | 正确示例 | 错误示例 | 说明 |
|---|---|---|---|
| 名称通用性 | serial@101f1000 |
uart1@101f1000 |
优先使用通用设备名(如 serial),而非具体型号(如 uart1)。 |
| 地址格式 | memory@80000000 |
memory@0x80000000 |
unit-address 应为十六进制数,不包含 0x 前缀。 |
| 标签唯一性 | i2c0: i2c@1000 |
i2c: i2c@1000 (重复) |
标签(label)在整个 DTS 文件及包含的头文件中必须唯一。 |
| 根节点 | / { ... }; |
无 | 所有设备树的根节点,使用正斜杠 / 表示。 |
避坑指南: 节点名称中的
@unit-address 不是必需的,但若存在,其值必须与 reg 属性中的第一个地址值匹配。不匹配会导致内核解析时产生警告或错误。
3.2 属性(Property)的定义规则与格式
属性是附着在节点上的“键-值”对,用于描述节点的特性,如兼容性、地址、中断信息等。属性名是字符串,值可以是多种数据类型。
3.2.1 属性定义基本语法
property-name = value;
- property-name:由小写字母、数字、连字符和点号组成,通常以非数字开头。如
compatible、reg、interrupt-parent。 - =:赋值操作符。
- value:属性的具体数据,其格式取决于数据类型。
- 分号
;:属性定义结束的标记。
3.2.2 属性值的数据类型与表示法
| 数据类型 | 表示法 | 示例 | 说明 |
|---|---|---|---|
| 空值 | 无值 | interrupt-controller; |
用于表示布尔型 true 或标志性属性。 |
| 字符串 | 双引号 | compatible = "vendor,device"; |
用于描述设备兼容性、模型名等。 |
| 32位整数 | 尖括号 < > |
reg = <0x1000 0x100>; |
用于地址、大小、中断号等。多个整数用空格分隔。 |
| 64位整数 | 尖括号 < > (两个32位) |
reg = <0x00000000 0x80000000 0x0 0x10000000>; |
在 32 位系统上表示 64 位地址/大小,高32位在前。 |
| 字节序列 | 方括号 [ ] |
local-mac-address = [00 11 22 33 44 55]; |
用于 MAC 地址、UUID 等原始二进制数据。 |
| 字符串列表 | 逗号分隔的字符串 | clock-names = "uart", "spi"; |
用于关联时钟、中断等资源的名称。 |
| 混合类型 | 组合使用 | gpios = <&gpio0 1 0>; |
常包含 phandle(节点引用)和整数。 |
关键属性示例:
serial@101f1000 {
compatible = "ns16550a"; // 字符串
reg = <0x101f1000 0x1000>; // 32位整数列表(地址,大小)
interrupts = <4 2>; // 32位整数列表(中断号,触发类型)
interrupt-parent = <&intc>; // phandle(引用另一个节点)
clock-frequency = <1843200>; // 32位整数
device_type = "serial"; // 字符串(已弃用,但仍常见)
};
3.3 值(Value)的详细定义规则
值的定义必须严格遵守其数据类型对应的格式,否则 DTC(Device Tree Compiler)将报错或产生非预期的二进制输出。
3.3.1 整数与地址值规则
- 字节序(Endianness):DTS 中的整数默认使用大端序(Big-Endian)。例如
<0x12345678>在内存中存储为12 34 56 78。 - 地址与大小组合:
reg属性通常由#address-cells和#size-cells决定每个地址和大小占用的 32 位整数个数。- 若
#address-cells = <1>,#size-cells = <1>,则reg = <addr1 size1 addr2 size2 ...>; - 若
#address-cells = <2>,#size-cells = <1>,则reg = <addr_high addr_low size ...>;
- 若
3.3.2 字符串与字符串列表规则
- 字符串:必须使用双引号包裹。字符串内部支持转义字符,如
\n、\t。 - 字符串列表:多个字符串用逗号分隔,整体作为一个属性值。例如
clock-names = "core", "bus", "ref";。
3.3.3 引用(Phandle)与标签
- Phandle:使用
&label语法引用另一个节点。DTC 会将其编译为指向该节点的 32 位整数句柄。 - 示例:
interrupt-parent = <&intc>;表示该设备的中断父控制器是标签为intc的节点。
实战技巧: 在编写 DTS 时,优先使用
&label 引用节点,而不是手动填写 phandle 数值。这不仅能提高代码可读性,还能避免因节点顺序变化导致的 phandle 值改变。
3.4 节点、属性、值的综合示例
以下是一个完整的 DTS 文件片段,展示了节点、属性、值的协同工作方式:
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
intc: interrupt-controller@10100000 {
compatible = "arm,gic-400";
reg = <0x10100000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
uart0: serial@101f1000 {
compatible = "ns16550a";
reg = <0x101f1000 0x1000>;
interrupts = <0 4 4>;
interrupt-parent = <&intc>;
clock-frequency = <1843200>;
};
};
3.5 常见错误与调试方法
| 错误类型 | 错误示例 | 正确做法 |
|---|---|---|
| 缺少分号 | compatible = "foo" |
每行属性定义后加 ; |
| 值类型不匹配 | reg = "0x1000"; |
地址值使用 <0x1000> |
| 节点名称含大写 | UART@1000 |
使用小写 uart@1000 |
| 标签重复 | 两个节点使用相同 label | 确保 label 全局唯一 |
| 地址格式错误 | memory@0x80000000 |
去掉 0x,写为 memory@80000000 |
调试建议: 使用
dtc -I dts -O dtb -o output.dtb input.dts 编译 DTS 文件。若编译失败,DTC 会输出具体的行号和错误原因。使用 dtc -I dtb -O dts -o output.dts input.dtb 可以反编译 DTB 文件,用于验证编译结果是否符合预期。