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.
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.
Zephyr is very portable and these steps will help on other hardware platforms with simple tweaks. But these steps were tested with the following:
mimxrt1060_evk@C//qspi
.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 |
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.
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
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;
};
};
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
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
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
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
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.