1. 设备树基础概念

1.1 什么是设备树(Device Tree)

设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它使用树形节点(Node)和属性(Property)来记录系统中所有可寻址设备、总线、中断控制器、时钟、GPIO 等硬件信息。设备树源文件(.dts)经过设备树编译器(DTC)编译为二进制设备树 Blob(.dtb),在系统启动时由 Bootloader 传递给内核解析。

核心定义:设备树 = 硬件拓扑的静态描述文件,将板级硬件细节从内核代码中剥离,实现“一份内核镜像,多板兼容”。

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

/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";
            reg = <0x0>;
        };
    };

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

    uart0: serial@44e09000 {
        compatible = "ti,am3352-uart", "ti,omap3-uart";
        reg = <0x44e09000 0x2000>;
        interrupts = <72>;
        status = "okay";
    };
};

每个节点由 节点名@地址 标识,compatible 属性用于内核匹配驱动,reg 描述地址空间,interrupts 描述中断号。这种结构使得硬件描述与驱动代码完全解耦。

设备树节点层次结构示例 / (根节点) cpus memory@80000000 chosen serial@44e09000 cpu@0 reg = <0x80000000 0x10000000> stdout-path status = "okay" compatible = "arm,cortex-a8" interrupts = <72> 说明 每个节点通过 compatible 属性匹配驱动,reg 描述地址,interrupts 描述中断资源

1.2 为什么需要设备树

在设备树出现之前,嵌入式 Linux 内核维护着大量板级文件(board-*.c),每个开发板都需要在 arch/arm/mach-xxx 下硬编码硬件信息。这种模式导致以下问题:

问题 传统板级文件方式 设备树方式
代码冗余 每块板子一份 board.c,大量重复代码 一份 .dts 文件描述硬件,内核通用
维护成本 修改硬件需重新编译内核 只需更新 .dtb,内核不变
社区合入 新板子需大量修改内核源码 提交 .dts 文件即可,驱动复用
多平台支持 内核镜像与板级绑定 同一内核镜像支持多板
启动灵活性 硬件信息编译进内核 Bootloader 动态选择 .dtb
工程经验:在 NXP i.MX6Q 平台上,传统方式需要维护 20+ 个 board-*.c 文件;迁移到设备树后,所有板级差异集中在 5 个 .dts 文件中,内核主线代码减少约 40%。

1.3 设备树与 ACPI 的区别

ACPI(Advanced Configuration and Power Interface)是 x86 平台的标准硬件描述规范,与设备树在架构、使用场景和灵活性上有本质区别:

对比维度 设备树(DT) ACPI
起源平台 嵌入式 ARM、RISC-V、PowerPC x86、x86_64
描述方式 纯文本 .dts → 二进制 .dtb ASL 源码 → AML 字节码
运行时能力 静态描述,启动后只读 支持动态热插拔、电源管理方法
驱动匹配 compatible 字符串匹配 HID/CID + _DSD 方法
资源描述 reg、interrupts、clocks 等属性 _CRS、_PRS、_SRS 资源模板
电源管理 依赖驱动实现,DT 不参与 内置 _PS0/_PS3、_CST 等对象
复杂度 轻量,适合资源受限设备 重量级,适合 PC/服务器
工具链 dtc 编译器,简单高效 iasl 编译器,ACPI 表生成
关键区别:设备树是“静态硬件清单”,ACPI 是“可执行硬件抽象层”。在嵌入式领域,设备树因其轻量、透明、易调试的特点成为绝对主流;ACPI 仅在部分高端 ARM 服务器(如 Ampere Altra)上使用。

1.4 设备树在嵌入式 Linux 中的角色

设备树在嵌入式 Linux 系统中扮演着“硬件描述层”的核心角色,贯穿从 Bootloader 到内核驱动的完整启动链路:

  1. Bootloader 阶段:U-Boot 从存储介质(eMMC、SD、NAND)加载 .dtb 文件到内存,在跳转内核前将 .dtb 地址通过寄存器 r2(ARM)或 a1(RISC-V)传递给内核。
  2. 内核初始化阶段:内核启动早期调用 unflatten_device_tree() 将 .dtb 解析为 device_node 树,建立全局设备链表。
  3. 驱动匹配阶段:总线驱动(platform、i2c、spi)遍历设备树节点,通过 of_match_table 匹配 compatible 属性,触发 probe 回调。
  4. 资源获取阶段:驱动通过 of_* API(如 of_iomap()、of_irq_get())从设备树节点读取寄存器地址、中断号、DMA 通道等资源。
  5. 运行时阶段:设备树在系统运行后保持只读,但支持 overlay 机制动态添加/移除节点(如 FPGA 加载外设)。
设备树在嵌入式 Linux 启动流程中的角色 Bootloader 加载 .dtb 到内存 内核早期初始化 unflatten_device_tree() 驱动匹配 of_match_table 资源获取 of_iomap / of_irq_get 运行时 只读 + Overlay 设备树生命周期 从 Bootloader 加载 → 内核解析 → 驱动匹配 → 资源获取 → 运行时只读
角色总结:设备树是嵌入式 Linux 的“硬件适配层”,它让内核与板级硬件解耦,使得 SoC 厂商只需维护通用驱动,板级厂商只需编写 .dts 文件,双方通过 compatible 字符串达成契约。在 NXP、TI、Rockchip、Allwinner 等主流嵌入式平台上,设备树已成为硬件描述的唯一标准。