☰
PetaLinux设备树单独编译实操:从dts修改到dtb打包全解析
2026/10/5 8:44:13 网站建设 项目流程

1. 从一次单独编译的需求说起:设备树在Petalinux工程里的真实角色

我在用PetaLinux做Zynq/ZynqMP项目时,经常遇到一个场景:跑着跑着发现某个外设的引脚配置不对,或者想把UART9改成UART1,又或者在调试AD9361这类需要大段spi/spidev配置的芯片时,发现设备树写错了需要重来。第一反应是修改设备树文件,然后跑petalinux-build,结果发现整个工程要重新编译一遍内核和根文件系统,快的时候十几分钟,慢的时候半小时起步。后来我仔细看了PetaLinux的构建日志,发现它其实区分了内核、U-Boot、设备树、FSBL这些组件的构建目标,也就是说,设备树完全支持单独编译,没有必要每次都把整个工程从头来过。

设备树(Device Tree,DT)在嵌入式Linux开发里的地位,说白了就是一张“硬件配置清单”。内核本身不关心你用的是哪块板子,它只负责读设备树,然后按设备树里描述的信息去匹配驱动、注册设备。PetaLinux工程中之所以容易让人迷糊,是因为它把设备树的来源、编译和打包分散在了好几个地方:有kernel里自带的dts,有meta-user层里的system-user.dtsi,还有硬件配置阶段生成的pl.dtsi,诸如此类。一旦没搞清楚它们的分工,单独编译时就容易改错文件,或者改了之后发现打包进image.ub里的还是旧版本。

这篇文章会围绕“PetaLinux工程中设备树的介绍”和“Linux单独编译设备树”这两个核心话题展开,把我自己调试过程中用到的命令、遇到的报错、踩过的坑都罗列出来,给正在被PetaLinux设备树折磨的开发者一个可复现的参考。适合有基础Linux操作经验、正在用PetaLinux做Zynq系列或Versal系列项目的嵌入式工程师,也适合刚接手PetaLinux工程但还没完全搞懂设备树构建逻辑的初级开发者。

2. 被分散存放的设备树:PetaLinux工程里的dts,dtsi与设备树生成规则

2.1 dts、dtsi、dtb、dtbo:四种文件别搞混

动手单独编译之前,先把概念梳理清楚。设备树相关的文件后缀有四个,初看容易混,实际上分工非常明确:

后缀完整名称作用能否直接运行
dtsDevice Tree Source设备树源文件,描述一块具体板子的完整硬件否,需要编译
dtsiDevice Tree Source Include被dts包含的公共片段,一般放SoC通用配置或某个外设模块配置否,不能单独编译成完整dtb
dtbDevice Tree Blob编译后的二进制设备树,Bootloader加载后传给内核是
dtboDevice Tree Overlay设备树叠加层,运行时动态加载,用于部分覆盖基础dtb否,需通过configfs加载

在PetaLinux工程里,单独编译的最终产物是dtb,不是dts。很多人一开始以为改完dts文件再编译就完事了,实际上编译生成的是dtb,而dtb需要放到正确的位置,或者打包进image.ub,Bootloader(U-Boot)才会加载它。这个逻辑在后面第三部分操作时会反复遇到。

2.2 PetaLinux设备树三大来源:kernel、hardware描述、用户层

PetaLinux工程中,设备树内容的来源大体上分三类,每一类有着完全不同的管理方式和编译时机。

第一类是Linux内核源码里自带的dts/dtsi。在PetaLinux中,内核源码通常位于工程目录下的components/plnx_workspace/source/linux-kernel/(不同版本路径会有差异),里面的arch/arm/boot/dts/(ARM 32位)或arch/arm64/boot/dts/(ARM 64位)保存着大量平台设备树。这里既有Xilinx官方板卡的dts,也有SoC级别的通用dtsi,比如zynqmp.dtsi。如果只改内核自带的dts,那是“最干净”的方式,但问题是升级PetaLinux版本时容易被覆盖,而且脱离了Petaliunx的增量管理逻辑,不利于团队协作。

第二类是硬件描述文件自动生成的设备树。PetaLinux构建工程时,会根据你的XSA硬件配置文件(由Vivado导出的硬件描述)生成一系列设备树相关文件,核心是pl.dtsi。这个文件描述了FPGA可编程逻辑部分的硬件信息,包括AXI外设、中断、地址映射等。它是由petalinux-config或petalinux-build阶段自动生成的,不建议手工修改,因为一旦重新配置硬件,这些修改就会被覆盖。

第三类是用户自定义层,也就是meta-user层中的system-user.dtsi。这个文件几乎是PetaLinux专门为开发者留的后门。默认情况下,project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi存在于工程中,内容大概是:

/include/ "system-conf.dtsi" / { };

