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)传递给内核,内核解析后动态创建平台设备,实现了“同一内核镜像支持多款硬件”的目标。

关键动机: 解决 ARM 架构下板文件泛滥、代码与硬件强耦合、无法支持单一内核镜像多平台启动的问题。
设备树解耦硬件描述与内核代码 旧方式:板文件硬编码 • arch/arm/mach-xxx/board.c • 寄存器地址写死在 C 代码中 • 中断号用 #define 宏定义 • 每款硬件需重新编译内核 • 无法支持多平台通用镜像 ✗ 维护成本高,扩展性差 解耦 新方式:设备树描述 • .dts / .dtsi 文本文件 • 硬件拓扑树形结构 • 属性名/值对描述资源 • 编译为 .dtb 二进制 • 内核解析 DTB 动态创建设备 ✓ 同一内核镜像适配多板 设备树工作流 .dts → dtc → .dtb ↓ 传递给内核 of_platform_populate() ↓ 创建 platform_device 驱动匹配 probe()

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 工具使用
实践建议: 将 SoC 级通用定义(如 CPU 核、中断控制器、时钟)放在 .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>; & 引用其他节点的标签
设备树节点与属性结构示例 / (根节点) compatible = "vendor,board" model = "Demo Board" cpus { } memory@80000000 device_type = "memory" reg = <0x80000000 0x20000000> i2c@021a0000 compatible = "fsl,imx6ull-i2c" reg = <0x021a0000 0x4000> touch@38 compatible = "edt,edt-ft5406" reg = <0x38>

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
调试技巧: 在 Linux 内核启动后,可以通过 /proc/device-tree 查看内核实际解析的设备树内容。每个节点对应一个目录,属性对应文件,使用 cat 或 hexdump 查看属性值。

4.6 本章小结

  • 设备树将硬件描述从内核代码中分离,解决了板文件泛滥和内核镜像多平台兼容问题。
  • .dts 是板级源文件,.dtsi 是 SoC 级包含文件,.dt.yaml 是校验 schema。
  • 节点通过 name@unit-address 命名,属性使用键值对描述硬件参数。
  • compatible 是驱动匹配的核心,reg 描述地址空间,status 控制设备启用状态。
  • 掌握 dtc 编译/反编译命令和 /proc/device-tree 调试接口是设备树开发的基本功。