Using QSPI XIP split image

The QSPI XIP split image feature lets you gain more flash storage space for applications by splitting application code into two parts:

  • Code that runs on the internal flash memory.

  • Code that runs on supported external flash memory over the Quad Serial Peripheral Interface (QSPI) using Execute in Place (XIP).

This feature is supported on nRF52840 and nRF5340.

Warning

On the nRF52840, do not relocate interrupts to the QSPI XIP flash. Doing so can lock up or brick the device by making the debug access port inaccessible.

The QSPI XIP split images are supported in MCUboot, which allows for updating them over-the-air.

For the nRF5340 DK and Nordic Thingy:53, you can also check out the SMP Server with external XIP sample, which demonstrates this feature.

Requirements

To use this feature, meet the following requirements:

  • Board based on nRF52840 or nRF5340, with file structure compatible with Hardware model v2 (HWMv2)

  • External QSPI flash chip with supported commands connected to QSPI pins

  • QSPI flash chip in always-on mode (meaning, no DPM or low-power modes)

  • MCUboot configuration in the Swap using move mode (MCUBOOT_SWAP_USING_MOVE), the Upgrade only mode (MCUBOOT_OVERWRITE_ONLY), or in the direct-XIP mode

  • Static partition manager file

  • Linker file with QSPI XIP flash offset and size

  • Sysbuild

Caution

Currently, MCUboot images cannot be linked together. This means that both parts of the firmware update must be loaded properly before an update is initiated. If one part fails to update, the image on the device will be unbootable and that will likely brick the device.

Network core image updates

The QSPI XIP split image feature supports network core image updates on nRF5340 devices if MCUboot is configured for using either the swap-using-move or the upgrade-only mode.

Create the Partition Manager static files

Create the following files:

  • A static Partition Manager file to set up the partitions that will be used on the device. The following three files from the SMP Server with external XIP sample provide an example of the Partition Manager layouts for nRF5340:

    app:
      address: 0x10200
      end_address: 0xe4000
      region: flash_primary
      size: 0xd3e00
    external_flash:
      address: 0x120000
      device: MX25R64
      end_address: 0x800000
      region: external_flash
      size: 0x6e0000
    mcuboot:
      address: 0x0
      end_address: 0x10000
      region: flash_primary
      size: 0x10000
    mcuboot_pad:
      address: 0x10000
      end_address: 0x10200
      region: flash_primary
      size: 0x200
    mcuboot_primary:
      address: 0x10000
      end_address: 0xe4000
      orig_span: &id001
      - mcuboot_pad
      - app
      region: flash_primary
      size: 0xd4000
      span: *id001
    mcuboot_primary_1:
      address: 0x0
      device: flash_ctrl
      end_address: 0x40000
      region: ram_flash
      size: 0x40000
    mcuboot_primary_app:
      address: 0x10200
      end_address: 0xe4000
      orig_span: &id002
      - app
      region: flash_primary
      size: 0xd3e00
      span: *id002
    mcuboot_secondary:
      address: 0x0
      device: MX25R64
      end_address: 0xd4000
      region: external_flash
      size: 0xd4000
    mcuboot_secondary_1:
      address: 0xd4000
      device: MX25R64
      end_address: 0x114000
      region: external_flash
      size: 0x40000
    EMPTY_1:
      address: 0x114000
      device: MX25R64
      end_address: 0x120000
      region: external_flash
      size: 0xc000
    mcuboot_primary_2:
      address: 0x120000
      device: MX25R64
      end_address: 0x160000
      orig_span: &id003
      - mcuboot_primary_2_pad
      - mcuboot_primary_2_app
      region: external_flash
      size: 0x40000
      span: *id003
    mcuboot_primary_2_pad:
      address: 0x120000
      end_address: 0x120200
      region: external_flash
      size: 0x200
    mcuboot_primary_2_app:
      address: 0x120200
      device: MX25R64
      end_address: 0x40000
      region: external_flash
      size: 0x3FE00
    mcuboot_secondary_2:
      address: 0x160000
      device: MX25R64
      end_address: 0x1a0000
      region: external_flash
      size: 0x40000
    otp:
      address: 0xff8100
      end_address: 0xff83fc
      region: otp
      size: 0x2fc
    pcd_sram:
      address: 0x20000000
      end_address: 0x20002000
      region: sram_primary
      size: 0x2000
    ram_flash:
      address: 0x40000
      end_address: 0x40000
      region: ram_flash
      size: 0x0
    rpmsg_nrf53_sram:
      address: 0x20070000
      end_address: 0x20080000
      placement:
        before:
        - end
      region: sram_primary
      size: 0x10000
    settings_storage:
      address: 0xf0000
      end_address: 0x100000
      region: flash_primary
      size: 0x10000
    sram_primary:
      address: 0x20002000
      end_address: 0x20070000
      region: sram_primary
      size: 0x6e000
    
  • A board overlay file to set the chosen Partition Manager external flash device:

    / {
    	chosen {
    		nordic,pm-ext-flash = &mx25r64;
    	};
    };
    

    You can either place this file as a board overlay in the boards folder of the application or name it app.overlay and place it in the application folder so that it applies to all board targets.

