做ZYNQ这行的都知道,程序跑死人容易,但要让系统在跑飞、死机、升级失败之后还能自己“翻回来”,才是最考验设计功底的部分。这篇文章想聊的就是ZYNQ里“软件复位重启”和“程序跳转”这一整套玩法,核心就是Xilinx的Multiboot机制。不管你是要做Bootloader在线升级,还是想让系统死机后能自动回滚到出厂固件,都绕不开这几个知识点:BootROM怎么从固定地址读镜像、FSBL怎么改Multiboot寄存器、软复位之后到底从哪里启动。我尽量把这里面的原理、寄存器、代码和踩坑经历一次性讲透。
这篇文章适合正在做ZYNQ裸机开发、嵌入式Linux启动定制、以及远程升级方案的工程师。内容会涉及FSBL、U-Boot、PetaLinux、QSPI/NAND/SD启动,也会把Multiboot回滚的实现方式拆开讲。读完你至少能自己搭一套“双镜像在线升级”的框架,而不是停留在跑通Demo的阶段。
1. 先说清楚:ZYNQ的启动流程和Multiboot到底解决什么问题
1.1 从BootROM到FSBL:ZYNQ上电后的第一段旅程
ZYNQ-7000上电后,片内的ARM Cortex-A9双核还处于复位状态,真正第一个执行代码的是片内BootROM。BootROM翻译过来就是“只读启动程序”,它固化在芯片里,用户改不了。它的任务是:根据MIO引脚上的启动模式选择信号,去QSPI Flash、NAND、NOR、SD卡或JTAG接口上找一份合法的启动镜像。
对QSPI和NAND这种串行启动设备而言,BootROM会去设备起始地址扫描一段数据,判断开头有没有合法的Boot Header。Boot Header里的第一个字段是魔术字,固定为0xAA995566。如果这个魔术字对不上,BootROM会继续往下一个搜索地址找,直到找到合法镜像或者搜索失败。这个“自动搜索”的行为,是整个Multiboot机制能够成立的最底层基础。
找到合法Boot Header后,BootROM会解析后面跟着的寄存器初始化表、分区信息表,然后加载FSBL(First Stage Boot Loader)到片上存储器执行。FSBL承担两件事:一是初始化PS端的DDR、PLL、MIO、CLK,把整个软硬件环境带起来;二是读取启动镜像头部里的分区表,把PL bitstream、U-Boot或者裸机APP依次加载到指定位置,最后跳转过去。
所以你看,ZYNQ的启动不是“点一下电源就运行Linux”这么简单,它是一层套一层的接力跑。BootROM传给FSBL,FSBL传给U-Boot,U-Boot再传内核,中间任何一环出了问题,系统就会卡住。而Multiboot这个机制,就是让我们能在这条接力链的起点处做文章:让BootROM不去默认地址找镜像,而是跳到我们指定的另一个地址去启动另一份镜像。
1.2 Multiboot、软件复位、程序跳转三者的关系
Multiboot这个词在Xilinx官方文档中的含义是“多启动”。它不是一个软件,而是一套由BootROM硬件行为、寄存器配置、镜像布局共同组成的机制。核心思想是:系统复位后,BootROM并不是死板地只读Flash的0地址,它会先检查一个叫做MULTIBOOT的寄存器,如果这个寄存器里被写入了合法的Magic Number和偏移,BootROM就会从那个偏移位置去读取镜像。
软件复位和程序跳转,这两件事和Multiboot是天然绑定的。比如你做在线升级,新固件下载到了Flash的0x200000偏移处,想让设备下次启动时从新固件跑,就必须做两步操作:第一步,把0x200000这个偏移按指定格式写入MULTIBOOT寄存器;第二步,触发一次软件复位。复位的目的是让BootROM重新开始干活,重新读取MULTIBOOT寄存器的值,并从新偏移启动。所以软件复位在这里更像是一个“触发器”,程序跳转的目标位置则通过MULTIBOOT寄存器提前设定好。
很多初学者会把“程序跳转”理解成普通的函数调用或者跳转指令,在ZYNQ的Bootloader场景里这是个误区。我们说的跳转,是“换一个镜像跑”,是整机级别的重启,而不是在同一个程序里跳到某个地址继续跑。搞清楚了这一点,后面理解看门狗恢复、回滚机制就容易多了。
2. Multiboot的硬件机制:BootROM、Boot Header和那几个关键寄存器
2.1 Boot Header里藏着什么:0xAA995566后面的内容
ZYNQ的启动镜像不只是一段纯粹的机器码,最前面有一块固定长度的头部,叫做Boot Header,前0x8C0字节左右都有固定格式。第一个word是0xAA995566,叫Magic Word。后面的字段包括镜像长度、源地址、目标地址、执行地址、偏移值、寄存器初始化表、安全标志、分区头信息等。
BootROM拿到镜像后,会按这些字段完成三件事:一是把FSBL从源地址搬到RAM目标地址,二是根据需要初始化一部分PL侧的寄存器,三是根据数据头里的信息决定跳转地址和执行方式。因此在做Multiboot时,我们不能只关注“在哪个偏移放了镜像”,还要确保每份镜像的Boot Header都是通过Bootgen正确生成的,里面各个字段对齐、长度计算正确。否则即使BootROM找到了镜像,也可能加载失败,表现为启动卡死或者进入不可预料的异常状态。
一个容易踩坑的地方是镜像偏移对齐。BootROM搜索镜像的粒度并不是1字节,QSPI的搜索步长通常是32字节的倍数,这也是为什么Multiboot寄存器的偏移值要按照32字节为单位折算。NAND启动的搜索步长还会更大,通常以块对齐。如果镜像放在一个不是32字节对齐的偏移上,写再多的寄存器也没用。
2.2 MULTIBOOT、REBOOT_STATUS和复位生成器:软件跳转的三块跳板
Xilinx在PS端的DevC(Device Configuration)模块里提供了两个关键寄存器:REBOOT_STATUS,地址是0xF8007028;MULTIBOOT,地址是0xF800702C。这两个寄存器的行为比较特殊,它们不是普通的用户寄存器,BootROM会参与读写。
MULTIBOOT寄存器的用法是:系统复位后,BootROM检查这个寄存器的值,看高16位是不是0xBEEF,如果是,就取低16位的值乘以32,作为本次启动所需镜像的Flash偏移地址。写成C语言就是:
#define MULTIBOOT_REG 0xF800702C #define REBOOT_STATUS_REG 0xF8007028 void set_multiboot_offset(unsigned int byte_offset) { unsigned int reg_val = (0xBEEF << 16) | (byte_offset >> 5); Xil_Out32(MULTIBOOT_REG, reg_val); }注意这里的(byte_offset >> 5)相当于除以32。如果你想把启动位置切到Flash的0x200000偏移,那低16位算出来是0x10000,整体写进寄存器的值就是0xBEEF0000 | 0x10000 = 0xBEEF10000?不是,要按16位截断,实际是0xBEEF0000 | 0x00010000?这里推演时要特别留神。正确的操作是把低16位填充成偏移除以32的值,由于偏移0x200000除以32等于0x10000,已经超过16位范围,这本身就说明该偏移对Multiboot来说是不合法的,偏移量不能超过2MB。Multiboot能覆盖的Flash偏移上限是0x1FFFFF(2MB减1)。更大范围的情况,往往需要先从一个小的跳板镜像,再由跳板镜像做二次跳转,这个后面讲在线升级架构时再说。
REBOOT_STATUS寄存器的作用是记录复位状态和BootROM的尝试次数。FSBL可以在启动时读取它,判断当前是从正常启动路径来的,还是因为上一次启动失败被BootROM重新捞回来的。一旦检测到多次启动失败,FSBL就应该执行回滚策略,把MULTIBOOT寄存器重新指回出厂Golden分区,避免设备反复在损坏的Update分区上死循环。
第三块跳板是PS端的复位发生器。ZYNQ的SLCR(System Level Control Register)模块负责产生系统级复位。触发软件复位前,需要先往SLCR的解锁寄存器写入解锁密钥0xDF0D,然后往PSS_RST_CTRL寄存器写入软复位标志。示例代码如下:
#define SLCR_UNLOCK_REG 0xF8000008 #define SLCR_LOCK_REG 0xF800000C #define PSS_RST_CTRL_REG 0xF8000200 #define SLCR_UNLOCK_KEY 0xDF0D void soft_reset_ps(void) { Xil_Out32(SLCR_UNLOCK_REG, SLCR_UNLOCK_KEY); Xil_Out32(PSS_RST_CTRL_REG, 0x1); while (1) { /* 等待复位到来 */ } }写这个寄存器前不解锁SLCR是没效果的,很多人第一次调跳转时只写了复位寄存器,系统没反应,折腾半天才发现少了解锁这一步。
2.3 Flash启动布局设计:Golden与Update分区怎么摆
Multiboot应用得最多的场景,就是双镜像在线升级。一般把Flash从物理上分成两个大区:Golden区(出厂固件区)和Update区(升级固件区)。Golden区放在低地址,Update区放在高地址。分区设计要满足几个原则:
- Golden区必须包含完整的FSBL、Bitstream和U-Boot,保证设备出厂后第一次上电就能用。
- Update区大小至少等于Golden区,否则新固件放不下。
- 分区之间至少留一点空白,防止镜像长度计算错误时发生越界覆盖。
- 如果Flash空间充足,建议在Update区后面再留一个备份区,用于存放升级前未覆盖的旧固件。
基于2MB的Multiboot偏移限制,如果分区分得比较大,比如Update区放在8MB偏移,就不能直接靠BootROM一步跳到Update区。实践中常见的做法是设置一个位于2MB以内的Bootloader Agent(跳板程序),它本身是一份很小的镜像,启动后负责再检查Update分区的合法性,然后往MULTIBOOT寄存器写入更大的偏移,再次复位跳转。这样虽然多了一次复位,但突破了2MB的寻址限制。
3. 软件复位与程序跳转的六种实现方式
3.1 最直接的FSBL层跳转:改MULTIBOOT寄存器再软复位
如果跳转动作发生在FSBL阶段,最简单可靠的方式就是先写MULTIBOOT寄存器,再写复位寄存器。这个方案在裸机Bootloader里非常常用,因为此时操作系统还没起来,APU处于单核裸跑状态,不用担心其他核的干扰。
FSBL源码中要做的事情可以抽象成下面几个步骤:
/* 1. 根据需要跳转的Flash偏移设置Multiboot */ set_multiboot_offset(0x80000); /* 2. 关闭D-Cache,避免复位瞬间脏数据回写异常 */ Xil_DCacheDisable(); Xil_ICacheDisable(); /* 3. 清空并禁止中断 */ Xil_Out32(0xF8F00214, 0x1); /* 屏蔽全局中断的一种方式 */ /* 4. 触发软复位 */ soft_reset_ps();需要注意的是,触发软复位之前最好把D-Cache关掉,否则在复位瞬间Cache里未写回的数据可能会造成启动后新镜像运行环境不干净。FSBL链路上还有一个细节,很多老工程师会在跳转前把MULTIBOOT寄存器再清除一次,防止下次意外上电时又从旧路径启动,但这个操作也要具体场景具体分析,如果你的跳板逻辑依赖复位后的寄存器状态,清之前要算好逻辑顺序。
3.2 利用看门狗复位实现异常恢复
在设备运行过程中突然死机,CPU已经没法执行正常的跳转代码了,这时候该用什么方式切换启动路径?答案就是看门狗(Watchdog Timer)。ZYNQ-7000的PS端有专用的看门狗定时器,超时后会产生复位信号,让系统整体回到BootROM。
看门狗的坑在于,它本身不懂什么Multiboot,只是完成“复位”这个动作。复位之后BootROM还会不会按你的意愿去启动Update镜像,取决于复位前MULTIBOOT寄存器里的值。所以设计看门狗方案时,需要把MULTIBOOT寄存器的写入时机提前。举个例子:你希望这次运行如果死机,重启后就进入诊断程序,那么在正常运行时就应该先把MULTIBOOT指向诊断分区,再去喂看门狗。一旦死机,看门狗复位,BootROM自动进入诊断模式。
Xilinx官方提供过基于REBOOT_STATUS计数回滚的参考设计,思路是:FSBL启动时检查REBOOT_STATUS的值,如果连续重启次数超过阈值,就认为当前镜像有问题,自动清除Multiboot偏移,回滚到Golden镜像。这套逻辑配合看门狗,才能做到“死机后不用人工干预,自动恢复出厂状态”。网上很多人吐槽“单片机死机后软件看门狗需要多次复位才能起来”,多半就是踩了这个坑——只写了看门狗复位,没在FSBL里做复位原因判断,结果反复在坏固件里重启。
3.3 U-Boot命令行里的跳转玩法
如果系统已经跑到U-Boot阶段,跳转选择就灵活多了。U-Boot本身支持从Flash加载多个镜像,环境变量里可以配置不同的启动命令。
一个常见做法是使用U-Boot的bootm命令,直接从内存地址启动FIT格式镜像:
setenv loadaddr 0x2000000 setenv bootargs console=ttyPS0,115200 root=/dev/mmcblk0p2 rw fatload mmc 0:1 ${loadaddr} image.ub bootm ${loadaddr}如果要实现多个分区切换,可以通过修改U-Boot的环境变量bootcmd实现。比如设定两套启动命令,一套从QSPI读取,一套从SD读取,通过按键或外部引脚选择。U-Boot阶段跳转的优点是调试方便,不用重新编译FSBL,缺点是U-Boot本身要依赖FSBL已经初始化DDR和时钟,如果Flash里的Bitstream加载有问题,U-Boot可能根本起不来。
U-Boot还支持go命令,直接从内存某个地址执行裸机程序。这个命令适合在调试阶段快速跳转到某个测试程序,但正式产品不建议依赖它,因为go跳转后U-Boot占用的外设和中断环境没有完全清理,裸机程序很容易踩到残留状态。
3.4 Linux用户态触发跳转升级
在线升级的场景里,大部分时候应用跑在Linux系统里,升级动作需要由用户的应用程序触发。此时可以写一个简单的Linux内核模块,把物理地址映射到内核虚拟地址,然后操作MULTIBOOT寄存器和SLCR复位寄存器。
内核模块的核心逻辑如下:
#include <linux/module.h> #include <linux/io.h> #include <linux/fs.h> #include <linux/uaccess.h> #define MULTIBOOT_REG 0xF800702C #define SLCR_UNLOCK 0xF8000008 #define PSS_RST_CTRL 0xF8000200 static void __iomem *multiboot_base; static void __iomem *slcr_base; static int trigger_jump(char *str) { unsigned int offset; void __iomem *reg; kstrtou32(str, 0, &offset); multiboot_base = ioremap(MULTIBOOT_ADDR, 4); slcr_base = ioremap(SLCR_UNLOCK, 0x200); /* 1. 设置multiboot目标 */ reg = multiboot_base; iowrite32((0xBEEF << 16) | (offset >> 5), reg); /* 2. 解锁SLCR */ reg = slcr_base; iowrite32(0xDF0D, reg); iowrite32(0x1, reg + (PSS_RST_CTRL - SLCR_UNLOCK)); return 0; }这类模块在正式产品里需要注意:一定不能随意暴露给普通用户,最好通过文件节点的权限控制,只允许特定进程写入。复位操作一旦触发,整个系统立即重启,任何未保存的数据都会丢失,所以应用层必须在上层做好升级状态记录和文件系统同步,比如把“升级准备完成”的标志写入一个单独的存储分区,重启后再由Bootloader读取。
3.5 PL(FPGA)侧发起复位跳转
有些系统希望由PL逻辑来控制整个升级流程,比如PL里跑了一个软核处理器,或者PL检测到外部信号后要求PS切换镜像。ZYNQ提供了多种PL到PS的交互途径,常见的有AXI GP接口和EMIO。
思路比较简单:FSBL在启动时把PS端的关键寄存器映射地址通过AXI接口开放给PL,PL可以往这些地址写入复位命令。更常见的做法是PL先把升级目标偏移写入一个固定的BRAM或寄存器,然后通过中断通知PS,PS收到中断后再执行正常的Multiboot跳转。这种方式的好处是职责清晰,PL只负责决策,PS负责执行复位。
使用EMIO引脚也可以实现类似效果。例如PL输出一个脉冲给PS的一个MIO复用引脚,PS端中断或轮询检测到这个脉冲后,启动软复位流程。这种方案简单,但要注意信号抖动和电平匹配,实际项目里建议加一个简单的按键消抖逻辑,防止把毛刺当成跳转信号。
3.6 裸机程序中的纯函数跳转(不加复位直接跳)
如果只是想在同一个程序里从一个裸机APP跳到另一个裸机APP,且目标程序已经加载在DDR里,可以不使用复位,直接修改栈指针和返回地址跳过去。原理是:把目标程序的入口地址写到PC,把目标程序约定的栈地址写到SP,中断向量表也需要切到目标程序的向量表基址。
但ZYNQ的裸机跳转远没有单片机那么“简单”。DDR里的缓存一致性、MMU表、中断控制器状态都需要处理,稍微一个地方没清理干净,目标程序跑起来就是各种诡异异常。我自己做过的项目里,除非是两个经过严格约定的裸机程序之间跳转,否则不推荐这种方式。从Bootloader跳转到U-Boot时,FSBL内部用的就是类似机制,但那是经过了仔细设计的跳转流程。
4. Bootloader与在线升级系统的完整设计
4.1 双镜像在线升级的总体架构
一个完整的ZYNQ在线升级系统,建议按下面这张逻辑链路来设计:出厂固件(Golden)位于Flash低地址,常态应用(Update)位于Flash高地址或另一个Flash设备上;每次启动时,FSBL检查升级标志和复位原因;如果存在合法Update标志,则先校验Update镜像的CRC和Boot Header,成功则加载Update,失败则回退Golden。
我实际项目里实践过的一种稳定架构是三级分区:
- 分区0:Golden一级镜像,包含FSBL+BIT+U-Boot,固定0偏移。
- 分区1:Jump Agent,放在1MB偏移以内,是一个极小的裸机程序,负责读取分区2里的镜像头并检查分区信息。
- 分区2:Update主镜像,可以是多Mbytes的完整Linux Image,偏移超过2MB也没关系。
在线升级时,应用程序先把新的分区2写入预留的升级缓冲区域,写完后在Jump Agent能读到的位置写一个“升级请求”标志,然后软复位。BootROM从Golden启动,FSBL识别到升级请求后,不直接加载U-Boot,而是跳转到Jump Agent,由Jump Agent去校验和跳转。这套架构的好处是把复杂逻辑集中在Jump Agent里,Golden镜像保持最小、最稳定,即使Update分区写坏了,也不影响Golden回滚。
4.2 制作boot.bin、image.ub与FSBL配置
制作Multiboot镜像离不开Bootgen工具。通常先用Vivado/Vitis编译出fsbl.elf和硬件描述文件,然后使用Bootgen按BIF文件规则打包。一个典型的BIF文件长这样:
the_ROM_image: { [bootloader] zynq_fsbl.elf [destination_cpu = cortex-a9] u-boot.elf }如果要把PL比特流一起打进BOOT.BIN,则在中间加上bitstream:
the_ROM_image: { [bootloader] zynq_fsbl.elf [destination_cpu = cortex-a9] system.bit [destination_cpu = cortex-a9] u-boot.elf }在Vitis(旧版SDK)中创建启动镜像时,Bootgen会自动为镜像添加Boot Header。但要注意,多份镜像各自需要独立的Boot Header,也就是说Golden和Update必须分别通过Bootgen打包,不能把两个镜像简单拼接在一起然后指望BootROM自己去区分。
PetaLinux环境下的流程稍微不一样。PetaLinux构建完成后,在images/linux目录下会生成zynq_fsbl.elf、u-boot.elf、image.ub等文件。执行下面的命令可以打包BOOT.BIN:
petalinux-package --boot --format BIN --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot --output boot.binimage.ub和boot.scr则通常放在SD卡的FAT分区或写进Flash。PetaLinux制作SD卡启动盘时,我记得的典型步骤是:整个SD卡分成两个分区,第一个分区格式化为FAT32,存放BOOT.BIN、boot.scr、image.ub;第二个分区格式化为ext4,放rootfs。很多新手制作SD卡失败,无非是分区表类型不对、FAT分区没激活、或者文件系统没有及时sync就拔卡,这些问题排查起来都很费时间。
4.3 烧写流程和分区管理
烧写QSPI或NAND时,最稳妥的做法是先用Vivado Hardware Manager通过JTAG加载FSBL,再通过Xilinx的Flash Programmer把镜像写入对应Flash偏移,或者直接在U-Boot下用sf和nand命令烧写。
U-Boot下烧写QSPI的命令示例:
# 先把要写入的镜像载入内存 tftp 0x2000000 boot.bin # 擦除QSPI偏移0的区域,长度按需设定 sf probe sf erase 0x0 0x800000 sf write 0x2000000 0x0 0x800000这里最容易犯的错误是擦除长度没算好。QSPI Flash的扇区大小通常是4KB,擦除时如果长度不是扇区的整数倍,部分扇区会残留旧数据,导致最终镜像在Flash里“拼接”出错,启动时出现莫名其妙的CRC错误或Header错位。NAND Flash还有坏块管理的问题,U-Boot下烧写NAND时,要确认使用的命令是否带坏块检查逻辑,否则写进坏块的数据会在启动时静默丢失。
另外,烧写完以后千万别急着断电,建议先读回校验一遍。把Flash读回内存,和原始bin文件做哈希对比,确认等于再断电。这个步骤在量产导入时尤其重要,能避免一批设备带着坏镜像出厂。
5. 常见问题与排查技巧实录
5.1 "Valid FSBL file is required"到底卡在哪
有段时间总有朋友问我,在Vivado/Vitis的Flash Programmer里烧写时,报了这么一句:A valid FSBL file is required for flash operation。这个报错看起来像是“找不到FSBL文件”,实际是烧写工具需要依赖FSBL来初始化DDR和Flash控制器。
出现这个报错,先检查三件事:一是当前工程里有没有编译生成fsbl.elf,如果工程只导入了硬件描述文件而没有创建FSBL应用,肯定会缺;二是fsbl.elf对应的器件型号是否和当前FPGA器件一致,复制过来用很容易忽略版本匹配;三是Flash Programmer界面的FSBL路径是否正确,新版Vitis里路径变了,很多老用户想当然地填了个空路径。
如果只是单纯想把JEDEC或BIN文件写进Flash,完全可以在U-Boot下用命令烧写,不依赖JTAG的Flash Programmer。调试阶段我觉得这种方式反而更高效,不用每次都打开Vivado图形界面。
5.2 跳转后程序跑飞,一步步查什么
Multiboot设置正确、复位也触发了,但新镜像跑起来后就是各种异常,先不要怀疑是Multiboot机制的问题,而是要按照“启动链”逐层排查。
第一步,确认镜像自己单独从JTAG或者单分区启动时能正常工作。如果单独启动都有问题,那就是镜像本身不合格,和Multiboot无关。第二步,打印BootROM的搜索偏移。通过在FSBL里读MULTIBOOT寄存器,确认BootROM实际是从哪个偏移读到镜像的,避免“你以为写的是A分区,实际上跳到B分区”的乌龙。第三步,检查DDR地址冲突。FSBL把U-Boot加载到DDR,但新镜像的入口地址是否和旧镜像加载地址重叠?目标程序运行时是否会覆盖自己正在运行的代码段?这些都需要在链接脚本里确认。第四步,检查Cache和MMU。跳转前建议关闭I-Cache和D-Cache,等目标程序自己做好初始化后再重新使能。
一个真实的案例:某项目把U-Boot从0x08000000加载改为0x10000000,结果每次跳转都起不来。查到最后发现FSBL里有一段代码在初始化后继续使用旧的物理地址映射,MMU没刷TLB,新地址被缓存命中到旧数据,整个执行流全部错乱。刷一次TLB、重映射以后问题就消失了。
5.3 看门狗需要多次复位才能恢复的坑
前面提到的“看门狗需要多次复位”现象,在ZYNQ平台上很典型。根源往往不是看门狗本身,而是FSBL对复位原因处理得不够细致。
推荐的做法是在FSBL启动代码最开始,就读取REBOOT_STATUS寄存器,把复位原因和计数信息保存到一个全局变量里。逻辑可以这样定:如果复位原因是看门狗超时,且当前启动计数小于3,则正常加载Update分区;如果启动计数已经等于3,说明Update分区大概率已经损坏,这时把MULTIBOOT寄存器清零,强制切回Golden分区。下面是伪代码:
unsigned int status = Xil_In32(REBOOT_STATUS_REG); if ((status & 0x7) >= 3) { /* 连续启动失败,回滚 */ Xil_Out32(MULTIBOOT_REG, 0x0); }很多设计失败在“过度信任Update分区”。一旦看门狗复位后FSBL又去加载同一个损坏镜像,于是看门狗再次超时,再次复位,无限循环。要跳出循环,必须让FSBL具备“失败计数”的能力,哪怕只是简单的三次回滚规则,都能让设备在几分钟内自动恢复正常。
5.4 PetaLinux 2025.1制作SD卡启动卡的细节
PetaLinux 2025.1对ZYNQ-7000的支持依旧沿用旧版本的镜像打包逻辑,但不少命令输出路径有些变化。制作SD卡时,我一般按下面的顺序操作:
# 构建工程 petalinux-build # 打包BOOT.BIN petalinux-package --boot --format BIN \ --fsbl images/linux/zynq_fsbl.elf \ --fpga images/linux/system.bit \ --u-boot # 检查镜像目录 ls images/linux/生成的BOOT.BIN(注意大小写)拷到SD卡的FAT分区,boot.scr和image.ub也一起拷进去。rootfs解压到第二个ext4分区。
实际工程中踩过两个坑:第一个是SD卡分区工具选了MBR但FAT分区没标记为active,导致U-Boot找不到boot.scr;第二个是FAT分区格式化时簇大小选太大,导致BOOT.BIN虽然拷进去了,但U-Boot读取时出现文件系统错误。建议FAT分区不要小于512MB,格式化时使用默认簇大小即可,不要为了省空间改装小簇。
5.5 NAND Flash选型与BootROM兼容性
ZYNQ启动时对NAND Flash并不是全兼容,BootROM内部有一份设备ID列表,只有ID匹配的NAND设备才会被识别和读取。很多工程师觉得“Linux内核支持什么NAND,BootROM就支持什么NAND”,这是不对的。Linux支持是一回事,BootROM能不能识别是另一回事。
选型时直接查UG585的BootROM支持列表,重点关注NAND的页大小、块大小、地址周期数、ECC要求等参数。如果选型已经定死了,但BootROM不识别,解决思路有两个:一是用QSPI先启动FSBL,FSBL初始化NAND控制器后再从NAND加载应用镜像;二是在U-Boot里增加NAND驱动支持,通过命令手动读取NAND。这两个方案都需要增加一层逻辑,项目周期上要提前留量。
NAND烧写时还要注意ECC算法和坏块管理策略,这部分单独就能写很长,这里只说一个最容易踩的坑:很多Flash Programmer默认使用线性地址写NAND,完全不管坏块,导致量产中出现部分设备启动异常。成熟的方案是让FSBL或U-Boot里的NAND驱动支持坏块跳过,像JFFS2/UBIFS文件系统那样,在存储层保证数据落盘安全。
6. 实际项目中的一点经验总结
做ZYNQ升级和复位这一整套方案,我个人最大的体会是:不要一开始就把逻辑塞进一个复杂的“超级Bootloader”,而是把启动链路拆成最小可用单元,一层一层验证。先让Golden单分区能跑,再把Update分区加上,最后才做看门狗回滚。任何一步没验证透就叠加上去,后面排查问题会非常痛苦。
还有一个容易被忽略的细节是镜像版本记录。建议在Boot Header之外的自定义保留字段里写一个版本号,每次生成镜像时更新。FSBL启动时把版本号打出来,配合显示在设备外壳上的版本标签,能在现场维护时快速判断设备到底跑的是哪份固件。没有这个记录,回滚排查时靠猜,效率实在太低。
最后再分享一个小技巧:在办公室里调试Multiboot时,别一上来就做远程升级全流程,先在U-Boot里手动执行sf write、再从内存跳转,一步步确认每一个环节。等手动路径全部验证通过,再把操作自动化,变成Linux应用脚本。这样可以节省大量打板、重新上电的时间,调试节奏会舒服很多。