2. 设备树编译与反编译

设备树源文件(.dts)是人类可读的文本描述,而内核引导时实际使用的是二进制格式的设备树二进制文件(.dtb)。dtc(Device Tree Compiler)是完成这一转换的核心工具。本章将系统讲解 dtc 的编译、反编译与内容查看操作,帮助你在调试 V4L2 驱动时快速定位设备树配置问题。

实战要点:在嵌入式 Linux 开发中,修改设备树后必须重新编译为 dtb 并烧录到目标板。反编译则常用于逆向分析现有 dtb 的配置,或验证编译过程是否丢失了关键节点。

2.1 dtc 工具概述

dtc 是设备树编译器的标准实现,通常集成在 Linux 内核源码的 scripts/dtc/ 目录下,也可通过系统包管理器单独安装。其核心功能包括:

  • 编译(dts → dtb):将文本格式的 .dts 文件转换为二进制 .dtb 文件。
  • 反编译(dtb → dts):将二进制 .dtb 还原为可读的 .dts 文本。
  • 内容查看:以人类可读格式输出 dtb 的节点树结构。
  • 语法检查:验证 dts 文件的语法正确性。
操作类型 输入文件 输出文件 典型命令
编译 .dts .dtb dtc -I dts -O dtb -o output.dtb input.dts
反编译 .dtb .dts dtc -I dtb -O dts -o output.dts input.dtb
内容查看 .dtb 终端输出 dtc -I dtb -O dts input.dtb
安装 dtc:在 Ubuntu/Debian 系统中执行 sudo apt-get install device-tree-compiler;在 Yocto/Buildroot 构建环境中,dtc 通常已包含在主机工具链中。

2.2 编译 dts 为 dtb

编译操作将设备树源文件转换为内核可识别的二进制格式。以下是一个针对 V4L2 摄像头节点的编译示例:

# 基本编译命令
dtc -I dts -O dtb -o imx8mm-camera.dtb imx8mm-camera.dts

# 启用空间优化(去除调试符号)
dtc -I dts -O dtb -o imx8mm-camera.dtb -@ imx8mm-camera.dts

# 指定输出文件路径
dtc -I dts -O dtb -o /boot/dtbs/imx8mm-camera.dtb imx8mm-camera.dts

参数说明:

  • -I dts:指定输入格式为 dts 文本。
  • -O dtb:指定输出格式为 dtb 二进制。
  • -o:指定输出文件名。
  • -@:生成符号节点,用于设备树叠加层(overlay)。
常见错误:如果 dts 文件中引用了 #include 的头文件(如 .dtsi),编译前必须确保头文件路径正确。建议使用 -I 参数添加搜索路径:dtc -I dts -O dtb -I /path/to/include -o output.dtb input.dts。

2.3 反编译 dtb 为 dts

反编译用于从现有 dtb 文件中提取可读的设备树描述,是调试和逆向分析的重要技能。例如,当从目标板中提取出 imx8mm-evk.dtb 后,可以反编译查看其完整配置:

# 基本反编译命令
dtc -I dtb -O dts -o imx8mm-evk-decompiled.dts imx8mm-evk.dtb

# 直接输出到终端(不保存文件)
dtc -I dtb -O dts imx8mm-evk.dtb

# 保留原始地址标签
dtc -I dtb -O dts -o output.dts -@ input.dtb

反编译后的 dts 文件会包含所有节点、属性和值,但注释和宏定义会被展开为具体数值。这对于验证编译过程是否丢失了关键配置非常有用。

调试技巧:在 V4L2 驱动调试中,如果摄像头节点在驱动中无法识别,可以先反编译目标板的 dtb,检查 compatible 属性、reg 地址、clocks 和 pinctrl 配置是否与驱动期望一致。

2.4 查看 dtb 内容

除了完整的反编译,dtc 还支持以多种格式查看 dtb 内容,便于快速浏览节点结构:

# 以 dts 格式输出到终端(最常用)
dtc -I dtb -O dts imx8mm-evk.dtb

# 以二进制 hex 格式查看原始数据
dtc -I dtb -O dtb imx8mm-evk.dtb | xxd | head -20

# 使用 fdtdump 工具查看结构摘要
fdtdump imx8mm-evk.dtb

fdtdump 是 dtc 工具包中的另一个实用程序,它以紧凑格式输出设备树的节点路径和属性值,适合快速浏览:

// fdtdump 输出示例(部分)
/dts-v1/;

