Code relocation to the external flash with Mcuboot

Hello,

I am using edge impulse library to achieve on-device classification. I now want to put the edge impulse related code to the external flash and use XIP function there rather than placing them inside my internal flash. I have learned something from the code relocation sample from NCS and zephyr. I now have my own linker script, modified CMakeList and prj.conf, which I show below. For cmakelist: 

zephyr_code_relocate(FILES src/feature/gait_analysis.c LOCATION EXTFLASH_TEXT NOCOPY)
zephyr_code_relocate(FILES src/feature/gait_analysis.c LOCATION EXTFLASH_RODATA NOCOPY)
For linker script: 
#include <zephyr/linker/sections.h>
#include <zephyr/devicetree.h>
#include <zephyr/linker/linker-defs.h>
#include <zephyr/linker/linker-tool.h>

MEMORY
{
    /* XIP region for Edge Impulse model code + rodata */
    EXTFLASH (rx) : ORIGIN = 0x10120000, LENGTH = 0x00400000
}
#include <zephyr/arch/arm/cortex_m/scripts/linker.ld>
and for prj.conf: 
CONFIG_CODE_DATA_RELOCATION=y
CONFIG_XIP=y
CONFIG_NORDIC_QSPI_NOR_XIP=y
CONFIG_HAVE_CUSTOM_LINKER_SCRIPT=y
CONFIG_CUSTOM_LINKER_SCRIPT="linker_arm_nocopy.ld"
. The build process can show part of the code is indeed placed into our external flash partition. But it is complaining about this: Error: Image size (0x10110be8) + trailer (0x630) exceeds requested size 0xe0000. I suspect this is because the relocated section is still included in the mcuboot binary image. We will need to keep mcuboot because we want to use dfu-util on our host machine for firmware upgrade. Is there a way to solve this issue? 

  • OK so this can be solved by following this guidance: https://docs.nordicsemi.com/bundle/ncs-latest/page/nrf/app_dev/device_guides/nrf53/qspi_xip_guide_nrf5340.html#indication_of_xip_performance

    My question now is how to actually place the edge impulse large machine learning model to the external flash. In the above page, it is clearly stated that the XIP performance is done by "move the Edge Impulse library to external memory." What LIBRARY name should be passed here in the CMakeLists file to make it work? I have tried: zephyr_code_relocate(LIBRARY edge_impulse LOCATION EXTFLASH_TEXT NOCOPY) but not working. Any help please. 

  • Hi there,

    SO ,

    Key point: MCUboot assumes the application image is one contiguous region of flash.
    It uses __rom_start__ and __rom_end__ from the linker to compute “image size”. That’s just:

    image_size = __rom_end__ - __rom_start__

    If any section is placed at 0x1012_0000 (external QSPI region), __rom_end__ becomes ~0x1012xxxx. So from the point of view of imgtool/MCUboot, your “image” appears to span from address 0x0000xxxx all the way up to 0x1012xxxx. That’s why the “image size” looks like 0x10110be8 – it’s not the amount of code, it’s the distance between the lowest and highest load address.

    So:

    • the relocated XIP section is still part of the MCUboot image, and it completely blows up the image size calculation.

    • Stock MCUboot does not understand “one image spread over disjoint flash ranges (internal + QSPI XIP)” as a single signed binary.

       Short version:

    You can’t just drop XIP sections into external flash and still expect a normal, single MCUboot image (with DFU, signatures, trailers, etc.) to “just work”.

    You have to choose one of these design patterns:

    Option 1 – Keep MCUboot simple, treat external flash as a separate asset

    Concept:

    • The “MCUboot image” lives entirely in internal flash.

    • External QSPI flash holds model / feature code or data, but it is not part of the MCUboot image.

    • You program/update external flash via some other mechanism (second DFU alt-setting, custom mcumgr command, dedicated updater image, etc.).

    What to change technically:

    1. Do not add EXTFLASH to MEMORY in your application linker script in a way that moves __rom_end__.

    2. Keep the normal Zephyr/MCUboot linker flow for internal flash.

    3. For QSPI XIP sections, either:

      • Build a second image or raw binary linked at 0x10120000, and flash it separately, or

      • Keep the model as data and explicitly read/execute from it (e.g., copy critical parts to RAM before running).

    This is the least fragile route if you “must keep MCUboot + dfu-util”.

    Option 2 – Multi-image / external-flash partition managed by NCS

    If you really want a “single update story”, then:

    • Use NCS Partition Manager (pm_static.yml) to define:

      • mcuboot, image_0 (primary), image_1 or scratch in internal flash.

      • A separate extflash_app (or ei_model) partition in external flash, not part of image_0.

    HTH

    GL :-) PJ :v:

  • Hi, yes that bit I can understand now. I am following your option 2 now, which is to create another image but to save data. This works quite well for my self defined source code like gait_analysis.c. But when I try to move edge impulse library, this won't work. There is no error, but the edge impulse code is still there in the internal flash. 

    However as I said earlier, I can see moving edge impulse library to the external flash is mentioned in nordic official page. So I was wondering how can I actually achieve that. 

  • Hi there,

    The error you’re seeing:

    Image size (0x10110be8) + trailer (0x630) exceeds requested size 0xe0000

    comes from the way MCUboot calculates the image size. It uses the lowest and highest load addresses in the linked image (__rom_start__ and __rom_end__).

    Because your XIP sections are linked into external flash at 0x10120000, the linker now thinks your application image runs from internal flash up to 0x1012xxxx, so the “image size” becomes enormous. That’s why it no longer fits into the configured slot (0xE0000).

    MCUboot doesn’t support a single signed image that spans both internal flash and an external XIP region like that. The usual patterns are:

    1. Keep the MCUboot-managed image entirely in internal flash, and treat the QSPI/XIP region as a separate asset that you program/update independently.

    2. Use NCS Partition Manager to define an external-flash partition and build the XIP code as another image or artifact, updated via a second DFU alt-setting or a custom mechanism.

    If you must keep MCUboot + dfu-util, the simplest fix is to move your XIP code out of the MCUboot image: don’t let the external flash MEMORY and sections extend __rom_end__. Keep the MCUboot app confined to internal flash, and handle the model/QSPI content separately.

    HTH

    GL :-) PJ :v:

  • Hi there,

    Here are the practical ways to do that.

    1. Factory / dev-time: flash QSPI as a second image

    Idea:
    Keep MCUboot + main app in internal flash only. Put the Edge Impulse model / code that lives in external flash into a separate .hex/.bin and flash that directly to QSPI at the fixed XIP address.

    Steps

    1. Define the external flash in devicetree and partition manager

      In app.overlay, make sure the QSPI flash is enabled and marked as external flash:

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

    (Use the actual node name for the chip on their board.)

    2. Use Zephyr’s code relocation for XIP
    What you already have is basically right:

    zephyr_code_relocate(FILES src/feature/gait_analysis.c LOCATION EXTFLASH_TEXT NOCOPY)
    zephyr_code_relocate(FILES src/feature/gait_analysis.c LOCATION EXTFLASH_RODATA NOCOPY)
    
    And a custom linker script that puts EXTFLASH_TEXT / EXTFLASH_RODATA at the QSPI address.
    3.Generate a separate image for the QSPI content

    After building, use arm-none-eabi-objcopy (or west build artifacts) to extract only the external-flash sections into a second .hex/.bin:

    # Example: create a secondary hex for QSPI contents
    arm-none-eabi-objcopy \
        -O ihex build/zephyr/zephyr.elf \
        --only-section=.extflash_text \
        --only-section=.extflash_rodata \
        extflash_model.hex
    
    (Section names depend on how EXTFLASH_TEXT/EXTFLASH_RODATA are defined.)
    4.Program both images separately
    • Internal flash (MCUboot + app):

    west flash  # or nrfjprog --program app_mcuboot.hex --chiperase
    

    External QSPI contents (at correct ORIGIN, e.g. 0x10120000):

    nrfjprog --program extflash_model.hex --verify --sectorerase --qspi
    

    Or configure a custom west flash --hex-file extflash_model.hex runner that knows how to talk to QSPI.

    RESULT:

    MCUboot image stays within its partition size, and QSPI holds the model/code at a fixed address. No more Image size (0x10110be8) + trailer ... exceeds requested size because those XIP sections are not counted inside the MCUboot image anymore.

    Use a proper SPLIT image to update via DFU the Image Model , Nordic has QSPI XIP split-image feature. V

    HTH

    GL :-) PJ :v:

Related