U-Boot 2020.1 changes to distro boot

One of the disrupting changes that Petalinux 2020.1 has brought, includes U-Boot moving to what’s called “distro boot”. In this post, we discuss how to handle them while upgrading from earlier versions.

Distro boot

Distro boot is an attempt from U-Boot to standarize their booting sequence among SoC platforms, to be closer to what it is in the PC world with BIOS/EFI. Xilinx moved to distro boot in their Petalinux 2020.1 release, and this Distro Boot with Xilinx U-Boot article explains the subject quite clearly.

Overview

The new U-Boot approach consists in a new boot script that is executed after U-Boot is ready to load the next stage. This script, namely boot.scr, contains instructions regarding where to load the binaries from media, to continue with the next stage. The boot script is expected to be found in the boot media selected by the boot mode pins, e.g. QSPI, SD, etc. This script is generated by Petalinux in the images/linux output directory. If the boot mode pins select SD or MMC card, the boot.scr script is expected to be found in the same FAT32 partition where boot.bin and image.ub reside. If instead the boot mode is set to QSPI, a new partition in the QSPI media must be created, and the script must be burned in that partition, as we’ll see below.

QSPI mode

In this boot mode, we need to create a new partition to hold the boot.scr script. We use the petalinux-config command to add it. Under Subsystem AUTO Hardware Settings -> Flash Settings, we add a new partition called “bootscr” which by default should be 0x80000 in size. This size is according to the U-Boot environment variable script_size_f that is hardcoded in the source. In the screenshot, it is set to 0x40000 but since the script is about 2k in size, this is not a problem.

The resulting offset to the bootscr partition will then be (0x2600000+0x40000=) 0x2640000.

Under Auto Config Settings, the u-boot autoconfig checkbox must be unchecked. Then under U-Boot configuration, U-Boot config should be set to “other”, and u-boot config target should be set to “xilinx_zynqmp_virt_defconfig”.

At this point, we have the boot.scr partition offset, so we have to let the build know where to find it. We have two ways, either:

  • Modify the recipes-bsp/u-boot/files/platform-top.h. Adding a line:

      CONFIG_BOOT_SCRIPT_OFFSET=0x2640000
    
  • Configure u-boot to set it automatically.

      # petalinux-config -c u-boot
    

    -> ARM Architecture -> Boot script offset -> 0x2640000 (near the bottom)

      # petalinux-build -c u-boot -x finish
    

    This change will generate the recipes-bsp/u-boot/files/devtool-fragment.h with the offset setting.

Now somewhat redundantly, we need to inform u-boot again, where to find the bootscr partition offset, by modifying the following lines in recipes-bsp/u-boot/u-boot-zynq-scr.bbappend:

QSPI_KERNEL_OFFSET = "0x2680000"
...
QSPI_FIT_IMAGE_SIZE = "0x5980000"

In my case, the device is a Zynq RFSoC, so the lines *_zynqmpdr had to be modified instead.

QSPI_KERNEL_OFFSET_zynqmpdr = "0x2680000"
...
QSPI_FIT_IMAGE_SIZE_zynqmpdr = "0x5980000"

The u-boot/u-boot-zynq-scr.bbappend script takes the u-boot-zynq-scr/boot.cmd.default.initrd as a template, uses the variables declared above, and generates the u-boot-zynq-scr/boot.cmd.default. This latter script in the build is compiled into the boot.scr using the mkimage command.

SD or [e]MMC mode

For these modes, the process is straighforward. Make sure to copy the generated boot.scr to the boot media in question, along with BOOT.BIN and image.ub. The relevant boot.scr script snippet is shown below, highlighting the method that loads the FIT image.ub using the fatload command, and then executing it, i.e. the kernel.

if test "${boot_target}" = "mmc0" || test "${boot_target}" = "mmc1" ; then
   if test -e ${devtype} ${devnum}:${distro_bootpart} /image.ub; then
      fatload ${devtype} ${devnum}:${distro_bootpart} 0x10000000 image.ub;
      bootm 0x10000000;
      exit;
   fi
   ...

Build

At this point, all settings are in place for QSPI, so we’ll build it.

# petalinux-build -c u-boot -x cleanall
# petalinux-build
# petalinux-package --boot --u-boot --fsbl --fpga --pmufw --force

The BOOT.BIN, image.ub and boot.scr will be generated under images/linux directory. From here, we burn them to QSPI using the program_flash command at their respective offsets:

program_flash -f BOOT.BIN -offset 0 -flash_type qspi_dual_parallel -fsbl zynqmp_fsbl.elf -cable type xilinx_tcf url TCP:192.168.1.177:3121

program_flash -f boot.scr -offset 0x2640000 -flash_type qspi_dual_parallel -fsbl zynqmp_fsbl.elf -cable type xilinx_tcf url TCP:192.168.1.177:3121

program_flash -f image.ub -offset 0x2680000 -flash_type qspi_dual_parallel -fsbl zynqmp_fsbl.elf -cable type xilinx_tcf url TCP:192.168.1.177:3121

Booting and looking under the hood

