3. 设备树语法入门:节点、属性、标签与包含文件
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将处理器、外设、中断、时钟、GPIO 等信息传递给操作系统内核。对于 UART 调试驱动的移植适配而言,掌握设备树的语法是第一步。本章将系统讲解设备树的四个核心语法要素:节点(node)、属性(property)、标签(label)和包含文件(#include)。
3.1 节点(node)——设备树的骨架
节点是设备树的基本组成单元,每个节点代表一个硬件设备或总线。节点以花括号 { } 包裹,内部可以包含属性或子节点。节点名称通常采用 名称@地址 的格式,其中地址部分为可选项,用于区分同一名称下的不同实例。
/dts-v1/;
/ {
model = "Embedded UART Debug Board";
compatible = "vendor,board-v1";
soc {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <33>;
status = "okay";
};
};
};
上述代码中,/ 是根节点,soc 是总线节点,serial@10000000 是 UART 设备节点。节点名称 serial@10000000 中的 10000000 表示该 UART 外设的基地址。
- 节点名称应使用小写字母、数字和连字符。
- 地址部分使用十六进制,不带
0x前缀。 - 同一层级下节点名称必须唯一。
3.2 属性(property)——设备的特征描述
属性是附着在节点上的键值对,用于描述设备的硬件特征。属性值可以是字符串、整数、字节数组、字符串列表或 <u32> 数组。常见的属性类型如下表所示:
| 属性类型 | 语法格式 | 示例 | 说明 |
|---|---|---|---|
| 字符串 | "string" |
compatible = "vendor,uart-ip"; |
用于匹配驱动 |
| 32 位整数 | <value> |
interrupts = <33>; |
中断号、时钟频率等 |
| 整数数组 | <val1 val2 ...> |
reg = <0x10000000 0x1000>; |
地址和长度对 |
| 字节数组 | [hex bytes] |
local-mac-address = [00 11 22 33 44 55]; |
MAC 地址、UUID 等 |
| 字符串列表 | "str1", "str2" |
compatible = "vendor,uart-ip", "simple-uart"; |
多个兼容驱动 |
| 布尔值 | 仅属性名,无值 | dma-coherent; |
表示特性存在 |
reg = <0x10000000 0x1000> 末尾缺少分号会导致编译错误。另外,字符串必须使用双引号,整数数组使用尖括号。
在 UART 调试场景中,最关键的属性是 compatible、reg 和 interrupts。compatible 用于内核匹配驱动;reg 描述 UART 控制器的寄存器地址范围;interrupts 指定 UART 的中断号。
3.3 标签(label)——节点的引用别名
标签是设备树中为节点定义的别名,用于在设备树文件的其他位置引用该节点。标签定义在节点名称之前,使用冒号 : 分隔。标签的主要用途包括:
- 在设备树内部引用:通过
&label语法引用节点,添加或覆盖属性。 - 在 C 语言头文件中引用:通过
#define宏定义,将标签转换为可用的宏。 - 在 overlay 文件中使用:动态加载设备树片段时,通过标签定位目标节点。
// 定义标签
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <33>;
status = "okay";
};
// 在其他位置引用标签,添加属性
&uart0 {
pinctrl-0 = <&uart0_pins>;
pinctrl-names = "default";
clock-frequency = <115200>;
};
标签的命名规则与 C 语言标识符类似:以字母或下划线开头,后跟字母、数字或下划线。建议使用有意义的名称,如 uart0、i2c1、spi2,便于维护。
- 节点名称用于在设备树中定位节点路径,如
/soc/serial@10000000。 - 标签用于在设备树源文件中快速引用节点,避免书写完整路径。
- 一个节点可以有多个标签,用空格分隔,如
uart0: serial0: serial@10000000。
3.4 包含文件(#include)——模块化与复用
设备树支持使用 C 语言预处理器指令 #include 来包含其他文件,实现代码复用和模块化设计。常见的包含文件类型包括:
| 文件类型 | 扩展名 | 用途 | 示例 |
|---|---|---|---|
| 设备树头文件 | .dtsi |
SoC 公共定义、外设模板 | #include "soc-common.dtsi" |
| C 语言头文件 | .h |
宏定义、GPIO/中断号常量 | #include "uart-defines.h" |
| 其他设备树 | .dts |
板级覆盖、overlay 片段 | #include "base-board.dts" |
// uart-defines.h
#define UART0_BASE 0x10000000
#define UART0_SIZE 0x1000
#define UART0_IRQ 33
// board.dts
#include "soc-common.dtsi"
#include "uart-defines.h"
&uart0 {
reg = <UART0_BASE UART0_SIZE>;
interrupts = <UART0_IRQ>;
status = "okay";
};
使用 #include 时,设备树编译器(DTC)会先调用 C 预处理器展开所有宏和包含文件,然后再进行设备树语法解析。这意味着你可以在 .h 文件中定义 #define 宏,在 .dts 文件中直接使用这些宏作为属性值。
- 将 SoC 级公共定义放在
.dtsi文件中,板级差异放在.dts文件中。 - 使用
.h头文件统一管理地址、中断号等常量,避免硬编码。 - 包含文件路径使用双引号
""表示相对路径,使用尖括号<>表示系统搜索路径。
3.5 综合示例:UART 调试节点的完整写法
将上述四个语法要素综合起来,一个完整的 UART 调试设备树节点通常包含以下内容:
// uart-debug.dtsi
#include "uart-defines.h"
/ {
model = "UART Debug Board";
compatible = "vendor,debug-board-v1";
chosen {
stdout-path = &uart0;
};
uart0: serial@10000000 {
compatible = "vendor,uart-ip", "ns16550a";
reg = <UART0_BASE UART0_SIZE>;
interrupts = <UART0_IRQ>;
interrupt-parent = <&gic>;
clock-frequency = <1843200>;
current-speed = <115200>;
reg-shift = <2>;
reg-io-width = <4>;
fifo-size = <16>;
dmas = <&dma0 0 &dma0 1>;
dma-names = "tx", "rx";
pinctrl-0 = <&uart0_default_pins>;
pinctrl-names = "default";
status = "okay";
};
};
| 属性 | 值示例 | 作用 |
|---|---|---|
compatible |
"vendor,uart-ip", "ns16550a" |
驱动匹配,优先使用第一个 |
reg |
<0x10000000 0x1000> |
寄存器基地址和映射长度 |
interrupts |
<33> |
UART 中断号 |
clock-frequency |
<1843200> |
UART 输入时钟频率 |
current-speed |
<115200> |
默认波特率 |
reg-shift |
<2> |
寄存器地址偏移量(32 位对齐) |
fifo-size |
<16> |
硬件 FIFO 深度 |
通过本章的学习,读者应掌握设备树中节点、属性、标签和包含文件的基本写法。这些语法要素是后续进行 UART 调试驱动移植适配的基础。在实际项目中,建议从简单的 .dts 文件开始,逐步添加节点和属性,并使用 dtc -I dts -O dtb 命令验证语法正确性。