2. 设备树语法入门:节点、属性、标签与包含文件

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给操作系统内核。在嵌入式Linux开发中,设备树源文件(.dts / .dtsi)是连接硬件与驱动的核心桥梁。本节将系统讲解设备树的基本语法要素:节点、属性、标签和包含文件。

2.1 设备树的基本结构

设备树从根节点 / 开始,向下展开为多级子节点,每个节点代表一个硬件设备或总线。节点内通过属性描述设备的寄存器地址、中断号、时钟频率、GPIO引脚等参数。

设备树层次结构示意图 / (根节点) soc (片上系统) chosen (启动参数) i2c0 (I2C控制器) uart0 (串口) eeprom@50 (EEPROM) 节点命名规则 • 节点名@地址(如 eeprom@50) • 地址部分为寄存器基址或设备地址 • 同一层级节点名必须唯一

2.2 节点(Node)

节点是设备树的基本组成单元,每个节点对应一个硬件设备或总线。节点可以嵌套,形成父子层次关系。

2.2.1 节点定义语法

node-name@unit-address {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
语法元素 说明 示例
node-name 节点名称,描述设备类型 uart, i2c, gpio
@unit-address 设备地址(寄存器基址或总线地址) @ff010000, @50
property 属性,描述设备特性 reg, interrupts
child-node 子节点,嵌套定义 eeprom, port

2.2.2 节点地址与命名规范

  • 节点名:使用小写字母、数字和连字符,如 serial, mmc-host。
  • 地址部分:十六进制,无 0x 前缀,如 @ff020000。
  • 同一层级:节点名+地址必须唯一,如 i2c@ff020000 和 i2c@ff030000。
  • 特殊节点:aliases、chosen、memory 为系统保留节点名。
提示:节点地址部分不是必须的。当设备在总线上有唯一地址(如I2C从设备地址)或寄存器基址时,才需要添加 @unit-address。根节点和纯容器节点通常不带地址。

2.3 属性(Property)

属性是节点的数据载体,用于描述设备的硬件特性。设备树规范定义了多种属性类型。

2.3.1 属性值类型

类型 语法 示例 说明
空属性 property; status = "disabled"; 布尔标志,存在即为真
32位整数 <value> <0x1000> 单个或多个整数,空格分隔
64位整数 <high low> <0x0 0x10000000> 高32位 + 低32位
字符串 "string" "okay" 双引号包裹
字节数组 [hex bytes] [00 01 02] 十六进制字节,空格分隔
混合类型 <...> "..." [...] <0x10> "irq" 多种类型组合

2.3.2 常用标准属性

/ {
    model = "Embedded Device Board v1.0";   // 字符串:板级型号
    compatible = "vendor,board";             // 字符串:兼容性匹配
    #address-cells = <1>;                   // 整数:地址宽度(32位)
    #size-cells = <1>;                      // 整数:大小宽度(32位)

    memory@80000000 {
        device_type = "memory";              // 字符串:设备类型
        reg = <0x80000000 0x40000000>;       // 整数对:基址 + 大小
    };

    chosen {
        bootargs = "console=ttyS0,115200";   // 字符串:内核启动参数
        stdout-path = &uart0;                // 引用:标准输出设备
    };
};

2.3.3 属性使用注意事项

  • compatible:驱动匹配的关键属性,格式为 "厂商,设备型号",如 "fsl,imx6q-uart"。
  • reg:描述设备地址空间,格式为 <基址1 大小1 基址2 大小2 ...>。
  • status:设备状态,可选 "okay"、"disabled"、"reserved"。
  • #address-cells / #size-cells:定义子节点 reg 属性的地址和大小字段宽度。
注意:#address-cells 和 #size-cells 必须在父节点中定义,影响所有子节点的 reg 属性解析。若未定义,默认值为 <2> 和 <1>。

2.4 标签(Label)

标签是设备树中用于引用节点的别名机制。通过标签,可以在设备树的其他位置方便地引用或修改节点。

2.4.1 标签定义与引用

// 定义标签:在节点名前加标签名 + 冒号
uart0: serial@ff010000 {
    compatible = "vendor,uart";
    reg = <0xff010000 0x1000>;
    interrupts = <0 33 4>;
};

// 引用标签:使用 & 符号
&uart0 {
    status = "okay";
    clock-frequency = <115200>;
};

2.4.2 标签的作用

用途 说明 示例
节点引用 在 .dtsi 中定义节点,在 .dts 中通过标签追加属性 &i2c1 { status = "okay"; };
路径别名 在 aliases 节点中建立短名称 aliases { serial0 = &uart0; };
中断/时钟引用 在 interrupt-parent 或 clocks 属性中引用 interrupt-parent = &gic;
覆盖(Override) 在板级文件中修改 SoC 级节点的属性 &gpio1 { pinctrl-0 = &pinctrl_gpio1; };

2.4.3 标签命名规范

  • 使用小写字母、数字和下划线,如 uart0, i2c_bus1。
  • 标签名应具有描述性,便于识别设备类型和实例。
  • 避免使用关键字(如 status, reg)作为标签名。
  • 同一设备树中标签必须唯一。

2.5 包含文件(.dtsi)

.dtsi 文件是设备树的包含文件,类似于 C 语言的 .h 头文件。它用于定义 SoC 级通用硬件描述,被多个板级 .dts 文件复用。

2.5.1 包含机制

// soc.dtsi — SoC 级通用定义
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

/ {
    compatible = "vendor,soc";

    cpu: cpu@0 {
        compatible = "arm,cortex-a7";
        device_type = "cpu";
        reg = <0x0>;
    };

    gic: interrupt-controller@ffc01000 {
        compatible = "arm,gic-400";
        reg = <0xffc01000 0x1000>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };

    uart0: serial@ff010000 {
        compatible = "vendor,uart";
        reg = <0xff010000 0x1000>;
        interrupts = <0 33 4>;
        status = "disabled";
    };
};

// board.dts — 板级文件
#include "soc.dtsi"

/ {
    model = "My Board v2.0";
    compatible = "vendor,myboard";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };
};

