Flash XIP with Split Images

Overview

The ATM34 platform supports executing all or portions of the user application directly from external flash memory using eXecute-in-Place (XIP) technology. This technology is mature and has been available in earlier SoCs. For these earlier SoCs, flash XIP is the normal operating mode with power or performance critical code loaded into RAM or executed directly from ROM (e.g. ROM-based BLE link layer). ATM34’s use and implementation of flash split XIP differs from these earlier platforms in that RRAM is used in place of RAM/ROM. The flash split XIP image feature available in ATM34 uses the programmability of RRAM to selectively place power/performance critical code in on-chip RRAM from the Zephyr RTOS and user application. This allows the application code to grow into the flash partition while keeping criticial and frequently used code in programmable RRAM. This level of partitioning was limited in previous flash XIP products that only offered less RAM and an immutable ROM. ATM34 has an additional requirement that some images (i.e. SPE, MCUBOOT bootloader) will always operate from RRAM for security reasons.

External Flash

External flash memory is accessed through a QSPI (Quad SPI) bridge that maps the external flash device into the memory space of the CPU. Flash read accesses (CPU data or instruction load) appear as ordinary memory read accesses that are fully cacheable by the ATM34 instruction cache (I-cache). Flash write and erase operations are managed via the HAL flash driver. Write and erase mechanisms (software and hardware) are designed to coexist with flash split XIP operation.

Flash read performance is limited by the QSPI bus protocol and can be significantly slower than accesses to on-chip RRAM. The available I-cache, however, mitigates this to some degree. Power consumption also increases as data must be fetched through an external I/O bus. These system limitations require that performance and power critical code should operate from internal RRAM memories. This results in a split image configuration where security, power, and performance critical code continues to execute in on-chip RRAM and the remaining user application logic executes from an increasingly expandable flash memory space (up to 16MB). RAM (SRAM) can still be used for the most power or performance critical code.

External flash can be partitioned for both XIP code and data storage. This includes filesystems and full support of MCUBOOT with upgrade partitions.

Configuration

This feature is controlled by the ATM34-specific device tree macro option: ATM_APP_FLASH_XIP. This can be supplied to the device tree macro processor as -DATM_APP_FLASH_XIP. When configured, various device tree nodes and properties will be instantiated in the final device tree configuration. The presence of these nodes and properties will auto configure Kconfig options to enable flash split XIP. The following sections walk through the changes to device tree, image building and MCUBOOT.

Device Tree

The device tree will contain a new fast_code_partition (high performance, low power code) located in RRAM and an nspe_partition located in external flash shown in the examples below:

