The flexibility of U-Boot distro boot feature is exercised in this post, where we configure the target to boot from QSPI flash, then load the rest of the binaries from MMC.
Introduction
The boot partition in QSPI will contain the necessary binaries, but will exclude the bitstream, the kernel and the rootfs. It will contain the FSBL, ATF, PMU, device tree and U-Boot, all binaries that would rarely need updating after the system is set up. U-Boot will read its boot script from MMC with instructions how to load the rest of the binaries also in MMC.
U-Boot handling boot script
This is a detailed summary how U-Boot handles the boot script internally for insight into the boot process. U-Boot is hardcoded to execute one command upon boot after the countdown expires, namely bootcmd. This is usually defined in the default U-Boot environment as:
bootcmd=run distro_bootcmd
The distro_bootcmd is in turn defined as:
distro_bootcmd=
scsi_need_init=;
for target in ${boot_targets};
do run bootcmd_${target};
done
The boot_targets is a list defined for potential boot sources, which is prepended by a value read from the boot mode pins, QSPI (first qspi0) in this case:
qspi0 jtag mmc0 mmc1 qspi0 nand0 usb0 usb1 scsi0 pxe dhcp
When booting from QSPI, this command below will be called, which initializes the QSPI and reads the boot script into memory from the expected offset, and proceeds to execute it.
bootcmd_qspi0=
sf probe 0 0 0 &&
sf read $scriptaddr $script_offset_f $script_size_f &&
echo QSPI: Trying to boot script at ${scriptaddr} &&
source ${scriptaddr}
In case of the MMC boot, the boot_targets would look instead as:
mmc0 jtag mmc0 mmc1 qspi0 nand0 usb0 usb1 scsi0 pxe dhcp
Which will cause this command to be called:
bootcmd_mmc0=
devnum=0;
run mmc_boot
Which is defined as:
mmc_boot=
if mmc dev ${devnum};
then devtype=mmc;
run scan_dev_for_boot_part;
fi
That checks for the MMC device and pre-sets the devtype variable, then calls scan_dev_for_boot_part. This will check if there is an MMC partition that is bootable, and if none found will default to the first partition.
scan_dev_for_boot_part=
part list ${devtype} ${devnum} -bootable devplist;
env exists devplist || setenv devplist 1;
for distro_bootpart in ${devplist};
do if fstype ${devtype} ${devnum}:${distro_bootpart} bootfstype;
then run scan_dev_for_boot; fi;
done;
setenv devplist
This will scan the boot partition for boot scripts “extlinux” or “boot.scr”…
scan_dev_for_boot=
echo Scanning ${devtype} ${devnum}:${distro_bootpart}...;
for prefix in ${boot_prefixes};
do
run scan_dev_for_extlinux;
run scan_dev_for_scripts;
done;
run scan_dev_for_efi;
Check for scripts and attempt to run them:
scan_dev_for_scripts=
for script in ${boot_scripts};
do if test -e ${devtype} ${devnum}:${distro_bootpart} ${prefix}${script};
then echo Found U-Boot script ${prefix}${script};
run boot_a_script;
echo SCRIPT FAILED: continuing...;
fi;
done
Finally load the script if found and execute it:
boot_a_script=
load ${devtype} ${devnum}:${distro_bootpart} ${scriptaddr} ${prefix}${script};
source ${scriptaddr}
The boot script (boot.scr) by default contains another loop over the boot targets:
for boot_target in ${boot_targets};
do
if test "${boot_target}" = "jtag" ; then
...
if test "${boot_target}" = "mmc0" || test "${boot_target}" = "mmc1" ; then
...
if test "${boot_target}" = "xspi0" || test "${boot_target}" = "qspi" || test "${boot_target}" = "qspi0"; then
...
done
This boot script needs some hacking to make it load the bitstream image from MMC but while it boots from QSPI. We will add a node that will:
- Check if boot mode is QSPI, and
- Check if the variable devtype is set to “mmc” as seen defined above in the mmc_boot command.
If these two conditions are set, we will proceed to [attempt to] load the bitstream and FIT image from MMC. We’ll change the test for “mmc0” as boot_target and replace it with our custom test, adding the bitstream loading. The scripts sets an arbitrary address to temporarily load the bistream from MMC (fatload command) and then program into the programmable logic (PL) with the fpga command. The bitstream size is fixed for this device.
From:
if test "${boot_target}" = "mmc0" || test "${boot_target}" = "mmc1" ; then
To:
if test "${boot_target}" = "qspi0" && test "${devtype}" = "mmc" ; then
if test -e ${devtype} ${devnum}:${distro_bootpart} /system.bit; then
bitstream_addr=0x10000000;
bitstream_size=0x20d795f;
fatload ${devtype} ${devnum}:${distro_bootpart} ${bitstream_addr} system.bit;
fpga load 0 ${bitstream_addr} ${bitstream_size};
fi
<proceed with loading from MMC as usual>
Configuration
In the Petalinux configuration menuconfig (petalinux-config) we’ll ensure the bootable images are set properly to boot from QSPI (primary flash) then look for the kernel image in MMC (primary SD). Other settings will remain unchanged from the stock configuration.
- petalinux-config
- -> Subsystem AUTO hardware Settings
- -> Advanced bootable images storage Settings
- boot image settings -> primary flash
- u-boot env partition settings -> primary flash
- kernel image settings -> primary sd
- jffs2 rootfs image settings -> primary flash
- dtb image settings -> from boot image
- -> Image Packaging Configuration
- -> Root filesystem type (INITRD)
The U-Boot boot script (boot.scr) is in charge of loading the rest of the binaries from other media, in this case from MMC. The default boot.scr configuration loads the binaries from the same boot media identified by the boot mode pins. We will modify this boot script to load the binaries from MMC instead of from QSPI as would be the default. The source boot script can be found in <project>/project-spec/meta-user/recipes-bsp/u-boot/u-boot-zynq-scr/boot.cmd.default.initrd. The if entry we’ve added above will load the binaries from MMC only when the boot mode is set to QSPI and the device type is set to “mmc”. The device type is set when U-Boot tests for potential boot sources and scans the boot_targets list.
Once the Petalinux project has been built, the BOOT.BIN needs to be packaged, and in this case including the bitstream is not necessary, as follows:
[user@host]$ petalinux-package --boot --u-boot --fsbl --pmufw --force
The packaged BOOT.BIN will be generated under <project>/images/linux directory.
Copying binaries to target
The generated binaries need to be programmed into the non-volatile memories (QSPI & MMC). The BOOT.BIN will be programmed into QSPI, and the boot script (boot.scr), bitstream (system.bit) and FIT image (image.ub) will go in the first partition of the MMC device. The BOOT.BIN may be programmed into QSPI in several ways.
- Vivado Hardware Manager.
- Boot into U-Boot then load from a TFTP server, then writing to QSPI with “sf” commands.
- petalinux-boot –jtag –u-boot
- Stop U-Boot at prompt
- ZynqMP> dhcp
- ZynqMP> tftpboot 0x10000000
:BOOT.BIN - ZynqMP> sf probe
- ZynqMP> sf write 0x10000000 0x0 0x164820
- Boot into U-Boot and load binaries from a host over JTAG, then program into QSPI.
- petalinux-boot –jtag –u-boot
- Stop U-Boot at prompt
- From xsct, load BOOT.BIN binary into memory
- xsct% dow -data BOOT.BIN 0x10000000
- On U-Boot, burn to QSPI using sf commands
- ZynqMP> sf probe
- ZynqMP> sf write 0x10000000 0x0 0x164820
- Using program_flash host app to program directly into QSPI over JTAG.
- [user@host]$ program_flash -f BOOT.BIN -offset 0x0 -flash_type qspi_dual_parallel -fsbl zynqmp_fsbl.elf -cable type xilinx_tcf url tcp:192.168.1.177:3121
- From within Linux, using the flashcp command.
- root@target]# flashcp -v BOOT.BIN /dev/mtd0
The binaries may be loaded into MMC memory in a number of ways as well. The most efficient is probably on a running Linux system, copying them over Ethernet (i.e. SSH). If Linux is not running on the target, then a similar procedure can be done as shown above, to load the binaries over JTAG and then from U-Boot to program them onto MMC, using the command:
ZynqMP> fatwrite mmc 0 <address> image.ub <size-in-hex>
Conclusion
This post presented a method to boot from QSPI and then load the rest of the binaries from MMC using U-Boot boot script to handle the process, with plenty of detail how the internals work.