概述
本文将以Zynq开发板为示例,介绍多种使用QSPI的方式启动PetaLinux的流程,并针对一些可能出现的问题提出解决方法。
1. Petalinux启动过程

总的来说,操作系统启动主要包括三个步骤:启动u-boot、加载内核、挂载根文件系统。
使用petalinux-package命令可以选择将哪些内容打包到BOOT.BIN中。例如,当使用以下命令时:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga |
则将fsbl.elf、system.bit(FPGA比特流文件)、u-boot.elf、system.dtb(设备树文件)打包到BOOT.BIN中,使用该BOOT.BIN可以启动至u-boot,还需要image.ub和boot.scr才能完整启动OS。

image.ub内部不仅包含内核 (Image),当Root filesystem type选择为INITRAMFS方式时,还包含了文件系统(rootfs.cpio)。内核启动时会在内存中解压这个文件系统,系统启动完成后所看到的根目录完全存在于DDR内存中。
boot.scr是u-boot启动后,用于加载内核的脚本。
使用QSPI启动时,内核文件可以通过petalinux-package命令打包到BOOT.BIN中并烧写到Flash上。另一种方式是先将不含内核的BOOT.BIN文件烧写到Flash中,启动至u-boot后再通过SD卡或网络读取内核文件。这正是本文第3、4、5章将要详细介绍的内容。2. 在PS配置中打开QSPI

使用QSPI,需要在Zynq IP核配置中打开QSPI功能,并根据手册选择对应的MIO引脚。
3. PetaLinux QSPI Flash启动
将启动文件和内核等内容全部写入Flash中,系统启动时从Flash读取内容到内存并启动。
3.1 flash分区管理
这里需要对flash的分区进行管理,通常包括boot、bootenv、kernel、bootscr分区。
在petalinux-config -> Subsystem AUTO Hardware Settings -> Flash Settings中可以设置各分区的大小。

boot分区用于启动u-boot通常包括fsbl.elf、system.bit、u-boot.elf、system.dtb,也就是使用petalinux-package --boot --force --u-boot --fsbl --fpga命令打包到BOOT.BIN中的文件,boot分区的大小需要大于这些文件大小总和。
bootenv分区存放u-boot的环境变量。
kernel分区存放image.ub。
bootscr分区存放boot.scr。
通常boot分区和kernel分区需要设置较大值,根据文件的大小决定,注意这些分区总大小不要超过flash的大小。
3.2 设置fit image offset
虽然设置了flash中各个分区的大小,但是仍需要在配置中指定各个分区的偏移和大小,其中boot分区偏移默认从0开始,无需设置。
在petalinux-config -> u-boot Configuration -> u-boot script configuration -> QSPI/OSPI image offsets中设置fit image offset,fit image指的就是image.ub,所以就是kernel分区的偏移0x1640000(0x1500000+0x100000+0x40000)。
Kernel offset和Ramdisk offset也可以设置一下,虽然应该没什么用。

3.3 设置Boot script offset
设置petalinux-config -c u-boot -> ARM architecture -> Boot script offset,默认为0x9C0000,这里需要更改为bootscr分区的偏移0x1600000(0x1500000+0x100000)。

3.4 设置u-boot环境变量
然后在petalinux-config -c u-boot -> Environment中:
(1)取消勾选Environment is not stored、Environment is in a FAT filesystem、Environment in a NAND device。
(2)可选:取消勾选Enable redundant environment support。(避免混淆)
(3)设置offset和size,与前面flash setting中的bootenv分区设置一致。

3.5 打包启动文件
petalinux-build之后,使用:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga --kernel |
此时,image.ub和boot.scr也会被打包进BOOT.BIN中。

但出现offset重叠的问题:
观察petalinux-config -c u-boot->Boot script offset,发现自动更改回默认值,观察project-spec/meta-user/recipes-bsp/u-boot,其中files文件夹下存有用户配置文件,先前所修改的Boot script offset的配置保存在该文件中,在u-boot-xlnx_%.bbappend中,引用了该文件,所以先前的修改的配置理应生效。


3.6 解决offset重叠问题
这里有两种方法可以解决该问题:
(1)修改images/linux/bootgen.bif文件

将boot.scr的offset修改为0x1600000。
然后使用以下命令指定该bootgen.bif运行:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga --kernel --bif bootgen.bif |
(2)在命令中指定offset
bootgen.bif其实是运行petalinux-package所产生的,所以在petalinux-package时就可以指定boot.scr的偏移:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga --kernel --boot-script --offset 0x1600000 |
这两种方法都会出现以下warning:

