ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PetaLinux设备树单独编译实操:从dts修改到dtb打包全解析

PetaLinux设备树单独编译实操:从dts修改到dtb打包全解析 1. 从一次单独编译的需求说起设备树在Petalinux工程里的真实角色我在用PetaLinux做Zynq/ZynqMP项目时经常遇到一个场景跑着跑着发现某个外设的引脚配置不对或者想把UART9改成UART1又或者在调试AD9361这类需要大段spi/spidev配置的芯片时发现设备树写错了需要重来。第一反应是修改设备树文件然后跑petalinux-build结果发现整个工程要重新编译一遍内核和根文件系统快的时候十几分钟慢的时候半小时起步。后来我仔细看了PetaLinux的构建日志发现它其实区分了内核、U-Boot、设备树、FSBL这些组件的构建目标也就是说设备树完全支持单独编译没有必要每次都把整个工程从头来过。设备树Device TreeDT在嵌入式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通用配置或某个外设模块配置否不能单独编译成完整dtbdtbDevice Tree Blob编译后的二进制设备树Bootloader加载后传给内核是dtboDevice Tree Overlay设备树叠加层运行时动态加载用于部分覆盖基础dtb否需通过configfs加载在PetaLinux工程里单独编译的最终产物是dtb不是dts。很多人一开始以为改完dts文件再编译就完事了实际上编译生成的是dtb而dtb需要放到正确的位置或者打包进image.ubBootloaderU-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而是通过一系列包含和覆盖关系把不同来源的内容合并起来。通常的处理流程是根据Vivado导出的XSA在components/plnx_workspace/source/linux-kernel/下准备基础dts文件比如system-top.dts。生成system-conf.dtsi包含硬件配置产生的信息。通过include机制把system-user.dtsi的内容包含进最终dts。把合并后的内容交给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-descriptionxsa路径或者完成过一次完整构建让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; spidev0 { 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分区中是不是混入了大小写不同的同名文件。曾经就遇到过文件名明明是对的但分区内还有一个残留的旧dtbU-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小文件。比如你正在做一个板级BSPdevice 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工程里少走一些弯路把更多时间留给真正需要思考的功能逻辑上。还是那句话能把工具链磨顺手了本身就是一件值得投入的事。
返回列表