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。路径中的 @ 后面的地址用于区分同一父节点下的多个同类型设备。
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 属性中的厂商前缀必须与内核驱动匹配表一致,否则驱动无法加载。
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 文件中进行板级定制。后包含的文件可以覆盖先包含文件中的节点属性。
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 可以反编译查看。