rram_controller: rram-controller@10000 {
    rram0: rram@10000 {
        ...
        partitions {
        ...
            spe_partition: partition@cece0011 {
                label = "spe_partition";
                secure;
                reg = < 0xc000 0x6000 >;
            };
        ...
            fast_code_partition: partition@cece0080 {
                label = "fast_code_partition";
                        reg = < 0x12000 0x6b000 >;
            };
        ...

flash_controller: flash-controller@200000 {
    flash0: flash@0 {
        ...
        partitions {
        ...
            nspe_partition: partition@cece0012 {
                label = "nspe_partition";
                reg = < 0x0 0xc4000 >;
            };
        ...

The SPE and fast code are placed in the RRAM region. Typically the Zephyr kernel, subsystems, C-runtime, HAL drivers and BLE link controller stacks operate within the fast_code_partition. Due to security reasons the SPE always executes from RRAM and its location remains the same. The fast_code_partition will contain code designated as fast_code from a linker settings described in the next section.

Linker Settings

A full account of what is placed in fast code can be found in openair/soc/atmosic/ATM34/linker.ld . For example:

SECTIONS {
    fast_code : {
            */libarch*.a:(.text ".text.*" .rodata ".rodata.*")
            */libblell*.a:(.text ".text.*" .rodata ".rodata.*")
            */libc*.a:(.text ".text.*" .rodata ".rodata.*")
            */libdrivers*.a:(.text ".text.*" .rodata ".rodata.*")
            */libgcc.a:(.text ".text.*" .rodata ".rodata.*")
            */libkernel.a:(.text ".text.*" .rodata ".rodata.*")
            */libmbedTLS*.a:(.text ".text.*" .rodata ".rodata.*")
            */lib*mbedtls.a:(.text ".text.*" .rodata ".rodata.*")
            */libopenthread*.a:(.text ".text.*" .rodata ".rodata.*")
            */libsubsys*.a:(.text ".text.*" .rodata ".rodata.*")
            */libzephyr.a:(.text ".text.*" .rodata ".rodata.*")
            *_core.po(.text ".text.*" .rodata ".rodata.*")
            . = ALIGN(4);
    } > RRAM
}

Lists all the Zephyr build object artifacts that are relocated to fast_code. The remainder of the application is placed in the nspe_partition located in external flash.

MCUBOOT Configuration

For an MCUBOOT configuration, the spe_partition and fast_code_partition are grouped together to form image 0 (merged and signed together). This image resides in the parent slot0_partition, also known as the primary image slot for image 0. Image 0’s upgrade (or secondary) slot is designated as slot1_partition. The flash image 1 is signed and placed in the designated parent slot2_partition, the primary slot for image 1. The upgrade slot for image 1 is designated as slot3_partition. MCUBOOT supports multi-image upgrade using this slot partition scheme when CONFIG_UPDATEABLE_IMAGE_NUMBER is set to 2 or more. Both images share the same scratch partition when configured for an image swap upgrade.

In summary the image to slot mapping is shown below.

Image to Slot Partition Mapping

Image Number

Composition

Primary Slot

Upgrade (Secondary) Slot

0

SPE and fast code

slot0_partition

slot1_partition

1

User application

slot2_partition

slot3_partition

Conceptually this results in the partition layout diagram below:

RRAM

   +----------------+
   | MCUBOOT  (48K) |
   +----------------+

   Slot0 :
       +----------------+ \
       | SPE (24K)      |  \
       +----------------+   \  Merged into a single signed binary (Image 0)
       +----------------+   /
       | Fast Code      |  /
       +----------------+ /
   +--------------------+
   | Factory Data (2K)  |   NVS Settings database (read only)
   +--------------------+
   +--------------------+
   | Storage (2K)       |   NVS Settings database (r/w)
   +--------------------+
   +------------------+
   | SecJrnl/keys (2K)|   Secure Journal and keys (reserved at end of RRAM)
   +------------------+

 FLASH

    Slot2 :
        +----------------+
        | NSPE  (NS App) | --> Single signed binary (Image 1)
        +----------------+
    +----------------+
    | Scratch  (16K) |
    +----------------+
    Slot1 :
        +---------------------------+
        | Staging for slot0         |
        | Size = slot0              |
        +---------------------------+
    Slot3 :
        +------------------------+
        | Staging for slot2      |
        | Size = slot2           |
        +------------------------+
    +-------------------+
    |  FS partitions    | Additional filesystem partitions as needed,  such as LittleFS
    |   (optional)      |
    +-------------------+

Some sizes such as NVS settings, MCUBOOT, SPE and Scratch are fixed for typical configurations.

Sizing the Image 0 Fast Code Partition

The size of the fast code partition is set to the remaining RRAM space after all other partition requirements are met. Required partitions include NVS settings storage (factory and storage partitions), SPE image reservation, and the MCUBOOT bootloader image reservation. The required partitions can be expanded in 2KB increments, taking away some of the allocation for fast code.

Sizing the Image 1 Code partition

The size of the flash image will be limited by the size of the QSPI bus attached flash device. Various evaluation boards will configure the device tree board files with the macro: #define FLASH_SIZE <size in bytes> where the size is set to 512KB, 1MB or 2MB depending on what is populated on the board or stacked on top of the chip die. Flash split XIP requires a minimum of 1MB to ensure that the requirements for MCUBOOT are met while leaving a reasonable amount of flash memory for the user application. Larger flash devices therefore is more desireable. In a flash split image configuration, the flash image can occupy all of the available flash memory less any MCUBOOT partition requirements. Given that the flash image will use the rest of flash (FLASH_SIZE less MCUBOOT partitions) it may be desirable to hold back a set amount of flash memory for other uses (i.e. custom storage, filesystem etc..). This reservation or hold back can be specified with a device tree macro: -DFLASH_XIP_RSVD_SIZE=<n>. Where <n> represents the reserve (hold-back) in bytes and is reserved from the end of FLASH_SIZE. An application specific device tree board overlay can then define partitions in this reserved space. For example, the application overlay for littleFS could be represented as:

#define LFS_SIZE FLASH_XIP_RSVD_SIZE
#define LFS_OFFSET (FLASH_SIZE - LFS_SIZE)

&flash0 {
    partitions {
        compatible = "fixed-partitions";
        #address-cells = <1>;
        #size-cells = <1>;
        lfs1_partition: partition@ceceff00 {
            label = "littlefs_storage";
            reg = <LFS_OFFSET LFS_SIZE>;
        };
};

Building

Flash split XIP applications should be built using sysbuild. Although manually building and flashing each image is possible, sysbuild ensures all configurations are applied to all images including MCUBOOT (if OTA is required). Sysbuild can also be used to flash all image artifacts (SPE, fast_code, user application) as well as any MCUBOOT artifacts (the bootloader image and signed image0/image1 binaries).

An example of a flash split XIP configuration can be found in openair/samples/subsys/mgmt/mcumgr/smp_svr/sample.yaml. The smp_svr sample demonstrate image management services that support MCUBOOT. The flash_xip configuration is defined as samples.subsys.mgmt.mcumgr.smp_svr.atm.mcuboot.flash_xip and the following snippet demonstrates the required sysbuild configurations to enable flash split XIP:

sysbuild: true
...
tags: atm34 mcuboot
extra_args:
  - SB_CONFIG_SPE=y
  - SB_CONFIG_BOOTLOADER_MCUBOOT=y
  - SB_CONFIG_BOOT_SIGNATURE_TYPE_ECDSA_P256=y
  - SB_CONFIG_MCUBOOT_MODE_SWAP_SCRATCH=y
  - SB_CONFIG_ATMWSTK_PD50=y
  - SB_ATM_DTS_EXTRA_CPPFLAGS="-DATM_APP_FLASH_XIP"
  - mcuboot_CONFIG_UPDATEABLE_IMAGE_NUMBER=2

This configuration only applies to ATM34 platforms. The configuration enables the DTS option (-DATM_APP_FLASH_XIP) and configures the MCUBOOT option CONFIG_UPDATEABLE_IMAGE_NUMBER=2. All other options are standard configurations for an MCUBOOT enabled system.

Basic sysbuild command:

west build -p always -b ${BOARD}@mcuboot//ns openair/samples/subsys/mgmt/mcumgr/smp_svr \
    --sysbuild -T samples.subsys.mgmt.mcumgr.smp_svr.atm.mcuboot.flash_xip

This will perform the following steps:

  • Build the MCUBOOT bootloader image.

  • Build the SPE image.

  • Build the user’s non-secure NS application image (smp_svr in this case).

  • Split out fast code sections from the user NS app. image into a fast code binary.

  • Merge the fast code binary with the SPE image binary and sign (ECDSA) the resulting binary as image 0

  • Sign the NS app flash split XIP image (fast code removed) as a single binary as image 1

This results in 3 binaries

  • bootloader image in RRAM as <build>/mcuboot/zephyr/zephyr.bin

  • signed image 0 in RRAM as <build>/smp_svr/zephyr/zephyr.signed.bin

  • signed image 1 in External FLASH as <build>/smp_svr/zephyr/zephyr.signed.flash.bin

Please refer to _Version_Dependencies on setting a version relationship to the two signed images.

Flashing

To flash images run west flash within the build directory of the sysbuild output (or use --build-dir):

west flash --skip-rebuild --build-dir <build-dir> --verify --device=<jlink ID> --jlink --erase_all --fast_load

If a console is attached the MCUBOOT logs will show Image index: 0/1 slot processing with the SPE also listing the fast code partition:

*** Booting MCUboot v2.1.0-270-ge9c24a4048c2 ***
...
...
I: Starting bootloader
I: Primary image: magic=good, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: primary slot
I: Image index: 0, Swap type: none
I: Primary image: magic=good, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Scratch: magic=unset, swap_type=0x1, copy_done=0x3, image_ok=0x3
I: Boot source: primary slot
I: Image index: 1, Swap type: none
I: Bootloader chainload address offset: 0xc000
I: Image version: v0.0.0
I: Jumping to the first image slot
...
*** Zephyr SPE ***
* SPE range: [0x1001c000 - 0x10021fff]
* NSPE Fast Code range: [0x10022000 - 0x1008cfff]
* NSPE range: [0x10200000 - 0x102c3fff]
* NSPE non-sec start addr: 0x200800
***

Version Dependencies

Version dependency checking is a feature in MCUBOOT when the system can upgrade two or more images. Version dependency checking determines if a minimum version criteria has been met between related images. Only when dependencies are met will an upgrade be allowed. The version dependency check uses the image version that is set in the image header by the MCUBOOT image signing tool during the signing process. This signing version is provided by CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION. This value represents the version string passed to the image signing tool in the format of major.minor.revision+build_number, e.g 1.2.3+4. The build number is optional. MCUBOOT currently defaults to just a comparison based on major.minor.revision with an optional build number check. It is recommended to use a build number as part of the signing version and the dependency check. This addresses the possibility that builds within the same release milestone (multiple build candidates) may be incompatible. Flash split XIP will default CONFIG_BOOT_VERSION_CMP_USE_BUILD_NUMBER to y to facilitate build number dependency checks.

The flash split XIP build process will apply the same signing version to both image 0 and image 1. The signing tool must then be directed to include a version dependency item (TLV) in the image header that specifies the minimum version required of a related and dependent image. This takes the form of a dependency tuple passed on the command line: -d "(<image number>, <version string>)". The <version> string follows the same format as the signing version. The <image number> is set to the dependent image. For example image 0 would use the value of 1 for the dependent image.

Given a build version 1.2.3+4, the SPE/FAST code image 0 is dependent on the flash NS application image 1:

-d "(1,1.2.3+4)"

And vice versa for the flash NS application image 1 to be dependent on SPE/FAST code image 0:

-d "(0,1.2.3+4)"

This command line option is passed through the extra signing arguments configuration: CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS. The build flow uses CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS for image 0 signing. Due to the platform-specific SPE+fastcode merge and signing process, Openair provides a separate extra imgtool arguments: CONFIG_ATM_MCUBOOT_EXTRA_IMGTOOL_ARGS_IMG1 for image 1.

The following uses the smp_svr as an example to set the version dependencies from a bash command shell invoking west:

west build -p always --sysbuild openair/samples/subsys/mgmt/mcumgr/smp_svr -b ATMEVK-3430e-YQN-5@mcuboot//ns \
   -T samples.subsys.mgmt.mcumgr.smp_svr.atm.mcuboot.flash_xip \
   -Dsmp_svr_CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="\"1.2.3+4\"" \
   -Dsmp_svr_CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS="\"-d \\\"(1,1.2.3+4)\\\"\"" \
   -Dsmp_svr_CONFIG_ATM_MCUBOOT_EXTRA_IMGTOOL_ARGS_IMG1="\"-d \\\"(0,1.2.3+4)\\\"\""

Attention

Version and dependency string escaping may vary with the environment used (e.g. Windows) when invoking the west build tool.

This results in the following Kconfig assignments:

CONFIG_MCUBOOT_EXTRA_IMGTOOL_ARGS "-d \"(1,1.2.3+4)\""
CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION "1.2.3+4"
CONFIG_ATM_MCUBOOT_EXTRA_IMGTOOL_ARGS_IMG1 "-d \"(0,1.2.3+4)\""

Matching Version Criteria

MCUBOOT defaults to using a minimum version criteria when satisfying dependencies. For flash split XIP there is a static linkage between image 1 and image 0 when the fast code section is located in RRAM. Dynamic linkage is not supported. This tight coupling requires careful consideration when version dependencies are checked. Instead of a minimum dependency, flash split XIP requires exact matching versions. The option to have MCUBOOT use exact matches is enabled by CONFIG_MULTI_IMAGE_VERSIONS_MUST_MATCH. The flash split XIP build will default this value to y. The criteria to apply an update requires both image 0 and image 1 to have the same version. When building your application you must ensure that the signing versions and dependency versions use the same version string.