2. DTS语法入门:节点(node)、属性(property)、标签(label)、包含文件(include)的语法规则

设备树源文件(.dts)是描述硬件拓扑与资源配置的领域特定语言。掌握其核心语法——节点、属性、标签与包含文件——是编写、阅读与调试设备树的基础。本章从这四个基本要素出发,结合实例与常见错误,建立清晰的语法认知。

2.1 节点(node):硬件拓扑的骨架

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能块。节点通过层级嵌套描述硬件之间的从属关系。

2.1.1 节点定义格式

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
  • 节点名称:由字母、数字、连字符、下划线组成,通常使用小写。例如 cpu、uart0、i2c-bus。
  • 节点体:用花括号 { } 包裹,内部包含属性与子节点。
  • 结束符:节点体后必须紧跟分号 ;。

2.1.2 根节点与特殊节点

节点类型 路径 说明
根节点 / 设备树的顶层容器,所有节点均挂载其下
CPU节点 /cpus 描述处理器核心信息,通常包含多个子节点
内存节点 /memory 声明系统物理内存的起始地址与大小
选择节点 /chosen 传递引导参数,如 bootargs、stdout-path
别名节点 /aliases 为复杂路径定义简短别名,方便引用

2.1.3 节点路径与引用

每个节点在设备树中拥有唯一路径,从根节点开始以斜杠分隔。例如:

/soc/spi@ff110000/display@0

路径用于在 dts 文件内部或 dtsi 包含文件中定位节点,也用于 phandle 引用。

常见错误:节点名称重复或路径冲突会导致 dtc 报错 duplicate node name。同一层级下节点名称必须唯一,不同层级允许同名但易引发混淆,应避免。

2.2 属性(property):描述硬件特征

属性是键值对,附着在节点上,用于描述设备的寄存器地址、中断号、时钟频率、兼容性等特征。属性值支持多种数据类型。

2.2.1 属性值类型

类型 语法示例 说明
空属性 status; 仅表示存在,无值,常用于布尔标志
32位整数 reg = <0x10000000 0x1000>; 尖括号包裹,多个值用空格分隔
64位整数 reg = <0x00000000 0x80000000>; 使用两个32位单元表示高32位和低32位
字符串 compatible = "arm,cortex-a72"; 双引号包裹,用于驱动匹配
字符串列表 compatible = "ti,am335x-uart", "ns16550"; 多个字符串用逗号分隔
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号包裹,十六进制字节
混合类型 foo = <0x1234>, "bar", [0a 0b]; 用逗号分隔不同类型单元

2.2.2 标准属性速查

  • compatible:驱动匹配的关键,格式为 "厂商,设备型号",如 "fsl,imx6q-uart"。
  • reg:描述地址空间,格式为 <地址 长度>,可重复出现表示多段地址。
  • interrupts:描述中断号与触发类型,依赖 interrupt-parent 指定中断控制器。
  • clocks:引用时钟源,通常配合 clock-names 使用。
  • status:设备状态,常用值 "okay"、"disabled"、"reserved"。
属性值陷阱:reg 属性中的地址与长度单元数由 #address-cells 和 #size-cells 决定。若父节点设置 #address-cells = <2>,则每个地址必须写两个32位整数,否则 dtc 报 bad cell count。

2.3 标签(label):节点的“别名”

标签为节点定义一个可引用的标识符,用于在 dts 文件内部或通过 &label 语法引用节点,实现节点内容的追加与覆盖。

2.3.1 标签定义

uart0: serial@ff110000 {
    compatible = "ns16550";
    reg = <0xff110000 0x1000>;
    interrupts = <0 33 4>;
};

上例中 uart0 即为标签,节点路径为 /soc/serial@ff110000,但通过标签可快速引用。

2.3.2 标签引用与节点追加

&uart0 {
    clock-frequency = <24000000>;
    status = "okay";
};

上述代码通过 &uart0 引用标签,向原节点追加 clock-frequency 属性和 status 属性。若原节点已存在同名属性,则新值覆盖旧值。

2.3.3 标签的典型用途

  • 板级覆盖:在 dtsi 中定义通用节点并加标签,在板级 dts 中通过 &label 调整参数。
  • 中断与时钟引用:使用 &label 作为 interrupt-parent 或 clocks 的值。
  • 引脚复用配置:引用 pinctrl 节点中的标签,指定引脚功能。
标签 vs 路径引用:标签是编译期符号,路径是运行时字符串。标签引用在 dtc 编译时解析为 phandle(整数句柄),效率更高且不易出错。推荐在需要跨文件引用节点时始终使用标签。

2.4 包含文件(include):模块化与复用

