Boot Software Updates on STM32MP1
Boot Artifacts
STM32 TF-A BL2 Image
The TF-A BL2 image is the first boot image loaded by the application processor’s trusted ROM. It is in charge of loading the next-stage images (secure and non-secure) and preliminary initialization of required peripherals:
- SoC clocks, DRAM
- Security IPs: memory firewalls, crypto engines
- Storage (MMC, QSPI etc).
The image is prepended with STM32 header.
It can also be signed using the STM32 Signing tool.
TF-A FIP Image
The Trusted Firmware-A Firmware Image Package (TF-A FIP) the signed
FIP-image that contains U-Boot proper (u-boot.bin
) and other firmware.
This file is verified by TF-A BL2 using RoT HASH (of the public key) in the BL2
device tree blob and public key, which are provided by the FIP image.
This artifact may be signed (on closed boards) as a part of CI and
can be included automatically in a boot software OTA package.
List of images inside FIP Image container:
- U-boot-nodtb.bin (U-Boot proper binary)
- U-boot.dtb (U-Boot device tree)
- OP-TEE runtime (OP-TEE core, which provides PSCI and secure services)
If the CI signing key has been rotated since the last OTA, update the BL2 verification data prior to trying to boot the new FIP-image.
MMC Boot Image Layout
The location of the boot image depends on what boot media is being used. For eMMC, the initial boot images are flashed to a 0x0 offset of the boot hw partitions.
When using a SD as boot media, boot images of different offsets are used within the same HW partition. For additional details see Flash Layout File (eMMC Boot) and WKS Layout (SD Boot).
Update Procedure
Primary vs Secondary Boot Paths
The STM32MP1 SoC supports booting from two copies of First Stage Boot Loaders:
- First Stage Boot Loader Copy 1 (FSBL1)
- First Stage Boot Loader Copy 2 (FSBL2)
Unfortunately, the SoC does not provide any mechanisms for a user to control in runtime what FSBL copy to boot —only the SoC ROM can make this decision based on image authentication/checksum results. As such, the setup for FSBL1 is treated as the primary boot path, with FSBL2 as a recovery path (secondary boot path) in case the authentication/loading of FSBL1 fails.
Libaktualizr and Aktualizr-lite
Libaktualizr and Aktualizr-lite make decisions during the boot phase. Those decisions are listed below, ordered as they appear in the source code:
- aktualizr-lite makes the decision if boot firmware needs to be updated based
on the contents of
${ostree_root}/usr/lib/firmware/version.txt`, where ``ostree_root
is root of newly deployed ostree sysroot. For example,bootfirmware_version=10
. - After parsing
bootfirmware_version
, it compares version number with the existing one, which is obtained via fiovb or ubootenv. - If
bootfirmware_version
fromversion.txt
is higher than existing one, Aktualizr-lite setsbootupgrade_available
via fiovb or ubootenv. - Reboot is to be performed.
The U-Boot boot.cmd Script
The following steps represent the boot path from U-Boot’s perspective.
- The update is done via the U-Boot boot.cmd (boot.scr) script.
- The boot.cmd script checks if the primary path was booted.
- In case
upgrade_available
is set, the script checks if a boot firmware upgrade is needed by looking intobootupgrade_available
flag. If both are true, boot firmware images are then obtained from the newly deployed ostree sysroot and written to the primary boot path offsets. Afterwards,bootupgrade_primary_updated
is set, and a regular reset signal is issued. - After the reboot, the SoC ROM tries to boot newly updated images from the primary boot path (FSBL1). If image verification fails, it will automatically fall back to FSBL2. When FSBL2 is booted, a rollback procedure will be issued, the previous version of FSBL1 will be restored. If FSBL1 does boot, but bootcount hits bootlimit (meaning the boot procedure has not finished successfully), the rollback procedure will be also issued.
- After Linux is booted, Aktualizr-lite confirms a successful update by clearing
the
upgrade_available
flag. At this point, new boot firmware images are already validated. An additional reboot is needed after this step. - After rebooting, U-Boot checks if
bootupgrade_primary_updated
is set and ifupgrade_available
is cleared. This means that Aktualizr-lite has confirmed a successful boot, and U-Boot clearsbootupgrade_primary_updated
flag. Otherwise thebootcount
value is incremented.
Adding a new Board
meta-lmp
Flash Layout File (eMMC Boot)
To deploy boot images to the destination board, STM32CubeProg is used. This tool uses a special configuration file (with a tsv extension) that contains the eMMC flash layout. For additional details, refer to the official documentation.
As STM32CubeProg does not support defining offsets inside boot0/boot1 hw partitions, TF-A BL2 and FIP are both combined into one image in advance. This is done with correct padding between them, so they are flashed to the correct offsets during STM32CubeProg execution:
$ cp tf-a-stm32mp157c-ev1-emmc.stm32 combo-emmc-tfa-fip-stm32mp157c-ev1.bin
$ dd if=fip-stm32mp157c-ev1-optee.bin combo-emmc-tfa-fip-stm32mp157c-ev1.bin bs=1024 seek=256 conv=notrunc
Note
Concatenation is done automatically via the flashlayouts-stm32mp1.bb recipe.
Example of TSV file:
#Opt Id Name Type IP Offset Binary
- 0x01 fsbl-boot Binary none 0x0 tf-a-stm32mp157c-ev1-usb.stm32
- 0x03 fip-boot Binary none 0x0 fip-stm32mp157c-ev1-optee.bin
PD 0x04 fsbl1 Binary mmc1 boot1 combo-emmc-tfa-fip-stm32mp157c-ev1.bin
PD 0x05 fsbl2 Binary mmc1 boot2 combo-emmc-tfa-fip-stm32mp157c-ev1.bin
PED 0x06 u-boot-env Binary mmc1 0x00080000 none
P 0x10 rootfs System mmc1 0x00100000 lmp-base-console-image-stm32mp15-eval.ext4
WKS Layout (SD Boot)
In a SD setup, two sets of FSBL images are stored, each with different offsets:
- FSBL1 17KB
- FSBL2 256KB
To support both images, the WKS file should be adjusted so that both copies are placed at the correct offsets:
part fsbl1 --source rawcopy --fstype=ext4 --fsoptions "noauto" --part-name=fsbl1 --sourceparams="file=tf-a-stm32mp157c-dk2-sdcard.stm32" --ondisk mmcblk --part-type 0x8301 --fixed-size 256K --align 17
part fsbl2 --source rawcopy --fstype=ext4 --fsoptions "noauto" --part-name=fsbl2 --sourceparams="file=tf-a-stm32mp157c-dk2-sdcard.stm32" --ondisk mmcblk --part-type 0x8301 --fixed-size 256K
part fip1 --source rawcopy --fstype=ext4 --fsoptions "noauto" --part-name=fip-a --sourceparams="file=fip-stm32mp157c-dk2-optee.bin" --ondisk mmcblk --part-type 0x8301 --fixed-size 4096K
part fip2 --source rawcopy --fstype=ext4 --fsoptions "noauto" --part-name=fip-b --sourceparams="file=fip-stm32mp157c-dk2-optee.bin" --ondisk mmcblk --part-type 0x8301 --fixed-size 4096K
part u-boot-env --source empty --part-name=uboot-env --ondisk mmcblk --part-type 0x8301 --fixed-size 16K --align 8192
part / --source otaimage --ondisk mmcblk --fstype=ext4 --align 4096
bootloader --ptable gpt
Testing FSBL Set and Auth Status
After applying the updates from previous steps, we should validate that everything is in place. This consists of two steps:
- FSBL1 vs FSBL2 detection (primary or secondary path)
- Obtain board security state (results of FSBL authentication)
To test FSBP copy detection, check the boot\_part
variable
using U-Boot shell:
STM32MP> print boot_part
boot_part=1
To check if the security status of your board is shown correctly, check
the boot\_auth
variable:
STM32MP> print boot_auth
boot_auth=0
Possible values are:
- 0 - No authentication done
- 1 - Authentication done and failed
- 2 - Authentication done and success
boot.cmd
Currently, LmP uses template-based generation for the final boot.cmd.
It’s constructed from common boot files
(./meta-lmp-base/recipes-bsp/u-boot/u-boot-ostree-scr-fit
),
which contains all SoC agnostic “DEFINEs” and common functionality, and
board-specific boot.cmd
, which includes the common scripts.
Example of board boot.cmd
(./meta-lmp-bsp/recipes-bsp/u-boot/u-boot-ostree-scr-fit/stm32mp15-eval/boot.cmd
):
setenv fdtfile stm32mp157c-ev1-scmi.dtb
echo "Using ${fdtfile}"
# Default boot type and device
setenv bootlimit 3
setenv devtype ${boot_device}
setenv devnum ${boot_instance}
setenv rootpart 2
setenv fit_addr 0xc4400000
setenv fdt_file_final ${fdtfile}
setenv fdt_addr 0xc4000000
setenv optee_ovl_addr 0xc4300000
setenv loadaddr 0xc4400000
setenv do_reboot "reset"
setenv check_board_closed 'if test "${boot_auth}" = "2"; then setenv board_is_closed 1; else setenv board_is_closed; fi;'
setenv check_secondary_boot 'if test "${boot_part}" = "2"; then setenv fiovb.is_secondary_boot 1; else setenv fiovb.is_secondary_boot 0; fi;'
# All values are provided in blocks (512 bytes each)
setenv bootloader 0x0
setenv bootloader2 0x200
setenv bootloader_size 0x1000
setenv bootloader_s ${bootloader}
setenv bootloader2_s ${bootloader2}
setenv bootloader_image "tf-a-stm32mp157c-ev1-emmc.stm32"
setenv bootloader_s_image ${bootloader_image}
setenv bootloader2_image "fip-stm32mp157c-ev1-optee.bin"
setenv bootloader2_s_image ${bootloader2_image}
setenv update_image_boot0 '\
echo "${fio_msg} writing ${image_path} ..."; \
run set_blkcnt && \
mmc dev ${devnum} && \
mmc partconf ${devnum} 1 1 1 && \
mmc write ${loadaddr} ${start_blk} ${blkcnt} && \
mmc partconf ${devnum} 1 1 0 \
'
setenv backup_primary_image '\
echo "${fio_msg} backing up primary boot image set ..."; \
mmc dev ${devnum} && \
mmc partconf ${devnum} 1 1 1 && \
mmc read ${loadaddr} ${bootloader} ${bootloader_size} && \
mmc partconf ${devnum} 1 1 0 && \
mmc dev ${devnum} && \
mmc partconf ${devnum} 1 1 2 && \
mmc write ${loadaddr} ${bootloader} ${bootloader_size} && \
mmc partconf ${devnum} 1 1 0 \
'
setenv restore_primary_image '\
echo "${fio_msg} restore primary boot image set ..." ; \
mmc dev ${devnum} && \
mmc partconf ${devnum} 1 1 2 && \
mmc read ${loadaddr} ${bootloader} ${bootloader_size} && \
mmc partconf ${devnum} 1 1 0 && \
mmc dev ${devnum} && \
mmc partconf ${devnum} 1 1 1 && \
mmc write ${loadaddr} ${bootloader} ${bootloader_size} && \
mmc partconf ${devnum} 1 1 0 \
'
setenv update_primary_image1 'setenv image_path "${ostree_root}/usr/lib/firmware/${bootloader_s_image}"; setenv start_blk "${bootloader_s}"; run load_image; run update_image_boot0'
setenv update_primary_image2 'setenv image_path "${ostree_root}/usr/lib/firmware/${bootloader2_s_image}"; setenv start_blk "${bootloader2_s}"; run load_image; run update_image_boot0'
setenv update_primary_image 'run update_primary_image1 && run update_primary_image2'
@@INCLUDE_COMMON_ALTERNATIVE@@
Sysroot and Signed Boot Artifacts
Boot artifacts (TF-A BL2 and FIP) are automatically deployed to sysroot during build time. On closed boards however, where the initial boot image has to be signed in advance by a subscriber private key, there is a way to add a signed binary instead of relying on the automatic inclusion of unsigned boot artifacts.
To do that, just add lmp-boot-firmware.bbappend
to your meta-subscriber-overrides
layer, adding the path to the signed binary and the signed binary itself.
Then define boot firmware version number by setting LMP_BOOT_FIRMWARE_VERSION
global variable in your lmp-factory-custom.inc
. Boot firmware version
information will be automatically added to ${osroot}/usr/lib/firmware/version.txt
file and U-Boot Device Tree Blob.
Versioning convention is up to user, the only requirement is that the version string should be unique and there should not be duplicates.
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://tf-a-stm32mp157c-ev1-mmc.stm32 \
+"
diff --git a/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/tf-a-stm32mp157c-ev1-mmc.stm32 b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/tf-a-stm32mp157c-ev1-mmc.stm32
new file mode 100644
index 0000000..50f5013
Binary files /dev/null and b/recipes-bsp/lmp-boot-firmware/lmp-boot-firmware/tf-a-stm32mp157c-ev1-mmc.stm32 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:stm32mp15-eval = "3"
Note
As LMP_BOOT_FIRMWARE_VERSION
is now a preferable way to set boot firmware version, defining PV
in lmp-boot-firmware.bbappend
is deprecated and should not be used. To switch to a new approach just remove PV = "<version>"
line from
lmp-boot-firmware.bbappend
and define LMP_BOOT_FIRMWARE_VERSION
with appropriate version value as shown above in the example.
See also