3. U-Boot设备树编译流程:从DTS到DTB
设备树源文件(DTS)说到底就是一份文本描述,U-Boot和内核都读不懂它。得先编译成二进制DTB,才能被加载解析。这一节我就带你走一遍完整的编译流程,包括U-Boot内置DTS的路径规则,以及怎么用手头的dtc工具手动编译。
一句话总结:DTS → dtc编译器 → DTB。U-Boot在编译时会自动完成这一步,但手动编译能帮你快速验证和调试。
3.1 DTS到DTB的编译过程
先看一张整体流程图,把编译链路理清楚:
实际编译时,U-Boot的构建系统会先调用C预处理器(cpp)处理.dts文件中的#include和宏定义,再把展开后的临时文件喂给dtc编译器。dtc输出.dtb,整个过程对开发者几乎是透明的。
我的习惯:调试阶段我喜欢手动跑一遍预处理,看看宏展开后的完整DTS长什么样。有时候#include路径写错了,或者宏定义冲突,光看原始DTS根本发现不了。
3.2 U-Boot内置DTS的路径规则
U-Boot源码里设备树文件放得很有规律。我刚开始接触时也翻过几次车,后来摸清了套路。核心路径就这几个:
| 路径 | 内容 | 说明 |
|---|---|---|
arch/arm/dts/ |
ARM架构各板级DTS/DTSI | 最常用,大部分板子都在这里 |
arch/riscv/dts/ |
RISC-V架构板级DTS | RISC-V平台专用 |
arch/sandbox/dts/ |
沙箱测试用DTS | 模拟环境调试用 |
include/dt-bindings/ |
设备树绑定头文件 | 宏定义、时钟/GPIO/中断常量 |
U-Boot怎么知道该编译哪个DTS?看板级配置。比如你的板子是myboard_defconfig,里面通常会有一行:
CONFIG_DEFAULT_DEVICE_TREE="myboard"
构建系统就会去arch/arm/dts/下找myboard.dts,把它编译成myboard.dtb,最后链接进U-Boot镜像。如果你改了DTS但没改这个宏,U-Boot用的还是旧设备树——我踩过这个坑,折腾了半天才发现。
我曾经犯过的错:在arch/arm/dts/里加了一个新DTS文件,但忘了在Makefile里注册它。编译没报错,但生成的U-Boot里根本没有我的设备树。后来查构建日志才发现,dtb-y列表里没加进去。
所以,添加新板级DTS时,记得检查两件事:
arch/arm/dts/Makefile中是否添加了myboard.dtb到dtb-yconfigs/myboard_defconfig中CONFIG_DEFAULT_DEVICE_TREE是否匹配
3.3 使用dtc工具手动编译
有时候你不想每次都跑整个U-Boot编译,只想快速验证DTS改得对不对。这时候手动调用dtc就派上用场了。
先确认你的系统有没有dtc。大多数Linux发行版都带,如果没有:
# Ubuntu/Debian
sudo apt install device-tree-compiler
# Fedora/RHEL
sudo dnf install dtc
基本用法很简单:
# 编译DTS到DTB
dtc -I dts -O dtb -o myboard.dtb myboard.dts
# 反编译DTB回DTS(调试利器)
dtc -I dtb -O dts -o myboard_decompiled.dts myboard.dtb
但注意,U-Boot的DTS文件通常包含#include和宏定义,直接喂给dtc会报错。你需要先预处理:
# 第一步:C预处理
cpp -nostdinc -I include -I arch/arm/dts \
-undef -x assembler-with-cpp \
myboard.dts myboard_preprocessed.dts
# 第二步:dtc编译
dtc -I dts -O dtb -o myboard.dtb myboard_preprocessed.dts
这里-I include和-I arch/arm/dts指定了头文件搜索路径,U-Boot构建系统内部也是这么干的。我个人习惯写个脚本,把这两步封装起来,省得每次敲一长串。
实用技巧:反编译DTB时加-s参数可以显示节点排序信息,加-V查看dtc版本。不同版本的dtc对某些语法支持程度不一样,U-Boot推荐使用dtc v1.4.7及以上。
3.4 常见编译错误与排查
手动编译时最容易碰到的几个问题,我列出来供你参考:
| 错误信息 | 原因 | 解决方法 |
|---|---|---|
FATAL ERROR: Unable to parse input tree |
DTS语法错误,比如漏了分号或大括号不匹配 | 检查DTS文件语法,用dtc -I dts -O dts做语法检查 |
Error: include/dt-bindings/... not found |
预处理时头文件路径没给对 | 确认-I参数包含了include/和arch/arm/dts/ |
WARNING: node xxx has a unit name but no reg property |
节点名带地址但缺少reg属性 |
检查节点命名规范,要么去掉地址后缀,要么加上reg |
ERROR: label 'xxx' already exists |
标签重复定义 | 检查.dts和.dtsi中是否有同名label |
嗯,这里要特别提一下标签重复的问题。U-Boot的DTS经常通过.dtsi层层包含,不同文件里可能定义了同名的&xxx引用节点。如果你在板级DTS里又定义了一个同名label,dtc就会报错。解决办法是给板级特有的节点加独特前缀,或者用/delete-node/先删掉继承来的节点。
避坑指南:我曾经在移植一块新板子时,直接复制了参考板的DTS,结果忘了改model和compatible属性。U-Boot启动时匹配不到正确的设备树,直接挂死。所以手动编译后,建议先用dtc -I dtb -O dts反编译看一眼,确认model字符串是你想要的。
3.5 小结
DTS到DTB的编译,说白了就是预处理加dtc两步。U-Boot内置DTS的路径规则很清晰,记住arch/arm/dts/是主战场,Makefile和defconfig是注册入口。手动编译时别跳过预处理,否则头文件找不到会让你抓狂。掌握了这些,你就能在U-Boot设备树的世界里自由穿梭了。