设备树支持通过 #include 预处理指令引入其他文件,实现硬件描述的分层与复用。包含文件通常使用 .dtsi 扩展名,表示设备树包含文件。

2.4.1 包含语法

#include "soc-common.dtsi"
#include "board-base.dtsi"
#include "pinmux.dtsi"

dtc 编译器在预处理阶段调用 C 预处理器(cpp)处理 #include,因此也支持 #define、#ifdef 等宏定义。

2.4.2 包含文件组织策略

文件类型 命名惯例 内容范围
SoC 级 soc-xxx.dtsi CPU、中断控制器、时钟、总线、外设 IP 核
板级 board-xxx.dtsi 内存布局、板载外设、GPIO 分配、电源管理
引脚复用 pinmux-xxx.dtsi 引脚功能选择、驱动强度、上下拉配置
最终文件 xxx.dts 包含上述 dtsi,并做板级覆盖与最终调整

2.4.3 包含文件中的节点覆盖

包含文件定义通用节点,最终 dts 文件通过标签引用覆盖或追加属性:

// soc-common.dtsi
uart0: serial@ff110000 {
    compatible = "ns16550";
    reg = <0xff110000 0x1000>;
    status = "disabled";
};

// board-foo.dts
#include "soc-common.dtsi"

&uart0 {
    clock-frequency = <24000000>;
    status = "okay";
};

编译后 uart0 节点包含 compatible、reg、clock-frequency 和 status = "okay"。

包含顺序陷阱:#include 的顺序影响节点覆盖结果。后包含的文件中若出现与先包含文件相同的节点(无标签引用),则后定义的节点会覆盖先定义的节点。建议始终使用标签引用进行覆盖,避免隐式覆盖导致的意外行为。

2.5 语法综合示例与SVG结构图

以下是一个完整的设备树片段,展示节点、属性、标签与包含文件的协同使用:

// main.dts
#include "soc.dtsi"
#include "board.dtsi"

/ {
    model = "Example Board v2";
    compatible = "example,board-v2";

    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  // 1GB RAM
    };
};

&uart0 {
    pinctrl-0 = <&uart0_pins>;
    pinctrl-names = "default";
    status = "okay";
};
设备树语法要素关系图 根节点 / cpus 节点 memory 节点 chosen 节点 soc 节点 uart0: serial@... i2c0: i2c@... 属性 (property) compatible = "ns16550"; reg = <0xff110000 0x1000>; status = "okay"; 标签 (label) uart0: serial@ff110000 { &uart0 { status = "okay"; }; 引用追加/覆盖属性 包含文件 (include) #include "soc.dtsi" #include "board.dtsi" 模块化复用硬件描述

2.6 常见语法错误与排查

错误类型 dtc 报错示例 原因与修复
缺少分号 syntax error before '}' 节点体或属性行末尾漏掉 ;,检查上一行
花括号不匹配 unmatched brace { 与 } 数量不一致,使用缩进对齐排查
标签重复 duplicate label 'uart0' 同一编译单元内标签必须唯一,检查 dtsi 包含链
属性值类型错误 bad cell count reg 的单元数与 #address-cells 不匹配
包含文件未找到 fatal error: soc.dtsi: No such file 检查 -I 包含路径或文件路径是否正确
节点路径冲突 duplicate node name 同一父节点下子节点名称重复,使用标签区分
调试建议:使用 dtc -I dts -O dtb -o output.dtb input.dts 2>&1 编译时,将错误重定向到标准输出。对于复杂包含链,可先用 cpp -P -I include_path input.dts preprocessed.dts 预处理,再检查预处理后的文件定位问题。
dtc编译流程与语法检查点 预处理 (cpp) #include 展开 语法解析 节点/属性/标签 语义检查 类型/单元/引用 DTB 生成 二进制输出 各阶段常见错误 • 预处理:包含文件路径错误、宏定义冲突 • 语法解析:缺少分号、花括号不匹配、标签重复、节点名称非法字符 • 语义检查:reg 单元数不匹配、phandle 引用无效、属性值类型错误 • DTB 生成:输出路径权限、磁盘空间不足

2.7 本章小结

  • 节点是设备树的骨架,通过层级嵌套描述硬件拓扑;根节点 / 是所有节点的起点。
  • 属性是节点的数据载体,支持整数、字符串、字节数组等多种类型;compatible 和 reg 是最核心的标准属性。
  • 标签为节点提供编译期别名,通过 &label 实现跨文件属性追加与覆盖,是模块化设计的关键。
  • 包含文件通过 #include 实现硬件描述的复用,.dtsi 文件按 SoC、板级、引脚复用分层组织。
  • 掌握这四个要素的语法规则与常见错误模式,是后续调试与故障分析的基础。