ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

手动移植ZynqMP U-Boot与Linux Kernel:摆脱PetaLinux的启动定制实践

手动移植ZynqMP U-Boot与Linux Kernel:摆脱PetaLinux的启动定制实践 简介面向ZynqMP平台嵌入式开发者这份教程提供一套不依赖Petalinux、基于Xilinx官方GitHub源码手动移植Uboot与Kernel的完整实践路径适合在自定义板卡上完成底层系统启动开发的工程师。PDF内容从aarch64交叉编译器安装配置讲起依次覆盖FSBL与PMU固件生成、ATF可信固件编译以及设备树修改、U-boot移植与Kernel定制等核心环节每一步都有具体命令、图例与编译指导并标注了板级适配时容易出错的细节帮助读者理解从Bootloader到内核启动的完整链路。资源为单个PDF文件整体仅5.89MB便于下载后集中学习。已有3335人学习是希望绕开Petalinux、自主掌控嵌入式移植流程的开发者值得参考的资料。 说实话ZynqMPZU系列这类的MPSoC芯片很多朋友一上来就直接打开PetaLinux点两下鼠标BOOT.bin、image.ub都给你生成得明明白白烧进去也能跑。但你要是想在真实板卡上做一些定制化启动、快速验证外设驱动、或者想彻底搞懂U-Boot和Kernel的启动链路全依赖PetaLinux反而会被它的封装绑住手脚。这篇博文我完整记录一下不用PetaLinux直接基于Xilinx官方源码手动移植U-Boot和Linux Kernel的过程。适合那些已经有点嵌入式基础、想彻底搞清楚ZynqMP整套启动流程的Linux开发工程师尤其是手上有自家板卡、不想被PetaLinux黑盒限制的朋友。我先说结论非Petalinux开发方式一点都不神秘本质上就是三件事——编译一份信任链镜像ATF/BL31、编译一份U-Boot、编译一份内核和设备树然后按规定的启动地址打包烧录。难点不在编译而在理解ZynqMP的分级启动机制、DDR初始化、设备树描述和启动介质布局。下面我会把这些点一个个拆开讲每一步都给到可直接执行的命令和参数。1. 整体思路为什么绕开Petalinux手动移植反而更高效1.1 非Petalinux方式的真实价值Petalinux本质上是一个集成构建系统它帮你管理了交叉编译工具链、内核配置、U-Boot配置、rootfs生成、打包脚本这一大堆事情。但它的工程结构非常重一个工程目录几个GB很正常而且版本升级、补丁管理、自定义设备树修改时经常要等它跑完整个构建流程。更麻烦的是Petalinux默认会生成一个它自己风格的BSP结构出了问题你很难定位是配置问题还是脚本问题。手动移植的收益非常直接编译速度快增量编译几十秒到一两分钟不用每次全量构建。对U-Boot、Kernel的配置完全透明每个配置项都能找到来龙去脉。方便版本管理一个Git仓库就能维护板级DTS、defconfig和打包脚本。调试启动异常时你可以手动逐段验证BL31、U-Boot、Kernel问题边界非常清晰。当然手动移植也有门槛最核心的是你得理解ZynqMP的启动流程而不是像用Petalinux那样只管点“Build”。但这个门槛跨过去之后收益是长期的。1.2 准备工作和工具链选型我这次使用的环境是Ubuntu 20.04 LTS64位目标板是Xilinx ZCU104开发板基于ZynqMP XCZU7EV。请不要照抄板卡型号重点看思路。需要的组件如下组件版本/仓库作用U-BootXilinx的u-boot-xlnx分支git clone引导Loader负责初始化DDR、外设加载KernelLinux KernelXilinx的linux-xlnx分支git clone目标操作系统内核Arm Trusted FirmwareATFXilinx的arm-trusted-firmware分支生成BL31EL3固件DDR初始化之后由它跳转到U-Boot交叉编译器aarch64-linux-gnu- (gcc 9.3)编译ARM64代码dtc / fdtdump系统自带或独立编译设备树编译和反编译打包工具bootgenXilinx Vitis安装自带的bootgen生成BOOT.BIN交叉编译器的安装很简单sudo apt update sudo apt install gcc-aarch64-linux-gnu device-tree-compilerbootgen我采用的是Vitis自带版本通常在/tools/Xilinx/Vitis/2022.2/bin/bootgen路径下。你也可以从Xilinx单独下载但直接用Vitis的就行。注意一定要把它加到PATH里否则后面打包BOOT.BIN会报找不到bootgen。2. ZynqMP的启动流程与U-Boot移植核心2.1 从BootROM到BL31再到U-Boot一条链路必须跑通在做U-Boot移植之前你得先搞清楚ZynqMP的启动顺序这是整个移植的地基。ZynqMP内部有 BootROM片上ROM它上电后根据启动模式引脚Boot Mode从QSPI/SD/eMMC等处把FSBLFirst Stage Boot Loader或BL31加载到片内OCM并执行。官方推荐的典型链是这样的BootROM加载FSBL或直接加载BL31取决于配置。FSBL初始化PS侧的DDR、MIO、时钟然后跳转BL31。BL31ATF是EL3固件它负责运行时服务、PSCI等功能然后再跳转BL33。这里的BL33就是U-Boot。U-Boot负责加载kernel Image和DTB最后跳转kernel。非Petalinux方式也完全遵循这条链只是FSBL不再使用Vitis里的FSBL工程而是直接用U-Boot SPL来替代。SPL就是U-Boot的二级引导程序它的体积小可以放在BootROM能访问的介质里。这样就能做到BootROM → SPL初始化DDR→ BL31 → U-Boot → Kernel。这个组合非常经典Xilinx官方也保留了SPL的使用方式。我实际移植时用的就是SPL BL31 U-Boot Kernel的组合boot.bin里包含SPL和BL31即可。SPL编译后会生成u-boot-spl.binBL31编译后生成bl31.elf后面打包时这两个文件都要进BOOT.BIN。2.2 U-Boot配置与编译defconfig该怎么选U-Boot源码我拉的是Xilinx维护的仓库因为针对ZynqMP的板级支持最全git clone https://github.com/Xilinx/u-boot-xlnx.git -b xlnx_release_v2022.2 cd u-boot-xlnx export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu-然后执行make xilinx_zynqmp_virt_defconfig make -j$(nproc)编译完成后当前目录下会生成u-boot.elf、u-boot.bin、u-boot-spl.bin等文件。这里有一个非常关键的选型xilinx_zynqmp_virt_defconfig。这个是Xilinx官方提供的“通用虚拟平台配置”适用于绝大多数ZynqMP板卡因为它默认使能了多个关键驱动DDR、SD、QSPI、GEM以太网、SPI、I2C、USB等。它不绑定具体板卡的DTS而是启动时通过编译进固件里的默认DTS来匹配。如果是自家板卡一般从这个defconfig出发然后修改设备树覆盖板级差异就行比从头写defconfig容易太多。编译时还可以加一个可选参数make u-boot.elf如果你的开发流程里有Vitis工具链依赖这个ELF格式会很方便如果不跑Vitis直接使用u-boot.bin搭配SPL方式也行。2.3 SPL配置让U-Boot跑起来的“前置Loader”这里有个大家容易忽略的细节在非Petalinux方式下U-Boot编译出来后还差一步——让BL31和U-Boot能在DDR里被正确加载。SPL就承担了DDR初始化工作。U-Boot的SPL配置需要确保打开了SPL相关的选项一般xilinx_zynqmp_virt_defconfig里已经默认开启了CONFIG_SPL和CONFIG_SPL_FS_FAT等选项。为了验证SPL是否正常可以让U-Boot串口打印日志。默认的CONFIG_SPL_TEXT_BASE是在OCM里的BootROM能够直接访问。串口输出重定向到PS UART0一般ZynqMP默认串口是uart0映射到MIO 18/19。这个在DTS里要检查是否匹配你的板卡原理图。常见错误是SPL编译后无法运行日志卡在BootROM阶段。这个时候优先检查启动模式拨码开关是不是拨到了SD启动对应ZCU104是SW6以及BOOT.BIN的分区是否正确。3. Linux Kernel移植实操defconfig和设备树才是重头戏3.1 内核源码拉取与基础编译内核我同样拉取Xilinx的linux-xlnx仓库版本和U-Boot保持一致git clone https://github.com/Xilinx/linux-xlnx.git -b xlnx_release_v2022.2 cd linux-xlnx export ARCHarm64 export CROSS_COMPILEaarch64-linux-gnu-然后编译make xilinx_zynqmp_defconfig make -j$(nproc) Image make -j$(nproc) dtbsxilinx_zynqmp_defconfig是Xilinx官方的通用ZynqMP配置它比defconfig更全面包含了ZynqMP的驱动支持和Xilinx常用IP驱动。编译完成后内核Image在arch/arm64/boot/Image设备树文件在arch/arm64/boot/dts/xilinx/目录下ZCU104对应的设备树是zynqmp-zcu104-revC.dtb。这一步没什么玄学只要交叉编译器版本对、依赖齐全基本能一次通过。如果有编译报错最可能是老内核配了新编译器出现头文件不兼容统一使用gcc 9.3左右会比较稳。3.2 设备树修改与验证串口、DDR、外设全是它的活内核编译过了不意味着板子能跑。ZynqMP这种芯片板级差异全靠设备树体现。这里我以ZCU104的DTS为例把关键节点拆开讲打开arch/arm64/boot/dts/xilinx/zynqmp-zcu104-revC.dts你会看到类似这样的结构/ { chosen { bootargs consolettyPS0,115200 root/dev/mmcblk0p2 rw rootwait; stdout-path serial0:115200n8; }; aliases { serial0 uart0; ethernet0 gem3; }; }; uart0 { status okay; pinctrl-names default; pinctrl-0 pinctrl_uart0_default; };这里面最核心的几处chosen节点里的bootargs是内核启动参数。console指定了串口直接用ttyPS0波特率115200。root指定了根文件系统所在设备。rootwait让内核等待存储设备准备好再挂载这个是SD/eMMC启动必须的。aliases节点很重要它把uart0、ethernet0等逻辑名称和实际节点关联起来。U-Boot也会读取这个节点来设置stdout和stdin。uart0里的 status 必须为 “okay”否则内核会跳过该外设的初始化。你自己画板卡的时候要重点查这几项内存大小DTS里默认可能写的是4GBZCU104如果板卡DDR只有2GB必须手动修改reg 0x0 0x0 0x0 0x80000000;这类内存节点实际用的是memory节点。MIO/GEM引脚复用ZynqMP的引脚复用关系要写进pinctrl节点里否则网口不通。PS侧外部时钟检查psu_pl_clk0和psu_pl_clk1是否符合你的晶振频率。修改完DTS之后直接编译dtbs得到zynqmp-zcu104-revC.dtb。如果板卡是自己画的建议在xilinx目录下新建一个zynqmp-custom-board.dts这样不污染官方文件。3.3 手动验证设备树用fdtdump检查关键信息内核设备树编译好了不急着烧。我强烈建议先反编译看一遍实际内容fdtdump -c arch/arm64/boot/dts/xilinx/zynqmp-zcu104-revC.dtb | less重点确认几个关键内容是否存在chosen节点里的bootargs和stdout-path。memory节点里的reg地址和大小。串口、网口节点的status是否为okay。CPU节点是否包含4个A53核。如果这些都没问题设备树这关基本就过了。要是某个节点没生效优先查是不是DTS里被覆盖了比如uart0覆盖了通用文件。Xilinx的dts include层级比较多经常出现“改了个寂寞”的情况反编译dtb是最快的验证方式。4. 打包BOOT.BIN与烧录启动的完整步骤4.1 使用bootgen打包启动镜像现在手上的材料有SPLu-boot-spl.binBL31bl31.elf内核Image设备树zynqmp-zcu104-revC.dtb打包BOOT.BIN需要写一个bif文件我个人习惯叫它boot.bif内容如下the_ROM_image: { [bootloader] u-boot-spl.bin bl31.elf }注意这里没有把U-Boot本体和内核放进去。U-Boot本体不是BootROM直接加载的它由SPL从启动介质里加载。所以最后SD卡里要有三个部分BOOT.BIN含SPL BL31U-Boot的镜像文件这里叫u-boot.itb或直接u-boot.bin内核Image DTB可以由U-Boot从FAT分区加载也可以打包进Image.ub以便统一管理如果使用U-Boot的标准加载方式比较稳妥的方案是生成一个Image.ub把内核和设备树打进去mkimage -f auto -A arm64 -O linux -T kernel -C none -a 0x8000 -e 0x8000 -d arch/arm64/boot/Image -d arch/arm64/boot/dts/xilinx/zynqmp-zcu104-revC.dtb Image.ubmkimage是U-Boot工具包里的命令一般编译完U-Boot后会在tools/mkimage路径下。没有的话sudo apt install u-boot-tools。然后执行bootgen生成BOOT.BINbootgen -image boot.bif -o i BOOT.BIN -w on这个步骤会生成一个带安全头等的BOOT.BIN。这里要注意bootgen的版本和你用的U-Boot代码库分支匹配最好否则可能出现FSBL或者BL31加载地址错位的问题。2022.2的U-Boot配2022.2的Vitis bootgen我实测下来无问题。4.2 SD卡分区与文件放置ZCU104的SD启动SD卡需要两个分区第一个分区FAT32存放BOOT.BIN、Image.ub或U-Boot DTB、u-boot.bin。第二个分区EXT4存放rootfs。分区可以用gparted或命令fdisk完成。我推荐用gparted图形化不易出错。FAT32分区大小给512MB足够剩下的给EXT4。拷贝文件时注意FAT分区不建议放太多小文件否则U-Boot读取变慢。放入文件后插上SD卡上电串口应该能看到类似日志U-Boot SPL 2022.01-... Trying to boot from MMC1 NOTICE: BL31: v2.5(release):... U-Boot 2022.01-...然后U-Boot会自动读取fat分区里的Image.ub如果有多个节点需要检查U-Boot环境变量bootcmd。手动输入命令也能启动fatload mmc 0:1 0x10000000 Image.ub bootm 0x10000000这里地址0x10000000是DDR里的一个临时加载地址确保它不和Kernel镜像地址冲突。ZynqMP的DDR起始地址是0x0通常内核Image的加载地址是0x8000所以把Image.ub加载到0x10000000是安全的。4.3 U-Boot环境变量与启动参数自动化的经验每次手动敲命令不现实。我发现最省事的做法是在U-Boot里设置默认环境变量把启动命令固化下来。方法是修改U-Boot源码里的include/configs/xilinx_zynqmp.h或者使用U-Boot的default environment机制bootcmdif fatload mmc 0:1 0x10000000 Image.ub; then bootm 0x10000000; fi bootargsconsolettyPS0,115200 root/dev/mmcblk0p2 rw rootwait如果你不想改源码也可以在U-Boot命令行用setenv和saveenv保存。但注意保存环境变量的地址空间ZynqMP在QSPI/SD不同介质里环境变量的存储位置不一样曾经我因为环境变量保存到了QSPI里导致一上电读了旧的环境变量启动参数一直不对排查了半天。后来干脆固定使用bootcmd里的分支判断优先读取SD读不到再回退网络启动这样可以避免环境变量污染带来的麻烦。5. 常见问题与排查技巧实录5.1 启动日志异常的分阶段定位ZynqMP启动链路长问题可能出现在任何一个环节。我的排查套路是先看日志停在哪再判断是哪个组件挂了。现象可能原因排查思路上电串口无任何输出BootROM没找到启动介质或启动模式不对查拨码开关确认SD卡是否识别用SD格式化工具重做FAT32分区SPL打印后卡住无BL31日志BL31没被正确加载ATF配置问题或加载地址不符检查bif文件里的BL31路径确认SPL对BL31地址设置BL31打印后没进U-BootU-Boot入口地址配置不对或BL31内存布局冲突检查U-Boot TEXT_BASE一般BL33加载地址在DDR的某个偏移位置U-Boot可以起来但加载kernel失败FAT分区读取失败或Image.ub路径不对检查文件名大小写确认FAT分区存在Kernel启动panic挂载不了rootfsrootfs分区设备名不对或root指定错误确认root/dev/mmcblk0p2是否对应EXT4分区用rootwait5.2 内存和DTS不一致导致的疑难杂症有一次我在自家板卡上发现Kernel启动到一半突然重启后来定位是DTS里memory定义成了3GB但板卡实际DDR是2GB导致内核访问了不存在的物理地址。这种问题最容易出现在修改DTS时只改一处、漏改另一处的情况。手动移植的板卡建议先让内核只使用安全内存范围跑通之后再去扩展DTS。还有个隐秘问题DDR的EPHYECC功能。如果板卡DDR硬件不支持ECC但在FSBL/SPL配置里开启了ECC位启动时会随机死机。排查方式是关闭所有ECC相关的配置只保留基础DDR控制器参数来自动训练。Xilinx在SPL里一般会用DDR DRAM driver自动训练不需要手动配置具体的延时参数别被那堆寄存器数吓到默认auto training就行。5.3 设备树改了半天没生效怕是没编进Image.ub这是个新手容易踩的坑。修改了DTS编译出新的DTB但启动时还是老设备树。原因通常是Image.ub里还包着旧的DTB。如果你用mkimage -f auto打包依赖里可能会带进旧的dtb或者你只更新了dtb文件但没有重新生成Image.ub。我一般在打包时会把内核Image和DTB分开加载先加载DTB再加载Image这样排查起来更直观fatload mmc 0:1 0x10000000 zynqmp-zcu104-revC.dtb fatload mmc 0:1 0x8000 Image bootm 0x8000 - 0x10000000这样每次只替换dtb不用反复重新打包Image.ub调试效率高很多。等设备树稳定了再合成Image.ub方便发布。5.4 网口PHY不识别、外设枚举不出来的通用解法ZynqMP的GEM网卡驱动兼容性很好遇到网口出不来绝大多数是设备树里PHY地址错了。PHY的地址是由硬件决定的比如ZCU104上CPLD配置PHY地址为0DTS里写0就通。如果你用的是第三方核心板和底板的奇怪组合PHY地址不确定一个技巧是在U-Boot里先执行mdio read扫描PHY地址mdio list如果PHY没列出确认GEM reset引脚有没有被复用配置好。外设枚举不到的问题同理也是优先查DTS里status、时钟频率和复位引脚。不要一上来就怀疑内核驱动先把设备树用fdtdump确认节点存在、status是okay再看驱动的devicetree匹配逻辑。写在最后的小体会手动移植ZynqMP的U-Boot和Kernel整套流程跑下来最大的收获不是那些命令而是对启动链路的敏感度。以后再遇到任何Linux启动异常你心里会有一个清晰的检查地图先看BootROM有没有找到介质再看SPL有没有初始化DDR然后确认BL31跳转最后才是Kernel和设备树的事。这套经验是Petalinux给不了的。最后再建议一点如果是在自家板卡上量产调试建议把U-Boot环境变量里的bootcmd设置为优先读取SDImage.ub同时保留一个串口交互的救急入口这样即使在现场烧坏了系统也能通过串口手动引导恢复。本文还有配套的精品资源点击获取
返回列表