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的参数分成三类:输入输出控制、格式转换、调试辅助。下面这张图能帮你快速建立整体认知:
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 的多种输出格式
除了最常见的dtb和dts,-O还支持其他格式。我个人觉得-O fs特别适合调试:
dtc -I dtb -O fs -o my_board_tree my_board.dtb
这条命令会把.dtb展开成一个目录树,每个节点变成一个文件。你可以用ls和cat直接查看设备树的结构,非常直观。
-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
-W参数,把警告当错误处理。
4.6 实战:完整的编译流程
好了,理论讲完了,咱们来一个完整的实战例子。假设你有一个my_board.dts文件,它引用了soc.dtsi和memory.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
这条命令做了几件事:
- 从
./include和/kernel/include两个路径搜索头文件 - 启用地址匹配检查
- 保留节点路径信息方便调试
- 输出最终的
my_board.dtb
编译成功后,你可以用strings命令快速查看.dtb里的字符串:
strings my_board.dtb | grep "model"
# 输出:My Custom Board
嗯,到这里,dtc的常用参数你应该都掌握了。说白了,日常开发中用到最多的就是-I、-O、-o和-i这四个。其他参数在遇到具体问题时再去查也来得及。
-W、-@这些调试参数。一口吃不成胖子,工具也是一样。