1. 设备树基础

设备树(Device Tree,DT)是嵌入式 Linux 系统中用于描述硬件拓扑结构的一种数据结构。它解决了传统板级文件(board-*.c)中硬编码硬件信息导致的代码冗余与维护困难问题,成为 ARM、RISC-V 等架构的主流硬件描述方案。

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

在 Linux 内核早期,ARM 架构的每个开发板都需要在 arch/arm/mach-* 下编写独立的板级文件,其中包含寄存器地址、中断号、时钟频率、GPIO 复用等硬件信息。这种模式导致内核代码中充斥着大量重复的硬件描述,且每增加一个新板卡就需要修改内核源码。

核心痛点: 硬件描述与内核代码紧耦合 → 板级文件膨胀 → 维护成本指数级上升 → 无法支持单一内核镜像启动多个平台。

设备树将硬件描述从内核源码中剥离,以独立的数据结构文件(.dts)存在,由 bootloader(如 U-Boot)传递给内核。内核在启动时解析设备树,动态注册平台设备、配置引脚复用、初始化时钟与中断控制器。

对比维度 传统板级文件方式 设备树方式
硬件描述位置 内核源码 arch/arm/mach-* 独立 .dts 文件,与内核分离
新增板卡代价 需修改内核、重新编译 仅需编写 .dts,无需改内核
多平台支持 需编译多个内核镜像 单一内核镜像 + 不同 dtb
维护性 差,代码冗余严重 好,硬件描述集中管理

关键里程碑: Linux 内核从 3.x 开始强制要求 ARM 架构使用设备树,PowerPC、RISC-V、MIPS 等架构也相继采用。

1.2 设备树语法结构:节点与属性

设备树源文件(.dts)采用树形层次结构,每个硬件设备或总线被抽象为一个节点(node),节点内部通过属性(property)描述硬件参数。根节点是 /,所有设备节点挂载在根节点下。

/dts-v1/;

/ {
    compatible = "vendor,board-name";
    #address-cells = <1>;
    #size-cells = <1>;

    cpu@0 {
        compatible = "arm,cortex-a7";
        reg = <0x00 0x100>;
        device_type = "cpu";
    };

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 0x20000000>;  /* 512MB RAM */
    };

    uart0: serial@11000000 {
        compatible = "vendor,uart";
        reg = <0x11000000 0x1000>;
        interrupts = <0 59 4>;
        clocks = <&clk_uart0>;
        pinctrl-0 = <&uart0_pins>;
        pinctrl-names = "default";
        status = "okay";
    };
};

常见错误: 忘记在文件头部添加 /dts-v1/; 版本声明,或节点名称中 @ 后的地址与 reg 属性不匹配,会导致编译警告或设备无法正确识别。

1.3 节点与属性的核心规则

语法元素 格式 说明
节点名称 node-name@unit-address @ 后为设备基地址,用于区分同名节点
属性值 property = <value>; 支持 32/64 位整数、字符串、字节数组、phandle
标签(label) uart0: serial@11000000 用于在其他节点中通过 &uart0 引用
包含文件 #include "soc.dtsi" 引入 .dtsi 头文件,实现 SoC 级描述复用
状态属性 status = "okay" / "disabled" 控制设备是否启用,常用于板级覆盖

1.4 dts / dtsi / dtb 的关系与编译流程

设备树源文件分为两种:.dts(板级描述文件)和 .dtsi(SoC 级公共描述文件,类似头文件)。.dtb 是编译后的二进制文件,由 bootloader 加载并传递给内核。

设计原则: .dtsi 描述 SoC 内部固定硬件(CPU、中断控制器、时钟、DMA),.dts 描述板级外设(型号、内存大小、引脚复用、外设启停)。

# 编译流程
dtc -I dts -O dtb -o board.dtb board.dts

# 反编译(调试用)
dtc -I dtb -O dts -o board_decompiled.dts board.dtb
文件类型 扩展名 作用 是否可编译
设备树源文件 .dts 板级硬件描述,包含具体外设配置 是(通过 dtc)
设备树头文件 .dtsi SoC 级公共描述,被 .dts 包含 否(仅被包含)
设备树二进制 .dtb 编译后的二进制,内核直接使用 否(已编译)

调试技巧: 使用 dtc -I dtb -O dts 反编译 .dtb 文件,可以验证 bootloader 实际传递给内核的设备树内容是否与预期一致。

1.5 设备树层次结构与硬件映射关系

下面这张 SVG 图展示了从 .dts 源文件到内核设备模型的核心映射流程,帮助理解设备树如何驱动硬件初始化。

设备树编译与内核映射流程 设备树编译与内核设备模型映射 board.dts 板级描述文件 soc.dtsi SoC 公共描述 include 引用 头文件/宏定义 dtc 编译器 Device Tree Compiler board.dtb 二进制设备树 U-Boot 加载 dtb 传递到内核 FDT 区域 Linux 内核解析设备树 → 注册 platform_device 驱动匹配 → pinctrl/时钟/中断初始化 关键调试点 dtc 编译是否报错?

1.6 实战要点:设备树调试常用命令

  • 查看当前系统设备树: ls /proc/device-tree/ — 内核启动后会将 dtb 解析为 proc 文件系统节点
  • 检查设备树节点状态: cat /proc/device-tree/soc/serial@11000000/status — 输出 "okay" 或 "disabled"
  • 验证 dtb 是否被正确加载: strings /proc/device-tree/compatible — 查看板级 compatible 字符串
  • 反编译 dtb 做对比: dtc -I dtb -O dts -o /tmp/current.dts /sys/firmware/fdt — 获取内核实际使用的设备树

避坑指南: 如果 /proc/device-tree/ 下缺少某个节点,首先检查 .dts 中该节点的 status 是否为 "okay",其次确认 bootloader 是否传递了正确的 dtb 文件路径。

1.7 本章小结