1. 设备树基础概念

1.1 什么是设备树 (Device Tree)

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中所有可发现的硬件设备、内存映射、中断连接、时钟关系等非可枚举信息。设备树源文件(.dts)经过编译生成设备树二进制文件(.dtb),在系统启动时由引导加载程序传递给内核,内核解析该结构完成硬件资源的注册与驱动匹配。

核心定义:设备树 = 硬件拓扑的静态描述文件,将板级细节从内核代码中剥离,实现“一套内核二进制,适配多种硬件平台”。
关键文件后缀:
.dts — 设备树源文件(文本,可读)
.dtsi — 设备树包含文件(类似头文件,用于 SoC 公共定义)
.dtb — 设备树二进制文件(编译产物,内核直接使用)
.dtbo — 设备树叠加层(运行时动态修改)
设备树结构示意图 / (根节点) cpus memory soc chosen serial@... (UART) i2c@... (I2C) gpio@... (GPIO) touch@1a eeprom@50 每个节点包含属性(reg、interrupts、clocks 等),子节点继承父节点总线上下文。 节点名称格式:name@unit-address,如 i2c@ff020000。

1.2 为什么需要设备树

在设备树出现之前(Linux 2.6 ~ 3.x 早期),ARM 架构的板级支持包(BSP)采用大量 board-*.c 和 mach-*.c 文件硬编码硬件信息。这种模式导致以下严重问题:

问题 传统方式(无设备树) 设备树方式
代码冗余 每块新板卡需要新增数百行 C 代码描述硬件 只需编写 .dts 文件,内核代码零改动
内核膨胀 ARM 内核包含大量板级文件,单内核镜像无法通用 同一内核镜像通过不同 .dtb 适配不同硬件
维护困难 硬件变更需要重新编译内核,社区难以统一 硬件描述与驱动分离,驱动只需匹配 compatible 字符串
启动流程耦合 Bootloader 与内核强依赖固定物理地址 Bootloader 动态加载 .dtb,内核解析后自动映射
典型痛点案例: 在设备树普及前,Linux 主线内核中 ARM 架构的 arch/arm/mach-* 目录包含超过 3000 个板级文件。每添加一款开发板,就需要向社区提交大量板级 C 代码,审核周期长达数月。设备树将这一过程简化为一个 .dts 文件(通常 200~500 行),大幅降低了硬件适配门槛。
传统 BSP vs 设备树:硬件适配流程对比 传统 BSP 方式 1. 编写 board_xxx.c(内存映射、GPIO、时钟) 2. 修改 Kconfig / Makefile 加入新板 3. 重新编译内核,生成 zImage 4. Bootloader 固定地址加载内核 5. 硬件变更 → 重新修改 C 代码 → 重编 ❌ 内核与板级硬件强耦合,维护成本高 设备树方式 1. 编写 board.dts(纯文本描述硬件) 2. dtc 编译生成 board.dtb 3. 使用通用内核镜像(无需重编) 4. Bootloader 加载 zImage + board.dtb 5. 硬件变更 → 只改 .dts → 重编 .dtb ✅ 内核与硬件解耦,一套内核适配多板

1.3 设备树与 ACPI 的区别

ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台的标准硬件抽象接口,与设备树在理念、使用场景和技术实现上有本质差异。以下从多个维度进行对比:

对比维度 设备树 (Device Tree) ACPI
主要平台 ARM、RISC-V、PowerPC、MIPS 等嵌入式架构 x86 / x86_64(PC、服务器)
描述方式 静态树形结构,编译为二进制 .dtb AML(ACPI Machine Language)字节码,解释执行
运行时灵活性 静态加载,启动后一般不修改(除 overlay) 支持运行时动态枚举、热插拔、电源事件
电源管理 依赖驱动自身实现,设备树仅提供描述 内建完整的电源状态、睡眠/唤醒、设备电源策略
硬件枚举 内核解析 .dtb 节点,匹配 compatible 驱动 BIOS/UEFI 提供 ACPI 表,内核通过 AML 解释器枚举
复杂度 轻量,源文件可读性强,学习曲线平缓 庞大,ASL 源码编译为 AML,调试工具链复杂
社区生态 Linux 内核主线 + 开源社区主导 UEFI 论坛标准化,微软/Intel 主导
选择原则:
• 嵌入式系统(ARM/RISC-V)→ 设备树,轻量、直接、可控
• PC/服务器(x86)→ ACPI,需要热插拔、高级电源管理、标准化接口
• 部分 ARM 服务器(如 Ampere、华为鲲鹏)同时支持设备树和 ACPI,由固件决定启动方式
设备树 vs ACPI:系统启动硬件发现流程 设备树流程 Bootloader 加载 .dtb 到内存 内核 early_init_dt_scan() 解析根节点 of_platform_populate() 创建设备 驱动匹配 compatible → probe() ACPI 流程 UEFI 提供 RSDP/XSDT 表 内核 ACPI 子系统解析 DSDT/SSDT AML 解释器执行 _INI/_STA 方法 驱动匹配 ACPI ID → probe()

1.4 设备树的核心语法速览

一个典型的设备树节点包含以下要素:

/dts-v1/;

/ {
    model = "TI AM335x BeagleBone Black";
    compatible = "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx";

    cpus {
        #address-cells = <1>;
        #size-cells = <0>;
        cpu@0 {
            compatible = "arm,cortex-a8";
            device_type = "cpu";
            reg = <0x00>;
        };
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512 MB */
    };

    &i2c0 {
        status = "okay";
        clock-frequency = <100000>;
        touchscreen@1a {
            compatible = "ti,tsc2046";
            reg = <0x1a>;
            interrupt-parent = <&gpio1>;
            interrupts = <18 2>;
        };
    };
};
语法要点:
• /dts-v1/; — 版本声明,必须位于文件首行
• compatible — 驱动匹配的核心字符串,优先级从具体到通用
• reg — 地址/长度对,由 #address-cells 和 #size-cells 决定宽度
• &i2c0 — 引用标签,用于在 .dtsi 基础上追加或覆盖属性
• status — 设备状态,"okay" 启用,"disabled" 禁用

1.5 设备树在驱动移植中的角色

在驱动移植适配工作中,设备树承担以下关键职责:

  • 硬件资源描述: 提供寄存器地址范围、中断号、DMA 通道、时钟频率等驱动所需的资源信息。
  • 驱动匹配锚点: 驱动通过 of_match_table 中的 compatible 字符串与设备树节点建立绑定关系。
  • 平台数据传递: 自定义属性(如 gpio-leds、pinctrl-*)传递板级特定配置,避免硬编码。
  • 总线拓扑定义: 描述 I2C、SPI、MMC 等总线上的从设备连接关系,驱动据此枚举子设备。
  • 电源域与时钟域: 通过 power-domains、clocks 属性建立设备与电源管理框架的关联。
驱动移植第一原则: 先确认设备树节点是否存在且属性完整,再调试驱动代码。80% 的移植问题源于设备树配置错误,而非驱动逻辑本身。