1. 设备树基础概念
1.1 什么是设备树
设备树(Device Tree)是一种描述硬件资源的数据结构,它使用树形节点来记录系统中的处理器、内存、外设、中断控制器、时钟、GPIO 等硬件组件及其连接关系。设备树源文件(.dts)经过设备树编译器(dtc)编译为二进制设备树 Blob(.dtb),在系统启动时由 Bootloader 传递给内核。
核心要点:设备树本质上是“硬件拓扑的静态描述文件”,它让同一份内核镜像可以适配不同硬件平台,而无需重新编译内核。
1.2 为什么需要设备树
在设备树出现之前,Linux 内核使用大量的 板级文件(board file) 来定义硬件信息。每个 ARM 平台都需要在 arch/arm/mach-* 目录下编写数百行 C 代码来描述硬件。这种方式存在严重问题:
| 对比维度 | 传统板级文件方式 | 设备树方式 |
|---|---|---|
| 硬件描述位置 | 内核源码 arch/arm/mach-* |
独立 .dts 文件,与内核解耦 |
| 适配新板卡 | 需修改内核源码,重新编译内核 | 只需编写新 .dts,编译 .dtb,无需改内核 |
| 内核镜像通用性 | 每个板卡需要专用内核镜像 | 同一内核镜像可启动不同板卡 |
| 社区维护成本 | 大量重复代码,难以维护 | 硬件描述集中管理,易于维护 |
| 启动流程 | Bootloader 固定传递硬件参数 | Bootloader 动态加载 .dtb 到内存 |
关键驱动因素:ARM 架构在 Linux 3.x 时代开始强制要求使用设备树,以解决“ARM 内核碎片化”问题。设备树使得 SoC 厂商只需提供
.dtsi 包含文件,板卡厂商通过 #include 方式复用,大幅降低硬件适配工作量。
1.3 设备树在 Linux 内核中的角色
设备树在 Linux 内核启动和运行过程中扮演着 硬件信息提供者 的核心角色。内核通过设备树完成以下关键任务:
- 启动阶段硬件探测: 内核在启动早期解析
.dtb,获取内存布局、CPU 数量、中断控制器、定时器等基础硬件信息。 - 驱动匹配与绑定: 设备驱动通过
compatible属性与设备树节点匹配,实现“驱动-设备”的自动绑定。 - 资源获取: 驱动从设备树节点读取
reg(寄存器地址)、interrupts(中断号)、clocks(时钟)等资源信息。 - 运行时动态配置: 内核提供
of_*系列 API(如of_property_read_u32),驱动可在运行时查询设备树属性。
注意:设备树是静态描述,不参与运行时硬件状态变更。如果硬件在运行过程中发生变化(如热插拔 PCIe 设备),设备树仅提供初始拓扑,动态变化由内核的运行时机制处理。
1.4 设备树相关文件类型
| 文件扩展名 | 全称 | 说明 |
|---|---|---|
.dts |
Device Tree Source | 设备树源文件,文本格式,描述具体板卡的硬件 |
.dtsi |
Device Tree Source Include | 设备树包含文件,通常由 SoC 厂商提供,定义 SoC 公共硬件 |
.dtb |
Device Tree Blob | 编译后的二进制文件,Bootloader 传递给内核 |
.dtbo |
Device Tree Blob Overlay | 设备树叠加层,用于运行时动态修改设备树 |
实践建议:调试设备树时,最常用的命令是
dtc -I dtb -O dts -o output.dts input.dtb,可以将二进制 .dtb 反编译为可读的 .dts 源文件,便于检查节点结构和属性值。
1.5 一个简单的设备树示例
/dts-v1/;
/ {
model = "Example Board v1";
compatible = "vendor,example-board", "vendor,soc";
#address-cells = <1>;
#size-cells = <1>;
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x40000000>; /* 1GB RAM */
};
soc {
#address-cells = <1>;
#size-cells = <1>;
ranges;
serial@1c000 {
compatible = "vendor,uart";
reg = <0x1c000 0x1000>;
interrupts = <0 33 4>;
clock-frequency = <24000000>;
};
gpio@1e000 {
compatible = "vendor,gpio";
reg = <0x1e000 0x1000>;
interrupts = <0 34 4>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
关键属性解读:
compatible:驱动匹配的关键字符串,格式通常为 "厂商,设备型号"。reg:描述地址和大小,格式由 #address-cells 和 #size-cells 决定。interrupts:描述中断号、触发类型等,格式依赖中断控制器。ranges:定义父地址空间到子地址空间的映射关系。