3. 设备树编译器:dtc工具的使用,编译与反编译,dtb文件的生成与加载

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

这就得靠dtc——设备树编译器。说白了,dtc就是把我们写的.dts文本文件,转成二进制的.dtb文件。反过来,也能把.dtb反编译回.dts,方便调试。

我刚开始接触设备树时,总觉得这步很简单。直到有一次,一个逗号写错了位置,折腾了我整整一个下午。嗯,从那以后,我对dtc的每个警告都不敢轻视了。

3.1 dtc工具是什么?

dtc全称是Device Tree Compiler。它是设备树生态的核心工具。你想想看,没有它,我们写的那些花里胡哨的节点、属性,都只是一堆文本而已。

dtc主要做三件事:

  • 编译:.dts → .dtb
  • 反编译:.dtb → .dts
  • 检查:验证设备树语法是否正确

我个人习惯,每次修改完设备树后,先用dtc编译一下,看看有没有报错。这比直接烧到板子上再调试,效率高太多了。

3.2 编译:从.dts到.dtb

最基本的编译命令长这样:

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

参数解释:

  • -I dts:输入格式是dts文本
  • -O dtb:输出格式是dtb二进制
  • -o my_board.dtb:指定输出文件名
  • 最后一个参数是输入文件

我在项目中遇到过,有些新手喜欢省略-I和-O参数。dtc确实能自动识别,但万一识别错了呢?还是显式指定更靠谱。

小技巧:加上-@选项,可以生成带符号表的dtb。这对后续动态加载overlay很有用。

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

调试时经常需要反编译。比如从内核或bootloader里拿到的dtb,想看看里面到底写了什么。

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

反编译出来的dts,和原始dts可能不完全一样。为什么?因为编译过程中,一些注释会被丢掉,属性顺序也可能变化。但逻辑上是等价的。

我曾经反编译过一个别人的dtb,发现里面有个GPIO的pinmux配置写错了。要不是能反编译回来看看,这bug还真不好找。

3.4 常用选项与实战技巧

dtc的选项不少,但常用的就这几个:

选项 作用 我的建议
-@ 生成符号表 建议默认加上
-H 指定phandle格式 一般用默认即可
-W 启用特定警告 调试时很有用
-E 启用特定错误检查 建议全开
-v 显示版本信息 确认工具版本

举个例子,我想检查设备树里有没有重复的节点名:

dtc -I dts -O dtb -o test.dtb -W duplicate_node_names test.dts

3.5 dtb文件的生成流程

在实际项目中,dtb的生成通常不是手动敲命令,而是集成在构建系统里。比如Linux内核的编译系统,或者Yocto、Buildroot等。

但不管用哪种构建系统,核心流程都一样:

  1. 编写或修改.dts/.dtsi源文件
  2. 调用dtc进行编译
  3. 生成.dtb文件
  4. 将.dtb打包到镜像中,或单独烧录

我见过一个项目,他们把dtb生成写成了一个独立的Makefile。每次修改设备树后,只需要make dtbs,所有板型的dtb就都重新生成了。这个做法值得借鉴。

核心要点:dtb文件是设备树的最终形态。它被bootloader加载,传递给内核。内核解析dtb,就知道硬件长什么样了。

3.6 dtb的加载过程

dtb怎么被加载到内核的?不同平台略有差异,但大体流程是:

  • U-Boot:从存储介质(如eMMC、SD卡、SPI Flash)读取dtb到内存,然后通过寄存器或参数传递给内核
  • UEFI:通过ACPI或设备树协议传递
  • 裸机:直接烧录到固定地址,由启动代码加载

以U-Boot为例,常见的加载命令:

load mmc 0:1 ${fdt_addr_r} my_board.dtb
bootz ${kernel_addr_r} - ${fdt_addr_r}

这里${fdt_addr_r}是dtb在内存中的地址。内核启动时,会从这个地址解析设备树。

我曾经踩过一个坑:U-Boot加载dtb的地址,和内核期望的地址不一致。结果内核启动到一半就崩了。后来查了半天,才发现是地址没对齐。嗯,这种低级错误,犯过一次就记住了。

3.7 知识体系:dtc工具的核心逻辑

下面这张图,帮你理清dtc工具在整个设备树生态中的位置和作用:

dtc工具核心逻辑与数据流 .dts / .dtsi 设备树源文件 dtc 编译器 -I dts -O dtb 语法检查 + 编译 .dtb 二进制设备树 反编译 反编译 .dts 用于调试分析 Bootloader 加载 dtb 到内存 Linux 内核 解析 dtb,初始化硬件 图:dtc工具在设备树开发中的核心位置与数据流向

3.8 避坑指南

最后,分享几个我踩过的坑:

  • 路径问题:dtc默认在当前目录找include文件。如果.dts里引用了其他目录的.dtsi,记得用-i指定搜索路径。
  • 版本兼容:不同版本的dtc,对语法的检查严格程度不一样。我建议用和内核版本匹配的dtc。
  • 大小写敏感:设备树是大小写敏感的。NodeName和nodename是两个不同的节点。我曾经因为这个,浪费了半天时间。
  • 不要手动编辑dtb:dtb是二进制文件,别用文本编辑器打开。想修改,就反编译成dts,改完再编译回去。
特别注意:生产环境中,一定要确保dtb和内核版本匹配。不匹配的dtb,轻则某些外设无法工作,重则内核直接panic。

好了,dtc工具的使用就讲到这里。记住,工具是死的,思路是活的。多动手编译几次,自然就熟练了。

可可精品资料库