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

设备树源文件(.dts)采用一种层次化、键值对的结构来描述硬件。理解其基础语法是编写和修改设备树的起点。本章将逐一拆解构成 DTS 文件的四个核心元素:节点(Node)、属性(Property)、标签(Label)和包含文件(Include)。

学习提示: 设备树语法与 JSON 或 XML 类似,但更简洁。它不描述软件行为,只描述“有什么硬件”以及“硬件如何连接”。

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

节点是设备树的基本组成单元,代表一个硬件设备、总线或控制器。每个节点从根节点(/)开始,形成一棵树状结构。

2.1.1 节点定义格式

// 节点基本语法
[node-name]@[unit-address] {
    [property-list];
    [child-node-1];
    [child-node-2];
};
  • node-name:节点名称,描述设备类型(如 cpu, uart, i2c)。
  • unit-address:可选,通常为设备在总线上的起始地址或寄存器偏移,用于区分同名节点。
  • 花括号 { }:包含该节点的属性和子节点。
  • 分号 ;:每个属性或子节点定义必须以分号结束。

2.1.2 根节点与子节点示例

/dts-v1/;

/ {                                 // 根节点
    model = "TI AM335x BeagleBone";
    compatible = "ti,am335x-bone", "ti,am33xx";

    cpus {                          // 子节点:CPU 容器
        #address-cells = <1>;
        #size-cells = <0>;

        cpu@0 {                     // 子节点:具体 CPU 核心
            compatible = "arm,cortex-a8";
            reg = <0x00>;
        };
    };

    memory@80000000 {               // 子节点:内存
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  // 起始地址 0x80000000,大小 512MB
    };
};
关键点: 节点名称中的 @unit-address 不是必需的,但强烈建议在存在多个同类型设备时使用,以保持唯一性。例如 uart@44e09000 和 uart@48022000。
设备树节点层次结构 / (根) cpus memory@80000000 chosen cpu@0 节点层级说明 • 根节点 "/" 是唯一入口,包含所有硬件设备。 • 每个节点可以有子节点,形成树形拓扑。 • 节点名称中的 "@unit-address" 用于区分同类型设备。

2.2 属性(Property):描述设备的键值对

属性是附着在节点上的数据,用于描述设备的特性、配置和连接关系。属性值可以是空、整数、字符串、字节数组或这些类型的组合。

2.2.1 常见属性类型与格式

属性类型 语法格式 示例 说明
空属性 property-name; disable-rtc; 仅表示存在该属性,无值。
字符串 property-name = "string"; compatible = "ti,am335x-bone"; 用双引号包裹。
32 位整数 property-name = <value>; reg = <0x80000000 0x20000000>; 尖括号内可包含多个整数,空格分隔。
64 位整数 property-name = <0x00000000 0x80000000>; reg = <0x00000000 0x80000000>; 用两个 32 位整数拼接表示。
字节数组 property-name = [hex-bytes]; local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节,空格分隔。
字符串列表 property-name = "str1", "str2"; compatible = "ti,am335x-bone", "ti,am33xx"; 逗号分隔多个字符串。
混合类型 property-name = <value>, "string", [bytes]; gpios = <&gpio0 15 0>; 不同数据类型用逗号分隔。

2.2.2 标准属性速查

  • compatible:最重要的属性,用于操作系统匹配驱动。格式为 "厂商,设备型号"。
  • reg:描述设备地址空间,格式为 <address length>。
  • interrupts:描述设备使用的中断号。
  • status:设备状态,常用值 "okay"(启用)或 "disabled"(禁用)。
常见错误: 忘记在属性值后加分号 ; 是 DTS 编译错误的最常见原因。检查每个 = 或 < 结尾是否都有分号。

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

标签为节点提供一个全局唯一的别名,方便在其他地方引用该节点,尤其是在修改或扩展设备树时。

2.3.1 标签定义与引用

// 定义标签:在节点名称前加冒号
uart0: serial@44e09000 {
    compatible = "ti,am3352-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
};

// 引用标签:使用 & 符号
&uart0 {
    status = "okay";
    pinctrl-0 = <&uart0_pins>;
};

2.3.2 标签的典型用途

