2. 设备树语法:设备树源文件(.dts)的语法结构
设备树源文件(.dts)是描述硬件拓扑结构的文本文件,采用树形层次结构组织节点(Node)和属性(Property)。本章将系统讲解 .dts 文件的语法要素,包括节点定义、属性赋值、标签引用、包含文件机制等核心概念,为后续解析设备树与驱动 probe 流程的关联打下基础。
2.1 设备树源文件的基本结构
一个典型的 .dts 文件由根节点(/)和若干子节点组成,每个节点可以包含属性(键值对)或进一步嵌套子节点。编译器(dtc)将 .dts 编译为二进制 .dtb,供内核在启动时解析。
- 节点以
node-name { ... };形式定义,节点名可包含@unit-address后缀。 - 属性以
property-name = value;或property-name;(空属性)形式出现。 - 注释风格与 C 语言一致:
//单行、/* */多行。 - 分号
;是语句结束符,花括号{}表示节点作用域。
// 最简单的 .dts 示例
/dts-v1/;
/ {
compatible = "acme,coyote-revenge";
#address-cells = <1>;
#size-cells = <1>;
cpus {
#address-cells = <1>;
#size-cells = <0>;
cpu@0 {
compatible = "arm,cortex-a9";
reg = <0>;
};
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x10000000>; // 256MB 内存
};
};
2.2 节点(Node)定义与层次结构
设备树的节点对应硬件总线、设备或功能单元。每个节点有唯一路径(如 /cpus/cpu@0),通过节点名和 reg 属性区分同级别节点。
| 节点元素 | 语法示例 | 说明 |
|---|---|---|
| 根节点 | / { ... }; |
设备树的顶层容器,所有节点均在其下 |
| 普通节点 | uart@1e000 { ... }; |
节点名 + 可选地址后缀,地址通常与 reg 属性对应 |
| 别名节点 | aliases { serial0 = &uart1; }; |
为其他节点提供简短引用名 |
| 空节点 | status = "disabled"; |
通过属性控制节点是否启用,而非删除节点 |
i2c@3ffd0000 优于 I2C@3FFD0000。
2.3 属性(Property)类型与赋值
属性是设备树中描述硬件特征的关键载体,其值可以是空、整数、字符串、字节数组或它们的组合。内核通过 of_property_read_* 系列 API 读取这些属性。
| 属性类型 | 语法格式 | 示例 | 内核读取 API |
|---|---|---|---|
| 空属性 | property-name; |
interrupt-parent; |
of_find_property() |
| 32 位整数 | <value> |
reg = <0x1000 0x200>; |
of_property_read_u32() |
| 64 位整数 | <high low> |
reg = <0x0 0x80000000>; |
of_property_read_u64() |
| 字符串 | "string" |
compatible = "arm,cortex-a9"; |
of_property_read_string() |
| 字符串列表 | "str1", "str2" |
compatible = "ti,am335x-uart", "ns16550"; |
of_property_read_string_index() |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
of_property_read_u8_array() |
| 混合类型 | <value> "string" |
gpios = <&gpio0 15 0>; |
of_parse_phandle_with_args() |
- 整数默认采用大端序(Big-Endian),与 ARM 架构一致。
#address-cells和#size-cells决定reg属性中地址和长度的单元数(32 位为 1 个 cell)。compatible属性是驱动匹配的核心,通常按“厂商,型号”格式组织,越具体的字符串越靠前。
2.4 标签(Label)与引用
标签(Label)为节点或属性提供符号名称,便于在 .dtsi 文件中通过 &label 语法引用和修改。这是设备树实现“增量覆盖”的关键机制。
// 定义标签
uart0: serial@1e000 {
compatible = "ns16550";
reg = <0x1e000 0x100>;
interrupts = <33>;
};
// 在另一个文件中通过 &uart0 引用并追加属性
&uart0 {
clock-frequency = <1843200>;
status = "okay";
};
| 标签用途 | 语法 | 说明 |
|---|---|---|
| 定义标签 | label: node-name { ... }; |
标签名在前,节点名在后,用冒号分隔 |
| 引用节点 | &label { ... }; |
通过 & 符号引用已定义的标签 |
| 引用属性值 | &label 作为 phandle |
在属性值中引用其他节点,如 clocks = <&clk_osc>; |
| 别名引用 | &{/path/to/node} |
直接使用节点路径引用(不推荐,可读性差) |
uart0、i2c1、mmc0,避免使用 node1、temp_label 等无意义名称。标签名不能以数字开头。
2.5 包含文件(Include)机制
设备树使用 C 预处理器(CPP)的 #include 指令来组织多文件结构。通常将 SoC 通用描述放在 .dtsi 文件中,板级特定描述放在 .dts 文件中。
// 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";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB
};
};
// 覆盖 SoC 级节点
&uart0 {
status = "okay";
pinctrl-0 = <&uart0_pins>;
};
| 文件类型 | 扩展名 | 用途 | 包含关系 |
|---|---|---|---|
| 设备树源文件 | .dts |
板级最终描述,包含所有硬件信息 | 可包含多个 .dtsi |
| 设备树包含文件 | .dtsi |
SoC 通用描述或板级公共描述 | 可嵌套包含其他 .dtsi |
| 头文件 | .h |
定义宏、常量、GPIO 编号等 | 通过 #include 引入 |
- SoC 级
.dtsi定义所有内部外设节点,但将status设为"disabled"。 - 板级
.dts通过&label引用并启用实际使用的外设。 - 使用
.h头文件定义 GPIO 编号、时钟索引等常量,提高可读性。 - 避免在
.dtsi中定义板级专属属性(如内存大小、MAC 地址)。
2.6 常见属性详解:compatible、reg、interrupts
这三个属性是驱动 probe 流程中最常解析的核心属性,理解其格式对后续章节至关重要。
2.6.1 compatible 属性
用于设备与驱动的匹配,格式为 "厂商,型号" 字符串列表。内核按照列表顺序依次尝试匹配驱动。
compatible = "ti,am335x-uart", "ns16550";
// 内核先匹配 "ti,am335x-uart",失败后尝试 "ns16550"
2.6.2 reg 属性
描述设备地址空间,格式由父节点的 #address-cells 和 #size-cells 决定。
// #address-cells = <1>; #size-cells = <1>;
reg = <0x1e000 0x100>; // 起始地址 0x1e000,长度 0x100
// #address-cells = <2>; #size-cells = <2>;
reg = <0x0 0x80000000 0x0 0x10000000>; // 64 位地址 + 64 位长度
2.6.3 interrupts 属性
描述设备的中断号与触发类型,需要配合 interrupt-parent 使用。
interrupt-parent = <&gic>; // 引用中断控制器节点
interrupts = <0 33 4>; // 中断类型 0(SPI)、中断号 33、触发类型 4(高电平)
<中断类型 中断号 触发类型> 三元素格式,而老式控制器可能只使用 <中断号 触发类型>。务必参考对应中断控制器的绑定文档。
2.7 设备树编译与反编译
设备树编译器 dtc 是处理 .dts/.dtb 文件的核心工具。掌握基本命令有助于调试和验证设备树配置。
| 操作 | 命令 | 说明 |
|---|---|---|
| 编译 .dts → .dtb | dtc -I dts -O dtb -o output.dtb input.dts |
生成二进制设备树 |
| 反编译 .dtb → .dts | dtc -I dtb -O dts -o output.dts input.dtb |
查看二进制设备树内容 |
| 检查 .dtb 语法 | dtc -I dtb -O dts input.dtb > /dev/null |
仅验证语法,不输出文件 |
| 生成 .dtb 时保留符号 | dtc -@ -I dts -O dtb -o output.dtb input.dts |
-@ 保留符号表,用于动态覆盖 |
/proc/device-tree 查看当前使用的设备树结构。每个节点对应一个目录,属性对应文件,直接 cat 即可查看属性值。
2.8 本章小结
设备树源文件语法是驱动开发的基础技能,核心要点包括:
- 节点结构:树形层次组织,根节点为
/,子节点通过路径唯一标识。 - 属性赋值:支持整数、字符串、字节数组、phandle 引用等多种类型,
compatible是驱动匹配的关键。 - 标签引用:通过
&label实现增量覆盖,实现 SoC 与板级描述的分离。 - 包含机制:
#include组织多文件,.dtsi用于通用描述,.dts用于板级定制。 - 编译工具:
dtc编译器将.dts转换为.dtb,内核在启动时解析并创建设备节点。