2. DTS 基础语法:节点、属性、标签、包含文件与注释

设备树源文件(.dts)采用树形结构描述硬件,其语法简洁、层次分明。掌握节点、属性、标签、包含文件和注释这五大基础元素,是编写和理解 DTS 文件的起点。本章将逐一解析这些语法单元,并通过对比和图示帮助建立直观认知。

2.1 节点(Node)—— 设备树的骨架

节点是设备树的核心结构单元,每个节点代表一个硬件设备、总线或控制器。节点从根节点 / 开始,层层嵌套,形成一棵完整的硬件树。

概念 说明 示例
根节点 设备树的起点,用 / 表示 / { ... };
子节点 挂在父节点下的设备或总线 cpu { ... };
节点名称 格式为 name@unit-address uart@fe001000
节点内容 用花括号 { } 包裹,以分号 ; 结束 uart@fe001000 { ... };
// 一个典型的节点结构
/ {
    model = "Example Board v1.0";
    compatible = "vendor,example-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;

        uart@fe001000 {
            compatible = "vendor,uart";
            reg = <0xfe001000 0x1000>;
            interrupts = <33>;
            status = "okay";
        };
    };
};
命名规范:节点名称应尽量使用通用名称(如 uart、i2c、mmc),@unit-address 部分通常填写该设备在总线上的基地址或片选号,用于区分同类型节点。

2.2 属性(Property)—— 描述设备的特征

属性是附着在节点上的键值对,用于描述设备的配置信息,如地址、中断号、时钟频率、驱动兼容性等。属性值支持多种数据类型。

数据类型 语法格式 示例 说明
字符串 "string" compatible = "vendor,device"; 用于匹配驱动
32位整数 <value> reg = <0x1000 0x100>; 地址、大小、频率等
64位整数 <0x00000000 0x00000000> reg = <0x0 0x80000000>; 高32位 + 低32位
字节数组 [hex bytes] local-mac-address = [00 11 22 33 44 55]; MAC 地址、UUID 等
布尔值 仅属性名,无值 status = "disabled"; 或 status; 存在即 true
字符串列表 "str1", "str2" compatible = "vendor,dev1", "vendor,dev2"; 多个兼容驱动
// 属性综合示例
ethernet@20000 {
    compatible = "vendor,eth", "simple-bus";
    reg = <0x20000 0x1000>;
    interrupts = <0 35 4>;
    local-mac-address = [00 0a 35 01 02 03];
    max-speed = <1000>;
    status = "okay";
};

2.3 标签(Label)—— 节点的别名引用

标签为节点提供一个全局唯一的别名,便于在其他位置引用该节点。标签通常写在节点名称之前,用冒号分隔。

// 定义标签
uart0: uart@fe001000 {
    compatible = "vendor,uart";
    reg = <0xfe001000 0x1000>;
};

// 通过 & 引用标签,追加或修改属性
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
语法元素 作用 示例
标签定义 为节点起别名 i2c1: i2c@f000
标签引用 通过 &label 访问节点 &i2c1 { ... };
引用用途 覆盖属性、添加子节点、引用 phandle &i2c1 { clock = <&clk_24m>; };
标签 vs 节点路径:标签是编译期别名,编译后转换为 phandle(指针句柄),比字符串路径更高效。在 .dtsi 文件中定义标签,在 .dts 文件中通过 &label 引用,是设备树复用的核心手段。

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

设备树支持 C 预处理器指令 #include,用于引入头文件(.dtsi、.h)或其它 DTS 片段。这是实现 SoC 级设备树复用的关键机制。

// 包含 SoC 公共定义
#include "soc-common.dtsi"
// 包含 GPIO 宏定义头文件
#include <dt-bindings/gpio/gpio.h>

/ {
    model = "My Board";
    compatible = "vendor,my-board";

    // 使用头文件中定义的宏
    led-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
};
包含方式 文件类型 典型用途
#include "file.dtsi" 设备树包含文件 SoC 公共节点、时钟、中断控制器定义
#include <file.h> C 头文件 宏定义(GPIO 电平、中断类型、时钟索引)
#include "board.dts" 另一个 DTS 较少用,通常用 /include/ 替代
注意:设备树编译器(DTC)默认支持 #include,但需要启用预处理(-E 或通过 cpp 预处理)。部分老旧工具链可能只支持 /include/ 指令,建议统一使用 #include 以兼容现代构建系统。

2.5 注释格式—— 文档化与可读性

DTS 文件支持两种注释风格,与 C 语言完全一致,用于解释节点用途、属性含义或记录修改历史。

// 单行注释:用于简短说明
/* 
 * 多行注释:用于详细描述
 * 例如解释 reg 地址段的含义
 */

/ {
    /* 根节点:描述整个板级硬件 */
    compatible = "vendor,board";

    // 内存节点
    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;  /* 1GB DDR3 */
    };
};
注释最佳实践:在 .dtsi 文件中使用注释说明 SoC 公共节点的默认状态;在 .dts 文件中注释说明板级定制修改的原因。避免在属性值内部插入注释,这会导致编译错误。

2.6 语法结构总览

下图展示了 DTS 文件的完整语法结构,从根节点到子节点、属性、标签和引用的关系。

DTS 语法结构总览 根节点 / soc { } memory@80000000 { } chosen { } uart@fe001000 i2c@f000 gpio@1000 属性示例 compatible = "vendor,uart"; reg = <0xfe001000 0x1000>; 标签示例 uart0: uart@fe001000 &uart0 { status = "okay"; }; 包含文件示例 #include "soc.dtsi" #include <dt-bindings/gpio.h> 注释示例 // 单行注释 | /* 多行注释:用于详细描述硬件配置 */ 节点构成树形结构,属性描述硬件特征,标签提供引用别名,包含文件实现复用,注释增强可读性

2.7 常见错误与避坑指南

  • 缺少分号:每个属性定义和节点结束必须加分号 ;,漏掉会导致编译失败。
  • 标签重复:同一编译单元内标签必须唯一,重复标签会导致覆盖或编译错误。
  • 属性值类型不匹配:如将字符串赋给期望整数的属性,DTC 会报类型错误。
  • 路径引用错误:使用 &label 时确保标签已定义,使用路径字符串时确保路径正确。
  • 包含文件循环依赖:#include 链中避免 A 包含 B、B 又包含 A 的死循环。
调试技巧:使用 dtc -I dts -O dtb -o output.dtb input.dts 编译 DTS 文件,错误信息会精确到行号和列号。使用 dtc -I dtb -O dts -o decompiled.dts input.dtb 反编译 DTB 检查最终生成的设备树结构。