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:子节点地址和长度字段的单元数。
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 平台唯一支持的硬件描述方式。
设备树带来的核心价值可以归纳为以下三点:
- 硬件描述与驱动分离:驱动开发者只需关注
compatible字符串匹配,无需关心板级细节。 - 减少内核分支:SoC 厂商和板卡厂商不再需要维护大量内核分支,只需提供
.dts文件即可。 - 支持多平台启动:Bootloader 可以动态选择
.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 规范),但嵌入式领域仍以设备树为主。
• 嵌入式系统(ARM/RISC-V)→ 使用设备树
• PC/服务器(x86/x86_64)→ 使用 ACPI
• 部分 ARM 服务器也开始支持 ACPI(如 SBSA/SBBR 规范),但嵌入式领域仍以设备树为主。
1.4 设备树在嵌入式系统中的定位
在典型的嵌入式 Linux 启动流程中,设备树处于 Bootloader 与内核之间的关键位置:
- ROM Code:芯片上电后执行固化代码,加载 Bootloader(如 U-Boot)。
- Bootloader:初始化 DDR、时钟、串口等基础硬件,从存储介质(eMMC、SD卡、NAND Flash)读取
.dtb文件和内核镜像。 - 设备树传递:Bootloader 将
.dtb文件加载到内存中,将地址通过寄存器(如 r2)传递给内核。 - 内核解析:内核启动早期调用
unflatten_device_tree()将扁平设备树展开为树形结构,随后遍历节点创建设备。 - 驱动匹配:总线驱动(如 I2C、SPI、Platform)在注册时,通过
of_match_table匹配设备树节点,触发probe()函数。
嵌入式开发者需要掌握的能力:
• 编写和修改
• 使用
• 理解
• 在驱动中使用
• 编写和修改
.dts / .dtsi 文件• 使用
dtc 编译和反编译设备树• 理解
compatible、reg、interrupts 等核心属性• 在驱动中使用
of_match_table 和 device_property_read_* API 读取设备树信息