Boot Software Updates on Zynq UltraScale+ MPSoC

Boot Artifacts

Boot Image

The boot image is generated by the bootgen tool. It contains PEK keys (if secure boot is enabled), Platform Management Unit (PMU) firmware, and Secondary Program Loader (SPL) obtained from a U-Boot build.

U-Boot FIT Image

“U-boot FIT-image” is a generic name for the signed FIT-image that contains U-Boot proper (u-boot.bin) and a host of other firmware. This file is verified by SPL via a public key stored in SPL’s dtb.

  • U-boot-nodtb.bin
  • U-boot.dtb
  • OP-TEE
  • Arm Trusted Firmware (ARMv8)
  • FPGA firmware
  • Boot script (bootscr)

If the CI signing key has been rotated since the last OTA, then the SPL.dtb verification data needs to be updated prior to booting the new U-Boot FIT-image.

Boot Media

Boot software updates are supported on both MMC and QSPI boot media. However, note that this requires different boot image layouts for each case.

MMC Boot Image Layout

All boot images (both boot.bin and FIT images) are stored in the first FAT partition on MMC with the following naming convention:

boot0001.bin       - primary boot image
boot0002.bin       - secondary boot image
u-boot0001.itb     - primary FIT image
u-boot0002.itb     - secondary FIT image

QSPI Boot Image Layout

All boot images (both boot.bin and FIT images) are written as raw images using these predetermined offsets:

0x0                - primary boot.bin
0x60000            - secondary boot.bin
0x100000           - primary u-boot.itb
0xaa0000           - secondary u-boot.itb

Boot Flow

PMU BootROM

  1. Reset and initialize CSU, prepare for the configuration stage
  2. Release the reset of CSU
  3. Enter a servicing mode

CSU BootROM

  1. Determine the boot mode
  2. Activate golden image search mechanism
  3. Load a boot image
  4. Perform verification of boot image (in case secure boot is enabled)
  5. Extract SPL and PMU firmware from boot image
  6. Load PMU firmware
  7. Load and jump to SPL

SPL

  1. Initialize DDR
  2. Calculate filename (MMC boot)/ QSPI offset for the FIT image
  3. Load U-Boot FIT-image
  4. Perform verification
  5. Extract components
  6. Load FPGA firmware
  7. Jump to ATF / OP-TEE

ATF (ARMv8)

  1. Perform memory permission setup
  2. Drop to EL-2 non-secure
  3. Jump to OP-TEE

OP-TEE

  1. Perform secure world setup
  2. Driver init
  3. Load TAs
  4. Drop to EL-2 secure world
  5. Jump to u-boot.bin

U-Boot

  1. Driver init
  2. Boot script
  3. Load kernel FIT-image
  4. Perform verification
  5. Extract components
  6. Jump to Linux kernel

Update Procedure

Primary vs Secondary Boot Paths

As mentioned under Golden Image Search, the offset used by BootROM for loading the boot image can also be enforced by the user. This implies that multiple boot images can be stored on the media. This gives the possibility to use the A/B approach in Over The Air updates. This is where “A” (primary boot path)represents stable boot image set, and “B” (secondary boot path) is the newly updated, not-validated-yet, images.

Libaktualizr and Aktualizr-lite

  1. Aktualizr-lite makes the decision if boot firmware needs to be updated based on the contents of ${ostree\_root}/usr/lib/firmware/version.txt,. Here, ostree\_root is root of the newly deployed ostree sysroot. Example of contents: bootfirmware\_version=10
  2. After parsing bootfirmware\_version, it compares the version number with the existing one, obtained via fiovb or ubootenv.
  3. If bootfirmware\_version from version.txt is higher than the existing one, aktualizr-lite sets bootupgrade\_available via fiovb or ubootenv.
  4. Reboot should be performed.

U-Boot boot.cmd Script

Boot firmware upgrade flow for QSPI boot

Fig. 89 Boot firmware upgrade flow for QSPI boot

  1. The actual update is done via the U-Boot boot.cmd script.
  2. boot.cmd checks if primary path is booted.
  3. If upgrade\_available is set, check if boot firmware upgrade is needed by checking the bootupgrade\_available flag. If both are true, boot firmware images are obtained from the newly deployed ostree sysroot, then written to the secondary boot path offsets. The multiboot offset value is then set, and a system reset is issued to enforce BootROM to boot secondary boot path.
  4. After reboot, the secondary boot path is executed, the condition verification from step 2 is being checked again. This time it is not true, so the regular boot of Linux is done.
  5. After Linux is booted, aktualizr-lite confirms a successful update by clearing upgrade\_available. At this point, new boot firmware images have been validated and are ready to be flashed to the stable primary path. An additional reboot is needed after this step.
  6. Regular reset