这是因为fsbl.elf、system.bit、u-boot.elf、system.dtb都在boot分区中,它们的偏移都从0开始,所以这里提示有交叠。这里可以修改bootgen.bit或在命令行中指定fsbl等的偏移,但实际上运行以下指令以打印详细信息:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga --kernel --boot-script --offset 0x1600000 --bootgen-extra-args "-log trace" |
在shell窗口或bootgen_log.txt中可以看到:

这里它自动给各个文件分配了offset和length,只有boot.scr和image.ub是我们先前手动指定的offset。
所以这个warning只是提示的作用,可以忽略。
另外,也可以使用以下命令生成MCS格式的启动文件:
1 | petalinux-package --boot --force --u-boot --fsbl --fpga --kernel --boot-script --offset 0x1600000 --format MCS |
运行后在images/linux下出现boot.mcs文件。
3.7 烧录启动文件到Flash
接下来使用Vivado或Vitis将BOOT.BIN或boot.mcs写入板子的flash中。
3.7.1 使用Vivado烧录
Open Target后点击Add Configuration Memory Device:

这里我的Zynq是2片256MB大小的Quad-SPIFLASH芯片(型号为W25Q256FVEI)组成8位带宽数据总线,所以需要选择qspi-x8-dual_parallel:

不过Vivado中并没有该型号,可以选择一个近似的比如w25q128fv,也可以手动添加,在Vivado/2023.1/data/xicom目录下找到xicom_cfgmem_part_table.csv,添加一栏:
| 998 | 0 | w25q256fv-qspi-x8-dual_parallel | zynq7000 - | w25q256fv | qspi | 256 | x8-dual_parallel | Winbond | 1 | w25q256fv-qspi-x8-dual_parallel | w25q | ||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|

重新启动Vivado即可看到该新增的型号:

在Hardware界面中选中该memory,点击Program Configuration Memory Device。

然后选择需要烧写的文件即可:

这里需要一个fsbl,在Vitis中创建一个platform build后就可以找到fsbl。
3.7.2 Vitis烧录
点击上方的Vitis -> Program Flash

只需要指定Image File的路径指向BOOT.BIN或boot.mcs,然后指定Flash Type。

3.8 启动
将板子设置成QSPI启动模式,打开串口调试,启动板子。由于u-boot配置中,Boot script offset默认为0x9c0000,所以这里启动时仍然从0x9c0000读取boot.scr,然而在petalinux-package时已经把boot.scr放到了0x1600000的位置上,所以这里会启动失败。

这里需要修改环境变量setenv script_offset_f 0x1600000并通过saveenv保存:

然后重新启动板子即可。如果环境变量有问题,无法使用先前设定的环境变量,可能会出现以下提示:

可以在u-boot中手动执行boot.scr的内容,加载位于Flash 0x1640000位置的内核镜像到内存0x10000000的位置上,然后启动该位置的内核:
1 | sf probe 0 0 0 |

如果不想每次都进入u-boot界面,希望一次性启动OS,可以修改project-spec/meta-user/recipes-bsp/u-boot/files/bsp.cfg,将0x9c0000改成0x1600000。也可以参考第九节设定自定义启动命令。

4. PetaLinux QSPI Flash+SD卡启动
先将不带内核的BOOT.BIN烧写到Flash中,启动u-boot后从SD卡读取内核并启动内核。
这样做理论上不需要去手动划分各个分区,并设置分区的偏移和大小,直接使用petalinux-package --boot --force --u-boot --fsbl --fpga即可得到不带内核的BOOT.BIN。
如果前面烧写了带内核的BOOT.BIN,这里烧写时应选择Entire Configuration Memory Device,否则只erase一部分flash,之前烧写的内核依然在flash中,启动时仍然可能会直接启动整个OS。

将image.ub拷贝到sd卡中(FAT32分区)。
可选:在u-boot中查看sd卡设备信息,及其文件系统中是否有image.ub:

加载内核并启动:
1 | mmc dev 0 |

5. PetaLinux QSPI Flash+TFTP启动
同样先将不带内核的BOOT.BIN烧写到Flash中启动u-boot,然后通过TFTP的方式获取内核并启动内核。
5.1 配置TFTP服务器
参考网站配置tftp服务器:
在petalinux-config -> Image Packaging Configuration中设置tftp文件夹路径:

这样就可以将images/linux下的文件自动拷贝到tftp文件夹中。当然,也可以自己手动将image.ub复制到tftp文件夹中。
5.2 网络配置
为了让板子能够连接到虚拟机,虚拟机需要配置桥接模式的网卡:

然后打开编辑 -> 虚拟网络编辑器:

