4、设备树编译器:dtc工具的使用、编译与反编译
设备树写好了,怎么变成内核能吃的二进制文件?
答案就是 dtc——Device Tree Compiler。
这玩意儿说白了就是设备树的「翻译官」。你把 .dts 文本文件丢给它,它给你吐出 .dtb 二进制。反过来也行,.dtb 反编译成 .dts,方便你调试。
我在项目里见过不少新人,拿着 .dts 直接往内核里塞,结果编译报错一脸懵。其实很多时候,就是没搞懂 dtc 的脾气。
4.1 dtc 工具从哪来?
dtc 的源码在 Linux 内核的 scripts/dtc/ 目录下。你编译内核的时候,它会被自动编译出来。
当然,你也可以单独编译它:
# 进入内核源码目录
make scripts/dtc
# 或者直接编译整个内核,dtc 也会生成
make defconfig
make -j4
编译完之后,工具在 scripts/dtc/dtc。我个人习惯把它拷到 /usr/local/bin/ 下,方便全局调用。
apt install device-tree-compiler。但版本可能比较老,我建议还是用内核自带的,版本匹配,坑少。
4.2 基本用法:编译 .dts → .dtb
这是最常用的操作。你写好了设备树源文件,想生成内核能识别的二进制:
dtc -I dts -O dtb -o myboard.dtb myboard.dts
参数解释:
-I dts:输入格式是 dts(文本)-O dtb:输出格式是 dtb(二进制)-o myboard.dtb:指定输出文件名- 最后一个参数是输入文件
就这么简单?嗯,大部分情况是的。但如果你遇到编译错误,dtc 会告诉你哪一行有问题。比如:
myboard.dts:45.3-11 syntax error
FATAL ERROR: Unable to parse input tree
这时候别慌。去第 45 行看看,多半是少了个分号,或者花括号没配对。我刚开始写设备树时,这种错误犯过不下十次。
4.3 反编译:.dtb → .dts
有时候你拿到一块开发板,厂商只给了 .dtb 文件。你想看看里面到底定义了哪些设备?反编译一下就行:
dtc -I dtb -O dts -o myboard.dts myboard.dtb
参数基本一样,只是输入输出格式互换了一下。
反编译出来的 .dts 文件,可读性还不错。但要注意,它会把所有节点都展开,包括那些被 /delete-node/ 或 /delete-property/ 删掉的内容。嗯,这里有个坑,我后面会讲。
status = "disabled" 给丢了。
4.4 常用选项与调试技巧
dtc 还有一些选项,调试时特别有用。我列个表:
| 选项 | 作用 | 我的使用场景 |
|---|---|---|
-@ |
生成符号节点(支持插件) | 做设备树插件(overlay)时必须加 |
-H |
指定 phandle 格式 | 兼容旧版内核时用,默认就好 |
-W |
启用警告 | 比如 -W unit_address_vs_reg 检查地址格式 |
-E |
启用错误检查 | 比如 -E no_unique_unit_address 检查地址唯一性 |
-v |
显示版本信息 | 确认 dtc 版本,避免踩坑 |
举个例子,我想检查设备树里有没有地址冲突:
dtc -I dts -O dtb -o test.dtb test.dts -W unit_address_vs_reg -E no_unique_unit_address
如果输出是空的,说明没问题。如果有警告,dtc 会告诉你哪个节点有问题。
4.5 设备树插件(Overlay)的编译
这个稍微进阶一点。设备树插件允许你在不修改主设备树的情况下,动态添加或修改节点。比如你想在运行时加载一个 FPGA 的配置,就可以用 overlay。
编译插件时,必须加 -@ 选项:
dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
注意输出后缀是 .dtbo,不是 .dtb。这是内核用来区分普通设备树和插件设备树的约定。
插件文件的内容长这样:
/dts-v1/;
/plugin/;
/ {
fragment@0 {
target = &i2c1;
__overlay__ {
status = "okay";
my-sensor@48 {
compatible = "my,pressure-sensor";
reg = <0x48>;
};
};
};
};
这里 /plugin/; 告诉 dtc 这是一个插件。fragment@0 定义了一个片段,target 指向要修改的节点,__overlay__ 里面放你要添加或修改的内容。
-@,否则插件找不到符号节点。我见过有人主设备树没加 -@,结果插件加载时内核报 symbols not found,折腾了一下午。
4.6 反编译的坑与避坑指南
前面提到反编译有坑,这里展开说说。
坑一:丢失 /delete-node/ 和 /delete-property/
如果你在 .dts 里用 /delete-node/ 删除了某个节点,反编译出来的 .dts 里这个节点又出现了。因为 dtc 在反编译时,只输出最终的内存表示,不会保留删除操作。
坑二:phandle 变成数字
原 .dts 里你写的 &gpio1,反编译后可能变成 &phandle 0x...<数字>。可读性差了很多。
坑三:标签丢失
你在节点前加的标签(label),比如 uart0: serial@... ,反编译后可能变成 serial@... ,标签没了。
我曾经在调试一个音频驱动时,反编译了厂商的 .dtb,想看看它的 codec 节点是怎么配的。结果反编译出来的文件里,所有标签都丢了,我对着原理图一个一个对地址,差点没崩溃。
避坑指南:
- 反编译只用来「看」,不要用来「改」
- 如果非要改,保留原始 .dts 源文件
- 用
dtc -O dts -o /dev/stdout myboard.dtb直接输出到终端,快速查看
4.7 实战:一个完整的编译流程
假设你有一个项目,设备树文件结构如下:
project/
├── myboard.dts # 主设备树
├── myboard.dtsi # 公共头文件(被 include)
└── my-overlay.dts # 插件
编译主设备树:
dtc -@ -I dts -O dtb -o myboard.dtb myboard.dts
编译插件:
dtc -@ -I dts -O dtb -o my-overlay.dtbo my-overlay.dts
检查生成的二进制:
dtc -I dtb -O dts myboard.dtb | head -50
这样你就能快速确认编译结果对不对。
dtc-check 目标,每次修改 .dts 后自动编译并检查警告。比如:
dtc-check:
dtc -@ -I dts -O dtb -o /dev/null myboard.dts -W unit_address_vs_reg
这样能提前发现很多低级错误。
4.8 本章小结
dtc 是设备树开发中最常用的工具。你不需要记住所有选项,但下面这几个一定要烂熟于心:
- 编译:
dtc -I dts -O dtb -o xxx.dtb xxx.dts - 反编译:
dtc -I dtb -O dts -o xxx.dts xxx.dtb - 插件编译: 加
-@,输出后缀.dtbo - 调试: 用
-W和-E检查潜在问题
嗯,工具本身不难。难的是你拿到一个 .dtb 后,能不能快速读懂它、修改它、验证它。下一节我们会深入设备树的语法和节点定义,到时候你会觉得,dtc 只是开胃菜。
公众号:蓝海资料掘金营,微信 deep3321