Add a New Board

U-Boot

SPL: FIT Filename Calculation During MMC Boot

U-Boot SPL automatically detects what next image to boot based on the CSU_MULTI_BOOT register value. In MMC boot, BootROM expects all boot images to be stored on the first FAT partition. We need to boot the FIT image which corresponds to the multiboot offset. Below is an example of how the final filename of the FIT image is calculated on ZynqMP SoCs (extract from board/xilinx/zynqmp/zynqmp.c):

int spl_mmc_get_uboot_payload_filename(char *filename, size_t len,
                                       const u32 boot_device)
{
        int ret;
        u32 multiboot;

        if (!filename)
                return -1;

        multiboot = multi_boot_get();

        if (multiboot)
                ret = snprintf(filename, len, "u-boot%04d.itb", multiboot);
        else
                ret = snprintf(filename, len, "u-boot.itb");

        if (ret < 0) {
                printf("Can't construct SPL payload filename");
                return ret;
        }

        printf("SPL: Booting %s\n", filename);

        return 0;
}

SPL: FIT Offset Calculation During QSPI Boot

The offset for the FIT image is calculated from the current value of the CSU_MULTI_BOOT register. The multiboot value is multiplied by 0x8000 (32 Kb boundary), and the final value is used as offset of the raw FIT image on QSPI. Below is an example of how final offset is calculated on ZynqMP SoCs (extract from board/xilinx/zynqmp/zynqmp.c):

unsigned int spl_spi_get_uboot_offs(struct spi_flash *flash)
{
        int ret;
        u32 multiboot;
        u32 payload_offset = 0;
        u32 boot_image_offset = 0x0;

        multiboot = multi_boot_get();
        boot_image_offset = golden_image_boundary * multiboot;

        /*
         * Default values are:
         * Primary boot.bin offset   - 0x0 (multiboot == 0)
         * Secondary boot.bin offset - 0x50000 (multiboot == 10,
         *                             as 10 * 32KB == 0x50000)
         */
        if (boot_image_offset == CONFIG_SYS_SPI_BOOT_IMAGE_OFFS) {
                payload_offset = CONFIG_SYS_SPI_U_BOOT_OFFS;
        } else if (boot_image_offset == CONFIG_SYS_SPI_BOOT_IMAGE_OFFS2) {
                payload_offset = CONFIG_SYS_SPI_U_BOOT_OFFS2;
        } else {
                printf("Invalid value of multiboot register, value = %d\n",
                       multiboot);
                hang();
        }

        printf("SPL: Booting next image from 0x%x SPI offset\n",
               payload_offset);

        return payload_offset;
}

meta-lmp

The lmp.cfg File: QSPI boot

To enable support of multiboot, adjust lmp.cfg for your board. Add the following config options:

CONFIG_SYS_SPI_BOOT_IMAGE_OFFS=0x0
CONFIG_SYS_SPI_BOOT_IMAGE_OFFS2=0x60000
CONFIG_SYS_SPI_U_BOOT_OFFS=0x100000
CONFIG_SYS_SPI_U_BOOT_OFFS2=0xaa0000

These values correspond to the offsets of primary and secondary boot image sets (boot.bin and u-boot.itb).

Pre-Load boot.cmd With SPL

As boot.cmd depends on U-Boot commands for booting Linux, it should align with the U-Boot version. In regular setups without boot firmware update support, boot.cmd is stored in the first FAT partition in eMMC/SD. To get boot.cmd updates together with other boot software images, it should be moved from the FAT partition to the U-Boot FIT image. To do this, edit lmp-machine-custom.inc, adding this line for your board:

BOOTSCR_LOAD_ADDR:sota = "0x21000000"

This change will include boot.cmd into the U-Boot FIT image, alongside the TF-A/OP-TEE/U-Boot proper/U-Boot dtb images. When SPL parses the U-Boot FIT image (u-boot.itb), it will pre-load boot.itb (compiled and wrapped boot.cmd) to the address specified in BOOTSCR\_LOAD\_ADDR variable.

To let U-Boot know where to get the boot script from, adjust the CONFIG\_BOOTCOMMAND param in your U-Boot lmp.cfg for the board.

