2、设备树基础概念:DTS/DTC/DTB的关系、设备树语法入门、节点与属性
2.1 设备树(Device Tree)的起源与作用
在早期的嵌入式 Linux 系统中,硬件描述信息被硬编码在板级文件(如 arch/arm/mach-xxx)中。每更换一款 SoC 或外设,都需要修改内核源码并重新编译,导致内核镜像与硬件高度耦合,维护成本极高。
设备树(Device Tree)是一种描述硬件资源的数据结构,它将硬件信息从内核源码中剥离出来,以文本形式独立描述。同一份内核镜像可以通过加载不同的设备树文件来适配不同的硬件平台,实现了“内核不变,硬件可配”的架构解耦。
核心价值:设备树是 Linux 内核与板级硬件之间的“契约”,驱动通过设备树获取硬件资源(地址、中断、GPIO、时钟等),无需在驱动代码中硬编码。
2.2 DTS / DTC / DTB 三者的关系
设备树从源文件到内核可识别的二进制数据,经历了三个关键角色:
| 术语 | 全称 | 格式 | 作用 |
|---|---|---|---|
| DTS | Device Tree Source | 文本文件(.dts) | 开发者编写的设备树源文件,使用设备树语法描述硬件 |
| DTC | Device Tree Compiler | 编译工具 | 将 DTS 编译为 DTB 的编译器,也支持反编译(DTB → DTS) |
| DTB | Device Tree Blob | 二进制文件(.dtb) | Bootloader 传递给内核的硬件描述数据,内核解析后生成设备节点 |
三者的转换流程如下:
DTS 源文件(文本) ──→ DTC 编译器 ──→ DTB 二进制(.dtb)
↑
DTSI 头文件(.dtsi,公共包含)
实践提示:在 Linux 内核源码中,
arch/arm/boot/dts/ 或 arch/arm64/boot/dts/ 目录下存放了各平台的 DTS 文件。编译内核时,DTC 会自动将选中的 DTS 编译为 DTB。
2.3 设备树语法入门
设备树语法基于树形结构,以“/”为根节点,每个硬件外设对应一个子节点。节点可以包含属性(键值对)和子节点。
2.3.1 基本语法规则
- 节点定义:
节点名@地址 { ... };,地址通常为寄存器基址。 - 属性格式:
属性名 = 值;,值可以是字符串、32位整数数组、64位整数数组、二进制数据等。 - 标签(Label):在节点前加
标签名:,便于在其他地方引用。 - 包含文件:使用
#include "xxx.dtsi"引入公共头文件。 - 注释:支持 C 风格
/* */和 C++ 风格//。
2.3.2 一个完整的 DTS 示例
/dts-v1/;
#include "imx6ul.dtsi"
/ {
model = "Freescale i.MX6 UltraLite EVK Board";
compatible = "fsl,imx6ul-evk", "fsl,imx6ul";
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB RAM
};
chosen {
stdout-path = &uart1;
};
leds {
compatible = "gpio-leds";
led0: led-red {
label = "red-led";
gpios = <&gpio1 4 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_uart1>;
};
};
常见错误:忘记写分号
; 或花括号不匹配是 DTS 编译失败的最常见原因。DTC 对语法严格,建议使用 dtc -I dts -O dtb -o test.dtb test.dts 验证语法。
2.4 节点与属性详解
2.4.1 节点(Node)
节点是设备树的基本组成单元,每个节点代表一个硬件设备或总线。节点路径从根节点 / 开始,如 /soc/aips-bus@02000000/spi@02010000。
| 节点类型 | 说明 | 示例 |
|---|---|---|
| 根节点 | 设备树的顶层节点,用 / 表示 |
/ { ... }; |
| 设备节点 | 描述具体外设,通常包含 compatible 和 reg 属性 |
uart@02010000 { ... }; |
| 总线节点 | 描述总线控制器,子节点为挂载在该总线上的设备 | i2c@021a0000 { ... }; |
| 别名节点 | 通过 aliases 节点为其他节点提供短名称 |
aliases { serial0 = &uart1; }; |
| chosen 节点 | 传递 Bootloader 参数给内核,如启动参数、控制台 | chosen { bootargs = "..."; }; |
2.4.2 属性(Property)
属性是节点的“键值对”,用于描述设备的硬件特征。驱动通过 of_match_table 匹配 compatible 属性后,再通过 of_property_read_* 系列 API 读取其他属性。
| 属性名 | 值类型 | 作用 |
|---|---|---|
compatible |
字符串列表 | 设备驱动匹配的关键,格式为 "厂商,设备型号" |
reg |
32位整数数组 | 描述设备地址空间,格式为 <基址 长度> |
interrupts |
32位整数数组 | 描述中断号、触发类型等 |
gpios |
phandle + 整数 | 引用 GPIO 控制器并指定引脚号和标志 |
status |
字符串 | 设备状态:"okay"、"disabled"、"reserved" |
pinctrl-* |
phandle 列表 | 引脚复用配置,引用 pinctrl 子节点 |
clock-frequency |
32位整数 | 时钟频率,单位 Hz |
关键理解:
compatible 是设备树匹配驱动的“身份证”。驱动通过 of_match_table 声明支持的 compatible 字符串列表,内核在启动时遍历设备树节点,找到匹配的节点后调用驱动的 probe 函数。
2.5 设备树匹配驱动的流程
当内核启动时,设备树匹配驱动的完整流程如下:
- Bootloader 加载 DTB:U-Boot 从存储介质读取 DTB 文件,将其放入内存指定地址。
- 内核解析 DTB:内核启动早期调用
unflatten_device_tree(),将 DTB 解析为device_node树。 - 创建 platform_device:对于每个 compatible 匹配的节点,内核创建对应的
platform_device。 - 驱动匹配:当驱动注册时,内核遍历
platform_device的 compatible 列表,与驱动的of_match_table比对。 - 调用 probe:匹配成功后,内核调用驱动的
probe函数,驱动通过of_*API 读取设备树属性获取硬件资源。
调试技巧:在 Linux 内核启动时,可以通过
of_platform_populate() 的日志或 /sys/firmware/devicetree/base 目录查看设备树解析结果。使用 dtc -I dtb -O dts -o dump.dts /sys/firmware/fdt 可以反编译当前使用的 DTB。
2.6 本章小结
- DTS 是开发者编写的硬件描述源文件,DTC 将其编译为 DTB 二进制,内核解析 DTB 生成设备节点。
- 设备树语法以 节点 和 属性 为核心,
compatible属性是驱动匹配的关键。 - 节点通过
reg描述地址空间,通过interrupts、gpios等属性描述资源。 - 驱动通过
of_match_table与设备树节点匹配,匹配成功后调用probe函数。