1. 设备树基础:起源、作用与基本语法结构

1.1 设备树的起源与设计动机

在早期的 Linux 内核(如 2.6 及更早版本)中,板级硬件信息被硬编码在 arch/arm/mach-xxx 目录下的 C 语言源文件中。这种“硬编码”方式带来了严重的维护问题:每更换一个 SoC 或外设,都需要修改内核源码并重新编译。

硬编码时代的痛点:
  • 内核源码与特定板卡高度耦合,无法实现“一次编译,多处运行”。
  • 厂商发布新硬件时,必须提供完整的内核源码补丁。
  • 同一 SoC 的不同开发板需要维护多份重复的板级文件。

为了解决上述问题,PowerPC 架构率先引入了 设备树(Device Tree) 技术,随后被 ARM、RISC-V 等架构广泛采纳。设备树的核心思想是:将硬件描述信息从内核源码中剥离出来,形成一个独立的数据结构,由 Bootloader(如 U-Boot)传递给内核,内核在启动时解析该结构来动态注册设备。

一句话理解: 设备树是描述硬件拓扑和配置的“硬件数据库”,它让内核不再需要硬编码板级信息,从而实现了内核与硬件的解耦。

1.2 设备树的作用与价值

作用维度 具体说明 对驱动开发的影响
硬件描述 描述 CPU、内存、总线、外设的地址、中断、时钟等资源 驱动通过设备树节点获取硬件资源,无需硬编码地址
平台解耦 同一内核镜像可运行在不同板卡上,只需更换设备树文件 驱动开发者只需关注设备树节点匹配规则
配置灵活 通过设备树属性可动态启用/禁用设备、调整参数 无需重新编译内核即可修改硬件配置
标准化接口 提供 compatible、reg、interrupts 等标准属性 驱动编写遵循统一规范,提高代码复用性

1.3 三种文件类型:.dts / .dtsi / .dtb

设备树涉及三种文件格式,理解它们的关系是学习设备树的第一步。

文件后缀 全称 格式 作用
.dts Device Tree Source 文本文件(可读) 设备树源文件,描述具体板卡的硬件信息
.dtsi Device Tree Source Include 文本文件(可读) 设备树头文件/包含文件,存放 SoC 公共定义
.dtb Device Tree Blob 二进制文件(不可读) 编译后的设备树,由 Bootloader 加载并传递给内核
文件关系流程:
  1. SoC 厂商提供 .dtsi 文件,定义 SoC 内部所有外设的默认配置。
  2. 板卡厂商编写 .dts 文件,通过 #include 引入 .dtsi,并添加或覆盖板级特有的设备。
  3. 使用 dtc(Device Tree Compiler)工具将 .dts 编译为 .dtb 二进制文件。
  4. Bootloader 读取 .dtb 并传递给内核,内核解析后完成设备注册。
.dts / .dtsi / .dtb 文件关系与编译流程 .dtsi SoC 公共定义 .dts 板级源文件 dtc 编译器 Device Tree Compiler .dtb 二进制 Blob Linux 内核 解析并注册设备 #include 输入 编译输出 Bootloader 加载 注意 .dts 可包含多个 .dtsi

1.4 设备树基本语法结构

设备树源文件采用树形层次结构,以 节点(Node) 和 属性(Property) 为核心元素。以下是一个典型的设备树片段:

/dts-v1/;

#include "imx6ull.dtsi"

/ {
    model = "EmbedFire i.MX6ULL Board";
    compatible = "embedfire,imx6ull", "fsl,imx6ull";

    chosen {
        stdout-path = &uart1;
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;
    };

    &uart1 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_uart1>;
    };
};

1.5 核心语法要素解析

语法要素 示例 说明
根节点 / { ... }; 设备树的顶层节点,所有设备节点都是它的子节点
节点名称 memory@80000000 格式为 名称@地址,地址用于区分同类型设备
属性 compatible = "vendor,device" 驱动匹配的关键属性,格式为 厂商,设备名
reg 属性 reg = <0x80000000 0x20000000> 描述设备地址空间,格式为 <起始地址 长度>
标签引用 &uart1 { ... }; 通过 & 引用已定义的节点,用于追加或覆盖属性
包含指令 #include "imx6ull.dtsi" 引入 SoC 公共定义文件,支持 C 语言预处理
常见错误:
  • 忘记在 .dts 文件开头写 /dts-v1/;,导致 dtc 编译失败。
  • compatible 属性值顺序错误:内核优先匹配列表中的第一个值,应将最具体的匹配项放在前面。
  • reg 属性中的地址和长度单位不匹配,导致内核无法正确解析地址空间。

1.6 设备树在驱动调试中的核心地位

对于驱动调试工程师而言,设备树是连接硬件与驱动的“桥梁”。当驱动无法正常工作时,首先应检查设备树节点是否正确描述了硬件资源:

  • 匹配失败: 检查 compatible 属性是否与驱动中的 of_match_table 一致。
  • 资源冲突: 检查 reg 和 interrupts 属性是否与其他设备重叠。
  • 状态禁用: 检查 status 属性是否为 "okay",若为 "disabled" 则内核不会注册该设备。
  • 引脚复用错误: 检查 pinctrl 子节点中的引脚配置是否正确。
调试技巧: 在 Linux 内核启动时,通过 earlycon 和 initcall_debug 参数可以查看设备树的解析过程。使用 dtc -I dtb -O dts -o dump.dts board.dtb 命令可以将 .dtb 反编译为可读的 .dts 文件,方便对比实际加载的设备树与源文件是否一致。
设备树节点层次结构示例 / (根节点) model / compatible #address-cells / #size-cells chosen memory@80000000 uart1 pinctrl_uart1 stdout-path = &uart1 bootargs 等 device_type = "memory" reg = <0x80000000 0x20000000>

1.7 本章小结

设备树是现代 Linux 驱动开发的基石。通过本章的学习,你应该掌握:

  • 设备树解决了内核与板级硬件耦合的问题,是驱动调试的第一道关卡。
  • .dts 是源文件,.dtsi 是公共头文件,.dtb 是编译后的二进制文件,三者通过 dtc 工具串联。
  • 设备树的基本语法包括根节点、子节点、属性(compatible、reg、status 等)和标签引用。
  • 在驱动调试中,设备树是排查硬件资源冲突、匹配失败、引脚复用错误的首要检查点。