点击右下角”更改设置”,然后将VMnet0的网卡选择为以太网的网卡。

分别设置主机和虚拟机的网络地址,使其在同一子网下:


在u-boot界面设置服务器ip为虚拟机的ip地址,同时设置板子的ip地址:
1 | setenv serverip 192.168.2.150 |

确保连接正常:

加载内核并启动:
1 | tftpboot 0x10000000 image.ub |

如果网络不稳定,可能loading时候会打印T,累计超过10个T就自动结束loading。此时可以更换网线,但最好是在petalinux-config -c u-boot -> Networking support中调整重传时间为10000ms和重传次数为255。同时增大size of buffer、block size和window size来提升网络传输速度(实测增大参数后由300多KB/s上升至10MB/s)。

6. bootm出现inflate() returned -5,gzip uncompress error

这里是解压内核出现了问题,可能是内核太大原因,可以在petalinux-config -> Image Packaging Configuration —> Root filesystem type中,选择EXT4,这样它就不会将文件系统放到内核中,而是直接挂载SD卡的文件系统。(实测内核大小从600M下降到20M)

也可以直接使用未压缩的内核。
查看petalinux-config -> u-boot Configuration -> u-boot script configuration -> Kernel image name,有的叫uImage,有的叫Image。
在images/linux目录下,将uImage/Image、system.dtb拷贝到SD卡中或者tftp目录下。
在u-boot界面,如果拷贝在SD卡中,则运行以下指令:
1 | mmc dev 0 |
读取到内存中的地址可以根据文件的大小决定,这边我的system.dtb大小为20多KB,从0x1F000000-0x20000000有16MB的大小,足够装下该文件。
booti命令格式为:booti addr [initrd[:size]] [fdt]
所需的三个参数分别是内核地址、文件系统地址和设备树地址。如果Root filesystem type选择为EXT4,则文件系统地址不需要指定,用”-“代替,否则它会解压并使用该位置的文件系统而不是来自SD卡的文件系统。如果Root filesystem type为INITRAMFS时,文件系统直接包含在Image里面,此时无论指不指定文件系统地址都可以。
如果需要指定该参数,先把rootfs.cpio.gz.u-boot也拷贝到SD卡里,然后加载到内存:
1 | fatload mmc 0:1 0x60000000 rootfs.cpio.gz.u-boot |
使用以下命令启动:
1 | booti 0x20000000 0x60000000 0x1F000000 |
此时无论Root filesystem type是什么,都会以该文件系统启动内核。
如果放在tftp文件夹下,则运行以下指令:
1 | tftpboot 0x1F000000 system.dtb |
7. 读取SD卡文件出现Error reading cluster,Failed to load ‘xxx’
如果出现以下问题:

可以尝试将文件放到SD卡的ext4分区(这里我的ext4分区还有文件系统),创建一个文件夹以放置这些文件:

然后使用ext4load读取文件:
1 | mmc dev 0 |
如果依然出现问题,可以反复运行这两条指令(两条指令之间间隔两三秒)或重启或更换SD卡,有可能成功:

如果多次出现Transfer data timeout且重启依然如此,可能是文件太大的问题:

通过以下命令将Image拆成多份:
1 | split -b 128M Image Image.part |
根据文件大小生成多个Image.partxx文件,将其复制到SD卡中。

然后在u-boot中依次加载各文件,需要注意地址,前面设置每份大小是128M,所以这里相隔0x8000000:
1 | mmc dev 0 |
8. 挂载SD卡文件系统
如果使用SD卡的文件系统启动OS有问题,可以使用INITRAMFS的方式启动OS,然后在/mnt下创建一个文件夹,将SD卡挂载到该文件夹下。

挂载之后可以读写SD卡文件系统中的文件:

9. 设置自定义启动命令
为了避免每次都进入u-boot然后手动启动,这里可以设置启动命令,启动u-boot后自动执行这些命令。
设置petalinux-config -c u-boot -> Boot options -> bootcmd value,各命令之间用”&&”分隔。

10. 总结
总结
根据自身的情况选择不同的方法,直接将各种启动文件打包到BOOT.BIN中启动是最简单的方法。但如果不想配置flash分区,则可以使用不带内核的BOOT.BIN启动至u-boot,再使用SD卡或TFTP获取内核。
如果执行bootm解压内核时出现问题,则尝试使用EXT4文件系统而非INITRAMFS从而降低BOOT.BIN的大小。如果没有SD卡或SD文件系统启动有问题无法选择EXT4文件系统,则使用booti启动未压缩的内核,此时需要在启动u-boot后使用TFTP的方式获取未压缩的内核、设备树文件、文件系统(可选)。