开发者在这个文件里添加的内容,会在最终设备树编译时被合并进来。比如你想修改某个外设的status属性、增加spidev节点、调整复位信号时序,都可以写在这个文件里。它的优先级相对较高,适合做增量修改。

2.3 覆盖层机制:PetaLinux如何把多段设备树合并成最终dtb

理解了三大来源之后,还有一个核心机制需要搞清楚:覆盖层(overlay)。

PetaLinux在生成最终dtb时,并不只是简单地把一个dts编译成dtb,而是通过一系列包含和覆盖关系,把不同来源的内容合并起来。通常的处理流程是:

  1. 根据Vivado导出的XSA,在components/plnx_workspace/source/linux-kernel/下准备基础dts文件(比如system-top.dts)。
  2. 生成system-conf.dtsi,包含硬件配置产生的信息。
  3. 通过include机制,把system-user.dtsi的内容包含进最终dts。
  4. 把合并后的内容交给dtc编译器,生成最终dtb。

这种设计的最大好处是:内核自带dts可以保持不动,Vivado生成的硬件配置也能自动同步,而用户只需要维护system-user.dtsi,就能完成大部分定制需求。单独编译设备树的能力也基于这套机制,我们可以指定只编译这棵“最终合并后的设备树”,而不去碰内核、根文件系统这些大头。

3. 单独编译的完整流程:从修改dtsi到生成image.ub的实操记录

3.1 第一步:确认设备树编译目标与依赖关系

在PetaLinux工程里,单独编译设备树,最常规的命令是:

petalinux-build -c device-tree

这条命令的意图很明确:只构建device-tree这个组件。PetaLinux的组件化构建方式跟OpenEmbedded/Yocto的recipe机制高度相关,-c参数指定的是构建目标。执行该命令后,PetaLinux会读取设备树相关的recipe,把前面提到的几个设备树来源汇总,生成最终的dtb,而不会去重新编译内核源码或者重新打包根文件系统。

实测下来,这条命令的执行速度非常快,通常在几十秒内完成(取决于你的机器性能)。相比完整petalinux-build动不动就几分钟甚至几十分钟,这个速度可以让你放开手脚做设备树调试。

需要注意一个前置条件:单独编译设备树前,必须保证project-spec中的硬件配置已经正确导入过。也就是说,至少运行过petalinux-config --get-hw-description=<xsa路径>或者完成过一次完整构建,让PetaLinux知道你的硬件平台是什么样的。否则设备树连基础地址映射都没有,编译出来的dtb毫无意义。

3.2 第二步:确认输出产物位置并检查dtb内容

执行完petalinux-build -c device-tree之后,生成的dtb会被放到一个随版本变化的路径下。对于常见版本,路径类似:

project-spec/plnx_workspace/components/plnx_workspace/device-tree/device-tree/system-top.dtb

不同PetaLinux版本和工程配置,这个路径可能有差异。如果你找不到,可以用find命令定位。我的习惯是直接到工程根目录搜索:

find . -name "system-top.dtb" -newer .config

一旦找到了生成的dtb,先别急着打包,建议先验证一下编译产物是否包含了你刚刚修改的内容。这里要用到设备树编译工具家族中的“反汇编”工具dtc,基本用法:

dtc -I dtb -O dts system-top.dtb -o system-top.dts

执行后,打开生成的system-top.dts,搜索你修改的节点名。这个方法比看编译日志可靠得多,能确认合并后的设备树里到底存了什么。我调试spidev时,就曾遇到这种情况:明明在system-user.dtsi里加了spidev节点,编译也不报错,但反汇编后发现节点根本没合并进去,最后排查发现是/include/路径写错,导致system-user.dtsi没有被引用。

提示:检查dtb内容这一步不能省。设备树编译不会像C语言那样对未知节点报错,语法合法但逻辑不对的情况太常见了。

3.3 第三步:把新编译的dtb打包进image.ub

单独编译出dtb只是第一步,开发板上电后U-Boot并不会凭空去读一个孤立dtb,它需要从image.ub或者boot分区中找到设备树文件。所以下一步,把新生成的dtb打包进image.ub。

PetaLinux提供了打包命令,常见做法是:

petalinux-package --boot --dtb --format BIN

或者比较老的做法是先生成完整镜像再手动替换dtb。实测下来,petalinux-package --boot --dtb这条命令会把当前工程里的image.ub重新生成一遍,内部包含的dtb替换成刚刚编译出来的新版本。这个过程中不会重新编译内核,速度同样很快。

另一种思路是直接操作U-Boot引导分区。如果你用的是SD卡启动方案,那就把生成的dtb拷贝到SD卡boot分区,然后修改U-Boot的环境变量,告诉它从指定文件名加载设备树。这种方式适合喜欢手动掌控引导流程的开发者。两种方式各有适用场景,表格对比如下:

