1. 设备树基础概念
1.1 什么是设备树(Device Tree)
设备树(Device Tree,简称 DT)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中所有可访问的设备、总线、中断控制器、时钟、GPIO、DMA 等硬件信息。在嵌入式 Linux 系统中,设备树充当了“硬件描述语言”的角色,将板级硬件细节从内核源码中剥离出来,使得同一份内核镜像可以支持多种硬件平台。
设备树的核心思想是:“数据驱动代码”——内核通过解析设备树文件来获知硬件拓扑,而不是通过硬编码的板级文件(如 arch/arm/mach-xxx)。
关键理解:设备树不是设备驱动,而是设备的“身份证”和“连接图”。驱动代码通过设备树获取寄存器地址、中断号、时钟频率等参数,从而完成初始化。
1.2 DTS / DTC / DTB 的关系
设备树体系由三种文件形态构成,它们分别处于开发、编译、运行三个阶段:
| 缩写 | 全称 | 形态 | 作用 | 可读性 |
|---|---|---|---|---|
| DTS | Device Tree Source | 文本文件(.dts / .dtsi) | 开发者编写的硬件描述源码 | 人类可读 |
| DTC | Device Tree Compiler | 编译工具 | 将 DTS 编译为 DTB | — |
| DTB | Device Tree Blob | 二进制文件(.dtb) | Bootloader 传递给内核的硬件描述 | 机器可读 |
三者的转换流程如下:
*.dts (源码) ──→ DTC 编译器 ──→ *.dtb (二进制)
*.dtsi (头文件) ──→ (通过 #include 包含) ──→ 合并到 DTS
实践提示:在 UART 调试适配中,通常只需要修改
.dts 或 .dtsi 文件,然后使用 dtc -I dts -O dtb -o xxx.dtb xxx.dts 命令重新编译即可。无需重新编译整个内核。
下图展示了 DTS → DTC → DTB 的完整编译链路以及在内核启动中的加载位置:
1.3 设备树在 Linux 内核中的作用
设备树在 Linux 内核中承担了以下四个核心职责:
- 硬件拓扑描述:定义 CPU、内存、总线、外设的层级关系,包括寄存器地址范围、中断连接关系、时钟依赖等。
- 驱动参数传递:通过
compatible属性匹配驱动,通过reg、interrupts、clocks等属性传递硬件资源。 - 板级差异隔离:同一 SoC 的不同板卡只需更换 DTB 文件,无需修改内核源码,实现“一次编译,多处运行”。
- 运行时探测:内核在启动阶段通过
of_platform_populate()遍历 DTB 节点,自动创建设备并绑定驱动。
常见误区:设备树不是用来替代设备驱动的。驱动仍然需要编写,但驱动不再硬编码寄存器地址和中断号,而是通过设备树动态获取。在 UART 调试场景中,设备树主要提供串口基地址、中断号、时钟频率和引脚复用配置。
下表总结了设备树引入前后,嵌入式 Linux 硬件适配方式的变化:
| 对比维度 | 传统方式(无设备树) | 设备树方式 |
|---|---|---|
| 硬件描述位置 | 内核源码 arch/arm/mach-xxx 板级文件 |
独立的 .dts 文件 |
| 适配新板卡 | 修改内核源码,重新编译内核 | 修改 DTS,重新编译 DTB,内核不变 |
| 驱动获取硬件信息 | 直接读取宏定义或平台数据 | 通过 of_property_read_xxx() 解析 DTB |
| 多板卡支持 | 需要多个内核镜像或运行时判断 | 同一内核镜像 + 不同 DTB 即可 |
| UART 调试适配 | 修改 platform_device 或 board-xxx.c |
修改 DTS 中 uart 节点属性 |
下图展示了设备树节点与内核驱动之间的匹配与数据传递关系:
1.4 设备树在 UART 调试中的特殊地位
在嵌入式系统调试阶段,UART 串口往往是开发者与目标板交互的唯一通道。设备树在 UART 调试中承担了以下关键角色:
- 早期串口(earlycon)支持:内核在
setup_arch()阶段就需要通过 DTB 获取串口基地址,以便在 MMU 初始化前输出调试信息。 - 控制台串口指定:通过
chosen节点中的stdout-path属性,指定哪个 UART 作为系统控制台。 - 引脚复用配置:通过
pinctrl子节点,配置 UART 的 TX/RX 引脚功能,确保硬件信号正常。 - 时钟与波特率:通过
clocks属性提供串口模块的工作时钟频率,驱动据此计算波特率分频系数。