Create a linker file

Create a linker file that has the same information as the static Partition Manager file for the external QSPI flash device. The following code example from the SMP Server with external XIP sample shows how to set up a linker file for direct-XIP mode, with the direct-XIP secondary image configuration in the CONFIG_NCS_IS_VARIANT_IMAGE section. You can omit this section if you are using other MCUboot operating modes.

Place this file in the application directory with a name similar to linker_arm_extxip.ld.

/*
 * Copyright (c) 2022 Carlo Caione <ccaione@baylibre.com>
 * Copyrigt# Copyright (c) 2024 Nordic Semiconductor
 *
 * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
 */

/**
 * @file
 * @brief External QSPI flash Linker command/script file
 *
 * Linker script for moving desired .text and and .data to the external
 * memory space.
 */

#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>

#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>


/* Let SystemInit() be called in place of z_arm_platform_init() by default. */
PROVIDE(z_arm_platform_init = SystemInit);

/*
 * nRF5340dk and thingy53 are shipping QSPI external flashes.
 * These memories are mapped beginning from 0x1000_0000 internal SoC memory
 * address. This addressing space can be used for XIP and direct data access.
 */
MEMORY
{
#if CONFIG_NCS_IS_VARIANT_IMAGE
     /* This maps in mcuboot_secondary_1 partition defined in pm_static_no_network_core_directxip.yml
      * components for ORIGIN calculation:
      *  - 0x10000000: offset of QSPI external memory in SoC memory mapping.
      *  - 0xD4000: mcuboot_secondary_1 offset in QSPI external memory
      *  - 0x200: image header size.
      * The size of this region is size of mcuboot_secondary_1 reduced by the
      * image header size.
      */
     EXTFLASH (wx) : ORIGIN = 0x100D4200, LENGTH = 0x3FE00
#else
     /* This maps in mcuboot_primary_2 partition defined in pm_static.yml
      * components for ORIGIN calculation:
      *  - 0x10000000: offset of QSPI external memory in SoC memory mapping.
      *  - 0x120000: mcuboot_primary_2 offset in QSPI external memory
      *  - 0x200: image header size.
      * The size of this region is size of mcuboot_primary_2 reduced by the
      * image header size.
      */
     EXTFLASH (wx) : ORIGIN = 0x10120200, LENGTH = 0x3FE00
#endif
}

#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>

Set additional Kconfig options

Set additional Kconfig option in the application’s prj.conf to allow it to execute code from XIP:

# Update the filename here if you have given it a different name
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_extxip.ld"

Configure sysbuild

