4. 设备树编译与反编译

设备树源文件(DTS)是工程师可读的文本描述,而内核在启动阶段真正解析的是二进制设备树(DTB)。两者之间的桥梁就是 DTC(Device Tree Compiler)工具链。理解 DTC 的安装、编译、反编译以及错误定位方法,是板级移植中最基础也最高频的工程能力。本章围绕“DTS → DTB → DTS”的闭环,讲清工具链使用与调试思路。

一、核心原理:DTC 在构建链中的位置

DTC 是一个独立的开源工具,源码位于内核源码树的 scripts/dtc/ 目录,也可以单独从 devicetree.org 获取。它的核心职责是把人类可读的 DTS 文本,经过词法分析、语法分析、语义检查后,生成扁平化设备树 Blob(Flattened Device Tree,FDT),即 DTB。内核启动时由 bootloader 把 DTB 地址传给内核,内核用 unflatten_device_tree() 将其展开为运行时结构。

DTC 支持三种主要操作:编译(-I dts -O dtb)、反编译(-I dtb -O dts)以及格式转换。编译过程会处理 #include 头文件、/include/ 包含文件、标签引用(&label)、/delete-node/、/delete-property/ 等语法,最终输出带结构块和字符串块的二进制。反编译则是把 DTB 还原成可读 DTS,常用于分析厂商提供的闭源 DTB。

DTC 工具链:DTS 与 DTB 的双向转换 DTS 源文件 board.dts / .dtsi 文本、可读、可编辑 DTC 编译器 dtc -I dts -O dtb 语法/语义检查 DTB 二进制 board.dtb FDT Blob,内核解析 DTC 反编译器 dtc -I dtb -O dts 还原可读结构 反编译 DTS decompiled.dts 用于分析/对比 编译方向:DTS → DTC → DTB(部署到板卡) | 反编译方向:DTB → DTC → DTS(分析调试)

二、DTC 工具链安装

在 Ubuntu/Debian 上最直接的方式是安装发行版包:

sudo apt update
sudo apt install device-tree-compiler
dtc --version

若需要与内核版本严格匹配的 DTC,建议从内核源码构建:

cd linux-6.x/scripts/dtc
make
sudo cp dtc /usr/local/bin/
# 或在内核根目录执行
make dtbs_check

交叉编译场景下,内核构建系统会自动调用 scripts/dtc/dtc,无需手动安装。若使用 Yocto/Buildroot,DTC 由构建系统作为 host 工具自动准备,工程中只需关注 DTS 本身。

三、DTS 编译为 DTB

最常用的命令形式如下,其中 -@ 表示生成 __symbols__ 节点以支持运行时 overlay:

dtc -I dts -O dtb -o board.dtb board.dts
dtc -@ -I dts -O dtb -o board.dtb board.dts
# 内核构建系统中
make ARCH=riscv CROSS_COMPILE=riscv64-linux-gnu- dtbs

编译时若 DTS 引用了 .dtsi,需保证 -i 包含路径正确:

dtc -I dts -O dtb -i include/ -i arch/riscv/boot/dts/ \
    -o board.dtb board.dts
建议在编译命令中加入 -Wno-unit_address_vs_reg 等选项按需抑制警告,但不要盲目关闭所有警告,很多警告正是地址与 reg 不匹配的真实问题。

四、DTB 反编译为 DTS

拿到厂商提供的 DTB 或排查启动问题时,反编译是第一步:

dtc -I dtb -O dts -o decompiled.dts board.dtb
# 查看结构而不落盘
dtc -I dtb -O dts board.dtb | less
# 查看头部信息
fdtdump board.dtb | head -40

反编译结果会丢失注释、宏和 #include 展开前的原始写法,但节点、属性、phandle 关系完整保留,适合做差异对比:

diff -u vendor.dts decompiled.dts

五、常见编译错误与调试方法

错误/警告典型原因处理方法
syntax error, expected ...缺少分号、括号不匹配、属性值格式错误按行号定位,检查上一行结尾
Label or path not found&label 引用的标签未定义或拼写错误确认 .dtsi 已包含且标签存在
unit_address_vs_reg 警告节点名含地址但无 reg,或反之补齐 reg 或修正节点名
Duplicate node name同一父节点下节点名重复重命名或使用 /delete-node/
ERROR: Input tree has errors语义检查失败,如 phandle 悬空用 -f 强制输出后反编译核对
FDT_ERR_BADMAGICDTB 未正确加载或地址错误检查 bootloader 传参和 DTB 路径
不要用 -f 强制输出掩盖错误。强制生成的 DTB 可能结构不完整,内核启动时表现为“设备树解析失败”或外设全部丢失,反而增加排查难度。

六、案例与复盘:一次 I2C 节点丢失的排查

某 RISC-V 板卡启动后 I2C 控制器不可用。排查步骤:先用 dtc -I dtb -O dts 反编译实际加载的 DTB,发现 i2c0 节点存在但 status 为 "disabled";再回到 DTS 源文件,发现板级 DTS 中 &i2c0 { status = "okay"; }; 被写在 #include 之前,导致覆盖顺序错误。调整包含顺序后重新编译,问题解决。

复盘要点:一是编译通过不等于语义正确,必须反编译核对最终 DTB;二是覆盖顺序、标签引用、/delete-property/ 的作用域要清晰;三是把 dtc -I dtb -O dts 作为板级调试的常规动作,而不是最后手段。

掌握 DTC 的编译与反编译闭环,等于掌握了设备树调试的“显微镜”和“手术刀”:编译把源码变成内核可用的 DTB,反编译把 DTB 还原成可读结构,两者配合才能快速定位板级移植中的真实问题。