U-Boot runs a default script (as environment variable) when the timeout count expires, called “bootcmd”. This script has been extended in distro boot to run another script:

ZynqMP> printenv bootcmd
bootcmd=run distro_bootcmd

The distro_bootcmd in turn runs through a list of boot devices and attempts to run one script for each, also defined as environment variables:

ZynqMP> printenv distro_bootcmd
distro_bootcmd=scsi_need_init=; for target in ${boot_targets}; do run boot_cmd${target}; done
ZynqMP> printenv boot_targets
boot_targets=qspi0 jtag mmc0 mmc1 qspi0 nand0 usb0 usb1 scsi0 pxe dhcp

The boot_targets is a list of possible media where to boot from, and Xilinx adds a first element according to the device that is set in the boot mode pins. Such that booting from QSPI, the first script that runs would be “bootcmd_qspi0”.

ZynqMP> printenv bootcmd_qspi0
bootcmd_qspi0=sf probe 0 0 0 && sf read $scriptaddr $script_offset_f $script_size_f && source ${scriptaddr}; echo SCRIPT FAILED: continuing...;

ZynqMP> printenv script_offset_f
script_offset_f=2640000
ZynqMP> printenv script_size_f
script_size_f=0x80000

Parsing through the bootcmd_qspi0 command, we see that it first will initialize the QSPI device (sf probe), then will attempt to read a block of memory from QSPI at offset $script_offset_f size $script_size_f and to place it in memory at address $scriptaddr. Once successfully read from QSPI, it will continue to execute (source) the script at that offset. If any of those commands fail, the command will state so and finish.

If the script is found and executed successfully, U-Boot runs these commands below from the script (in QSPI case). You can see these commands in the u-boot-zynq-scr/boot.cmd.default file.

if test "${boot_target}" = "xspi0" || test "${boot_target}" = "qspi" || test "${boot_target}" = "qspi0"; then
   sf probe 0 0 0;
   if test "image.ub" = "image.ub"; then
      sf read 0x10000000 0x2680000 0x5980000;
      bootm 0x10000000;
      exit;
   fi
...

The U-Boot output in a successful QSPI boot would look like the following. Here you can see the first QSPI access at offset 0x2640000 to read the boot.scr partition, and once loaded and executed, the boot.scr script does another QSPI access to read the actual kernel at offset 0x2680000.

ZynqMP> boot
Warning: SPI speed fallback to 100 kHz
SF: Detected n25q512a with page size 512 Bytes, erase size 128 KiB, total 128 MiB
device 0 offset 0x2640000, size 0x80000
SF: 524288 bytes @ 0x2640000 Read: OK
## Executing script at 20000000
SF: Detected n25q512a with page size 512 Bytes, erase size 128 KiB, total 128 MiB
device 0 offset 0x2680000, size 0x5980000
SF: 93847552 bytes @ 0x2680000 Read: OK
## Loading kernel from FIT Image at 10000000 ...
...

Boot analysis

When U-Boot runs, it prints very helpful information regarding its actions, and can be used to debug when something is not expected, as shown in these two failing cases.

In this first case, we see U-Boot attempt to read the boot.scr script at the default offset 0x3e80000 and failing to find it. This may happen if the CONFIG_BOOT_SCRIPT_OFFSET hasn’t been set, as shown above.

SF: Detected n25q00a with page size 256 Bytes, erase size 64 KiB, total 128 MiB
device 0 offset 0x3e80000, size 0x80000
SF: 524288 bytes @ 0x3e80000 Read: OK
## Executing script at 20000000
Wrong image format for "source" command
SCRIPT FAILED: continuing...

Then, in this other case, the boot.scr script is found in QSPI at the given offset (i.e. 0x2640000), but the script itself points to offset 0xf00000 and this does not match the kernel partition offset, resulting in another error. This is likely the case if the u-boot-zynq-scr.bbappend hasn’t been modified to include the proper offset to the kernel partition, as shown above.

SF: Detected n25q00a with page size 256 Bytes, erase size 64 KiB, total 128 MiB
device 0 offset 0x2640000, size 0x80000
SF: 524288 bytes @ 0x2640000 Read: OK
## Executing script at 20000000
SF: Detected n25q00a with page size 256 Bytes, erase size 64 KiB, total 128 MiB
device 0 offset 0xf00000, size 0x6400000
SF: 104857600 bytes @ 0xf00000 Read: OK
Wrong Image Format for bootm command
ERROR: can't get kernel image!
SCRIPT FAILED: continuing...
switch to partitions #0, OK

Conclusion

This U-Boot change was quite frustrating when 2020.1 was released, because of very poor or inexistent documentation regarding how Xilinx handled it. This has been since corrected with an excelent article in the wiki, as pointed above. The extra points highlighted in this post are missing from the article and in my opinion are necessary for a smooth upgrade to 2020.1.

JTAG boot in QSPI mode

Booting a ZynqMP target over JTAG is very useful for a development cycle, as the alternative to programming the flash...

Previous
Using PS SPI to access flash

The ZynqMP Processing System (PS) counts with two embedded SPI controllers that can be used to access SPI Flash devices....

Next