3. 设备树文件结构:dts、dtsi、dtb、dtbo 文件的区别与作用,编译与反编译工具(dtc)

在嵌入式 Linux 开发中,设备树(Device Tree)是描述硬件资源的标准化数据结构。理解其文件体系与编译工具链,是编写正确设备树节点的前提。本章将系统梳理四种核心文件类型(.dts、.dtsi、.dtb、.dtbo)的定位、差异与协作关系,并详解 dtc 工具的使用方法。

3.1 四种文件类型概述

设备树源文件以文本形式描述硬件,经过编译生成二进制格式供内核解析。下表从文件格式、用途、生成阶段和典型场景四个维度进行对比:

文件类型 全称 格式 用途 生成阶段 典型场景
.dts Device Tree Source 文本 顶层设备树源文件,描述单板硬件 开发阶段编写 单板级硬件描述
.dtsi Device Tree Source Include 文本 公共设备树头文件,被 .dts 包含 开发阶段编写 SoC 公共定义、IP 核复用
.dtb Device Tree Blob 二进制 编译后的设备树二进制文件,供内核加载 编译阶段输出 内核启动时传递给内核
.dtbo Device Tree Blob Overlay 二进制 设备树覆盖(overlay)二进制,支持运行时修改 编译阶段输出 动态设备插入、FPGA 重配置
关键理解:.dts 是“主文件”,.dtsi 是“头文件”,.dtb 是“可执行文件”,.dtbo 是“补丁文件”。四者构成从源码到运行时配置的完整链路。

3.2 文件结构关系与包含机制

.dtsi 文件通过 #include 预处理器指令被 .dts 文件引用。这种机制实现了 SoC 级公共描述与单板级特定描述的解耦。一个典型的包含链如下:

// 文件:imx6ull.dtsi(SoC 公共定义)
/ {
    soc {
        aips1: aips-bus@02000000 {
            gpio1: gpio@0209c000 {
                compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
                reg = <0x0209c000 0x4000>;
                interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>;
                #gpio-cells = <2>;
                gpio-controller;
            };
        };
    };
};

// 文件:myboard.dts(单板级描述)
/dts-v1/;
#include "imx6ull.dtsi"

/ {
    model = "MyBoard i.MX6ULL Board";
    compatible = "myboard,imx6ull", "fsl,imx6ull";

    &gpio1 {
        status = "okay";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_gpio1>;
    };
};
包含规则:一个 .dts 可以包含多个 .dtsi,.dtsi 之间也可以互相包含。但必须避免循环包含。内核中 .dtsi 通常按 SoC 系列、IP 核、板级外设等维度组织。

3.3 编译与反编译工具:dtc

dtc(Device Tree Compiler)是设备树的核心工具,负责将文本源文件编译为二进制 .dtb,也支持从 .dtb 反编译回 .dts。其基本用法如下表:

操作 命令 说明
编译 .dts → .dtb dtc -I dts -O dtb -o myboard.dtb myboard.dts 将文本源文件编译为二进制
反编译 .dtb → .dts dtc -I dtb -O dts -o myboard.dts myboard.dtb 将二进制反编译为可读文本
编译 .dts → .dtbo dtc -I dts -O dtb -@ -o overlay.dtbo overlay.dts -@ 选项启用 overlay 支持
检查 .dtb 语法 dtc -I dtb -O dts /sys/firmware/fdt 查看当前内核加载的设备树
常见错误:编译时若缺少 -@ 选项,生成的 .dtbo 将无法被内核 overlay 系统正确加载。反编译时若 .dtb 版本与 dtc 版本不匹配,可能导致节点丢失或属性解析错误。

3.4 编译流程与文件转换关系

下图展示了从源文件到最终内核加载的完整转换链路:

设备树文件编译与加载流程 .dts 源文件 .dtsi 头文件 C 预处理器 (cpp) dtc 编译器 .dtb 二进制 .dtbo 覆盖 内核设备树子系统 预处理器展开 #include 指令

3.5 反编译实战:从 .dtb 恢复可读源文件

在实际调试中,经常需要从运行中的系统提取设备树进行分析。以下步骤演示反编译流程:

  1. 提取当前内核设备树:cp /sys/firmware/fdt /tmp/current.dtb
  2. 反编译为文本:dtc -I dtb -O dts -o /tmp/current.dts /tmp/current.dtb
  3. 查看节点结构:grep -n "gpio\|interrupt\|pinctrl" /tmp/current.dts | head -20
  4. 验证语法:dtc -I dts -O dtb -o /dev/null /tmp/current.dts(仅检查语法)
调试技巧:反编译后的 .dts 文件会包含所有 .dtsi 展开后的完整内容,便于定位硬件资源冲突。使用 dtc -f 可强制忽略某些错误继续处理。

3.6 文件组织最佳实践

在大型项目中,合理的文件组织能显著提升维护效率。推荐采用以下目录结构:

arch/arm/boot/dts/
├── imx6ull.dtsi              # SoC 级公共定义
├── imx6ull-pinfunc.h         # 引脚复用宏定义
├── imx6ull-uart.dtsi         # UART 控制器定义
├── imx6ull-gpio.dtsi         # GPIO 控制器定义
├── myboard.dts               # 单板级顶层文件
├── myboard-rev1.dts          # 版本变体
└── overlays/
    ├── myboard-spi.dtbo      # SPI 外设覆盖
    └── myboard-camera.dtbo   # 摄像头覆盖
核心原则:将 SoC 公共部分、IP 核定义、板级外设、版本变体、动态覆盖分层管理。每个 .dtsi 只负责一个逻辑模块,避免单个文件过于庞大。

3.7 常见问题与避坑指南

问题 原因 解决方案
编译报错“syntax error” 缺少分号或花括号不匹配 使用 dtc -s 仅检查语法,定位错误行
反编译后节点丢失 .dtb 被内核修改或版本不兼容 使用与内核版本匹配的 dtc 工具
overlay 加载失败 编译时未加 -@ 选项 重新编译:dtc -@ -I dts -O dtb -o overlay.dtbo overlay.dts
属性值显示为乱码 二进制 .dtb 损坏或字节序错误 使用 hexdump -C 检查文件头魔数(应为 d00dfeed)