4、设备树编译与反编译:dtc工具的使用、编译dts为dtb、反编译dtb为dts

设备树源文件(.dts)是给人阅读和编辑的文本格式,但内核和引导加载程序(如U-Boot)在运行时使用的是二进制格式的设备树(.dtb)。设备树编译器(Device Tree Compiler,dtc) 是完成这两种格式之间转换的核心工具。掌握 dtc 的使用,是进行设备树开发和调试的基本功。

💡 核心概念:dtc 将 .dts 编译为 .dtb(编译),也可以将 .dtb 反编译为 .dts(反编译)。此外,它还能将设备树转换为 .dtbo(设备树覆盖层)和 .dtsi(包含文件)的中间表示形式。
dtc 工具编译与反编译流程 dtc 工具核心工作流 .dts 源文件 (文本格式,可读) 编译 .dtb 二进制文件 (机器可读,高效) 反编译 .dts 反编译文件 (带注释,可读) 💡 编译时,dtc 会检查语法错误、节点重复和属性合法性。 反编译常用于调试、验证或从现有 .dtb 恢复源文件。

4.1 dtc 工具安装与基本用法

在大多数 Linux 发行版中,dtc 工具包含在 device-tree-compiler 软件包中。如果内核源码树已经存在,也可以使用内核源码中的 scripts/dtc/dtc 可执行文件。

发行版 安装命令
Ubuntu / Debian sudo apt-get install device-tree-compiler
Fedora / RHEL sudo dnf install dtc
Arch Linux sudo pacman -S dtc
Yocto / Buildroot 通常已包含在 host-dtc 或 dtc 包中

dtc 的基本命令行语法如下:

dtc [选项] [输入文件]

常用选项包括:

  • -I <格式>:指定输入文件格式,可选 dts、dtb、fs(文件系统)。
  • -O <格式>:指定输出文件格式,可选 dts、dtb、dtbo、asm。
  • -o <文件>:指定输出文件名。
  • -@:生成设备树覆盖层(.dtbo)时使用,保留 __symbols__ 节点。
  • -H:指定 phandle 格式(legacy、epapr、both)。
  • -V:打印版本信息。

4.2 编译 dts 为 dtb

将 .dts 源文件编译为内核可识别的 .dtb 二进制文件,是设备树开发中最常用的操作。编译过程会进行语法检查、节点合并和属性验证。

基本编译命令:

dtc -I dts -O dtb -o my_board.dtb my_board.dts

其中:

  • -I dts:明确指定输入格式为 dts(可省略,dtc 会根据文件后缀自动识别)。
  • -O dtb:指定输出格式为 dtb。
  • -o my_board.dtb:指定输出文件名为 my_board.dtb。
  • my_board.dts:输入源文件。
🔧 实际案例:编译包含 I2C 设备的设备树

假设我们有一个 imx6ull_myboard.dts 文件,其中包含一个 I2C 温度传感器节点。编译命令如下:

dtc -I dts -O dtb -o imx6ull_myboard.dtb imx6ull_myboard.dts

如果编译成功,没有任何输出,并生成 imx6ull_myboard.dtb 文件。如果存在语法错误,dtc 会输出详细的错误信息,包括行号和错误类型。

常见编译错误示例:

Error: imx6ull_myboard.dts:45.12-13 syntax error
FATAL ERROR: Unable to parse input tree

这表示在第 45 行第 12-13 个字符附近存在语法错误,通常是由于缺少分号、括号不匹配或属性值格式错误导致的。

4.3 反编译 dtb 为 dts

反编译是将二进制 .dtb 文件还原为可读的 .dts 文本文件。这在以下场景中非常有用:

  • 调试内核启动时实际使用的设备树配置。
  • 从现有的 .dtb 文件(如从开发板厂商获取的)中提取设备树源文件。
  • 验证编译后的 .dtb 是否包含了预期的节点和属性。

基本反编译命令:

dtc -I dtb -O dts -o my_board_recovered.dts my_board.dtb

其中:

  • -I dtb:指定输入格式为 dtb。
  • -O dts:指定输出格式为 dts。
  • -o my_board_recovered.dts:输出反编译后的源文件。
⚠️ 反编译注意事项

