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

设备树(Device Tree,DT)是一种描述硬件拓扑与资源配置的树形数据结构,由一系列节点(node)、属性(property)、标签(label)、包含(include)与宏定义组成。掌握这些基本语法要素,是编写与调试板级设备树文件(.dts / .dtsi)的前提。

💡 核心认知:设备树不是C语言代码,而是一种结构化描述语言,其语法风格类似JSON与C的混合体,但更简洁。所有硬件信息通过节点与属性的层级关系表达,内核在启动时解析DTB(Device Tree Blob)并据此初始化驱动。

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

节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点通过路径(path)唯一标识,根节点为 /,子节点通过层级关系组织。

2.1.1 节点语法格式

/dts-v1/;

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

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            device_type = "cpu";
            reg = <0x00>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };
};

节点命名规则:节点名@单元地址,其中@单元地址可选,用于区分同一父节点下地址不同的同类设备。

📌 节点关键规则:
  • 每个节点以 节点名 { ... }; 定义,花括号内包含属性和子节点。
  • 节点名应使用小写字母、数字和连字符,推荐使用通用名称(如 serial、mmc、i2c)。
  • 同一父节点下,节点名必须唯一;若地址不同,可通过 @地址 区分。
  • 根节点 / 是唯一没有父节点的节点,所有设备挂载其下。

2.1.2 节点层级关系 SVG 图

设备树节点层级关系示例 / (根节点) cpus memory@80000000 ocp cpu@0 serial@44e09000 i2c@44e0b000 eeprom@50 每个节点代表一个硬件单元,子节点继承父节点的总线环境与地址空间

2.2 属性(Property)—— 描述硬件特征

属性是附着在节点上的键值对,用于描述硬件的兼容性、地址、中断、时钟、GPIO等特征。属性值可以是字符串、整数、字节数组、字符串列表或二进制数据。

2.2.1 属性类型与写法

属性类型 语法示例 说明
字符串 compatible = "ti,am335x-bone-black"; 用于驱动匹配,格式为 厂商,型号
字符串列表 compatible = "ti,am335x-bone-black", "ti,am33xx"; 多个字符串用逗号分隔,内核按顺序匹配
32位整数 reg = <0x80000000 0x20000000>; 尖括号内为32位整数,可多个,空格分隔
64位整数 reg = <0x00000000 0x80000000>; 使用两个32位整数拼接表示64位地址
字节数组 local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节,空格分隔
布尔值 status = "disabled"; 通过字符串 "okay" / "disabled" 表示
混合类型 gpios = <&gpio0 15 0>; 可包含标签引用、整数等混合内容

2.2.2 标准属性速查表

属性名 用途 必选/可选
compatible 驱动匹配字符串,格式 厂商,设备名 必选(根节点与设备节点)
reg 寄存器地址与大小,格式 <地址 长度> 必选(有地址的设备)
#address-cells 子节点 reg 中地址字段占用的32位字数 必选(总线节点)
#size-cells 子节点 reg 中长度字段占用的32位字数 必选(总线节点)
interrupt-parent 指定中断控制器节点引用 可选(有中断的设备)
interrupts 中断号与触发类型 可选(有中断的设备)
status 设备状态:"okay" / "disabled" / "reserved" 可选
device_type 设备类型,如 "cpu"、"memory" 可选(CPU/内存节点建议使用)
⚠️ 常见错误:
  • reg 属性中地址与长度的数量必须与父节点的 #address-cells 和 #size-cells 一致,否则内核解析会出错。
  • compatible 字符串顺序很重要:内核按列表顺序匹配驱动,最精确的匹配项应放在最前面。

2.3 标签(Label)—— 节点引用与复用

标签是设备树中用于引用节点的标识符,语法为 标签名: 节点名 { ... };。标签允许在设备树的其他位置通过 &标签名 引用该节点,实现节点属性的追加或覆盖。

2.3.1 标签定义与引用

// 定义标签
uart0: serial@44e09000 {
    compatible = "ti,am335x-uart";
    reg = <0x44e09000 0x2000>;
    interrupts = <72>;
    status = "disabled";
};

// 在其他文件中引用标签,追加属性
&uart0 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};

2.3.2 标签的作用域与最佳实践

场景 说明 示例
板级覆盖 在 .dts 文件中引用 .dtsi 中定义的节点,修改其属性 &uart0 { status = "okay"; };
引脚复用 通过标签引用 pinctrl 节点,配置引脚功能 pinctrl-0 = <&uart0_pins>;
GPIO/中断引用 在属性中引用其他节点作为句柄 gpios = <&gpio2 4 GPIO_ACTIVE_LOW>;
时钟/电源引用 引用时钟控制器或电源管理节点 clocks = <&clk_uart0>;
🔖 标签命名规范:
  • 使用小写字母、数字和下划线,如 uart0、i2c1、mmc0_pins。
  • 标签名应具有描述性,便于在多个文件中识别。
  • 避免使用过于简短的标签(如 a、b),防止命名冲突。

2.4 包含(Include)—— 模块化与复用

