3. DTS语法入门:节点、属性、标签、路径
设备树源文件(.dts)使用一种树形结构来描述硬件,其核心语法围绕四个基本概念展开:节点(Node)、属性(Property)、标签(Label)和路径(Path)。掌握这四个要素是编写和理解设备树的基础。
核心认知:设备树本质上是一个键-值数据库,节点是层级目录,属性是具体的键值对,标签是节点的别名,路径是节点的唯一地址。
3.1 节点(Node)—— 设备树的骨架
节点是设备树的基本组成单元,代表一个硬件设备、总线或控制器。节点可以嵌套,形成父子层级关系。
3.1.1 节点定义语法
node-name {
property1 = <value>;
property2 = "string";
child-node {
...
};
};
- 节点名称:由字母、数字、连字符(
-)、逗号(,)和点(.)组成,必须以字母开头。 - 节点单元地址:在节点名后可以跟
@加上地址(如uart@fe001000),用于区分同一类型的不同实例。 - 根节点:整个设备树的起点,用
/表示,所有其他节点都是它的后代。
实战要点:节点名应尽量使用标准名称(如
uart、i2c、spi),@ 后的地址通常与寄存器基地址或设备编号一致,便于调试时快速定位。
3.2 属性(Property)—— 描述节点的特征
属性是附着在节点上的键值对,用于描述硬件的具体特征,如寄存器地址、中断号、时钟频率、设备兼容性等。
3.2.1 属性值类型
| 类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 空值 | property; |
status = "disabled"; |
仅表示属性存在,无具体值 |
| 32位整数 | <value> |
reg = <0xfe001000 0x1000>; |
一个或多个32位值,用空格分隔 |
| 64位整数 | <high low> |
reg = <0x0 0x80000000 0x0 0x10000000>; |
高32位在前,低32位在后 |
| 字符串 | "string" |
compatible = "snps,dw-apb-uart"; |
双引号包裹 |
| 字符串列表 | "str1", "str2" |
compatible = "ti,am335x-uart", "ns16550"; |
逗号分隔多个字符串 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
十六进制字节,空格分隔 |
| 混合值 | <...> "..." [...] |
foo = <0x10> "bar" [0xab]; |
不同类型值可组合 |
3.2.2 标准属性速查
compatible:最重要的属性,用于操作系统匹配驱动。格式为"厂商,型号",如"ti,am335x-uart"。reg:描述设备地址空间,格式为<基地址 长度>。interrupts:描述中断号,格式依赖interrupt-parent。status:设备状态,常用值"okay"(启用)、"disabled"(禁用)。#address-cells/#size-cells:定义子节点reg属性中地址和长度的单元数。
常见陷阱:
compatible 属性中的厂商前缀必须使用内核维护的官方列表(如 ti、snps、fsl),不要随意编造。错误的 compatible 会导致驱动无法匹配。
3.3 标签(Label)—— 节点的别名
标签为节点提供一个全局唯一的别名,方便在其他地方引用该节点,尤其在 .dtsi 文件中覆盖或扩展节点时非常有用。
3.3.1 标签定义语法
uart0: uart@fe001000 {
compatible = "snps,dw-apb-uart";
reg = <0xfe001000 0x1000>;
interrupts = <33>;
};
在上例中,uart0 就是标签,它指向 uart@fe001000 节点。标签名必须以字母或下划线开头,由字母、数字、下划线组成。
3.3.2 标签的典型用途
| 用途 | 示例 | 说明 |
|---|---|---|
| 引用节点 | &uart0 { status = "okay"; }; |
通过 &label 引用节点并修改属性 |
| 中断父节点 | interrupt-parent = &gic; |
指定中断控制器 |
| 时钟引用 | clocks = &clk_uart0; |
引用时钟提供者 |
| GPIO引用 | reset-gpios = &gpio0 15 0; |
引用GPIO控制器 |
实战技巧:在
.dtsi 文件中为所有重要节点定义标签,然后在 .dts 文件中通过 &label 进行覆盖或启用。这种模式可以极大提高设备树的可维护性,避免重复定义整个节点。
3.4 路径(Path)—— 节点的绝对地址
路径是从根节点到目标节点的完整层级描述,类似于文件系统中的绝对路径。路径以 / 开头,各节点名用 / 分隔。
3.4.1 路径语法
/soc@0/uart@fe001000
上述路径表示:根节点下的 soc@0 节点下的 uart@fe001000 节点。
3.4.2 路径的典型使用场景
aliases节点:为常用路径定义简短别名。chosen节点:指定内核启动参数,如bootargs。phandle引用:编译时自动为节点生成唯一句柄,通过<&label>引用。
3.4.3 路径 vs 标签 vs phandle
| 方式 | 语法 | 特点 | 适用场景 |
|---|---|---|---|
| 路径 | /soc/uart@fe001000 |
完整、可读性强,但冗长 | 手动调试、aliases 定义 |
| 标签 | &uart0 |
简洁、可维护,推荐使用 | 节点覆盖、属性引用 |
| phandle | <&uart0> |
编译后为整数句柄,高效 | 中断、时钟、GPIO 等跨节点引用 |
最佳实践:在
.dts 文件中优先使用 标签 进行节点引用,避免硬编码路径。路径更适合在 aliases 节点中一次性定义,然后通过别名使用。
3.5 综合示例:一个完整的 UART 节点
下面是一个综合了节点、属性、标签和路径的完整示例:
/dts-v1/;
/ {
model = "Example Board";
compatible = "example,board-v1";
aliases {
serial0 = &uart0;
};
soc@0 {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: uart@fe001000 {
compatible = "snps,dw-apb-uart";
reg = <0xfe001000 0x1000>;
interrupts = <33>;
interrupt-parent = <&gic>;
clocks = <&clk_uart0>;
status = "disabled";
};
gic: interrupt-controller@fe002000 {
compatible = "arm,gic-400";
reg = <0xfe002000 0x1000>;
interrupt-controller;
#interrupt-cells = <3>;
};
clk_uart0: clock@fe003000 {
compatible = "fixed-clock";
#clock-cells = <0>;
clock-frequency = <24000000>;
};
};
};
&uart0 {
status = "okay";
};
代码解析:
uart0标签指向uart@fe001000节点。aliases中serial0 = &uart0为 UART 节点创建了路径别名。interrupt-parent = <&gic>通过 phandle 引用中断控制器。clocks = <&clk_uart0>通过 phandle 引用时钟提供者。- 文件末尾通过
&uart0覆盖节点,将status改为"okay"。
3.6 常见错误与避坑指南
| 错误类型 | 错误示例 | 正确写法 | 后果 |
|---|---|---|---|
| 缺少分号 | status = "okay" |
status = "okay"; |
编译报错 |
| 标签重复 | 两个节点使用相同标签 | 确保标签全局唯一 | 编译报错或引用混乱 |
| 路径错误 | &/soc/uart |
&uart0 或 /soc/uart@fe001000 |
引用失败,驱动无法加载 |
| 属性值类型错误 | reg = 0x1000; |
reg = <0x1000>; |
编译报错或运行时异常 |
忘记 #address-cells |
子节点 reg 使用多值 |
父节点定义 #address-cells |
地址解析错误 |
调试建议:使用
dtc -I dts -O dtb -o output.dtb input.dts 编译设备树,dtc 会给出详细的语法错误提示。编译后使用 fdtdump output.dtb 查看二进制内容,验证节点和属性是否正确。