This article details using MCUboot's RAM Loading feature with Zephyr on an NXP i.MX RT microcontroller. MCUboot is a popular open-source bootloader that easily integrates with Zephyr applications. Using the RAM Load feature, the bootloader will find a valid image in flash, copy it to RAM, and jump to the app in RAM. Therefore, the Zephyr app must be built to execute from the RAM region, but write the image to flash.
These steps leverage Zephyr's Sysbuild system, which simplifies managing multiple images like this MCUboot bootloader and the application. There are two examples provided that load the blinky sample to RAM: one loading to external SDRAM, and one loading to internal DTCM.
Recommended Reading
This article delves into the use of MCUboot, Sysbuild, memory maps, and RAM loading. If these topics are new to you, we recommend reviewing the following resources to build a solid foundation before exploring the details within this document.
Sysbuild with MCUboot
AN12437 i.MX RT Series Performance Optimization
AN13970 RT Series Memory Relocation in Zephyr
SW/HW Requirements
Zephyr is very portable and these steps will help on other hardware platforms with simple tweaks. But these steps were tested with the following:
Zephyr v4.2
Zephyr SDK v0.17.2
MIMXRT1060-EVKC board using the default QSPI external flash. The Zephyr build target is mimxrt1060_evk@C//qspi .
RT1060 Memory Map
The table below details the memory maps used in these examples. Starting with the RT1060 default memory map in Zephyr v4.2, this is the address map if no changes are made, and RAM Loading is not used. The partition map in the QSPI flash are common for MCUboot and the app, since all images must be stored in separate address ranges. And this partition map is not changed in these examples.
zephyr,flash and zephyr,sram are devicetree chosen nodes, and tell the linker where to place code/data in the memory map. zephyr,flash includes the executable code and read-only data, which are typically placed in flash, but these examples place in RAM. zephyr,sram is where the linker places writable data like the .data and .bss sections. These examples modify the zephyr,sram location, and keep zephyr,sram of MCUboot and the app in different address ranges to avoid contention during the RAM loading.
Region
v4.2 Default
SDRAM example
DTCM example
blinky zephyr,flash (code and rodata)
slot0_partition 0x6002_0000
SDRAM 0x8000_0000
DTCM 0x2000_0000
blinky zephyr,sram (rwdata)
SDRAM 0x8000_0000
SDRAM 0x8000_0000
DTCM 0x2000_0000
MCUboot zephyr,sram (rwdata)
SDRAM 0x8000_0000
DTCM 0x2000_0000
SDRAM 0x8000_0000
FlexSPI QSPI flash (non-volatile boot mem)
0x6000_0000
boot_partition (for bootloader)
0x6000_0000
slot0_partition (for app image)
0x6002_0000
slot1_partition (for app image)
0x6032_0000
RAM Loading to SDRAM
The attached ramload_sdram.patch applied to the Zephyr v4.2 repo will modify the blinky sample to be loaded to SDRAM by MCUboot.
Note that NXP's i.MX RT development boards that include external SDRAM, like the MIMXRT1060-EVK, use SDRAM for the default zephyr,sram node. The SDRAM is enabled and configured by the ROM bootloader before that bootloader boots any application, including the MCUboot secondary bootloader. This configuration enables a Zephyr app to access the SDRAM immediately after booting. The ROM bootloader is configured to do this by the DCD stored in flash with the boot image, see Memory details with Zephyr for more details. Therefore, when MCUboot is used with SDRAM, the DCD is required in the MCUboot image.
The sections below detail the changes made to MCUboot, the blinky sample, and to both domains using Sysbuild. All these files are located in the repo folder samples/basic/blinky.
Sysbuild changes for both domains
The file sysbuild.conf is added to the app to configure Sysbuild for MCUboot. This also enables the RAM Load feature used in building both MCUboot and the app.
SB_CONFIG_BOOTLOADER_MCUBOOT=y SB_CONFIG_MCUBOOT_MODE_RAM_LOAD=y
MCUboot changes
The file sysbuild/mcuboot.conf configures the MCUboot domain. MCUboot will load the image stored in the slot partition to this address, which is the base address for SDRAM. The RAM_SIZE is used during the copy, and should be scaled as needed.
CONFIG_BOOT_IMAGE_EXECUTABLE_RAM_START=0x80000000 CONFIG_BOOT_IMAGE_EXECUTABLE_RAM_SIZE=131072
The file sysbuild/mcuboot.overlay modifies the devicetree for this bootloader. Since MCUboot will load the app to SDRAM, the zephyr,sram node is moved to DTCM to avoid overwriting any MCUboot data. The zephyr,code-partition node tells the linker to place the MCUboot bootloader code in the boot_partition in the flash.
/ { chosen { zephyr,sram = &dtcm; zephyr,code-partition = &boot_partition; }; };
Application changes
The file boards/mimxrt1060_evk_mimxrt1062_qspi_C.overlay modifies the devicetree for this app, and places the executable code in SDRAM, to be loaded by MCUboot.
/ { chosen { zephyr,flash = &sdram0; }; };
The file boards/mimxrt1060_evk_mimxrt1062_qspi_C.conf adjusts the address used when programming the blinky signed image binary to flash. Since the zephyr,flash node is placed in SDRAM, normally the "west flash" command would try to program the binary to 0x8000_0000, the base address of the SDRAM. This Kconfig selects the address of the slot0_partition to store the blinky image in flash.
CONFIG_FLASH_BASE_ADDRESS=0x60020000
Build and flash
These steps use the Command Line Interface (CLI) to build and flash. Another option is to use VS Code with NXP's MCUXpresso extension, see Zephyr app with MCUboot in VS Code.
This build command uses Sysbuild to build both images for MCUboot and the blinky app. This creates the folder ramload_blinky with all the generated files for both domains, which is used to flash the board.
west build -b mimxrt1060_evk@C//qspi samples/basic/blinky -d ../../ramload_blinky --sysbuild --pristine
This flash command uses Sysbuild to flash both images to the board, first the MCUboot image, then the signed blinky app image. This example specifies using the JLink debug probe, or LinkServer is another option.
west -v flash -d ../../ramload_blinky/ -r jlink
Console output from SDRAM
Connect a terminal to the debug console. After flashing, the LED will blink and the RT1060 will boot and print like this:
*** Booting MCUboot v2.1.0-rc1-389-g4eba8087fa60 *** *** Using Zephyr OS build v4.2.0 *** I: Starting bootloader I: Primary slot: version=0.0.0+0 I: Image 0 Secondary slot: Image not found I: Image 0 RAM loading to 0x80000000 is succeeded. I: Image 0 loaded from the primary slot I: Bootloader chainload address offset: 0x80000000 I: Image version: v0.0.0 I: Jumping to the first image slot *** Booting Zephyr OS build v4.2.0 *** LED state: OFF LED state: ON
RAM Loading to DTCM
The attached ramload_dtcm.patch modifies the repo to load the blinky sample to DTCM. The patch is very similar to the SDRAM patch detailed above. The same build and flash steps are used for this patch. The console output is also very similar, but shows the difference in the load address:
*** Booting MCUboot v2.1.0-rc1-389-g4eba8087fa60 *** *** Using Zephyr OS build v4.2.0 *** I: Starting bootloader I: Primary slot: version=0.0.0+0 I: Image 0 Secondary slot: Image not found I: Image 0 RAM loading to 0x20000000 is succeeded. I: Image 0 loaded from the primary slot I: Bootloader chainload address offset: 0x20000000 I: Image version: v0.0.0 I: Jumping to the first image slot *** Booting Zephyr OS build v4.2.0 *** LED state: OFF LED state: ON
Notes
Working on this, I ran into some issues using other memory map options. It seems there are some limitations with the imgtool settings with CONFIG_MCUBOOT_BOOTLOADER_MODE_RAM_LOAD=y . The Zephyr code base seems to make some assumptions about where in RAM the image will be loaded. For example, when using RAM Load, the imgtool utility must be called with the argument --load-addr and the address in RAM, which configures the boot header when signing the application image. But this Cmake file assumes the load address is the base of the zephyr,sram node. For that reason, the examples provided here place the zephyr,sram and zephyr,flash nodes for blinky in the same RAM. Separating these nodes to different RAMs caused issues for me. If more flexibility is required, one potential option is to modify the app's Cmake file using the needed imgtool settings to generate the needed signed binary for RAM Load. Imgtool can also be called manually if needed.
View full article