CONFIG_BOOTCOMMAND="setenv verify 1; source 0x44800000; reset"

Test Basic API

After applying updates from the previous steps, we should validate that everything is in place. This consists of two steps:

  • Check that the multi boot U-Boot command is functional
  • Obtain board security state (open/closed states).

To test booting the primary/secondary boot path, use the two U-Boot commands multi\_boot and reset.

Example of test:

U-Boot SPL 2022.01+xlnx+g9039256f80 (Jan 24 2022 - 14:57:34 +0000)
...
Chip ID:    zu3eg
Multiboot:  0
Trying to boot from SPI
SPL: Booting next image from 0x100000 SPI offset
.....
ZynqMP> multi_boot 0xc && reset
Set multiboot register to:  0xc (dec: 12)
QSPI boot offset to be used after reboot:   0x60000
resetting ...

U-Boot SPL 2022.01+xlnx+g9039256f80 (Jan 24 2022 - 14:57:34 +0000)
....
Multiboot:  12
Trying to boot from SPI
SPL: Booting next image from 0xaa0000 SPI offset

From the output, you can see that after setting the secondary boot (multi_boot 12 or multi_boot 0xc; both dec and hex values are supported), and performing a reset, BootROM boots images from secondary boot path (SPL: Booting next image from 0xaa0000 SPI offset).

To check if the security status of your board is detected correctly, use the is\_boot\_authenticated command:

ZynqMP> is_boot_authenticated
Board is in open state

boot.cmd

LmP uses a template-based generation for the final boot.cmd. It is constructed from common boot files (./meta-lmp-base/recipes-bsp/u-boot/u-boot-ostree-scr-fit). These contain SoC agnostic DEFINEs, common functionality, and a board specific boot.cmd, which includes common scripts.

Example of board boot.cmd (./meta-lmp-bsp/recipes-bsp/u-boot/u-boot-ostree-scr-fit/uz/boot.cmd):

# set default fdt_file
setenv fdt_file system-top.dtb
echo "Using ${fdt_file}"

# Default boot type and device
setenv bootlimit 3
setenv devtype mmc
setenv devnum ${bootseq}
setenv bootpart 1
setenv rootpart 2

setenv loadaddr 0x10000000
setenv fdt_addr 0x40000000
setenv optee_ovl_addr 0x22000000
setenv fdt_file_final ${fdt_file}
setenv fit_addr ${ramdisk_addr_r}

setenv bootloader_image "boot.bin"
setenv bootloader_s_image ${bootloader_image}
setenv bootloader2_image "u-boot.itb"
setenv bootloader2_s_image ${bootloader2_image}

setenv check_board_closed "is_boot_authenticated"
setenv check_secondary_boot "multi_boot"

if test "${modeboot}" = "qspiboot"; then
    # Use SD for open boards, and eMMC for closed
    run check_board_closed

    if test -n "${board_is_closed}"; then
            # Use eMMC for further loading/booting Linux FIT image
            setenv devnum 0
    else
            # Use SD for further loading/booting Linux FIT image
            setenv devnum 1
    fi

    setenv qspi_bootloader_offset 0x0
    setenv qspi_bootloader_s_offset 0x60000

    setenv qspi_bootloader2_offset 0x100000
    setenv qspi_bootloader2_s_offset 0xaa0000

    setenv setup_update 'sf probe && setenv update_cmd "sf update ${loadaddr}"'

    # Boot images (primary and secondary)
    setenv bootloader_image_update '${qspi_bootloader_offset}'
    setenv bootloader_s_image_update '${qspi_bootloader_s_offset}'

    # FIT image (primary and secondary)
    setenv bootloader2_image_update '${qspi_bootloader2_offset}'
    setenv bootloader2_s_image_update '${qspi_bootloader2_s_offset}'

    setenv set_primary_boot "multi_boot 0"
    setenv set_secondary_boot "multi_boot 12"

else
    setenv setup_update 'setenv update_cmd "mmc dev ${devnum} && fatwrite mmc ${devnum}:${bootpart} ${loadaddr}"'

    # Boot images (primary and secondary)
    setenv bootloader_image_update 'boot0001.bin'
    setenv bootloader_s_image_update 'boot0002.bin'

    # FIT image (primary and secondary)
    setenv bootloader2_image_update 'u-boot0001.itb'
    setenv bootloader2_s_image_update 'u-boot0002.itb'

    setenv set_primary_boot "multi_boot 1"
    setenv set_secondary_boot "multi_boot 2"
fi

