3、设备树编译器:DTC工具的使用、编译与反编译、常见错误排查

设备树写好了,怎么变成机器能懂的东西?

答案就是 DTC——Device Tree Compiler。说白了,它就是把 .dts 文本文件,编译成 .dtb 二进制文件的工具。反过来,也能把 .dtb 反编译成 .dts,方便你调试。

我最早接触 DTC 时,觉得它就是个黑盒子。后来踩了不少坑,才慢慢摸清它的脾气。今天咱们就把它彻底搞明白。

3.1 DTC 工具从哪里来?

DTC 的源码在 Linux 内核的 scripts/dtc/ 目录下。你编译内核时,它会被自动编译出来。当然,你也可以单独下载 dtc 项目编译安装。

我个人习惯直接用内核自带的版本,因为版本匹配,不容易出幺蛾子。

检查 DTC 是否安装:

$ dtc --version
Version 1.6.0

如果提示命令找不到,要么没装,要么路径没配好。

3.2 编译:从 .dts 到 .dtb

编译是最常用的操作。命令格式很简单:

$ dtc -I dts -O dtb -o output.dtb input.dts

参数说明:

  • -I dts:输入格式是 dts(文本)
  • -O dtb:输出格式是 dtb(二进制)
  • -o output.dtb:指定输出文件名

举个例子,我有个 myboard.dts,想编译成 myboard.dtb

$ dtc -I dts -O dtb -o myboard.dtb myboard.dts

编译成功的话,没有任何输出。失败了才会报错。

小技巧:加上 -@ 选项可以生成插件(overlay)支持。如果你要做动态设备树,这个选项很有用。

$ dtc -@ -I dts -O dtb -o myboard.dtb myboard.dts

3.3 反编译:从 .dtb 回到 .dts

反编译在调试时特别有用。比如你拿到一个别人的 .dtb 文件,想看看里面到底配了什么。

$ dtc -I dtb -O dts -o output.dts input.dtb

参数基本一样,只是输入输出格式互换了一下。

我记得有一次,客户给了一个 .dtb 文件,说板子启动有问题。我反编译一看,发现 GPIO 的中断号配错了。这种问题,光看二进制文件根本看不出来。

注意:反编译出来的 .dts 文件,和原始的 .dts 可能不完全一样。比如注释会丢失,一些宏定义会被展开成数值。所以反编译主要用于调试,不建议用它来修改后再编译回去。

3.4 常用选项与进阶用法

除了基本的编译反编译,DTC 还有一些实用选项:

选项 说明 使用场景
-V 指定设备树版本(如 -V 17) 兼容旧版内核
-S 指定 .dtb 文件大小(bytes) 预留空间给 overlay
-p 填充空闲空间(padding) 对齐需求
-H 指定 phandle 格式(legacy/epapr) 兼容不同 bootloader

举个例子,我想生成一个带 1024 字节填充的 .dtb:

$ dtc -I dts -O dtb -p 1024 -o myboard.dtb myboard.dts

3.5 常见错误排查

DTC 的错误信息,说实话,有时候挺让人头疼的。我把自己踩过的坑整理一下:

3.5.1 语法错误

最常见的错误,就是 .dts 文件写错了。比如:

Error: myboard.dts:10.1-2 syntax error
FATAL ERROR: Unable to parse input tree

这表示第 10 行有语法错误。我建议你检查:

  • 分号有没有漏掉?每个属性结尾都要有 ;
  • 大括号是否匹配?
  • 引号是否成对?字符串要用双引号

我曾经花了一下午,就为了找一个漏掉的分号。后来养成了习惯:写完 .dts 先跑一遍 dtc -I dts -O dtb /dev/null input.dts,只检查语法,不生成文件。

3.5.2 引用错误

设备树里经常用 &label 引用节点。如果标签没定义,就会报:

Reference to non-existent node or label "uart1"

解决办法:检查标签名是否拼写正确,或者对应的节点是否被 /delete-node/ 删掉了。

3.5.3 类型不匹配

属性值的类型搞错了,也会报错。比如:

reg = <0x10000000 0x1000>;  // 正确:两个 32 位整数
reg = "0x10000000 0x1000";  // 错误:这是字符串

DTC 会提示:

Expected integer, got string

3.5.4 重复定义

同一个节点或属性定义了两次,DTC 会报:

Duplicate property "compatible" in node

嗯,这里要注意:/delete-property/ 可以删除继承的属性,避免重复定义。

3.6 实战:编译与反编译完整流程

咱们走一遍完整的流程。假设我有一个 demo.dts

/dts-v1/;

/ {
    compatible = "myboard, demo";
    #address-cells = <1>;
    #size-cells = <1>;

    uart@10000000 {
        compatible = "ns16550";
        reg = <0x10000000 0x1000>;
        interrupts = <0 33 4>;
    };
};

编译:

$ dtc -I dts -O dtb -o demo.dtb demo.dts

反编译看看效果:

$ dtc -I dtb -O dts -o demo_reverse.dts demo.dtb

打开 demo_reverse.dts,你会发现内容基本一样,但注释没了,格式也变了。这就是我说的「反编译不是完美还原」。

调试小技巧:dtc -I dtb -O dts 反编译后,再用 diff 对比原始 .dts,可以快速发现差异。

3.7 总结

DTC 是设备树开发的基础工具。你想想看,没有它,我们写的 .dts 就是一堆文本,内核根本读不懂。

我个人建议:

  • 养成编译前检查语法的习惯
  • 反编译是调试的好帮手,但别依赖它做修改
  • 遇到错误别慌,先看行号,再查类型

下一章,咱们聊聊设备树中的中断控制器配置。那才是真正让人头大的地方。