1. 设备树(DTS)基础概念

1.1 什么是设备树

设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树状节点来记录系统中所有可访问的设备、总线、中断控制器、时钟、GPIO等硬件信息。在嵌入式Linux系统中,设备树充当了硬件描述与内核驱动之间的“桥梁”。

核心定义:设备树是一个由节点(node)和属性(property)组成的树形结构,每个节点代表一个硬件设备或总线,属性则描述该设备的寄存器地址、中断号、时钟频率等具体参数。

设备树文件通常以文本形式编写,经过编译后生成二进制格式供内核解析。其核心思想是将硬件描述从内核源码中剥离出来,使得同一份内核镜像可以支持多种硬件平台。

设备树概念结构图 根节点 "/" cpus memory soc chosen cpu@0 cpu@1 i2c@0 uart@0 stdout eeprom@50 rtc@51 每个节点包含属性(reg、interrupts、clocks等),子节点继承父节点总线上下文

1.2 为什么需要设备树

在设备树出现之前,嵌入式Linux内核通过大量的板级文件(board file)来描述硬件,每个开发板都需要在 arch/arm/mach-xxx 目录下编写数百行C代码来注册平台设备、配置资源。这种方式存在严重问题:

对比维度 传统板级文件方式 设备树方式
硬件描述位置 内核源码 arch/arm/mach-xxx 独立的 .dts 文件
内核与硬件耦合度 高耦合,每块板子需修改内核 低耦合,同一内核镜像适配多板
维护成本 高,板级文件数量爆炸 低,设备树文件独立维护
启动时硬件发现 静态注册,编译时确定 动态解析,运行时加载
社区支持 ARM社区已弃用 主流架构强制使用
关键驱动因素:2011年Linux 3.0之后,ARM社区正式要求所有新平台必须使用设备树。PowerPC、RISC-V、ARM64等架构从一开始就基于设备树设计。设备树使得SoC厂商只需提供一份.dtsi文件,板级厂商在此基础上扩展即可。

设备树带来的核心收益包括:

  • 硬件描述标准化:使用统一的语法和绑定规范,不同厂商的硬件描述风格一致。
  • 内核镜像通用化:一个内核镜像可以启动多个硬件平台,Bootloader负责选择正确的设备树。
  • 驱动与硬件解耦:驱动通过设备树中的 compatible 属性匹配硬件,无需硬编码地址。
  • 外设配置灵活化:修改管脚复用、时钟频率等参数只需改设备树,无需重新编译内核。

1.3 设备树文件类型

设备树涉及三种主要文件类型,它们在开发流程中扮演不同角色:

文件类型 扩展名 格式 用途
设备树源文件 .dts 文本 板级设备树描述,包含完整硬件信息
设备树头文件 .dtsi 文本 SoC公共描述,可被多个 .dts 包含
设备树二进制 .dtb 二进制 编译后的设备树,供内核和Bootloader使用

文件关系说明:

  • .dtsi 类似于C语言的 .h 头文件,通过 #include 指令被 .dts 引用。
  • 一个 .dts 文件通常包含一个 .dtsi(SoC级),再叠加板级特有的外设和配置。
  • .dtb 是最终交付给Bootloader和内核的二进制产物。
注意:设备树头文件 .dtsi 使用C预处理器,因此支持 #define、#include 等宏定义。内核源码中通常将GPIO号、时钟ID等常量定义在 include/dt-bindings/ 目录下,供 .dtsi 引用。
设备树文件类型与编译关系 soc.dtsi SoC公共描述 board.dts 板级完整描述 board.dtb 二进制设备树 #include dtc 编译 编译流程说明 1. 预处理器展开 #include 和 #define 2. dtc 编译器将 .dts 转换为 .dtb 二进制格式 3. Bootloader 将 .dtb 加载到内存,传递给内核

1.4 设备树编译流程

从 .dts 源文件到 .dtb 二进制文件,需要经过预处理和编译两个阶段。整个流程由设备树编译器 dtc(Device Tree Compiler) 完成。

1.4.1 编译命令

最常用的编译命令如下:

# 编译 .dts 为 .dtb
dtc -I dts -O dtb -o board.dtb board.dts

# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb

# 在内核源码树中使用 make 编译
make dtbs
make board.dtb

1.4.2 编译流程详解

完整的设备树编译流程包含以下步骤:

  1. 预处理阶段:C预处理器(cpp)处理 #include、#define、#ifdef 等指令,将 .dtsi 文件和绑定头文件展开到 .dts 中。
  2. 语法解析阶段:dtc解析预处理后的文本,构建内部节点树结构,检查语法正确性。
  3. 标签解析阶段:处理 &node 引用标签,将覆盖/追加的属性合并到对应节点。
  4. 二进制编码阶段:将节点树编码为扁平化设备树(FDT)二进制格式,生成 .dtb 文件。
关键工具链:内核构建系统使用 scripts/dtc/dtc 作为编译器,同时依赖 cpp 进行预处理。在 arch/<arch>/boot/dts/ 目录下执行 make dtbs 即可批量编译所有设备树。
设备树编译流程详解 预处理 (cpp) 展开 #include / #define 语法解析 构建节点树 / 检查语法 标签解析 合并 &node 引用 二进制编码 生成 FDT 格式 .dtb 输入: board.dts 输出: board.dtb 编译产物与调试技巧 • 反编译 .dtb 可验证编译结果:dtc -I dtb -O dts -o dump.dts board.dtb • 使用 fdtdump 查看 .dtb 原始结构:fdtdump board.dtb • 内核启动时传递 earlycon 和 dtb 地址可调试设备树解析过程

1.5 设备树语法速览

一个典型的 .dts 文件结构如下,包含根节点、子节点、属性和标签引用:

/dts-v1/;

#include "soc.dtsi"

/ {
    model = "MyBoard v1.0";
    compatible = "vendor,myboard", "vendor,soc";

    chosen {
        stdout-path = &uart0;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x40000000>;
    };
};

&uart0 {
    status = "okay";
    clock-frequency = <115200>;
};
语法元素 说明 示例
/dts-v1/; 版本声明,必须位于文件开头 /dts-v1/;
/ { }; 根节点定义 / { model = "..."; };
node@addr 节点名+地址单元 uart@fe000000
compatible 驱动匹配字符串 "vendor,device"
reg 寄存器地址和大小 <0xfe000000 0x1000>
&label 标签引用,追加属性 &uart0 { status = "okay"; };
学习建议:初学者可以从内核源码中的 arch/arm/boot/dts/ 或 arch/arm64/boot/dts/ 目录下找到实际设备的 .dts 文件进行阅读。推荐从 vexpress-v2p-ca9.dts 或 sun50i-a64.dtsi 等经典文件入手。