首页 / 资讯中心 / 文章详情

Petalinux中单独编译设备树:原理、方法与避坑指南

Petalinux中单独编译设备树:原理、方法与避坑指南 ★ FEATURED ARTICLE
做嵌入式Linux的兄弟应该都有过这种经历Petalinux工程里改了一行设备树比如给某个SPI设备换个片选脚、把某路UART的时钟频率调整一下然后顺手跑一遍petalinux-build结果一等等了大半天最后发现就生成了一个几百KB的dtb文件内核、FSBL、rootfs根本没动。这事搁谁身上都挺窝火的。设备树Device Tree说白了就是一张描述“板子上有哪些硬件、它们挂在哪个地址、用什么中断”的清单。Linux内核启动时要拿它做硬件对接。Petalinux作为Xilinx/AMD官方嵌入式Linux工具把内核、U-Boot、根文件系统、设备树这一整套都管理起来了但它的全量构建链路确实长。如果只想调试设备树完全没必要每次都全量编译。这篇文章我就从设备树的基本概念讲起结合Petalinux工程里的实际文件组织把“单独编译设备树”这件事彻底讲透怎么改、怎么编译、怎么验证、踩过哪些坑。适合正在做Zynq/ZynqMP/MPSoC开发或者刚接触Petalinux但对全量编译效率不满意的同学参考。这套思路不仅适用于Xilinx系换成其他SoC平台比如瑞芯微RK3568这类同样用设备树描述硬件的方案单独抽离设备树来编译调试的逻辑也基本一致一通百通。1. 设备树Linux内核怎么“认识”你的板子1.1 从“写死驱动”到“配置文件”在没有设备树的时代Linux内核每支持一块板子都要在源码里写死一堆板级信息比如内存基地址、外设寄存器地址、中断号。代码里到处都是#ifdef CONFIG_BOARD_XXX这种宏开关换一块板子哪怕只是改个GPIO极性都要改内核源码重新编译。久而久之内核源码里板级文件堆积如山代码复用性差维护成本极高。我记得早年间给一块开发板适配BSP光改arch目录下的board文件就要折腾一整天而且一不小心就会影响其他平台。设备树把“硬件长什么样”从“内核对硬件的操作方法”里彻底剥离开来。硬件信息写在一个独立的数据结构里也就是dts/dtsi源文件经过编译变成dtb二进制内核启动时把dtb解析成设备模型然后驱动通过设备模型去匹配、访问硬件。驱动不再关心板子细节只要硬件符合某个compatible字符串就能绑定对应驱动。这个设计很像装修时用的“户型图”。水电工、木工、油漆工不需要知道你家电表装在哪、水管怎么走他们只需要按户型图干活就行。设备树就是那张户型图内核对各种硬件的驱动就是那些工种。换户型只需要换图不用换人。1.2 dts、dtsi、dtb、dtc名字相近别搞混这四个名词是新手最容易搞混的我在这里逐个拆开说清楚。dtsDevice Tree Source设备树源文件描述某一款具体板子的完整硬件树通常以/ { ... };为根节点。板级厂商拿到的往往就是这张“最终图纸”。dtsiDevice Tree Source Include公共片段类似于C语言的头文件。SoC厂家一般把CPU、中断控制器、UART、SPI、I2C等通用控制器写在dtsi里板级厂商再在dts里引用并加入具体外设、引脚复用配置。dtbDevice Tree Blobdts经过编译后的二进制文件大小通常只有几十KB到几百KB。U-Boot启动时把它加载到内存再传给内核解析。dtcDevice Tree Compiler编译设备树的工具。Linux内核源码里自带一份系统里也经常能装上独立的device-tree-compiler包。编译链路并不复杂dtc把.dts和.dtsi按#include的方式组合起来经过C预处理器完成宏展开得到完整树再生成.dtb。不过Petalinux里并不只靠裸的dtc它还会经过一个开源设备树生成器把XSA硬件描述文件里的PL侧IP、PS端配置转换成对应的dtsi片段然后再和用户自定义片段合并。所以单独编译时我们要明白Petalinux拿到的是一套由XSA和用户片段拼接出的完整源文件而不是你手写的一个dts。1.3 设备树文件里的关键四要素快速看懂一个设备树节点重点看四个东西这也是排查设备树问题时最先要核对的四个属性。compatible驱动的“身份证”。Linux驱动通过这个字符串匹配硬件节点格式通常是厂商名,设备型号。若名称和驱动里不匹配驱动压根不会probe设备在用户空间压根不可见。reg寄存器或内存区域地址。具体含义取决于父节点的#address-cells和#size-cells——前者决定地址占几个32位单元后者决定长度占几个32位单元。ARM64平台上经常是address length两段结构地址和长度各占2个cell。interrupts中断号和触发方式。这个必须和中断控制器如GIC的配置对得上不然中断来了内核不知道路由给谁外设就会一直卡在等待中断的状态。status节点是否启用。SoC厂商默认把大量用不到的外设节点写成disabled板级厂商用到哪个外设就在哪个节点上显式改成okay。我见过不少开发者在设备树里把外设地址写错、中断号跟原理图对不上导致设备工作不正常的案例。排查时第一件事就是反编译dtb确认最终加载的这颗dtb里节点内容确实是自己改的。2. Petalinux工程里设备树到底藏在哪2.1 Petalinux目录结构速览Petalinux创建工程后通常会有这样几个核心目录。先把它捋顺了后面操作才不会迷路。project-spec/工程配置、用户自定义层、补丁等都在这。你日常90%的定制工作都在这个目录下完成。components/由配置阶段生成的各种子系统源码目录、编译配置和中间产物。images/linux/最终产物输出目录。system.dtb、image.ub、BOOT.BIN基本都在这里也是做烧录启动时最常看的目录。build/实际执行构建的工作目录。BitBake会把各个recipe拉到这里展开源码、编译中间文件非常多一般不建议手动去翻里面的文件。对大多数开发者来说日常打交道最多的是project-spec/meta-user/这是Petalinux专门给用户预留的自定义层。你要做的绝大多数修改——不管是设备树、U-Boot环境变量还是内核配置——都会被收集到这里再通过BitBake的追加机制覆盖默认内容。这个设计有点像Git里的覆盖分支底层的官方配置是主分支你的个性化修改是上面叠加的commit干净又隔离。2.2 设备树源文件的“三段式”组织Petalinux里最终编译用的设备树源文件并不是一个单一dts而是多个文件按层级拼接出来的。以ZynqMP为例典型结构如下system-top.dts顶层文件是所有片段的汇总入口。它通过#include把SoC描述、板级配置、用户片段都拉进来。zynqmp.dtsiSoC本身的硬件描述来自上游内核一般不需要动。这里定义了CPU、GIC、各种控制器基础节点。system-conf.dtsi由XSA自动生成包含PS端配置、DDR地址、MIO映射等。这个文件会随硬件描述更新而重新生成。system-user.dtsi用户自定义片段默认存在且内容基本为空。我们改设备树的地方就是这里。pl.dtsi由PL逻辑的XSA生成描述你在Vivado里搭的IP比如AXI GPIO、自定义IP的寄存器映射。如果PL设计变了需要重新生成XSA、重新配置Petalinux否则设备树和实际硬件就对不上。这套三段式分层组织核心价值在于不同来源的信息互不污染芯片原厂维护SoC基础描述硬件工程自动生成PS/PL配置用户手动补板级细节。出问题的时候你可以快速定位是哪一层的信息出了偏差。2.3 修改设备树应该动哪个文件正确且唯一的答案是优先改project-spec/meta-user/recipes-bsp/device-tree/files/下的system-user.dtsi。我先说一下为什么不能瞎改其他文件。Petalinux在构建时会把工程重新生成一份设备树工作副本像system-top.dts、system-conf.dtsi这种由XSA生成的中间文件可能在一次petalinux-config或重新生成硬件描述后就被覆盖。你的修改如果写在这些文件里等于白改甚至会让工程出现“改了又变回去”的诡异现象。而system-user.dtsi位于meta-user层配置阶段不会覆盖它追加和覆盖的逻辑是稳定的。改法也很直观。比如你要给某个SPI控制器加一个spidev子节点就在该文件里追加/include/ system-conf.dtsi / { }; spi1 { status okay; spidev0 { compatible rohm,dh2228fv; reg 0; spi-max-frequency 1000000; }; };这里用了spi1的方式引用已有节点比重新写整个节点更安全、更清晰。因为spi1的定义在SoC dtsi里已经存在覆盖引用只会修改我们关心的属性不会破坏其他配置。如果是新加的板级节点比如一颗外部ADC挂在I2C总线上则在根节点之外新增独立节点再由i2c0追加子节点即可。2.4 XSA变了设备树要怎么同步还有个高频场景你改了Vivado里的PL设计导出了新的XSA然后执行petalinux-config --get-hw-description重新导入硬件描述。此时Petalinux会基于新XSA重新生成system-conf.dtsi和pl.dtsisystem-user.dtsi不会被覆盖。但你在里面引用的某些节点名、reg地址如果和新的XSA不再对得上就会出现编译警告或运行异常。所以每次更新硬件后建议顺手打开system-user.dtsi把里面引用的标签、地址重新核对一遍别等到上板了才发现寄存器地址已经变了。3. 单独编译设备树的几种姿势实测对比3.1 姿势一Petalinux自带target只编设备树我已经反复确认过最稳妥也最被官方支持的方式就是petalinux-build -c device-tree这条命令只针对设备树这个recipe执行BitBake构建。它会把system-top.dts以及所有include进来的dtsi重新预编译、转换、生成新的system.dtb。构建结束后去images/linux/下面看system.dtb的时间戳就是刚才的生成时间。我实测过一个中等复杂度的ZynqMP工程冷启动执行这条命令大概一到两分钟热缓存时能快到十几秒。相比整包petalinux-build动不动一两个小时效率完全不是一个数量级。如果改完设备树还要更新启动镜像记得手动执行打包petalinux-package --boot --dtb images/linux/system.dtb这会生成新的BOOT.BIN并把新dtb一并打包进去省得后面单独拷贝dtb文件。3.2 姿势二直接从内核源码目录make dtbs如果你平时喜欢直接操作内核源码树不想依赖Petalinux的完整构建体系或者你使用的是纯Linux主线内核而不是Petalinux自带的xlnx内核也可以直接在源码目录单独编译设备树export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu- make dtbs这条命令会编译所有在arch/arm64/boot/dts/下的目标dtb。如果你的板子配置在arch/arm64/boot/dts/xilinx/下也可以只编单个文件make xilinx/zynqmp-zcu104-revC.dtb编完的dtb在arch/arm64/boot/dts/对应路径下。这种方式更适合想绕开Petalinux、自己driven整个构建流程的开发者。注意依赖关系设备树里很多头文件宏来自内核源码比如include/dt-bindings/下的宏定义所以dtbs编译需要内核源码已经完成基本的配置准备比如运行过make defconfig或相关配置。不同平台的ARCH值不一样32位ARM用make ARCHarmRISC-V用make ARCHriscv交叉编译前缀也要换成对应的工具链。3.3 姿势三用dtc工具手动编译单个dts如果你只是想把一个孤立dts快速变成dtb做测试不牵扯整个工程直接用系统里安装的dtc是最快的sudo apt install device-tree-compiler dtc -I dts -O dtb -o test.dtb test.dts也支持反向操作把dtb还原成可读的dts这在排查问题上非常有用dtc -I dtb -O dts -o decompiled.dts system.dtb需要注意手动dtc编译时通常不带预处理器像#include和宏展开这些需要你先用cpp跑一遍或者干脆用内核提供的scripts/dtc/dtc并配合cpp。更省事儿的做法是让Petalinux自己处理别自己给自己找麻烦。这种方法适用于快速验证一段孤立设备树语法是否正确但并不适合完整的Petalinux工程场景。3.4 快速验证QEMU里先跑一遍每次改完设备树就烧板卡来回插拔SD卡确实烦。Petalinux提供了QEMU支持可以先把新dtb在QEMU里验证一下petalinux-boot --qemu --kernel images/linux/Image --dtb images/linux/system.dtb如果设备树里的内存配置、外设地址有硬伤QEMU启动阶段通常就会暴露出来不用等板子回来。这个方法特别适合远程开发、手头暂时没有板子的场景。不过QEMU对PL侧自定义IP的模拟能力有限涉及复杂PL逻辑的设备树改动最终还是得上板验证。QEMU能帮你过滤掉45%以上的低级错误尤其是内存大小、UART地址这类PS侧基础配置错误上板前跑一遍能省很多时间。3.5 打包进启动介质SD卡和image.ub设备树编出来不是终点关键是让它真正在启动时被用到。Petalinux默认会生成image.ub里面融合了内核和设备树。如果你改完设备树直接拿去烧录但不动image.ub板子启动时用的还是旧的设备树。要把单独编译的dtb更新到启动介质里常见做法分两种。一种是SD卡启动把SD卡分成BOOT分区和rootfs分区BOOT分区是FAT格式里面放着BOOT.BIN、image.ub或者单独的system.dtb。你只要把新的system.dtb覆盖到BOOT分区对应文件再重启即可U-Boot会按预设环境变量从BOOT分区读取dtb。另一种是重新打包image.ubpetalinux-package --image --dtb images/linux/system.dtb --format uImage打包完再替换SD卡上的image.ub这样内核和dtb是打包在一起的可靠性更高但更新也没那么灵活。这里我补充一下如果你走的是U-Boot TFTP NFS这种网络启动调试模式单独编译设备树简直太方便了——只需把dtb文件扔到TFTP服务器目录U-Boot里重新下载对应文件即可不需要重新打包任何镜像。整个调试循环从“改文件-交叉编译-烧卡-上电”变成“改文件-编译-传服务器-重启”节省的时间是按小时计的。4. 我踩过的设备树编译与启动坑4.1 编译期异常syntax error和引用无效设备树语法错误是第一类高频问题。最常见的是忘了分号或者大括号不匹配编译器会提示Error: system-top.dts: 12.13-14 syntax error FATAL ERROR: Unable to parse input tree这种问题通常发生在你手动编辑dts、复制网上的片段时。我的经验是先缩进对齐再数括号能用dtc的-或-O dts做语法校验就赶紧校验别两眼一抹黑直接上板。另一个高频错误是引用了不存在的节点比如uart99 { ... };如果SoC dtsi里根本没有uart99这个标签编译直接报ERROR (phandle_references): Reference to non-existent node or label。这个原因多半是你从别的芯片平台照搬了片段没有改成自己平台上的实际标签。尤其是从旧工程迁移设备树时比如把AD9361相关节点从另一套Petalinux工程搬过来最容易出现这种问题——标签不同、层级结构不同、引用的时钟节点对不上直接编译肯定炸。反编译当前工作副本的dtb用dtc查一遍有效标签再回填到自己的dtsi里比盲猜靠谱得多。4.2 改完没生效这种坑最扎心改完设备树也编译了上板却发现行为没变。我总结下来按概率排序有这么几个原因。第一你改的文件根本没参与构建。常见于直接改了build/下的生成dts而不是project-spec/meta-user/下的system-user.dtsi。下次构建时生成文件被覆盖白改。第二启动脚本没加载新dtb。比如SD卡里还有旧的image.ubU-Boot优先读了它你单独更新的system.dtb根本没人用。第三内核里没有对应驱动。设备树节点写得再对内核没编译进驱动设备节点就不会出现在/dev/下或者driver probe直接失败。第四你改的是dtb但U-Boot在启动时传了覆盖参数或者用了fdt overlay运行时的树和你编出来的不一致。排查顺序建议是先确认生成的system.dtb时间戳再看启动日志里U-Boot打印的设备树地址最后进内核看/proc/device-tree的实际内容。三层对上了基本不会错。这个排查逻辑我用了很多年每次都管用。4.3 运行时设备树故障的排查方法启动阶段设备树问题最容易表现为内核panic或外设失灵。我提供几个亲测有效的排查手段。第一个是内核启动日志。如果设备树内存地址配置错误内核可能直接死在早期启动加earlycon能在串口上看到更多信息bootargs... earlycon第二个是运行时的/proc/device-tree。这是当前内核解析后的实时设备树和编译的dtb不一定完全一样但能直观看出哪些节点被加载、哪些属性是最终值ls /proc/device-tree/ cat /proc/device-tree/model第三个是U-Boot下的fdt命令fdt print /soc/spiff040000如果U-Boot能正确解析dtb而内核起不来问题多半在设备树里的内存和时钟配置上。第四个是设备树覆盖overlay的排查。如果你启用了U-Boot的overlay机制同时又有多个dtbo在加载运行时设备树就和基础dtb不一样了。这时候必须把overlay dtbo也反编译出来才能看清完整树的结构。说实话overlay排查看起来比普通设备树麻烦一个量级能用基础dtb解决的尽量别引入overlay。4.4 专项场景spidev、GPIO复位、显示设备结合我实际处理过的案例单独提三个高频场景都是后台留言里经常被问到的。spidev设备树很多板子要挂SPI外设比如LCD、Flash想通过用户态spidev访问必须在设备树上加子节点且compatible要写成rohm,dh2228fv或者驱动可匹配到的spidev兼容字符串。如果写成外设真实型号但内核里没有对应驱动设备就不会probe更不会有/dev/spidevX.Y。GPIO复位信号时间有的外设上电后需要复位脚拉低一段时间再释放设备树里通过reset-gpios属性配置。常见错误是复位极性反了——GPIO_ACTIVE_LOW写成了GPIO_ACTIVE_HIGH导致复位永远没生效。另外有的驱动支持reset-delay-ms之类的属性来设定复位后的延时等待这个时间要根据外设数据手册来配配太短外设可能没准备好就访问了表现为偶发读写失败。显示设备比如给SSD1306这类OLED屏配设备树需要配置compatible、regI2C地址、width、height等属性。这里最容易错的是I2C地址没按7位格式写导致驱动读不到设备。比如器件手册写0x3C通常对应7位地址0x3C但有时驱动内部会自动左移一位填错之后要么识别不了要么读出来的寄存器全是FF。这时候用i2c-tools里的i2cdetect -y 0扫一下总线能少走很多弯路。4.5 设备树编译相关命令速查表最后整理一张速查表方便你直接抄也方便贴到团队内部文档里。目的命令Petalinux工程内仅编译设备树petalinux-build -c device-tree重新打包BOOT.BIN并加入新dtbpetalinux-package --boot --dtb images/linux/system.dtb源码树编译所有设备树make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- dtbs源码树编译单个设备树make ARCHarm64 CROSS_COMPILEaarch64-linux-gnu- xilinx/zynqmp-zcu104-revC.dtb系统dtc编译独立dtsdtc -I dts -O dtb -o test.dtb test.dts反编译dtbdtc -I dtb -O dts -o out.dts system.dtbQEMU启动验证petalinux-boot --qemu --kernel images/linux/Image --dtb images/linux/system.dtb查看运行时设备树ls /proc/device-tree/U-Boot查看设备树节点fdt print /soc/spiff040000我个人做Petalinux开发这几年最大的体会是设备树单独编译并不难难点在于建立“改-编-验-跑”的闭环思维。很多人习惯一上来就全量编译等得久不说出了问题还分不清是设备树的问题还是其他环节的问题。先把单独编译设备树的流程跑顺整个调试效率能提升一大截。最后再分享一个小技巧在多核服务器上编译时我会在petalinux-build -c device-tree之后马上用dtc -I dtb -O dts反编译一次新生成的system.dtb和改动前的版本做个对拍确认节点差异完全符合预期再考虑打包和上板。这几乎不花时间但能帮你筛掉八成低级错误。尤其是当你在system-user.dtsi里用引用覆盖多个节点时对拍能一眼看出你是否不小心覆盖错了对象。设备树这东西看着简单真到排查的时候差一个逗号都能让你多熬一个通宵。把这些命令和流程记熟能让你少熬很多个通宵。
阅读完成 · 觉得有帮助?
咨询建站