2. 设备树语法入门:节点、属性、标签与包含机制
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形层次结构将系统中的CPU、内存、外设、中断控制器、GPIO控制器等硬件信息传递给操作系统内核。对于嵌入式Linux驱动开发者而言,掌握设备树语法是编写可移植驱动的第一步。
本章将围绕设备树的四个核心语法要素展开:节点(node)、属性(property)、标签(label)和包含(include)机制。理解这些基础概念后,你就能读懂并编写简单的设备树源文件(.dts)。
2.1 节点(Node)
节点是设备树的基本组成单元,每个节点代表一个硬件设备、总线、控制器或功能模块。节点以层次结构组织,从根节点 / 开始向下展开。
节点语法格式:
[label:] node-name[@unit-address] {
[property-list];
[child-node1 {
...
}];
[child-node2 {
...
}];
};
- 根节点:用
/表示,是整个设备树的起点,每个 .dts 文件有且只有一个根节点。 - 节点名称:描述设备类型,如
cpu、memory、uart、gpio。名称应尽量通用。 - 单元地址(unit-address):可选,通常为设备在总线上的地址或寄存器基址,用于区分同类型设备。例如
uart@ff010000。 - 子节点:节点可以嵌套,形成树形结构。子节点继承父节点的总线环境。
| 节点路径 | 含义 | 单元地址 |
|---|---|---|
/soc/uart@ff010000 | SoC 内部的 UART 控制器,基址 0xff010000 | ff010000 |
/soc/gpio@ff020000 | GPIO 控制器,基址 0xff020000 | ff020000 |
/soc/gpio@ff020000/gpio-leds | GPIO 控制器下的 LED 子节点 | 无(功能节点) |
/chosen | 系统启动参数节点,无硬件对应 | 无 |
2.2 属性(Property)
属性是附着在节点上的键值对,用于描述设备的配置参数、寄存器地址、中断信息、时钟频率等。属性值可以是空、整数、字符串、字节数组或这些类型的组合。
属性类型速查: 设备树规范定义了多种标准属性类型,驱动开发者最常用的是
compatible、reg、interrupts、gpios 和 status。
| 属性类型 | 格式示例 | 说明 |
|---|---|---|
compatible | "vendor,device" | 驱动匹配字符串,格式为"厂商,设备型号" |
reg | <0xff010000 0x1000> | 寄存器地址和长度,可包含多组 |
interrupts | <0 31 4> | 中断号、触发类型等,格式依赖中断控制器 |
gpios | <&gpio0 12 0> | GPIO 引用:控制器 phandle、引脚号、标志 |
status | "okay" 或 "disabled" | 设备启用状态 |
label | "led0" | 设备别名,便于用户空间识别 |
属性定义示例(gpio-leds 节点):
gpio-leds {
compatible = "gpio-leds";
led0 {
label = "power";
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
led1 {
label = "status";
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
default-state = "off";
};
};
2.3 标签(Label)
标签是节点的别名,用于在设备树的其他位置引用该节点。标签以 : 结尾,放在节点名称之前。标签的主要用途是:
- 简化引用:通过
&label语法引用节点,避免写完整路径。 - 节点覆盖:在 .dtsi 文件中定义带标签的节点,在 .dts 文件中通过
&label追加或修改属性。 - phandle 生成:编译器自动为带标签的节点生成唯一的 phandle(整数句柄),供其他节点引用。
注意: 标签名称在全局范围内必须唯一,不能与节点名称或其他标签冲突。标签通常使用小写字母和下划线,如
gpio0、uart1。
2.4 包含(Include)机制
设备树源文件支持 #include 预处理指令,用于引入公共定义、头文件或其他 .dtsi 文件。包含机制是设备树模块化设计的核心,它允许:
- 复用公共定义:将 SoC 通用外设定义放在 .dtsi 文件中,不同板级 .dts 文件通过
#include引入。 - 引入头文件:包含
.h头文件,使用其中的宏定义(如GPIO_ACTIVE_HIGH、IRQ_TYPE_EDGE_RISING)。 - 分层覆盖:通过
#include链实现多级覆盖(SoC级 → 板级 → 变体级)。
典型包含链示例:
// board.dts
#include "soc.dtsi" // 引入 SoC 通用外设
#include "board-common.dtsi" // 引入板级公共配置
#include // 引入 GPIO 宏定义
/ {
model = "MyBoard v2";
compatible = "vendor,myboard-v2", "vendor,myboard";
};
| 文件类型 | 扩展名 | 用途 | 包含方式 |
|---|---|---|---|
| SoC 定义文件 | .dtsi | 定义 CPU、中断控制器、GPIO、UART 等 SoC 级外设 | #include "soc.dtsi" |
| 板级公共文件 | .dtsi | 定义板级 LED、按键、电源管理等公共配置 | #include "board.dtsi" |
| 头文件 | .h | 定义 GPIO 标志、中断类型、时钟 ID 等宏 | #include <dt-bindings/gpio/gpio.h> |
| 最终板级文件 | .dts | 包含所有 .dtsi 和 .h,并做最终覆盖 | 编译入口文件 |
最佳实践: 使用
#include 时,优先包含 .dtsi 文件(双引号),再包含系统头文件(尖括号)。不要在 .dtsi 文件中放置板级特有配置,保持 SoC 定义的通用性。标签覆盖应在最终的 .dts 文件中完成。
2.5 节点、属性、标签与包含的协同工作
在实际的 LED 驱动配置中,这四个语法要素协同工作:
- SoC 级 .dtsi 定义 GPIO 控制器节点,并为其添加标签(如
gpio0: gpio@ff020000)。 - 板级 .dts 通过
#include引入 SoC 定义,并通过&gpio0覆盖其status属性为"okay"。 - 板级 .dts 在根节点或合适位置定义
gpio-leds子节点,使用gpios = <&gpio0 12 0>引用 GPIO 控制器标签。 - 头文件宏 如
GPIO_ACTIVE_HIGH通过#include引入,使属性值更具可读性。
完整示例:gpio-leds 节点配置
// soc.dtsi
gpio0: gpio@ff020000 {
compatible = "vendor,gpio";
reg = <0xff020000 0x1000>;
#gpio-cells = <2>;
status = "disabled"; // 默认关闭,板级覆盖
};
// board.dts
#include "soc.dtsi"
#include
/ {
model = "LED Test Board";
compatible = "vendor,led-board";
};
&gpio0 {
status = "okay";
};
/ {
gpio-leds {
compatible = "gpio-leds";
led-power {
label = "power";
gpios = <&gpio0 12 GPIO_ACTIVE_HIGH>;
linux,default-trigger = "heartbeat";
};
led-alert {
label = "alert";
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
default-state = "off";
};
};
};
常见错误:
- 忘记在板级 .dts 中通过
&gpio0将status设为"okay",导致 GPIO 控制器未启用,LED 无法工作。 gpios属性中引用的标签(如&gpio0)未在包含链中定义,编译时报 phandle 未找到错误。- 在 .dtsi 文件中直接定义板级 LED 节点,导致不同板级文件冲突。应将板级特有节点放在 .dts 中。