/ {
    compatible = "fsl,imx8mm-evk";
    #address-cells = <0x2>;
    #size-cells = <0x2>;

    cpus {
        #address-cells = <0x1>;
        #size-cells = <0x0>;
        cpu@0 {
            compatible = "arm,cortex-a53";
            reg = <0x0>;
        };
    };

    i2c@30a20000 {
        compatible = "fsl,imx8mm-i2c", "snps,designware-i2c";
        reg = <0x0 0x30a20000 0x0 0x10000>;
        #address-cells = <0x1>;
        #size-cells = <0x0>;
        clock-frequency = <0x61a80>;

        camera@3c {
            compatible = "ovti,ov5640";
            reg = <0x3c>;
            clocks = &clk_cam;
            pinctrl-names = "default";
            pinctrl-0 = &pinctrl_camera;
        };
    };
};

2.5 设备树编译与反编译流程

下图展示了从 dts 源文件到 dtb 二进制,再到反编译验证的完整工作流:

设备树编译与反编译工作流 .dts 源文件 (人类可读文本) .dtsi / .h 头文件 dtc 编译 .dtb 二进制 (内核引导使用) dtc 反编译 .dts 反编译结果 (验证/调试用) #include fdtdump / hex 查看 (快速浏览结构) V4L2 驱动调试与验证 (检查 compatible / reg / clocks / pinctrl) ⚠ 常见问题 • 头文件路径缺失导致编译失败 • 反编译后宏定义被展开为数值

2.6 实战:编译与反编译 V4L2 摄像头设备树

以下是一个完整的实战流程,演示如何为 OV5640 摄像头节点编译设备树,并通过反编译验证配置:

  1. 准备 dts 源文件:创建 ov5640-camera.dts,包含摄像头节点和必要的 pinctrl 配置。
  2. 编译为 dtb:dtc -I dts -O dtb -o ov5640-camera.dtb ov5640-camera.dts
  3. 反编译验证:dtc -I dtb -O dts ov5640-camera.dtb,检查输出中是否包含 compatible = "ovti,ov5640"、reg = <0x3c>、clocks 和 pinctrl-0 属性。
  4. 对比差异:使用 diff 命令比较原始 dts 与反编译后的 dts,确认没有属性丢失。
# 步骤1:创建源文件(示例片段)
cat > ov5640-camera.dts << EOF
/dts-v1/;

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

    i2c@300a0000 {
        compatible = "snps,designware-i2c";
        reg = <0x300a0000 0x10000>;
        #address-cells = <1>;
        #size-cells = <0>;

        camera@3c {
            compatible = "ovti,ov5640";
            reg = <0x3c>;
            clocks = <&clk_cam>;
            clock-names = "xclk";
            pinctrl-names = "default";
            pinctrl-0 = <&pinctrl_camera>;
            DOVDD-supply = <&reg_camera_dovdd>;
            AVDD-supply = <&reg_camera_avdd>;
            DVDD-supply = <&reg_camera_dvdd>;
        };
    };
};
EOF

# 步骤2:编译
dtc -I dts -O dtb -o ov5640-camera.dtb ov5640-camera.dts

# 步骤3:反编译并检查
dtc -I dtb -O dts ov5640-camera.dtb | grep -E "compatible|reg|clocks|pinctrl"
输出示例:
compatible = "ovti,ov5640";
reg = <0x3c>;
clocks = <0x00000001>;    /* 引用被展开为 phandle 值 */
clock-names = "xclk";
pinctrl-names = "default";
pinctrl-0 = <0x00000002>; /* 引用被展开为 phandle 值 */

注意:反编译后,&clk_cam 和 &pinctrl_camera 等标签引用会被展开为数字 phandle 值。这是正常现象,不影响功能验证。

2.7 常见问题与避坑指南

问题 原因 解决方案
编译报错 "FATAL ERROR: Unable to parse input tree" dts 文件语法错误,如缺少分号、括号不匹配 使用 dtc -I dts -O dts input.dts 进行语法检查,定位错误行
反编译后节点丢失 dtb 文件可能被裁剪或使用了不同版本的 dtc 确认 dtb 来源,使用相同版本的 dtc 进行反编译
phandle 值显示为数字而非标签 反编译无法恢复原始标签名 在原始 dts 中使用 phandle = <...>; 显式定义,或保留原始 dts 作为参考
编译后 dtb 体积过大 未使用 -@ 或包含调试符号 使用 -@ 仅生成必要符号,或使用 dtc -O dtb -H epapr 选择紧凑格式
重要提醒:在 V4L2 驱动调试中,如果修改了设备树后驱动行为未改变,请务必确认:
1. 新编译的 dtb 已正确烧录到目标板的分区(如 /boot/dtbs/ 或 /boot/)。
2. 引导加载程序(U-Boot)加载的是新 dtb 文件,而非缓存中的旧版本。
3. 使用 fdtdump /sys/firmware/fdt 查看内核当前使用的设备树,确认与预期一致。

2.8 小结