Configure Sysbuild to enable the required Kconfig options for this functionality:

  1. Create a sysbuild.conf file in the application directory withs the following options, depending on the required functionality:

    SB_CONFIG_BOOTLOADER_MCUBOOT=y
    SB_CONFIG_PM_EXTERNAL_FLASH_MCUBOOT_SECONDARY=y
    SB_CONFIG_NETCORE_APP_UPDATE=y
    SB_CONFIG_SECURE_BOOT_NETCORE=y
    SB_CONFIG_QSPI_XIP_SPLIT_IMAGE=y
    
    # This will enable the hci_ipc image for the network core, change to the desired image
    SB_CONFIG_NETCORE_HCI_IPC=y
    
  2. Create a sysbuild.cmake file. This file applies the devicetree overlay file for enabling the external flash device for the Partition Manager (created in Create the Partition Manager static files) to MCUboot. The following examples assume you created the app.overlay file valid for all board targets; adjust as needed if you created as a board overlay instead.

    list(APPEND mcuboot_EXTRA_DTC_OVERLAY_FILE "${CMAKE_CURRENT_SOURCE_DIR}/app.overlay")
    

Relocate code to QSPI flash

Relocate the code to run from the QSPI flash inside the applications CMakeLists.txt file using the zephyr_code_relocate function. This can be used to relocate files and libraries.

