1. 设备树基础概念

1.1 什么是设备树(Device Tree)

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点(Node)和属性(Property)来向操作系统内核传递板级硬件信息。在 Linux 内核中,设备树通常以 .dts(Device Tree Source)源文件形式编写,经过 dtc(Device Tree Compiler)编译为 .dtb(Device Tree Blob)二进制文件,由 Bootloader 加载并传递给内核。

核心定义:设备树是一个硬件描述文件,它告诉内核“板上有什么设备、连接在哪个总线、使用什么资源”,从而让同一份内核镜像支持不同的硬件平台。

一个典型的设备树节点结构如下:

/dts-v1/;

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

    cpus {
        cpu@0 {
            compatible = "arm,cortex-a8";
            reg = <0x0>;
        };
    };

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

    i2c0: i2c@44e0b000 {
        compatible = "ti,omap4-i2c";
        reg = <0x44e0b000 0x1000>;
        interrupts = <70>;
        clock-frequency = <100000>;
        #address-cells = <1>;
        #size-cells = <0>;

        eeprom@50 {
            compatible = "atmel,24c32";
            reg = <0x50>;
            pagesize = <32>;
        };
    };
};
关键字段说明:
compatible:驱动匹配的核心标识,格式为 "<制造商>,<型号>";
reg:设备地址或寄存器基址;
interrupts:中断号;
#address-cells / #size-cells:子节点地址和长度字段的单元数。
设备树层级结构示意图 / (根节点) cpus memory@80000000 i2c0: i2c@44e0b000 cpu@0 eeprom@50 节点属性示例 compatible = "ti,omap4-i2c" → 驱动匹配标识 reg = <0x44e0b000 0x1000> → 寄存器基址和长度 interrupts = <70> → 中断号

1.2 为什么需要设备树

在设备树出现之前,Linux 内核使用大量的 板级文件(Board File) 来注册硬件信息。每个 ARM 开发板都需要在 arch/arm/mach-xxx 目录下编写一个 board-xxx.c 文件,其中包含平台设备、资源、GPIO、中断等硬编码信息。这种方式的弊端日益突出:

对比维度 板级文件时代(旧) 设备树时代(新)
硬件变更影响 每换一个板子,必须修改内核源码并重新编译 只需修改 .dts 文件,重新编译 .dtb,内核不变
内核镜像通用性 一个内核镜像只支持一个板子 一个内核镜像通过不同 .dtb 支持多个板子
代码维护成本 arch/arm 下充斥大量重复板级代码 板级代码被设备树替代,内核主线更干净
社区接受度 ARM 社区长期抱怨代码混乱 设备树成为 ARM 平台标准,被 RISC-V、MIPS 等架构采纳
驱动开发效率 驱动与板级信息耦合,移植困难 驱动只关心 compatible 匹配,硬件描述与驱动分离
重要里程碑:Linux 内核从 3.x 版本开始,ARM 架构强制要求使用设备树。从 4.x 版本起,大量板级文件被移除,设备树成为 ARM 平台唯一支持的硬件描述方式。

设备树带来的核心价值可以归纳为以下三点:

  1. 硬件描述与驱动分离:驱动开发者只需关注 compatible 字符串匹配,无需关心板级细节。
  2. 减少内核分支:SoC 厂商和板卡厂商不再需要维护大量内核分支,只需提供 .dts 文件即可。
  3. 支持多平台启动:Bootloader 可以动态选择 .dtb 文件,实现一个内核镜像启动不同硬件。
设备树解决的核心问题 板级文件时代(旧) board-A.c → 内核编译 → 只支持板A board-B.c → 内核编译 → 只支持板B board-C.c → 内核编译 → 只支持板C 设备树时代(新) board-A.dtb → 内核不变 → 支持板A board-B.dtb → 内核不变 → 支持板B board-C.dtb → 内核不变 → 支持板C 同一份内核镜像 + 不同 .dtb = 支持不同硬件平台

1.3 设备树与 ACPI 的区别

ACPI(Advanced Configuration and Power Interface)是 x86/x86_64 平台的标准硬件描述和电源管理接口。设备树和 ACPI 虽然都用于向操作系统描述硬件,但设计哲学、使用场景和实现方式有本质区别。

对比维度 设备树(Device Tree) ACPI
主要应用平台 ARM、RISC-V、MIPS、PowerPC 等嵌入式平台 x86/x86_64 PC、服务器平台
描述方式 纯文本 .dts 源文件,编译为二进制 .dtb ASL(ACPI Source Language)编译为 AML(ACPI Machine Language)二进制表
运行时能力 静态描述,启动时一次性传递给内核,不可动态修改 支持动态命名空间、热插拔、电源管理事件、运行时方法调用
电源管理 不包含电源管理逻辑,由驱动或 PM 框架处理 内置完整的电源管理状态机(C-states、P-states、D-states)
复杂度 轻量级,语法简单,学习成本低 重量级,规范庞大(上千页),实现复杂
驱动匹配方式 基于 compatible 字符串匹配 基于 HID(Hardware ID)和 CID(Compatible ID)匹配
内核支持 Linux、U-Boot、FreeBSD、Zephyr 等 Windows、Linux、FreeBSD(主要 x86 平台)
选择原则:
• 嵌入式系统(ARM/RISC-V)→ 使用设备树
• PC/服务器(x86/x86_64)→ 使用 ACPI
• 部分 ARM 服务器也开始支持 ACPI(如 SBSA/SBBR 规范),但嵌入式领域仍以设备树为主。
设备树 vs ACPI 架构对比 设备树(Device Tree) .dts 源文件 → dtc 编译 → .dtb Bootloader 加载 .dtb → 传递给内核 内核解析设备树 → 创建 platform_device 驱动通过 compatible 匹配 → probe ACPI ASL 源文件 → iasl 编译 → AML BIOS/UEFI 提供 ACPI 表 → 内核解析 内核 ACPI 子系统 → 枚举设备命名空间 驱动通过 HID/CID 匹配 → probe 共同目标:向内核描述硬件,实现驱动与硬件信息解耦 差异:设备树轻量静态,ACPI 重量动态(含电源管理、热插拔)

1.4 设备树在嵌入式系统中的定位

在典型的嵌入式 Linux 启动流程中,设备树处于 Bootloader 与内核之间的关键位置:

  1. ROM Code:芯片上电后执行固化代码,加载 Bootloader(如 U-Boot)。
  2. Bootloader:初始化 DDR、时钟、串口等基础硬件,从存储介质(eMMC、SD卡、NAND Flash)读取 .dtb 文件和内核镜像。
  3. 设备树传递:Bootloader 将 .dtb 文件加载到内存中,将地址通过寄存器(如 r2)传递给内核。
  4. 内核解析:内核启动早期调用 unflatten_device_tree() 将扁平设备树展开为树形结构,随后遍历节点创建设备。
  5. 驱动匹配:总线驱动(如 I2C、SPI、Platform)在注册时,通过 of_match_table 匹配设备树节点,触发 probe() 函数。
嵌入式开发者需要掌握的能力:
• 编写和修改 .dts / .dtsi 文件
• 使用 dtc 编译和反编译设备树
• 理解 compatible、reg、interrupts 等核心属性
• 在驱动中使用 of_match_table 和 device_property_read_* API 读取设备树信息