打包方式适用场景优点缺点
petalinux-package --boot --dtb标准PetaLinux镜像部署生成统一的image.ub,启动流程规范路径封装较深,出问题时排查链路长
手动拷贝dtb到boot分区调试阶段频繁修改设备树改完直接拷贝,不用反复打包需要维护U-Boot环境变量,容易遗忘

3.4 实际测试:只改设备树不动内核的完整链路

以我最近调试的一块ZynqMP开发板为例,简单演示一下整个单独编译的链路。

需求:在设备树中增加一个spidev节点,用于测试FPGA逻辑中挂载的SPI从设备。我先在project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi中添加:

/include/ "system-conf.dtsi" / { }; &spi1 { status = "okay"; spidev@0 { compatible = "rohm,dh2228fv"; reg = <0>; spi-max-frequency = <1000000>; }; };

执行:

petalinux-build -c device-tree

构建完成后,生成了新的system-top.dtb。为了确认,我用dtc反汇编并查找spidev节点,确认无误。接着执行:

petalinux-package --boot --dtb

得到新的image.ub,拷贝到SD卡boot分区,上电后,挂载/dev/spidev1.0成功,验证通过。整个过程从修改到验证,大约只需几分钟,相比完整构建节省了大量时间。

4. 我踩过最多次的坑:设备树单独编译时的常见报错与排查思路

4.1 编译不报错,但新设备树没生效

这个问题很隐蔽,也是最容易让人怀疑人生的一个坑。现象是:修改了system-user.dtsi,执行了单独编译,dtb文件的时间戳也确实变了,但开发板启动后还是旧配置。

排查链路可以从三个层面展开:

第一,检查dtsi是否真的被包含进了编译流程。PetaLinux的device-tree recipe在编译时,会读取project-spec/meta-user/recipes-bsp/device-tree/device-tree.bbappend等配置文件来决定最终的dts内容。如果system-user.dtsi没有被正确include,那么所有修改都不会进入最终dtb。此时用dtc反汇编生成的dtb,搜索你添加的节点,立刻就能定位。

第二,检查U-Boot实际加载的dtb是不是你打包的那一份。使用SD卡启动时,U-Boot会读取boot分区中的image.ub,并将其中的dtb传递给内核。如果你手动更新了某个dtb文件,但没有把新的image.ub拷贝到SD卡,那一切都白搭。这里我的排查习惯是用U-Boot命令行直接查看:

printenv

重点看bootcmd、kernel_addr、fdt_addr这些环境变量,确认加载地址和文件名是否与预期一致。

第三,检查是不是有多个dtb在“打架”。比如你既修改了内核源码树里的dts,又使用了system-user.dtsi,最终PetaLinux可能以某个优先级更高的来源为准。清理不一致修改的最快办法,是把内核自带dts中对应的修改回退,统一在system-user.dtsi中维护。

4.2 dtc编译器版本不一致导致的编译错误

设备树源文件看起来简单,但对编译器版本还是有一定要求的。尤其是在较老PetaLinux版本里,默认的dtc版本可能比较旧,不支持某些语法。最常见的是#include和/include/混用的问题,以及prop = <&label>这类引用宏的解析。

我遇到过一次典型报错:

Error: system-top.dts:125.1-2 syntax error FATAL ERROR: Unable to parse input tree

排查后发现在dtsi中引用了一个宏,但是宏定义所在的头文件没有通过include引入,导致dtc把宏名当成普通字符串处理,进而语法错乱。解决方法是确保在dts开头正确include相关头文件,比如:

#include <dt-bindings/gpio/gpio.h> #include <dt-bindings/interrupt-controller/irq.h>

然后重新执行单独编译。

4.3 U-Boot环境下设备树加载失败的常见信号

另一个高频问题,是开发板启动时U-Boot已经打印了加载设备树的日志,但内核启动后完全没有感知到新的外设配置。这种情况很大概率是设备树在加载阶段就发生了截断或地址错误。

U-Boot加载设备树有两种常见方式:一种是从boot分区读image.ub,解析出dtb后放到内存指定地址;另一种是通过fdt addr命令手动加载一个独立dtb文件。不管哪种方式,加载地址要跟内核期望的地址一致。如果设置不当,内核在启动早期解析设备树时会报:

FDT: Failed to parse chosen node

或者更直接的:

Error: fdt header not found

这种时候,先把U-Boot环境变量里的fdt地址、image.ub加载地址确认清楚,再检查boot分区中是不是混入了大小写不同的同名文件。曾经就遇到过,文件名明明是对的,但分区内还有一个残留的旧dtb,U-Boot通过某种glob方式加载到了旧文件。

4.4 关于“我用dtc反汇编看不到修改”的三种原因