zephyr_code_relocate(FILES src/complex_sensor_calculcation_code.c LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(FILES src/complex_sensor_calculcation_code.c LOCATION RAM_DATA)

Note

This assumes that the application has a src/complex_sensor_calculcation_code.c` file.

Set QSPI flash initialization priority

Set the initialization priority of the QSPI flash so that it is configured before any code on the device runs, otherwise the application operation is going to be undefined.

There is no built-in check for this setting. Therefore, you must manually set the priority based on the relocated code. For example, if you relocate the shell subsystem, its initialization priority must be higher than the priority of QSPI, and the value of CONFIG_SHELL_BACKEND_SERIAL_INIT_PRIORITY must be greater than CONFIG_NORDIC_QSPI_NOR_INIT_PRIORITY.

Note

Not all libraries or subsystems can be relocated. Any code that runs early in the boot process or uses SYS_INIT() with a non-configurable init priority cannot be relocated. Library and subsystem code needs to be manually checked before relocating it.

Programming with the QSPI XIP split image

Programming of the application is supported using the standard procedure. The standard procedure will program the firmware using the default nrfjprog configuration which, for QSPI, is PP4IO mode.

Programming using a different SPI mode

If you are using an a different SPI mode on the QSPI interface, such as DSPI, you must use a custom Qspi.ini file. The following is an example for the Thingy:53, which supports DSPI and PP:

;----------------------------------------------
; Deprecated, use config.toml format instead
;----------------------------------------------

; nrfjprog QSPI configuration file.

[DEFAULT_CONFIGURATION]
; Define the capacity of the flash memory device in bytes. Set to 0 if no external memory device is present in your board.
MemSize = 0x800000

; Define the desired ReadMode. Valid options are FASTREAD, READ2O, READ2IO, READ4O and READ4IO
ReadMode = READ2IO

; Define the desired WriteMode. Valid options are PP, PP2O, PP4O and PP4IO
WriteMode = PP

; Define the desired AddressMode. Valid options are BIT24 and BIT32
AddressMode = BIT24

; Define the desired Frequency. Valid options are M2, M4, M8, M16 and M32
Frequency = M16

; Define the desired SPI mode. Valid options are MODE0 and MODE3
SpiMode = MODE0

; Define the desired SckDelay. Valid options are in the range 0 to 255
SckDelay = 0x80

; Define SPI interface timing. Valid options are in the range of 0 to 7.
; This argument is only used for devices where the dll function NRFJPROG_qspi_set_rx_delay() is supported.
RxDelay = 2

; Define the desired IO level for DIO2 and DIO3 during a custom instruction. Valid options are LEVEL_HIGH and LEVEL_LOW
CustomInstructionIO2Level = LEVEL_LOW
CustomInstructionIO3Level = LEVEL_HIGH

; Define the assigned pins for the QSPI peripheral. Valid options are those existing in your device
; For nRF53, QSPI pins are not configurable and these values are ignored.
CSNPin = 17
CSNPort = 0
SCKPin = 19
SCKPort = 0
DIO0Pin = 20
DIO0Port = 0
DIO1Pin = 21
DIO1Port = 0
DIO2Pin = 22
DIO2Port = 0
DIO3Pin = 23
DIO3Port = 0

; Define the Index of the Write In Progress (WIP) bit in the status register. Valid options are in the range of 0 to 7.
WIPIndex = 0

; Define page size for commands. Valid sizes are PAGE256 and PAGE512.
PPSize = PAGE256

; Custom instructions to send to the external memory after initialization. Format is instruction code plus data to send in between optional brakets.
; These instructions will be executed each time the qspi peripheral is initiated by nrfjprog.
; To improve execution speed on consecutive interations with QSPI, you can run nrfjprog once with custom initialization, and then comment out the lines below.
; Numbers can be given in decimal, hex (starting with either 0x or 0X) and binary (starting with either 0b or 0B) formats.
; The custom instructions will be executed in the order found.
; This example includes two commands, first a WREN (WRite ENable) and then a WRSR (WRite Satus Register) enabling the Quad Operation and the High Performance
; mode for the MX25R6435F memory present in the nRF52840 DK.
;InitializationCustomInstruction =  0x06
;InitializationCustomInstruction =  0x01, [0x40, 0, 0x2]

; If retention is enabled, device RAM contents will be read and buffered during QSPI driver initialization.
; The buffered data will be written back to the device when unitializing the driver, restoring the original device RAM state.
; Enabled: RetainRAM = 1, Disabled: RetainRAM = 0
RetainRAM = 0

To use this file when programming, add the following lines to the application’s CMakeLists.txt file before the find_package() line:

macro(app_set_runner_args)
  # Replace with the filename of your ini file
  board_runner_args(nrfjprog "--qspiini=${CMAKE_CURRENT_SOURCE_DIR}/Qspi_thingy53.ini")
endmacro()

This will enable programming the target board successfully when using west flash.

Firmware updates

The build system will automatically generate the signed update files that can be loaded to a device to run the newer version of the application. The dfu_application.zip file also contains these files and you can use the nRF Connect for Mobile application (available for Android and iOS) to upload the firmware over Bluetooth® Low Energy.

File descriptions

The following table lists the files generated when building a QSPI XIP split-image application.

In some file names, <application> is the name of the application and <kernel_name> is the value of CONFIG_KERNEL_BIN_NAME.

File name and location

Description

<application>/zephyr/<kernel_name>.hex

Initial HEX output file, unsigned, containing internal and external QSPI flash data.

<application>/zephyr/<kernel_name>.internal.hex

Initial HEX output file, unsigned, containing internal flash data.

<application>/zephyr/<kernel_name>.external.hex

Initial HEX output file, unsigned, containing external QSPI flash data.

<application>/zephyr/<kernel_name>.internal.signed.hex

Signed internal flash data HEX file.

<application>/zephyr/<kernel_name>.external.signed.hex

Signed external QSPI flash data HEX file.

<application>/zephyr/<kernel_name>.signed.hex

Signed internal and external QSPI flash data HEX file.

<application>/zephyr/<kernel_name>.internal.signed.bin

Signed internal flash data binary file (for firmware updates).

<application>/zephyr/<kernel_name>.external.signed.bin

Signed external QSPI flash data binary file (for firmware updates).

mcuboot_secondary_app/zephyr/<kernel_name>.hex

Initial HEX output file, unsigned, containing internal and external QSPI flash data for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.internal.hex

Initial HEX output file, unsigned, containing internal flash data for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.external.hex

Initial HEX output file, unsigned, containing external QSPI flash data for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.internal.signed.hex

Signed internal flash data HEX file for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.external.signed.hex

Signed external QSPI flash data HEX file for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.signed.hex

Signed internal and external QSPI flash data HEX file for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.internal.signed.bin

Signed internal flash data binary file (for firmware updates) for direct-XIP mode secondary slot image.

mcuboot_secondary_app/zephyr/<kernel_name>.external.signed.bin

Signed external QSPI flash data binary file (for firmware updates) for direct-XIP mode secondary slot image.

merged.hex

Merged HEX file containing all images, includes both internal and external QSPI flash data for all images.

dfu_application.zip

Created if SB_CONFIG_DFU_ZIP is set, will contain the internal and external QSPI flash FOTA update images if SB_CONFIG_DFU_ZIP_APP is set.