反编译生成的 .dts 文件与原始源文件在格式上可能略有不同:

  • 所有注释(// 和 /* */)都会被丢弃。
  • 节点中的属性顺序可能会被重新排列。
  • 包含文件(#include)会被展开,所有内容合并到一个文件中。
  • 标签(labels)和引用(&node)会被解析为实际的路径或 phandle 值。

因此,反编译文件主要用于调试和验证,不建议作为长期维护的源文件。

4.4 编译与反编译的进阶用法

4.4.1 编译设备树覆盖层(.dtbo)

设备树覆盖层允许在不修改主设备树的情况下,动态添加或修改节点。编译覆盖层时需要添加 -@ 选项以保留符号信息。

dtc -I dts -O dtbo -@ -o my_overlay.dtbo my_overlay.dts

4.4.2 从 /sys/firmware/devicetree 反编译

在运行中的 Linux 系统上,可以通过 /sys/firmware/devicetree/base 目录访问当前使用的设备树。可以使用 dtc 的 -I fs 选项直接从文件系统格式反编译:

dtc -I fs -O dts -o current_dtb.dts /sys/firmware/devicetree/base

这可以获取内核当前实际使用的设备树配置,对于调试驱动匹配问题非常有帮助。

4.4.3 使用 -H 选项处理 phandle 格式

不同版本的设备树规范使用不同的 phandle 表示方式。使用 -H 选项可以指定输出格式:

dtc -I dts -O dtb -H epapr -o output.dtb input.dts

可选的 phandle 格式包括:

  • legacy:使用 linux,phandle 属性。
  • epapr:使用 phandle 属性(推荐,现代标准)。
  • both:同时生成两种属性,确保最大兼容性。
设备树编译与反编译详细流程 设备树编译与反编译详细流程 编译流程 (dts → dtb) .dts 源文件 .dtsi 包含文件 (#include 展开) C 预处理器 (cpp) dtc 编译器 .dtb 二进制文件 反编译流程 (dtb → dts) .dtb 二进制文件 dtc 反编译器 .dts 反编译文件 (注释丢失,包含展开)

4.5 常用 dtc 命令速查表

操作 命令 说明
编译 dts → dtb dtc -I dts -O dtb -o output.dtb input.dts 标准编译,生成二进制设备树
反编译 dtb → dts dtc -I dtb -O dts -o output.dts input.dtb 标准反编译,生成可读源文件
编译覆盖层 dtc -I dts -O dtbo -@ -o output.dtbo input.dts 编译设备树覆盖层,保留符号
从 /sys 反编译 dtc -I fs -O dts -o current.dts /sys/firmware/devicetree/base 反编译当前系统使用的设备树
检查 dtb 有效性 dtc -I dtb -O dts /dev/null input.dtb 仅检查语法,不生成输出文件
指定 phandle 格式 dtc -I dts -O dtb -H epapr -o output.dtb input.dts 使用 epapr 标准的 phandle 格式

4.6 实战:编译与反编译一个包含 I2C 设备的设备树

下面通过一个完整的示例,演示如何编译一个包含 I2C 温度传感器节点的设备树,并验证反编译结果。

步骤 1:创建源文件 i2c_sensor.dts

/dts-v1/;

/ {
    model = "I2C Sensor Test Board";
    compatible = "vendor,i2c-sensor-board";

    #address-cells = <1>;
    #size-cells = <1>;

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        i2c@1000 {
            compatible = "vendor,i2c-controller";
            reg = <0x1000 0x100>;
            #address-cells = <1>;
            #size-cells = <0>;
            clock-frequency = <100000>;

            temperature_sensor: sensor@48 {
                compatible = "vendor,temperature-sensor";
                reg = <0x48>;
                interrupt-parent = <&gpio1>;
                interrupts = <5 2>;
                vcc-supply = <&reg_3v3>;
            };
        };
    };

    gpio1: gpio@2000 {
        compatible = "vendor,gpio-controller";
        reg = <0x2000 0x100>;
        #gpio-cells = <2>;
        gpio-controller;
    };

    reg_3v3: regulator {
        compatible = "regulator-fixed";
        regulator-name = "3.3V";
        regulator-min-microvolt = <3300000>;
        regulator-max-microvolt = <3300000>;
    };
};

步骤 2:编译为 dtb

$ dtc -I dts -O dtb -o i2c_sensor.dtb i2c_sensor.dts
$ ls -l i2c_sensor.dtb
-rw-r--r-- 1 user user 1024 Apr  1 10:00 i2c_sensor.dtb

步骤 3:反编译验证

$ dtc -I dtb -O dts -o i2c_sensor_recovered.dts i2c_sensor.dtb
$ head -30 i2c_sensor_recovered.dts
/dts-v1/;

/ {
    model = "I2C Sensor Test Board";
    compatible = "vendor,i2c-sensor-board";
    #address-cells = <0x01>;
    #size-cells = <0x01>;

    soc {
        #address-cells = <0x01>;
        #size-cells = <0x01>;
        ranges;

        i2c@1000 {
            compatible = "vendor,i2c-controller";
            reg = <0x1000 0x100>;
            #address-cells = <0x01>;
            #size-cells = <0x00>;
            clock-frequency = <0x186a0>;  /* 100000 */

            sensor@48 {
                compatible = "vendor,temperature-sensor";
                reg = <0x48>;
                interrupt-parent = <0x01>;  /* phandle 被解析为数字 */
                interrupts = <0x05 0x02>;
                vcc-supply = <0x02>;        /* phandle 被解析为数字 */
            };
        };
    };
    ...
};
🔍 反编译结果分析

观察反编译后的文件,可以发现以下变化:

  • 所有数值被转换为十六进制格式(如 100000 → 0x186a0)。
  • 标签(如 temperature_sensor)被移除,节点名变为 sensor@48。
  • 引用(如 &gpio1、&reg_3v3)被解析为数字 phandle(0x01、0x02)。
  • 所有注释被丢弃。

这些变化说明反编译文件主要用于调试和验证,不能直接替代原始源文件进行后续开发。

4.7 常见问题与避坑指南

问题 原因 解决方案
编译报错 syntax error 缺少分号、括号不匹配、属性值格式错误 检查报错行号附近的语法,确保每个属性以分号结尾,节点用大括号包裹
编译报错 FATAL ERROR: Unable to parse input tree 文件开头缺少 /dts-v1/; 版本标识 在 .dts 文件第一行添加 /dts-v1/;
反编译后 phandle 显示为数字 引用被解析为数字 phandle,原始标签丢失 这是正常现象,反编译文件主要用于调试,不应作为源文件维护
反编译后节点顺序与源文件不同 dtc 会按字母顺序重新排列节点 不影响功能,但增加了阅读难度
编译覆盖层时符号丢失 未使用 -@ 选项 编译 .dtbo 时务必添加 -@ 选项
💡 调试技巧

在开发过程中,建议使用以下工作流:

  1. 在 .dts 源文件中进行修改和注释。
  2. 使用 dtc 编译为 .dtb 并部署到目标板。
  3. 如果遇到问题,从目标板的 /sys/firmware/devicetree/base 反编译出实际使用的设备树。
  4. 对比反编译文件与源文件,找出差异和问题所在。

这种方法可以确保你始终基于实际运行的设备树进行调试,避免因编译过程或引导加载程序修改导致的偏差。