1. 设备树基础概念:DTS 的起源、作用与基本语法结构
1.1 设备树的起源与设计动机
在早期的 Linux 内核中,板级硬件描述信息(如内存基址、中断号、GPIO 映射)被直接硬编码在 arch/arm/ 等平台目录下的 C 语言源文件中。这种“硬编码”方式导致每次硬件变更都需要重新编译内核,且不同厂商的板级代码相互耦合,维护成本急剧上升。
设备树(Device Tree)是一种描述硬件资源的数据结构,最早源于 Open Firmware(IEEE 1275)规范。Linux 从 2.6 内核开始逐步引入设备树,到 3.x 时代已成为 ARM、PowerPC、RISC-V 等架构的标准硬件描述方式。其核心思想是:将硬件配置从内核代码中剥离,以独立的数据文件形式存在,同一内核二进制可以适配多种硬件平台。
关键目标: 实现“内核不变,硬件可配”——通过更换设备树文件即可支持不同板级硬件,无需重新编译内核。
1.2 设备树的作用与核心价值
| 作用维度 | 具体说明 | 传统方式(无设备树) | 设备树方式 |
|---|---|---|---|
| 硬件描述 | 描述 CPU、内存、外设、总线、中断控制器等硬件拓扑 | 板级 C 文件 + 宏定义 | 独立 .dts 文本文件 |
| 资源传递 | 向驱动传递寄存器地址、中断号、时钟频率、DMA 通道等 | 平台数据(platform_data) | 设备树节点属性 |
| 驱动匹配 | 通过 compatible 字符串实现驱动与设备的绑定 | 设备 ID 表 + 板级注册 | of_match_table 匹配 |
| 多平台支持 | 同一内核镜像支持不同硬件配置 | 需编译多个内核 | 更换 .dtb 文件即可 |
| 固件接口 | Bootloader(如 U-Boot)将设备树传递给内核 | ATAG 列表(ARM 旧方式) | 设备树二进制块(DTB) |
1.3 .dts / .dtsi / .dtb 文件关系
设备树体系包含三种文件形态,它们之间存在明确的编译与包含关系:
- .dts(Device Tree Source):设备树源文件,是开发者直接编写或修改的文本文件,描述一个具体板级的硬件配置。
- .dtsi(Device Tree Source Include):设备树包含文件,通常存放 SoC 公共定义、通用外设节点、头文件宏等,通过
#include被 .dts 引用。 - .dtb(Device Tree Blob):设备树二进制文件,由 .dts 经过
dtc(Device Tree Compiler)编译生成,是内核实际解析的二进制格式。
类比理解: .dtsi 相当于 C 语言的 .h 头文件(公共声明),.dts 相当于 .c 源文件(具体实现),.dtb 相当于编译后的 .o 或 .elf 二进制文件。
1.4 设备树基本语法结构
设备树源文件采用树形层级结构,以节点(node)和属性(property)为核心元素。以下是一个典型的设备树片段:
/dts-v1/;
#include "soc-common.dtsi"
/ {
model = "EmbeddedAI Board v2.0";
compatible = "vendor,board-v2", "vendor,board";
chosen {
stdout-path = &uart0;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; /* 512MB RAM */
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
uart0: serial@10000000 {
compatible = "vendor,uart-ip";
reg = <0x10000000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
status = "okay";
};
gpio0: gpio@10001000 {
compatible = "vendor,gpio-ctrl";
reg = <0x10001000 0x100>;
interrupts = <0 45 4>;
gpio-controller;
#gpio-cells = <2>;
status = "okay";
};
};
};
1.5 语法要素详解
| 语法要素 | 表示方式 | 说明 | 示例 |
|---|---|---|---|
| 根节点 | / { } |
设备树的顶层容器,所有设备节点都挂在根下 | / { model = "..."; }; |
| 节点名称 | node-name@unit-address |
节点名 + 可选的单元地址(寄存器基址) | uart0: serial@10000000 |
| 标签(label) | label: node-name |
为节点起别名,便于引用或覆盖 | uart0: serial@10000000 |
| 属性 | key = value; |
描述节点特性,值可以是字符串、数字、数组等 | compatible = "vendor,uart-ip"; |
| reg 属性 | reg = <addr size>; |
描述设备地址空间,格式由 #address-cells / #size-cells 决定 | reg = <0x10000000 0x1000>; |
| interrupts | interrupts = <type irq flags>; |
描述中断号、触发类型等 | interrupts = <0 33 4>; |
| compatible | "vendor,device" |
驱动匹配的关键字符串,优先使用具体型号 | "vendor,uart-ip" |
| status | "okay" / "disabled" |
控制设备是否启用 | status = "okay"; |
| 包含指令 | #include "file.dtsi" |
引入公共定义,支持 C 预处理宏 | #include "soc-common.dtsi" |
1.6 节点与属性的层级关系
设备树的树形结构通过节点嵌套体现硬件拓扑。每个节点可以包含子节点,形成从 SoC 总线到具体外设的完整路径。属性则附着在节点上,提供驱动所需的配置参数。
1.7 设备树覆盖(Device Tree Overlay)简介
设备树覆盖(DTO)允许在不修改原始 .dts 文件的情况下,动态添加或修改设备树节点。这在以下场景中尤为重要:
- FPGA 动态加载:FPGA 逻辑加载后,对应的外设节点通过覆盖动态注入。
- 扩展板 / 模块支持:树莓派等平台的 HAT(Hardware Attached on Top)通过覆盖描述扩展板硬件。
- 运行时配置调整:在 Bootloader 或内核启动后,根据硬件检测结果动态修改设备树。
注意: 设备树覆盖使用
.dtbo 文件格式,编译命令为 dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts。其中 -@ 选项用于生成覆盖所需的符号信息。