# Writing images
run setup_update
setenv update_primary_image 'echo "${fio_msg} writing ${image_path} ..."; setenv run_update "${update_cmd} ${bootloader_image_update} ${filesize}"; run run_update'
setenv update_secondary_image 'echo "${fio_msg} writing ${image_path} ..."; setenv run_update "${update_cmd} ${bootloader_s_image_update} ${filesize}"; run run_update'
setenv update_primary_image2 'echo "${fio_msg} writing ${image_path} ..."; setenv run_update "${update_cmd} ${bootloader2_image_update} ${filesize}"; run run_update'
setenv update_secondary_image2 'echo "${fio_msg} writing ${image_path} ..."; setenv run_update "${update_cmd} ${bootloader2_s_image_update} ${filesize}"; run run_update'

setenv do_reboot "reset"

@@INCLUDE_COMMON@@

Sysroot and Signed Boot Artifacts

All boot artifacts (boot.bin and U-Boot FIT) are automatically deployed to sysroot during build time. However, for closed boards where the initial boot image must be signed in advance by a private key, there is way to add the signed binary instead.

To do that, add lmp-boot-firmware.bbappend to your meta-subscriber-overrides layer. You will add the path and the signed binary itself.

Then define the boot firmware version number by setting the LMP_BOOT_FIRMWARE_VERSION global variable in lmp-factory-custom.inc. Boot firmware version information wil be added automatically to ${osroot}/usr/lib/firmware/version.txt and the U-Boot Device Tree Blob.

Example:

diff --git a/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware.bbappend b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware.bbappend
new file mode 100644
index 0000000..6c11380
--- /dev/null
+++ b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware.bbappend
@@ -0,0 +1,7 @@
+FILESEXTRAPATHS:prepend := "${THISDIR}/${PN}:"
+
+SRC_URI = " \
+       file://SPL \
+"
diff --git a/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/SPL b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/SPL
new file mode 100644
index 0000000..50f5013
Binary files /dev/null and b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/SPL differ
--- a/conf/machine/include/lmp-factory-custom.inc
+++ b/conf/machine/include/lmp-factory-custom.inc
@@ -22,4 +22,4 @@ UEFI_SIGN_KEYDIR = "${TOPDIR}/conf/factory-keys/uefi"
 # TF-A Trusted Boot
 TF_A_SIGN_KEY_PATH = "${TOPDIR}/conf/factory-keys/tf-a/privkey_ec_prime256v1.pem"

+LMP_BOOT_FIRMWARE_VERSION:uz3eg-iocc-sec = "3"

Note

LMP_BOOT_FIRMWARE_VERSION is now the preferred way to set boot firmware version. Defining PV in lmp-boot-firmware.bbappend is deprecated and should not be used. To switch, remove the PV = "<version>" line from lmp-boot-firmware.bbappend. Then define LMP_BOOT_FIRMWARE_VERSION with the appropriate version value, as shown in the example above.

Deploy Boot Images to QSPI Flash

If QSPI is chosen as the main boot media, you can use the U-Boot shell—loaded via serial console mode or other boot media—for image provisioning with corresponding offsets on QSPI:

ZynqMP> sf probe; setenv loadaddr 0x8000000; mmc dev ${bootseq}; fatload mmc ${bootseq}:1 ${loadaddr} boot0001.bin; sf update ${loadaddr} 0x0 ${filesize}; sf update ${loadaddr} 0x60000 ${filesize}; fatload mmc ${bootseq}:1 ${loadaddr} u-boot0001.itb; sf update ${loadaddr} 0x100000 ${filesize}; sf update ${loadaddr} 0xaa0000 ${filesize};
SF: Detected n25q256ax1 with page size 512 Bytes, erase size 128 KiB, total 64 MiB
switch to partitions #0, OK
mmc1 is current device
280752 bytes read in 37 ms (7.2 MiB/s)
device 0 offset 0x0, size 0x448b0
0 bytes written, 280752 bytes skipped in 0.405s, speed 709851 B/s
device 0 offset 0x60000, size 0x448b0
0 bytes written, 280752 bytes skipped in 0.405s, speed 709851 B/s
7179209 bytes read in 505 ms (13.6 MiB/s)
device 0 offset 0x100000, size 0x6d8bc9
0 bytes written, 7179209 bytes skipped in 7.433s, speed 1025601 B/s
device 0 offset 0xaa0000, size 0x6d8bc9
0 bytes written, 7179209 bytes skipped in 7.433s, speed 1025601 B/s