4、编译实战:使用dtc将.dts编译为.dtb,常用参数详解

好,咱们直接进入正题。上一章我们搞清楚了.dts和.dtb的关系——一个是人能读的源码,一个是机器能吃的二进制。那怎么把源码变成二进制呢?靠的就是今天的主角:dtc(Device Tree Compiler)。

说实话,我刚接触设备树那会儿,觉得dtc就是个黑盒子。敲一行命令,.dtb就出来了。后来踩了几次坑才发现,这工具的参数设计得挺讲究。今天我就把常用的参数掰开揉碎了讲给你听。

4.1 dtc的基本用法

先看最基础的命令:

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

这条命令干了三件事:

  • -I dts:告诉dtc,输入文件是.dts格式(文本)
  • -O dtb:输出文件要生成.dtb格式(二进制)
  • -o my_board.dtb:指定输出文件名

嗯,这里要注意:-I-O是大写的i和o,别写成小写。我刚开始就犯过这错误,小写的-i是别的意思,后面会讲到。

4.2 常用参数详解

我个人习惯把dtc的参数分成三类:输入输出控制格式转换调试辅助。下面这张图能帮你快速建立整体认知:

dtc 参数分类与核心流程 dtc 编译器 输入输出控制 格式转换 调试辅助 -I / -O -o / -i -O dtb / dts -O asm / fs -V / -H -@ / -W 典型工作流程 .dts 文件 → dtc -I dts -O dtb -o output.dtb input.dts .dtb 文件 → dtc -I dtb -O dts -o output.dts input.dtb

4.3 输入输出控制参数

-I 和 -O:指定输入输出格式

这两个参数是dtc的核心。支持的格式有:

格式标识 说明 典型场景
dts 文本格式的设备树源码 开发阶段编写和修改
dtb 二进制格式的设备树 最终烧录到板子上的格式
fs 文件系统格式(目录树) 调试时查看设备树结构
asm 汇编语言格式 极少数底层调试场景

举个例子,把.dtb反编译回.dts:

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

我在项目中遇到过好几次这样的情况:拿到一块开发板,厂商只给了.dtb文件,没有源码。这时候反编译就是救命稻草。不过要提醒你,反编译出来的.dts会丢失注释和宏定义,可读性差一些。

-o:指定输出文件

这个参数很直白,就是指定输出文件名。如果不加-o,dtc会把结果打印到标准输出(也就是终端)。

小技巧: 如果你只想快速检查设备树有没有语法错误,可以不加-o,直接看终端输出。输出正常就说明编译通过了。

-i:添加包含路径

这个参数容易被忽略,但实际开发中特别重要。当你的.dts文件里用#include引用了其他头文件时,dtc需要知道去哪里找这些文件。

dtc -I dts -O dtb -o my_board.dtb -i /path/to/include/dir my_board.dts

我曾经因为忘记加-i参数,编译报错说找不到头文件,排查了半天才发现是路径问题。你想想看,如果你的.dts里引用了#include "imx6ul.dtsi",而那个文件在/kernel/arch/arm/boot/dts/目录下,不加-i指定路径,dtc当然找不到。

4.4 格式转换参数

-O 的多种输出格式

除了最常见的dtbdts-O还支持其他格式。我个人觉得-O fs特别适合调试:

dtc -I dtb -O fs -o my_board_tree my_board.dtb

这条命令会把.dtb展开成一个目录树,每个节点变成一个文件。你可以用lscat直接查看设备树的结构,非常直观。

-O asm:汇编格式

这个格式我用的不多,但在某些特殊场景下有用。比如你想把设备树直接链接到内核镜像里,汇编格式更方便处理。

dtc -I dts -O asm -o my_board.S my_board.dts

4.5 调试辅助参数

-V:指定设备树版本

设备树规范有多个版本。目前主流的是版本17(v17),但有些老内核可能还在用v16。

dtc -I dts -O dtb -o my_board.dtb -V 16 my_board.dts
注意: 除非你明确知道目标内核需要老版本,否则不要随意指定-V参数。默认的v17兼容性最好。

-H:指定引导协议

这个参数跟引导加载程序(如U-Boot)有关。不同的引导程序传递设备树的方式不同,-H可以指定使用哪种协议。

协议标识 说明
epapr ePAPR标准协议(默认)
linux Linux内核专用协议

-@:生成节点路径信息

这个参数会在.dtb中保留每个节点的完整路径信息。调试时特别有用,但会增加.dtb文件的大小。

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

-W 和 -E:警告和错误控制

这两个参数用来控制编译器的检查严格程度:

  • -W:启用特定警告(如-W no-unit_address_vs_reg
  • -E:启用特定错误检查

举个例子,如果你想让dtc对地址和大小不匹配的问题发出警告:

dtc -I dts -O dtb -o my_board.dtb -W unit_address_vs_reg my_board.dts
避坑指南: 我曾经在编译一个复杂的设备树时,dtc只报了警告没报错,我就没在意。结果板子启动时外设驱动死活加载不上。后来发现是reg属性的大小和实际硬件不匹配。从那以后,我编译时都会加上-W参数,把警告当错误处理。

4.6 实战:完整的编译流程

好了,理论讲完了,咱们来一个完整的实战例子。假设你有一个my_board.dts文件,它引用了soc.dtsimemory.h

#include "soc.dtsi"
#include "memory.h"

/ {
    model = "My Custom Board";
    compatible = "mycompany,myboard", "mycompany,soc";

    memory@80000000 {
        device_type = "memory";
        reg = <0x80000000 MEMORY_SIZE>;
    };
};

编译命令:

dtc -I dts -O dtb \
    -o my_board.dtb \
    -i ./include \
    -i /kernel/include \
    -W unit_address_vs_reg \
    -@ \
    my_board.dts

这条命令做了几件事:

  1. ./include/kernel/include两个路径搜索头文件
  2. 启用地址匹配检查
  3. 保留节点路径信息方便调试
  4. 输出最终的my_board.dtb

编译成功后,你可以用strings命令快速查看.dtb里的字符串:

strings my_board.dtb | grep "model"
# 输出:My Custom Board

嗯,到这里,dtc的常用参数你应该都掌握了。说白了,日常开发中用到最多的就是-I-O-o-i这四个。其他参数在遇到具体问题时再去查也来得及。

我的建议: 刚开始用dtc时,可以先不加任何额外参数,只做最基本的编译。等遇到问题了,再逐步加上-W-@这些调试参数。一口吃不成胖子,工具也是一样。
专注资料整理