用途 示例 说明
覆盖节点属性 &uart0 { status = "okay"; }; 在 .dtsi 或 .dts 中修改已有节点。
引用为 phandle clocks = <&clk_osc 0>; 在属性中引用另一个节点(如时钟、GPIO 控制器)。
条件编译 /delete-node/ &uart0; 在包含文件中删除某个节点。
标签引用与覆盖流程 原始节点定义 uart0: serial@44e09000 { status = "disabled"; } 引用覆盖 &uart0 { status = "okay"; }; 最终节点 serial@44e09000 status = "okay"; 标签工作机制 1. 标签 uart0 是节点 serial@44e09000 的全局别名。 2. 使用 &uart0 引用时,编译器会将属性合并到原始节点中。 3. 如果属性冲突,后定义的覆盖先定义的(类似 C 语言宏覆盖)。

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

设备树支持使用 #include 预处理指令来引入其他文件,实现代码复用和模块化。通常,SoC 厂商提供 .dtsi(设备树包含文件),板级开发者在此基础上添加外设。

2.4.1 包含文件的使用

// am335x-boneblack.dts
#include "am33xx.dtsi"          // 包含 SoC 级定义
#include "am335x-bone-common.dtsi" // 包含板级公共定义

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    // 板级特有设备
    leds {
        pinctrl-0 = <&user_led_pins>;
        compatible = "gpio-leds";

        led0 {
            label = "beaglebone:green:usr0";
            gpios = <&gpio1 21 0>;
            linux,default-trigger = "heartbeat";
        };
    };
};

// 覆盖 SoC 级节点
&uart0 {
    status = "okay";
};

2.4.2 文件组织规范

文件后缀 用途 示例
.dtsi SoC 级或板级公共定义,通常不包含板级特有设备。 am33xx.dtsi, imx6qdl.dtsi
.dts 具体板卡设备树源文件,包含 .dtsi 并添加板级特有内容。 am335x-boneblack.dts, imx6q-sabresd.dts
.dts + .dtsi 通过 #include 组合,实现 SoC 与板级分离。 所有主流 BSP 均采用此模式。
最佳实践: 永远不要在 .dtsi 中直接定义板级特有的设备(如 LED、按键)。将 .dtsi 视为“SoC 硬件描述”,将 .dts 视为“板级配置”。这样同一 SoC 的不同板卡只需更换 .dts 文件。

2.5 综合示例:一个完整的 DTS 片段

以下示例综合了节点、属性、标签和包含文件,展示一个典型嵌入式系统的设备树描述。

// 文件:my-board.dts
/dts-v1/;

#include "soc.dtsi"             // 包含 SoC 定义

/ {
    model = "My Custom Board";
    compatible = "my-company,my-board", "my-company,soc";

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x10000000>;  // 256MB RAM
    };

    leds {
        compatible = "gpio-leds";
        led-red {
            label = "red";
            gpios = <&gpio2 5 1>;      // GPIO2_5,低电平有效
        };
    };
};

// 引用 SoC 中的 uart0 节点并启用
&uart0 {
    status = "okay";
    clock-frequency = <48000000>;
};

// 引用 SoC 中的 i2c0 节点并添加设备
&i2c0 {
    status = "okay";
    clock-frequency = <100000>;

    eeprom@50 {
        compatible = "atmel,24c02";
        reg = <0x50>;
        pagesize = <16>;
    };
};
编译验证: 使用 dtc -I dts -O dtb -o my-board.dtb my-board.dts 编译。如果语法正确,将生成 my-board.dtb 二进制文件。常见错误包括缺少分号、括号不匹配、属性值格式错误。
完整 DTS 文件结构 my-board.dts (板级文件) #include "soc.dtsi" / { ... } 根节点定义 chosen { stdout-path = &uart0; } memory@80000000 { reg = <...>; } leds { ... } &uart0 { status = "okay"; } &i2c0 { eeprom@50 { ... }; } 文件结构总结 • .dtsi 提供 SoC 基础节点(如 uart0, i2c0),通常状态为 disabled。 • .dts 通过 #include 引入,然后使用标签覆盖启用节点并添加板级设备。

2.6 本章小结

  • 节点是设备树的骨架,从根节点 / 开始,形成树形结构。
  • 属性是附着在节点上的键值对,使用字符串、整数、字节数组等格式描述硬件特性。
  • 标签为节点提供全局别名,支持跨文件引用和属性覆盖。
  • 包含文件通过 #include 实现 SoC 与板级分离,是设备树模块化的核心机制。
下一步实践: 打开你开发板对应的 .dts 和 .dtsi 文件,尝试找出根节点、一个子节点、一个标签引用和一个包含文件。这是快速掌握设备树语法的有效方法。