2. DTS语法入门:节点(node)、属性(property)、标签(label)、包含(include)与宏定义

设备树源文件(.dts)是一种结构化的描述语言,用于向操作系统(尤其是Linux内核)传递硬件拓扑、内存布局、外设寄存器地址、中断号等静态信息。掌握DTS语法是编写和修改设备树覆盖(DTO)的基础。本章将系统讲解DTS的核心语法元素:节点、属性、标签、包含机制以及宏定义的使用。

学习提示: DTS语法与C语言中的结构体定义有相似之处,但更强调层级关系和键值对描述。建议读者在阅读时对照实际的内核设备树源文件(如 arch/arm64/boot/dts/ 目录下的文件)进行练习。

2.1 节点(Node)

节点是设备树的基本组成单元,用于表示一个硬件设备、总线、控制器或功能块。每个节点都有一个名称,并可以包含子节点和属性。节点通过层级结构(树形结构)来描述硬件之间的从属关系。

2.1.1 节点定义语法

node-name {
    property1 = <value>;
    property2 = "string";
    child-node {
        ...
    };
};
  • 节点名称:通常为小写字母、数字和连字符的组合,如 cpu、i2c0、serial@ff000000。当节点有寄存器地址时,名称后常跟 @address 以区分同类型设备。
  • 节点体:由花括号 { } 包裹,内部包含属性和子节点定义。
  • 分号:每个属性定义和子节点定义结束后必须加分号 ;。

2.1.2 根节点与标准节点

每个设备树文件有且只有一个根节点,用 / 表示。所有硬件设备都挂载在根节点下。常见的标准节点包括:

节点路径 描述 常见子节点
/ 根节点,设备树的入口 cpus, memory, chosen, soc
/cpus CPU核心描述 cpu@0, cpu@1
/memory@address 物理内存区域 无
/chosen 引导参数(如启动命令行) 无
/soc 片上系统总线,挂载外设控制器 i2c, spi, uart, gpio
节点路径: 每个节点可以通过从根节点开始的完整路径唯一标识,例如 /soc/i2c@ff000000/touchscreen@38。路径中的 @ 后面的地址用于区分同一父节点下的多个同类型设备。
设备树节点层级结构示例 / (根节点) cpus memory@80000000 chosen cpu@0 cpu@1 soc i2c@ff000000 uart@ff010000 spi@ff020000 touchscreen@38 节点通过层级路径唯一标识,如 /soc/i2c@ff000000/touchscreen@38

2.2 属性(Property)

属性是节点中用于描述硬件特性的键值对。属性值可以是空、整数、整数数组、字符串、字符串列表、二进制数据或对另一个节点的引用(phandle)。

2.2.1 属性值类型与编码

属性值类型 DTS编码格式 示例 说明
空值 无等号 interrupt-controller; 用于声明节点具备某种能力(如中断控制器)
32位整数 <value> reg = <0xff000000 0x1000>; 尖括号内可包含多个整数,用空格分隔
64位整数 <high low> reg = <0x0 0x80000000 0x0 0x40000000>; 使用两个32位整数拼接表示64位地址
字符串 "string" compatible = "samsung,exynos-uart"; 双引号包裹,用于设备驱动匹配
字符串列表 "str1", "str2" compatible = "ti,am3352-uart", "ns16550a"; 逗号分隔多个字符串,优先级从高到低
二进制数据 [hex bytes] local-mac-address = [00 11 22 33 44 55]; 方括号内为十六进制字节序列
phandle引用 &label interrupt-parent = &gic; 引用另一个节点的标签,生成指向该节点的指针

2.2.2 标准属性

设备树规范定义了一系列标准属性,用于描述设备的基本信息:

  • compatible:最重要的属性,用于操作系统匹配设备驱动。格式为 "厂商,设备型号",如 "fsl,imx6q-uart"。
  • reg:描述设备的寄存器地址空间,格式为 <address length>。
  • interrupts:描述设备使用的中断号,格式取决于中断控制器。
  • interrupt-parent:指定该设备连接的中断控制器节点。
  • status:设备状态,常用值为 "okay"(启用)或 "disabled"(禁用)。
  • clocks:描述设备使用的时钟源。
  • pinctrl-0:引脚复用配置。
常见错误: 属性值末尾忘记加分号 ; 是DTS编译中最常见的语法错误。另外,compatible 属性中的厂商前缀必须与内核驱动匹配表一致,否则驱动无法加载。
节点与属性关系示意图 uart@ff010000 节点名称 + 地址 compatible = "ns16550a" reg = <0xff010000 0x1000> interrupts = <0 32 4> interrupt-parent = &gic status = "okay" 属性是节点的描述信息,每个属性由键和值组成,值类型包括整数、字符串、数组、phandle引用等 标准属性如 compatible、reg、interrupts 是驱动匹配和资源获取的关键

2.3 标签(Label)

标签是DTS中用于给节点或属性起别名的机制,方便在其他地方引用。标签以冒号 : 结尾,放在节点名称之前。

2.3.1 标签定义与引用

// 定义标签
gic: interrupt-controller@f0001000 {
    compatible = "arm,gic-400";
    reg = <0xf0001000 0x1000>;
    interrupt-controller;
};

