3. 设备树(Device Tree)基础:设备树的作用、dts/dtsi/dtb文件格式、节点与属性
3.1 设备树的作用
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,用于将硬件配置信息从Linux内核源码中分离出来。在嵌入式Linux系统中,设备树是驱动开发者与硬件平台之间的“硬件配置契约”。
核心作用: 设备树将“硬件是什么”与“驱动怎么用”解耦,使得同一份内核镜像可以支持多种硬件平台,无需重新编译内核。
设备树的主要作用包括:
- 硬件描述: 描述CPU、内存、外设(I2C、SPI、UART、GPIO、中断控制器等)的地址、中断、时钟、复位等资源。
- 驱动匹配: 提供
compatible属性,供内核驱动通过of_match_table进行设备与驱动的匹配。 - 资源传递: 驱动通过设备树API(如
of_property_read_u32、irq_of_parse_and_map)获取硬件配置参数,避免硬编码。 - 平台无关化: 同一份内核镜像,通过更换不同的设备树文件(.dtb),即可适配不同硬件板卡。
3.2 dts / dtsi / dtb 文件格式
设备树涉及三种主要文件格式,它们在开发流程中扮演不同角色:
| 文件后缀 | 全称 | 作用 | 是否可编译 | 典型用途 |
|---|---|---|---|---|
.dts |
Device Tree Source | 设备树源文件,描述一个具体板卡的硬件 | 是(→ .dtb) | 板级硬件描述,如 imx6ull-evk.dts |
.dtsi |
Device Tree Source Include | 设备树头文件/包含文件,可被 .dts 或其它 .dtsi 包含 | 否(仅被包含) | SoC 公共定义、IP 核模板,如 imx6ull.dtsi |
.dtb |
Device Tree Blob | 设备树二进制文件,由 .dts 编译生成,供 Bootloader 加载 | 否(已编译) | 内核启动时由 Bootloader 传递给内核 |
开发习惯: 将 SoC 公共部分(如 CPU、中断控制器、时钟)放在
.dtsi 中,板级差异(如 GPIO 按键、LED、外设地址)放在 .dts 中。通过 #include 机制复用,避免重复定义。
编译流程:
# 将 .dts 编译为 .dtb
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译 .dtb 为 .dts(调试用)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
3.3 节点与属性
设备树的核心语法由 节点(Node) 和 属性(Property) 组成,采用树形层级结构描述硬件拓扑。
3.3.1 节点(Node)
节点是设备树的基本单元,每个节点代表一个硬件设备或总线。节点名称格式为 node-name@unit-address,其中 unit-address 通常为设备基地址。
/ {
#address-cells = <1>;
#size-cells = <1>;
soc {
compatible = "simple-bus";
#address-cells = <1>;
#size-cells = <1>;
ranges;
gpio0: gpio@2090000 {
compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
reg = <0x02090000 0x4000>;
interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
gpio-controller;
#gpio-cells = <2>;
};
};
};
节点关键概念:
- 根节点:
/是设备树的根,所有设备节点都是根节点的子节点。 - 标签(Label): 如
gpio0:,用于在其它地方引用该节点(通过&gpio0)。 - 节点路径: 如
/soc/gpio@2090000,唯一标识一个节点。 - 子节点: 节点可以嵌套,形成树形结构。
3.3.2 属性(Property)
属性是节点的键值对,用于描述设备的配置信息。常见属性类型如下:
| 属性名 | 类型 | 说明 | 示例 |
|---|---|---|---|
compatible |
字符串列表 | 驱动匹配标识,格式为 "厂商,型号" | "fsl,imx6ul-gpio" |
reg |
u32/u64 数组 | 设备地址范围,格式为 (地址, 长度) 对 | <0x02090000 0x4000> |
interrupts |
u32 数组 | 中断描述,格式依赖中断控制器 | <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH> |
interrupt-parent |
phandle | 指定该设备连接的中断控制器 | &intc |
gpio-controller |
空属性 | 标记该节点为 GPIO 控制器 | 无值 |
#gpio-cells |
u32 | GPIO 描述符需要的 cell 数量 | <2> |
status |
字符串 | 设备状态:"okay" / "disabled" |
"okay" |
clock-frequency |
u32 | 时钟频率(Hz) | <24000000> |
注意:
#address-cells 和 #size-cells 决定 reg 属性中地址和长度的 cell 数量。例如 #address-cells = <1> 表示地址占 1 个 u32(32位),#size-cells = <1> 表示长度占 1 个 u32。对于 64 位地址空间,应设置为 <2>。
3.3.3 节点与属性的树形结构
3.4 设备树中的中断描述
中断是设备树中最重要的资源之一。设备树通过 interrupts、interrupt-parent、interrupt-controller 等属性描述中断拓扑。
中断相关属性:
| 属性 | 作用 | 示例 |
|---|---|---|
interrupt-controller |
空属性,标记该节点为中断控制器 | interrupt-controller; |
#interrupt-cells |
指定中断描述符需要的 cell 数量 | #interrupt-cells = <3>(GIC 格式) |
interrupt-parent |
指定该设备连接的中断控制器 phandle | interrupt-parent = &intc; |
interrupts |
中断号、触发类型等描述 | interrupts = <0 66 4> |
GIC 中断 cell 格式(ARM 通用):
<中断类型 中断号 触发类型>- 中断类型: 0 = SPI(共享外设中断),1 = PPI(私有外设中断)
- 中断号: SPI 从 32 开始编号,如 SPI 66 表示中断号 66
- 触发类型: 1 = 上升沿触发,4 = 高电平触发
3.5 设备树在驱动中的使用流程
驱动开发者通过设备树 API 获取硬件配置,典型流程如下:
- 编写设备树节点: 在 .dts 中定义设备节点,填写 compatible、reg、interrupts 等属性。
- 编译设备树: 使用 dtc 将 .dts 编译为 .dtb。
- 驱动匹配: 驱动通过
of_match_table与设备树节点的compatible属性匹配。 - 资源解析: 驱动使用
of_property_read_u32、irq_of_parse_and_map、of_iomap等 API 读取设备树中的配置。 - 硬件操作: 驱动根据解析到的地址、中断号、GPIO 号等参数初始化硬件。
调试技巧: 在 Linux 内核启动后,可以通过
/proc/device-tree 查看当前使用的设备树内容。使用 dtc -I fs -O dts /proc/device-tree 可以导出当前设备树的 .dts 格式。
设备树是嵌入式 Linux 驱动开发的基石,掌握节点、属性、中断描述和编译流程,是后续学习中断申请与属性解析的前提。