3. U-Boot设备树编译流程:从DTS到DTB

设备树源文件(DTS)说到底就是一份文本描述,U-Boot和内核都读不懂它。得先编译成二进制DTB,才能被加载解析。这一节我就带你走一遍完整的编译流程,包括U-Boot内置DTS的路径规则,以及怎么用手头的dtc工具手动编译。

一句话总结:DTS → dtc编译器 → DTB。U-Boot在编译时会自动完成这一步,但手动编译能帮你快速验证和调试。

3.1 DTS到DTB的编译过程

先看一张整体流程图,把编译链路理清楚:

DTS → DTB 编译流程 .dts 源文件 .dtsi 头文件 C预处理器 (cpp) dtc 编译器 .dtb 二进制 关键步骤说明: 1. #include "xxx.dtsi" 由C预处理器展开,支持宏定义和条件编译 2. dtc 解析预处理后的文件,检查语法、合并节点、生成扁平二进制 3. 最终 .dtb 文件可直接被 U-Boot 或内核的 libfdt 库解析

实际编译时,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-y
  • configs/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设备树的世界里自由穿梭了。