1. 设备树(DTS)概述:设备树的历史、为什么需要设备树、设备树在Linux内核中的角色

1.1 设备树的历史演进

设备树(Device Tree)是一种描述硬件资源的数据结构,最早源于Open Firmware规范。在嵌入式Linux发展早期,硬件描述信息被硬编码在板级文件(board file)中,导致内核代码充斥着大量平台相关的arch/arm/mach-*目录下的C语言结构体。

关键时间节点:
  • 2005年 — PowerPC架构率先引入设备树,替代Open Firmware的FDT(Flattened Device Tree)格式
  • 2011年 — ARM Linux社区开始大规模迁移至设备树,Linus Torvalds明确要求新平台必须使用DTS
  • 2014年 — Linux 3.16内核正式移除对旧板级文件的支持,设备树成为ARM架构唯一硬件描述方式
  • 至今 — RISC-V、MIPS、X86等架构也逐步采用设备树,形成跨架构统一硬件抽象层

设备树的引入彻底改变了嵌入式Linux的硬件适配方式。下图展示了从传统板级文件到设备树的架构变迁:

设备树引入前后硬件适配架构对比 传统方式(板级文件) board_xxx.c mach_xxx.c 硬编码寄存器地址、中断号、时钟频率 每个板子一份独立C文件 内核升级需同步修改板级代码 arch/arm/mach-xxx/ 目录膨胀 设备树方式(DTS) soc.dtsi board.dts 硬件描述与内核代码分离 同一SoC可复用.dtsi文件 板级差异仅需修改.dts arch/arm/boot/dts/ 目录 迁移

1.2 为什么需要设备树

在设备树普及之前,嵌入式Linux内核维护者面临一个严峻问题:每增加一块新开发板,就需要在内核源码中新增一个板级文件。这些文件包含大量重复的硬件初始化代码,导致内核体积膨胀、维护成本急剧上升。

对比维度 板级文件方式 设备树方式
硬件描述位置 内核C代码中硬编码 独立文本文件(.dts/.dtsi)
平台适配方式 修改内核源码,重新编译 修改DTS文件,编译DTB,无需改内核
多板支持 每个板子一份独立C文件 同一内核镜像+不同DTB即可支持多板
硬件变更响应 需修改内核代码,提交patch 仅修改DTS,快速迭代
二进制兼容性 内核镜像与硬件绑定 内核镜像与硬件解耦,DTB传递硬件信息
社区维护成本 高(ARM架构曾超过3000个板级文件) 低(统一描述规范,减少冗余)
核心价值:设备树实现了硬件描述与内核代码的彻底分离。同一份内核镜像可以通过加载不同的DTB(Device Tree Blob)来适配不同的硬件平台,这是嵌入式Linux产品化的重要基础。

设备树解决了以下关键痛点:

  • 避免内核源码膨胀 — 硬件描述从C代码中剥离,内核源码只保留驱动逻辑
  • 降低平台适配门槛 — 硬件工程师无需深入内核源码,即可通过修改DTS完成板级适配
  • 支持多板共镜像 — Bootloader根据实际硬件选择对应的DTB加载,实现单镜像多平台
  • 加速产品开发 — 硬件改版只需更新DTS,无需重新编译内核,缩短开发周期

1.3 设备树在Linux内核中的角色

设备树在Linux内核启动和运行过程中扮演着硬件信息传递者和驱动与硬件之间的桥梁双重角色。其核心工作流程如下:

设备树在Linux内核中的角色与数据流 .dts / .dtsi 源文件 DTC 编译器 DTB (二进制) Bootloader 加载DTB 内核解析DTB → device_node 驱动匹配 → platform_device

设备树在Linux内核中的具体角色可以归纳为以下四个层面:

1.3.1 硬件拓扑描述层

设备树以树形结构描述SoC内部和外设的拓扑关系。每个节点代表一个硬件设备或总线,节点属性描述设备的寄存器地址、中断号、时钟频率、DMA通道等资源信息。内核通过解析设备树,自动构建硬件拓扑图。

// 设备树节点示例:描述一个UART控制器
uart0: serial@10000000 {
    compatible = "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x10000000 0x4000>;          // 寄存器基地址+长度
    interrupts = <0 26 4>;              // 中断号+触发类型
    clocks = <&clks IMX6Q_CLK_UART_IPG>;
    clock-names = "ipg";
    status = "disabled";                 // 默认关闭,由板级DTS开启
};

1.3.2 驱动匹配与资源分配层

内核的platform总线机制利用设备树中的compatible属性与驱动进行匹配。当驱动注册时,内核遍历设备树中所有节点,找到compatible值匹配的节点,然后自动分配资源(I/O内存、中断、DMA等)并调用驱动的probe()函数。

匹配流程:
  1. Bootloader将DTB物理地址传递给内核(通过r2寄存器或特定参数)
  2. 内核启动早期调用unflatten_device_tree()将DTB解析为device_node树
  3. 驱动注册时,platform总线调用of_match_table进行compatible匹配
  4. 匹配成功后,内核从设备树节点提取reg、interrupts等资源,填充到platform_device结构体
  5. 驱动probe()函数通过platform_get_resource()等API获取硬件资源

1.3.3 运行时配置与覆盖层

设备树支持运行时覆盖(overlay)机制,允许在系统运行过程中动态添加或修改硬件描述。这一特性在FPGA动态加载、外设热插拔、产品变体管理等场景中尤为重要。

特性 说明 典型应用
DTB Overlay 通过.dtbo文件在运行时动态加载设备节点 FPGA重配置、扩展板识别
chosen节点 传递内核启动参数、控制台路径等运行时信息 bootargs、initrd地址
status属性 控制设备节点的启用/禁用状态 产品变体裁剪、故障隔离
aliases节点 为设备节点提供简短别名,方便Bootloader和用户空间引用 mmc0 = &sdhci0

1.3.4 用户空间接口层

设备树信息通过/sys/firmware/devicetree/base伪文件系统暴露给用户空间。开发者可以在此目录下查看完整的设备树结构,无需反编译DTB文件即可调试硬件配置。

# 查看设备树中所有compatible属性
find /sys/firmware/devicetree/base -name "compatible" -exec cat {} \;

# 查看某个设备节点的寄存器地址
cat /sys/firmware/devicetree/base/soc/uart@10000000/reg | xxd -p
注意事项:设备树是静态硬件描述,不适用于描述动态变化的硬件状态(如传感器读数、GPIO电平)。内核驱动应通过常规的I/O操作读取硬件状态,设备树仅提供硬件资源的静态配置信息。

1.4 设备树与内核驱动的关系总结

设备树在Linux内核中不是独立存在的,它与驱动框架深度耦合。下图展示了设备树节点与内核驱动对象之间的映射关系:

设备树节点 → 内核驱动对象映射 DTS 节点 compatible + reg + interrupts of_platform_populate() 创建platform_device platform_device resource[] 已填充 platform_driver probe() → 驱动初始化 驱动通过of_match_table匹配 compatible属性值

总结来说,设备树在Linux内核中承担以下不可替代的角色:

  • 硬件描述标准化 — 提供统一的硬件描述语法,跨架构、跨平台通用
  • 驱动与硬件解耦 — 驱动通过设备树获取硬件资源,不依赖具体板级代码
  • 启动参数传递 — 承载内核启动所需的硬件配置和Bootloader传递的运行时信息
  • 产品变体管理 — 同一SoC的不同产品通过不同DTS文件实现差异化配置
  • 调试与诊断 — 通过sysfs暴露设备树信息,方便开发人员验证硬件配置正确性