如果你反汇编dtb后发现修改不存在,基本可以从三个方向入手:

  • 修改写在了错误的文件里。比如把节点加在了某一个dtsi中,但这个dtsi根本没有被包含。
  • 修改写在了正确的文件里,但节点路径或别名引用错误。比如&spi1在某个SoC上实际并不存在,导致合并时该段被静默丢弃。
  • 编译缓存导致产物未更新。虽然petalinux-build -c device-tree理论上会重新编译设备树,但如果工程存在外部修改且时间戳未变化,个别版本确实出现过未重新生成的情况。我的做法是找到build目录下的临时设备树源文件,确认其内容是否已包含修改,再考虑是否需要先petalinux-build -x distclean清理后重建。

5. 独立编译之外的进阶操作:覆盖层、动态加载设备树和其他实用命令

5.1 在PetaLinux之外使用dtc直接编译dts的场景

PetaLinux提供了工程化管理手段,但有一个场景它处理得并不高效——频繁迭代一个dts小文件。比如你正在做一个板级BSP,device tree的修改粒度非常小,跑一次PetaLinux的recipe解析有点“杀鸡用牛刀”。这时完全可以脱离PetaLinux,直接用系统自带的dtc工具编译:

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

这种方式适合已经能独立维护dts文件的开发者,跳过PetaLinux的工程封装,直接得到dtb。但要注意,这种方式不再自动处理include和覆盖,你必须自己保证所有依赖的dtsi都已包含。

5.2 设备树覆盖层:修改硬件配置但不重新编译dtb的另一种思路

PetaLinux本身在构建时已经做了一层“编译期覆盖”,但在运行时修改设备树的方案同样存在:设备树叠加层。通过configfs挂载并加载dtbo,可以在Linux运行过程中覆盖基础dtb中的节点属性,适合外设驱动支持热插拔或需要临时修改配置的场景。

开发阶段我一般不推荐用这个方式,因为它增加了调试复杂度——你要区分哪些配置来自基础dtb,哪些来自overlay,出了问题不好定位。但在产品化部署或者需要动态管理多个硬件配置时,overlay是一个很值得了解的方向。

5.3 配合单独编译一起使用的常用Linux命令

整个设备树调试流程中,除了PetaLinux和dtc工具,还有几个Linux命令几乎必用。整理如下:

命令用途使用时机
find定位dtb或dts文件不确定产物路径时
dtc反汇编/编译设备树修改后验证
fdtdump快速查看dtb内容简单查看节点时
grep在dts文件树中搜索节点查找指定外设配置
diff对比两个dts/dtb反汇编内容确认修改是否生效

例如确认内核实际用的是哪个设备树:

dmesg | grep "Machine model"

这一条命令能打印内核启动时识别的machine型号,通过对照设备树里的model属性,能快速判断加载的dtb是不是你想要的那份。

5.4 工程维护建议:设备树修改的版本管理与团队协作

调试之外,还有一个容易被忽视的问题:多人协作时,设备树文件往往成为冲突重灾区。我建议在工程中约定一套规则,把改动统一收敛到system-user.dtsi或专门的用户层dts文件里,并在文件头部写清楚改动记录。同时,提交代码时把system-top.dtb一并提交,方便其他人直接基于同一个二进制产物开发,而不是每个人都重新编译一遍产生漂移。

6. 回到起点:设备树单独编译在PetaLinux开发流程中的定位

从工具链的角度看,设备树单独编译只是PetaLinux众多构建目标中的一个选项,但在实际开发中,它带来的效率提升非常明显。特别是当你处于“硬件调试-设备树配置-外设驱动验证”这个高频循环中时,能只花一分钟换出新的设备树,而不是陪着整个Linux系统做全套编译,整个人的状态完全不一样。至少我的体会是,连续调试一周设备树后,单独编译功能直接把我的开发节奏从“一改一等待”变成了“一改一验证”,这种体验上的差异是巨大的。

基于我个人的经验,如果你刚接触PetaLinux设备树,建议先搞懂三个问题:设备树源文件在哪、谁在执行编译、最终产物去了哪里。把这三条链路打通,你再遇到任何设备树相关的问题,都不会像无头苍蝇一样乱转。至于怎么改设备树、怎么配置某个外设节点,那是另一个层面的问题——改错了顶多功能不生效,但链条搞不清,你连问题出在哪个环节都找不到。

设备树是嵌入式Linux里上限很高的话题,它连接着硬件描述、内核驱动、Bootloader和用户空间四层逻辑,但也是少数几个“不依赖特殊设备、一根串口线就能调试”的模块。希望这篇分享能帮你在PetaLinux工程里少走一些弯路,把更多时间留给真正需要思考的功能逻辑上。还是那句话,能把工具链磨顺手了,本身就是一件值得投入的事。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询