设备树使用 #include 预处理指令引入其他文件,实现硬件描述的模块化。通常 SoC 厂商提供 .dtsi(设备树包含文件),板级 .dts 文件通过 #include 引入 SoC 定义,再覆盖或追加板级特有属性。

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";

    &uart0 {
        status = "okay";
    };
};

2.4.2 包含文件组织原则

文件类型 扩展名 内容范围 示例
SoC 定义文件 .dtsi CPU、中断控制器、时钟、外设控制器等 SoC 级硬件 am33xx.dtsi
板级公共文件 .dtsi 同一参考设计下多个板卡共享的硬件描述 am335x-bone-common.dtsi
板级最终文件 .dts 具体板卡的完整设备树,包含所有覆盖与追加 am335x-boneblack.dts
头文件 .h 宏定义、GPIO 常量、中断类型等 dt-bindings/gpio/gpio.h
📂 包含路径提示:设备树编译器(DTC)在解析 #include 时,默认搜索 -I 参数指定的路径。内核源码中,arch/arm/boot/dts/ 和 include/dt-bindings/ 是常用的包含路径。

2.5 宏定义 —— 增强可读性与可维护性

设备树支持 C 预处理器宏定义,通过 #define 定义常量,在属性值中直接使用。宏定义通常放在 .h 头文件中,配合 #include 引入,使设备树更易读、易维护。

2.5.1 宏定义示例

// dt-bindings/gpio/gpio.h
#define GPIO_ACTIVE_HIGH   0
#define GPIO_ACTIVE_LOW    1

// dt-bindings/interrupt-controller/irq.h
#define IRQ_TYPE_EDGE_RISING     1
#define IRQ_TYPE_EDGE_FALLING    2
#define IRQ_TYPE_LEVEL_HIGH      4
#define IRQ_TYPE_LEVEL_LOW       8

// 在设备树中使用宏
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

&gpio0 {
    led-red {
        gpios = <&gpio2 4 GPIO_ACTIVE_LOW>;
        interrupts = <IRQ_TYPE_EDGE_RISING>;
    };
};

2.5.2 宏定义 vs 直接数值

对比项 使用宏定义 使用直接数值
可读性 GPIO_ACTIVE_LOW 一目了然 1 需要查阅文档才能理解
可维护性 修改宏定义一处生效 需要逐个修改所有数值
跨平台兼容 头文件统一管理,不同 SoC 可复用 数值含义可能因 SoC 而异
编译检查 预处理器可检测宏未定义错误 数值错误只能在运行时发现
⚠️ 宏定义使用注意事项:
  • 宏定义必须在 #include 头文件之后才能使用,注意包含顺序。
  • 不要重新定义内核头文件中已有的宏,避免冲突。
  • 宏定义只支持整数常量,不支持字符串拼接或复杂表达式。
  • 在 .dts 文件中使用 #define 时,需确保 DTC 启用了预处理(默认启用)。

2.6 综合语法示例:完整节点结构

以下是一个综合了节点、属性、标签、包含与宏定义的完整设备树片段:

// am335x-boneblack.dts
/dts-v1/;

#include "am33xx.dtsi"
#include "am335x-bone-common.dtsi"
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>

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

    chosen {
        stdout-path = &uart0;
    };

    leds {
        pinctrl-names = "default";
        pinctrl-0 = <&user_led_pins>;

        led-red {
            label = "beaglebone:red:usr0";
            gpios = <&gpio1 21 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "heartbeat";
        };

        led-green {
            label = "beaglebone:green:usr1";
            gpios = <&gpio1 22 GPIO_ACTIVE_HIGH>;
            linux,default-trigger = "mmc0";
        };
    };
};

&uart0 {
    status = "okay";
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
};

&i2c0 {
    status = "okay";
    clock-frequency = <400000>;

    eeprom@50 {
        compatible = "atmel,24c32";
        reg = <0x50>;
        pagesize = <32>;
    };
};

2.7 语法要素关系 SVG 图

设备树语法要素关系图 设备树文件 (.dts) 节点 (Node) 属性 (Property) 标签 (Label) 包含 (Include) 宏定义 (Macro) 语法要素协作关系 节点是骨架,属性描述特征,标签提供引用能力,包含实现模块化,宏定义增强可读性 五者结合,构成完整、可复用、易维护的设备树描述体系

2.8 本章小结

  • 节点是设备树的基本单元,通过层级关系组织硬件拓扑。
  • 属性以键值对形式描述硬件特征,compatible 和 reg 是最核心的属性。
  • 标签提供节点引用机制,支持跨文件属性覆盖与追加。
  • 包含实现设备树模块化,SoC 定义与板级定制分离。
  • 宏定义提升代码可读性与可维护性,是设备树工程化的关键手段。
🔧 实践建议:在开始编写板级设备树前,先阅读对应 SoC 的 .dtsi 文件,熟悉已有的节点、标签和宏定义。遵循“包含 + 覆盖”的模式,避免直接修改 .dtsi 文件,保持板级定制的清晰与可追溯。