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 的完整编译链路以及在内核启动中的加载位置:

DTS/DTC/DTB 编译与加载流程 DTS → DTC → DTB 编译与加载流程 DTS 源码 *.dts / *.dtsi DTC 编译器 dtc 工具 DTB 二进制 *.dtb 编译 生成 Bootloader U-Boot / Barebox Linux 内核 解析 DTB 设备驱动 UART / I2C / SPI 加载 DTB 传递 DTB 地址 of_xxx API 读取 说明: DTS 是开发者编写的硬件描述源码,经 DTC 编译为 DTB 二进制文件。Bootloader 在启动时将 DTB 加载到内存, 并将 DTB 地址传递给内核。内核通过 DTB 中的节点信息,调用 of_ 系列 API 为各设备驱动提供硬件参数。

1.3 设备树在 Linux 内核中的作用

设备树在 Linux 内核中承担了以下四个核心职责:

  1. 硬件拓扑描述:定义 CPU、内存、总线、外设的层级关系,包括寄存器地址范围、中断连接关系、时钟依赖等。
  2. 驱动参数传递:通过 compatible 属性匹配驱动,通过 reg、interrupts、clocks 等属性传递硬件资源。
  3. 板级差异隔离:同一 SoC 的不同板卡只需更换 DTB 文件,无需修改内核源码,实现“一次编译,多处运行”。
  4. 运行时探测:内核在启动阶段通过 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 节点属性

下图展示了设备树节点与内核驱动之间的匹配与数据传递关系:

设备树节点与内核驱动匹配关系 设备树节点与内核驱动匹配关系 DTB 中的设备节点 uart1 compatible = "ns16550" reg = <0x10001000 0x1000> uart2 compatible = "ns16550" reg = <0x10002000 0x1000> Linux 内核驱动层 serial8250 驱动 .of_match_table "ns16550" 平台设备 platform_device 资源绑定 compatible 匹配 reg / interrupts / clocks 参数传递 内核通过 compatible 属性匹配驱动,然后从 DTB 节点中读取 reg、interrupts 等属性,填充到 platform_device 的资源结构中。

1.4 设备树在 UART 调试中的特殊地位

在嵌入式系统调试阶段,UART 串口往往是开发者与目标板交互的唯一通道。设备树在 UART 调试中承担了以下关键角色:

  • 早期串口(earlycon)支持:内核在 setup_arch() 阶段就需要通过 DTB 获取串口基地址,以便在 MMU 初始化前输出调试信息。
  • 控制台串口指定:通过 chosen 节点中的 stdout-path 属性,指定哪个 UART 作为系统控制台。
  • 引脚复用配置:通过 pinctrl 子节点,配置 UART 的 TX/RX 引脚功能,确保硬件信号正常。
  • 时钟与波特率:通过 clocks 属性提供串口模块的工作时钟频率,驱动据此计算波特率分频系数。