4、设备树编译与反编译:dtc工具的使用、编译dts为dtb、反编译dtb为dts
设备树源文件(.dts)是给人阅读和编辑的文本格式,但内核和引导加载程序(如U-Boot)在运行时使用的是二进制格式的设备树(.dtb)。设备树编译器(Device Tree Compiler,dtc) 是完成这两种格式之间转换的核心工具。掌握 dtc 的使用,是进行设备树开发和调试的基本功。
dtc 将 .dts 编译为 .dtb(编译),也可以将 .dtb 反编译为 .dts(反编译)。此外,它还能将设备树转换为 .dtbo(设备树覆盖层)和 .dtsi(包含文件)的中间表示形式。
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:输入源文件。
假设我们有一个 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:同时生成两种属性,确保最大兼容性。
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 = <®_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、®_3v3)被解析为数字 phandle(0x01、0x02)。 - 所有注释被丢弃。
这些变化说明反编译文件主要用于调试和验证,不能直接替代原始源文件进行后续开发。
4.7 常见问题与避坑指南
| 问题 | 原因 | 解决方案 |
|---|---|---|
编译报错 syntax error |
缺少分号、括号不匹配、属性值格式错误 | 检查报错行号附近的语法,确保每个属性以分号结尾,节点用大括号包裹 |
编译报错 FATAL ERROR: Unable to parse input tree |
文件开头缺少 /dts-v1/; 版本标识 |
在 .dts 文件第一行添加 /dts-v1/; |
| 反编译后 phandle 显示为数字 | 引用被解析为数字 phandle,原始标签丢失 | 这是正常现象,反编译文件主要用于调试,不应作为源文件维护 |
| 反编译后节点顺序与源文件不同 | dtc 会按字母顺序重新排列节点 |
不影响功能,但增加了阅读难度 |
| 编译覆盖层时符号丢失 | 未使用 -@ 选项 |
编译 .dtbo 时务必添加 -@ 选项 |
在开发过程中,建议使用以下工作流:
- 在
.dts源文件中进行修改和注释。 - 使用
dtc编译为.dtb并部署到目标板。 - 如果遇到问题,从目标板的
/sys/firmware/devicetree/base反编译出实际使用的设备树。 - 对比反编译文件与源文件,找出差异和问题所在。
这种方法可以确保你始终基于实际运行的设备树进行调试,避免因编译过程或引导加载程序修改导致的偏差。