// 引用标签
uart0: serial@ff010000 {
    compatible = "ns16550a";
    reg = <0xff010000 0x1000>;
    interrupt-parent = <&gic>;  // 通过 &gic 引用 gic 节点
    interrupts = <0 32 4>;
};

2.3.2 标签的用途

用途 语法 示例
节点引用 &label interrupt-parent = &gic;
节点覆盖(overlay) &label { ... }; &uart0 { status = "okay"; };
生成别名(aliases) aliases { uart0 = &uart0; }; 在根节点的 aliases 子节点中定义
标签命名规范: 通常使用小写字母、数字和下划线,如 uart0、i2c1、mmc0。标签应具有描述性,便于在大型设备树中快速定位。

2.4 包含(Include)

DTS支持使用 #include 预处理指令来包含其他文件,实现代码复用和模块化。包含机制基于C预处理器(CPP),因此也支持宏定义。

2.4.1 包含语法

// 包含设备树头文件(.dtsi)
#include "soc.dtsi"
#include "board-common.dtsi"

// 包含头文件(.h),通常用于宏定义
#include 
#include 

/ {
    // 节点定义
};

2.4.2 包含文件类型

文件扩展名 用途 示例
.dtsi 设备树包含文件,通常描述SoC公共部分或板级公共部分 imx6q.dtsi、imx6qdl.dtsi
.dts 设备树源文件,通常描述具体板卡 imx6q-sabresd.dts
.h C语言头文件,用于定义宏常量 gpio.h、irq.h
包含顺序: 通常先包含SoC级别的 .dtsi 文件,再包含板级公共 .dtsi 文件,最后在 .dts 文件中进行板级定制。后包含的文件可以覆盖先包含文件中的节点属性。
设备树包含机制与文件层级 board.dts (板级文件) board-common.dtsi soc.dtsi (SoC公共) gpio.h (宏定义) irq.h (宏定义) clock.h (宏定义) #include 机制支持 .dtsi 和 .h 文件,实现SoC公共定义、板级公共定义和宏常量的分层复用

2.5 宏定义

由于DTS文件经过C预处理器处理,因此可以使用 #define 宏定义来定义常量,提高代码的可读性和可维护性。宏定义通常放在 .h 头文件中,通过 #include 引入。

2.5.1 宏定义示例

// gpio.h
#define GPIO_ACTIVE_HIGH 0
#define GPIO_ACTIVE_LOW  1

// 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

// board.dts
#include 
#include 

/ {
    gpio-keys {
        compatible = "gpio-keys";
        power-key {
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
            interrupts = <3 IRQ_TYPE_EDGE_FALLING>;
        };
    };
};

2.5.2 宏定义的优势

优势 说明 示例对比
可读性 使用有意义的宏名代替魔法数字 GPIO_ACTIVE_LOW 比 1 更直观
可维护性 修改宏定义一处生效,无需逐个修改 修改 IRQ_TYPE_EDGE_FALLING 的值
跨平台一致性 内核提供标准宏定义头文件 dt-bindings/gpio/gpio.h 等
注意事项: 宏定义在DTS中展开后必须是合法的属性值格式。例如,宏展开后不能出现语法错误,如缺少尖括号或引号。另外,宏定义不能跨文件覆盖,需确保 #include 顺序正确。

2.6 综合示例:完整的DTS文件

以下是一个综合示例,展示了节点、属性、标签、包含和宏定义的综合使用:

// soc.dtsi - SoC公共定义
#include 

/ {
    compatible = "arm,cortex-a72";
    
    gic: interrupt-controller@f0001000 {
        compatible = "arm,gic-400";
        reg = <0xf0001000 0x1000>;
        interrupt-controller;
        #interrupt-cells = <3>;
    };
    
    soc {
        #address-cells = <2>;
        #size-cells = <2>;
        
        uart0: serial@ff010000 {
            compatible = "ns16550a";
            reg = <0x0 0xff010000 0x0 0x1000>;
            interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
            interrupt-parent = <&gic>;
            status = "disabled";
        };
    };
};

// board.dts - 板级定制
#include "soc.dtsi"
#include 

/ {
    model = "MyBoard v2";
    compatible = "mycompany,myboard-v2", "mycompany,myboard";
    
    chosen {
        bootargs = "console=ttyS0,115200 root=/dev/mmcblk0p2";
    };
    
    gpio-keys {
        compatible = "gpio-keys";
        #address-cells = <1>;
        #size-cells = <0>;
        
        button@0 {
            label = "Power";
            gpios = <&gpio1 3 GPIO_ACTIVE_LOW>;
            linux,code = <116>;
        };
    };
};

// 覆盖uart0节点,启用并配置
&uart0 {
    status = "okay";
    clock-frequency = <24000000>;
};
编译验证: 使用 dtc -I dts -O dtb -o board.dtb board.dts 命令可以将DTS编译为DTB二进制文件。使用 dtc -I dtb -O dts -o board.dts board.dtb 可以反编译查看。
board.dts soc.dtsi (包含) gpio.h (宏定义) <