4. 设备树(Device Tree)基础:设备树起源、dts/dtsi/dt.yaml文件格式、节点与属性、reg/status/compatible等标准属性
4.1 设备树的起源与设计动机
在早期的 Linux 内核中,板级硬件描述信息(如内存基址、中断号、GPIO 映射)被硬编码在 arch/arm/plat-xxx 和 arch/arm/mach-xxx 目录下的 C 语言板文件中。这种“代码即配置”的方式导致每次硬件变更都需要重新编译内核,且不同架构的板文件难以复用,形成了严重的“板文件爆炸”问题。
设备树(Device Tree,DT)是一种描述硬件资源的数据结构,它将硬件拓扑、寄存器地址、中断、时钟、DMA 等参数从内核源码中剥离,以树形文本文件的形式独立存在。引导加载程序(如 U-Boot)将设备树二进制(DTB)传递给内核,内核解析后动态创建平台设备,实现了“同一内核镜像支持多款硬件”的目标。
4.2 dts / dtsi / dt.yaml 文件格式
设备树源文件使用三种格式协同工作,下表总结了它们的用途与编译关系:
| 文件后缀 | 全称 | 用途 | 编译产物 |
|---|---|---|---|
.dts |
Device Tree Source | 单板级设备树源文件,包含完整硬件描述 | .dtb(二进制) |
.dtsi |
Device Tree Source Include | 公共 SoC 定义、IP 核模板,通过 #include 被 .dts 引用 |
不单独编译,被 .dts 展开 |
.dt.yaml |
Device Tree YAML Schema | 设备树绑定(binding)的 YAML 格式校验文件,用于验证 .dts 合法性 | 不编译,供 dt-validate 工具使用 |
.dtsi 中,板级差异化配置(如 I2C 设备地址、GPIO 按键)放在 .dts 中。这样当 SoC 升级时只需修改 .dtsi,各板级文件自动继承。
一个典型的 .dts 文件结构如下:
// SPDX-License-Identifier: GPL-2.0 OR MIT
/dts-v1/;
#include "imx6ull.dtsi" // 引用 SoC 级 .dtsi
/ {
model = "Embedded Device Tree Demo Board";
compatible = "vendor,imx6ull-demo", "fsl,imx6ull";
chosen {
stdout-path = &uart1;
};
memory@80000000 {
device_type = "memory";
reg = <0x80000000 0x20000000>; // 512MB RAM
};
&i2c1 { // 引用 .dtsi 中定义的 i2c1 节点
clock-frequency = <100000>;
status = "okay";
touch@38 {
compatible = "edt,edt-ft5406";
reg = <0x38>;
irq-gpios = <&gpio1 9 GPIO_ACTIVE_LOW>;
};
};
};
4.3 节点与属性
设备树的核心概念是“节点”(node)和“属性”(property)。每个节点代表一个硬件设备或总线,属性描述该设备的配置参数。
4.3.1 节点命名规则
- 节点名称:格式为
name@unit-address,例如i2c@021a0000。其中name是通用设备名(如 i2c、spi、serial),unit-address是设备在父总线上的地址(通常是寄存器基址或 I2C 从机地址)。 - 标签(label):可选别名,用于在
.dts中通过&label引用节点,例如i2c1: i2c@021a0000。 - 根节点:用
/表示,是设备树的顶层容器。
4.3.2 属性类型与写法
| 属性类型 | 语法示例 | 说明 |
|---|---|---|
| 字符串 | compatible = "vendor,device"; |
双引号包裹,多个值用逗号分隔 |
| 32 位整数 | reg = <0x021a0000 0x4000>; |
尖括号包裹,空格分隔多个值 |
| 64 位整数 | reg = <0x00000000 0x80000000 0x0 0x20000000>; |
两个 32 位单元拼接表示 64 位 |
| 布尔值 | status = "okay"; 或 status = "disabled"; |
用字符串表示,无布尔类型 |
| 字节数组 | local-mac-address = [00 11 22 33 44 55]; |
方括号包裹,十六进制字节 |
| 字符串列表 | clock-names = "pclk", "hclk"; |
逗号分隔多个字符串 |
| phandle 引用 | interrupt-parent = <&intc>; |
& 引用其他节点的标签 |
4.4 标准属性详解
设备树规范定义了一组标准属性,所有设备节点都应遵循这些属性的语义。以下是最核心的三个属性:
4.4.1 compatible —— 驱动匹配的关键
compatible 属性是设备树与驱动之间建立关联的桥梁。内核通过 of_match_table 中的 compatible 字符串与设备树节点的 compatible 值进行匹配,匹配成功则调用驱动的 probe() 函数。
- 格式:
"厂商,设备型号",例如"fsl,imx6ull-i2c"。 - 优先级:列表中的第一个字符串最精确,后续字符串作为兼容降级。内核优先匹配最精确的字符串。
- 驱动侧对应:在
of_device_id表中声明:
static const struct of_device_id imx_i2c_of_match[] = {
{ .compatible = "fsl,imx6ull-i2c", .data = &imx6ull_i2c_hw },
{ .compatible = "fsl,imx6q-i2c", .data = &imx6q_i2c_hw },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_i2c_of_match);
4.4.2 reg —— 地址与大小描述
reg 属性描述设备在总线上的地址空间。其格式为 <address1 length1 address2 length2 ...>,每个地址-长度对称为一个“reg 单元”。
| 总线类型 | reg 含义 | 示例 |
|---|---|---|
| 内存映射总线(如 AXI、AHB) | 基地址 + 映射长度 | reg = <0x021a0000 0x4000> |
| I2C 总线 | 7 位从机地址(无长度) | reg = <0x38> |
| SPI 总线 | 片选号 + 无长度 | reg = <0> |
| PCI 总线 | Bus/Device/Function 编码 | reg = <0x00000800 0 0 0 0> |
reg 的地址单元大小由父节点的 #address-cells 决定,长度单元大小由 #size-cells 决定。例如 #address-cells = <1>; #size-cells = <1> 表示每个地址和长度各占一个 32 位单元。
4.4.3 status —— 设备启用状态
status 属性控制设备节点是否被内核启用。常用值如下:
| 值 | 含义 | 内核行为 |
|---|---|---|
"okay" |
设备已启用 | 正常创建设备并匹配驱动 |
"disabled" |
设备已禁用 | 不创建设备,驱动不会 probe |
"reserved" |
保留状态 | 内核不处理,由固件或安全区使用 |
"fail" |
设备检测失败 | 内核记录错误,不创建设备 |
在 .dtsi 中,SoC 级节点通常默认 status = "disabled",板级 .dts 中再通过 &i2c1 { status = "okay"; } 显式启用。这种设计避免了未使用的 IP 核占用资源。
4.4.4 其他常用标准属性
| 属性名 | 用途 | 示例 |
|---|---|---|
interrupt-parent |
指定该设备的中断控制器 phandle | interrupt-parent = <&intc>; |
interrupts |
描述中断号、触发类型 | interrupts = <0 56 IRQ_TYPE_LEVEL_HIGH>; |
clocks |
引用设备使用的时钟 phandle | clocks = <&clks IMX6ULL_CLK_I2C1>; |
clock-names |
时钟名称列表,与 clocks 一一对应 | clock-names = "ipg", "per"; |
pinctrl-0 |
引脚复用配置 phandle | pinctrl-0 = <&pinctrl_i2c1>; |
pinctrl-names |
引脚配置状态名称 | pinctrl-names = "default"; |
dmas |
DMA 通道 phandle 列表 | dmas = <&sdma 1 2>; |
dma-names |
DMA 通道名称 | dma-names = "rx", "tx"; |
4.5 设备树编译与反编译
设备树编译器(dtc)是处理 .dts/.dtsi 文件的工具。常用命令如下:
# 编译 .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
# 检查设备树语法
dtc -I dts -O dtb -o /dev/null myboard.dts 2>&1
# 使用 YAML schema 验证(需要 dt-validate)
dt-validate -s /path/to/schemas myboard.dtb
/proc/device-tree 查看内核实际解析的设备树内容。每个节点对应一个目录,属性对应文件,使用 cat 或 hexdump 查看属性值。
4.6 本章小结
- 设备树将硬件描述从内核代码中分离,解决了板文件泛滥和内核镜像多平台兼容问题。
.dts是板级源文件,.dtsi是 SoC 级包含文件,.dt.yaml是校验 schema。- 节点通过
name@unit-address命名,属性使用键值对描述硬件参数。 compatible是驱动匹配的核心,reg描述地址空间,status控制设备启用状态。- 掌握 dtc 编译/反编译命令和
/proc/device-tree调试接口是设备树开发的基本功。