&uart0 {
    status = "okay";
    pinctrl-0 = <&pinctrl_uart0>;
};

2.5.2 .dtsi 与 .dts 的分工

文件类型 内容定位 典型内容 复用范围
.dtsi SoC 级通用定义 CPU、中断控制器、外设控制器、时钟、GPIO 控制器 同一 SoC 的所有板级文件
.dts 板级具体配置 内存大小、外设使能、引脚复用、设备实例 单块电路板

2.5.3 包含文件的使用原则

  • 分层设计:SoC 级定义放在 .dtsi,板级差异放在 .dts。
  • 标签覆盖:在 .dts 中通过 &label 修改 .dtsi 中定义的节点属性。
  • 头文件包含:使用 #include <dt-bindings/...> 引入宏定义,如 GPIO 号、中断类型。
  • 避免重复:不要在 .dts 中重新定义 .dtsi 已定义的节点,应使用标签引用。
最佳实践:设备树文件组织应遵循“SoC 通用 → 系列通用 → 板级具体”的三层结构。例如:soc.dtsi → soc-family.dtsi → board.dts。这样可最大化复用,减少冗余。

2.6 完整示例:GPIO 控制器与引脚配置

以下是一个包含 GPIO 控制器和 pinctrl 配置的完整设备树片段,综合运用了节点、属性、标签和包含文件。

// gpio-controller.dtsi
#include <dt-bindings/gpio/gpio.h>

/ {
    soc {
        gpio0: gpio@ff020000 {
            compatible = "vendor,gpio-controller";
            reg = <0xff020000 0x1000>;
            gpio-controller;
            #gpio-cells = <2>;
            interrupt-controller;
            #interrupt-cells = <2>;
        };

        pinctrl: pinctrl@ff030000 {
            compatible = "vendor,pinctrl";
            reg = <0xff030000 0x1000>;
            pinctrl-0 = <&pinctrl_default>;

            pinctrl_uart0: uart0-pins {
                function = "uart0";
                pins = "GPIO0_A0", "GPIO0_A1";
                bias-pull-up;
            };

            pinctrl_i2c0: i2c0-pins {
                function = "i2c0";
                pins = "GPIO0_B0", "GPIO0_B1";
                drive-strength = <4>;
            };
        };
    };
};

// board.dts
#include "gpio-controller.dtsi"

/ {
    model = "GPIO Demo Board";
    compatible = "vendor,gpio-demo";

    leds {
        compatible = "gpio-leds";
        led-red {
            label = "red";
            gpios = <&gpio0 15 GPIO_ACTIVE_HIGH>;
        };
    };
};

&uart0 {
    pinctrl-0 = <&pinctrl_uart0>;
    pinctrl-names = "default";
    status = "okay";
};
关键点:gpio-controller 和 #gpio-cells 属性声明该节点为 GPIO 控制器,#gpio-cells = <2> 表示引用时需要两个参数(GPIO 编号 + 标志)。pinctrl-0 属性将引脚复用配置绑定到外设节点。

2.7 常见错误与调试

错误类型 现象 原因 解决方法
节点重复 编译报错 "duplicate node name" 同一层级出现同名节点 检查节点名+地址是否唯一
标签未定义 编译报错 "label not found" 引用了未定义的标签 确认标签在 .dtsi 中已定义
属性类型错误 内核解析失败或驱动不匹配 属性值类型与驱动期望不符 对照驱动文档检查属性类型
reg 地址越界 设备无法访问或系统崩溃 寄存器地址超出物理地址范围 核对芯片手册中的地址映射
包含文件路径错误 编译报错 "file not found" #include 路径不正确 使用相对路径或 -I 指定搜索目录

掌握节点、属性、标签和包含文件这四大语法要素,是编写和理解设备树的基础。在实际开发中,建议从 SoC 厂商提供的 .dtsi 文件入手,通过标签覆盖的方式逐步添加板级配置,避免从头编写复杂的设备树结构。