Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,8 @@
/samples/nrf5340/empty_app_core/ @nrfconnect/ncs-si-muffin
/samples/nrf5340/extxip_smp_svr/ @nrfconnect/ncs-eris
/samples/nrf54h20/empty_app_core/ @nrfconnect/ncs-aurora
/samples/nrf54h20/idle_relocated_tcm/ @nrfconnect/ncs-si-muffin
/samples/nrf54h20/radio_loader/ @nrfconnect/ncs-si-muffin
/samples/ironside_se/ @nrfconnect/ncs-aurora
/samples/nrf_compress/ @nordicjm
/samples/nrf_profiler/ @nrfconnect/ncs-si-bluebagel
Expand Down Expand Up @@ -646,6 +648,7 @@
/samples/net/**/*.rst @nrfconnect/ncs-cia-doc
/samples/net/coap_client/*.rst @nrfconnect/ncs-iot-oulu-tampere-doc
/samples/nfc/**/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf54h20/idle_relocated_tcm/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf5340/empty_app_core/*.rst @nrfconnect/ncs-si-muffin-doc
/samples/nrf5340/extxip_smp_svr/*.rst @nrfconnect/ncs-eris-doc
/samples/nrf5340/netboot/*.rst @nrfconnect/ncs-eris-doc
Expand Down
29 changes: 26 additions & 3 deletions cmake/modules/kconfig.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
else()
include(${ZEPHYR_NRF_MODULE_DIR}/cmake/sysbuild/bootloader_dts_utils.cmake)

dt_chosen(code_partition PROPERTY "zephyr,code-partition")
dt_chosen(code_partition PROPERTY "fw-to-relocate")
if("${code_partition}" STREQUAL "")
dt_chosen(code_partition PROPERTY "zephyr,code-partition")
endif()

Comment on lines +32 to +36
Copy link
Contributor

@tejlmand tejlmand Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is all of this done under variant image ?

All the adjustments in this kconfig.cmake module was made so that one highlevel build can produce one image, and from there also create a variant image which uses the exact same configuration.
e4be060

I don't see how fw-to-relocated fits into this ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To my understanding (though I may be wrong), in the TCM-loader-enabled mode we have:

  • zephyr,code-partition: pointing to the same RAM memory, for both slots
  • fw-to-relocate: pointing to the MRAM, where the binary to be copied is stored

Since this logic is used when there are variant apps built, it means that we are in the Direct XIP update mode(s).

So now - as the zephyr,code-partition must be different for slot 0 and slot 1 in a regular Direct XIP, the fw-to-relocate must be different (to copy from either slot 0 or slot 1) once TCM-loader-enabled mode is used.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you're using devicetree to adjust Kconfig behind the scenes.

This file is kconfig.cmake, and kconfig.cmake is for kconfig handling and not intended for a devicetree handling / adjustments.

And before you start replying that devicetree is already done in this file, then I would like to direct to this PR #23562 .

This commit introduces a (probably temporary) way of fixing passing Kconfigs to a variant image.
...
Note: there were attempts to make a non-hack, clean fix in zephyr:
...
A quick solution for 3.1.0 is needed, as we already get complains about the issue

Starting to manipulate n-kconfig settings depending on featureA, featureB, featureC, ... etc is not scalable, and definitely not making it easier for NCS customers to adjust the solution to their needs without patching NCS itself.

Please take a look at the original implemetation for variant image e4be060 and notice how clean that is as it doesn't need special treatment for xyz settings.

just because something was introduced as a quick fix for 3.1 doesn't mean it's a proper way to do things.

@ahasztag why wasn't #23562 followed up after 3.1 was done ?

dt_partition_addr(code_partition_offset PATH "${code_partition}" REQUIRED)
dt_reg_size(code_partition_size PATH "${code_partition}" REQUIRED)

Expand All @@ -44,8 +48,24 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
# Additionally, convert primary slot dependencies to secondary slot dependencies.
set(dotconfig_variant_content)
foreach(line IN LISTS dotconfig_content)
dt_chosen(fw_to_relocate_property PROPERTY "fw-to-relocate")
if("${line}" MATCHES "^CONFIG_FLASH_LOAD_OFFSET=.*$")
string(REGEX REPLACE "CONFIG_FLASH_LOAD_OFFSET=(.*)" "CONFIG_FLASH_LOAD_OFFSET=${code_partition_offset}" line ${line})
# Change the CONFIG_FLASH_LOAD_OFFSET value only if the fw_to_relocate_property is empty - meaning that the firmware is not being relocated.
if("${fw_to_relocate_property}" STREQUAL "")
string(REGEX REPLACE "CONFIG_FLASH_LOAD_OFFSET=(.*)" "CONFIG_FLASH_LOAD_OFFSET=${code_partition_offset}" line ${line})
endif()
endif()
Comment on lines +51 to +57
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The dt_chosen() call for fw_to_relocate_property is executed inside the loop for every line in dotconfig_content. This is inefficient as the chosen node value doesn't change between iterations. Move the dt_chosen(fw_to_relocate_property PROPERTY \"fw-to-relocate\") call outside the loop, before line 50.

Copilot uses AI. Check for mistakes.

# Change the CONFIG_BUILD_OUTPUT_ADJUST_LMA value only if the fw_to_relocate_property is not empty - meaning that the firmware is being relocated.
if(NOT "${fw_to_relocate_property}" STREQUAL "")
if("${line}" MATCHES "^CONFIG_BUILD_OUTPUT_ADJUST_LMA=.*$")

dt_partition_addr(fw_to_relocate_offset ABSOLUTE PATH "${fw_to_relocate_property}" REQUIRED)
dt_chosen(tcm_code_property PROPERTY "zephyr,code-partition")
dt_reg_addr(tcm_code_addr PATH "${tcm_code_property}" REQUIRED)

string(REGEX REPLACE "CONFIG_BUILD_OUTPUT_ADJUST_LMA=(.*)" "CONFIG_BUILD_OUTPUT_ADJUST_LMA=${flash_base_addr}+${fw_to_relocate_offset}-${tcm_code_addr}" line ${line})
endif()
endif()

if("${line}" MATCHES "^CONFIG_FLASH_LOAD_SIZE=.*$")
Expand All @@ -62,7 +82,10 @@ if(CONFIG_NCS_IS_VARIANT_IMAGE)
set(autoconf_variant_content)
foreach(line IN LISTS autoconf_content)
if("${line}" MATCHES "^#define CONFIG_FLASH_LOAD_OFFSET .*$")
string(REGEX REPLACE "#define CONFIG_FLASH_LOAD_OFFSET (.*)" "#define CONFIG_FLASH_LOAD_OFFSET ${code_partition_offset}" line ${line})
# Change the CONFIG_FLASH_LOAD_OFFSET value only if the fw_to_relocate_property is empty - meaning that the firmware is not being relocated.
if("${fw_to_relocate_property}" STREQUAL "")
string(REGEX REPLACE "#define CONFIG_FLASH_LOAD_OFFSET (.*)" "#define CONFIG_FLASH_LOAD_OFFSET ${code_partition_offset}" line ${line})
endif()
endif()

if("${line}" MATCHES "^#define CONFIG_FLASH_LOAD_SIZE .*$")
Expand Down
6 changes: 5 additions & 1 deletion cmake/sysbuild/sign_nrf54h20.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ function(check_merged_slot_boundaries merged_partition images)
set(end_offset)
sysbuild_get(start_offset IMAGE ${image} VAR CONFIG_ROM_START_OFFSET KCONFIG)
sysbuild_get(end_offset IMAGE ${image} VAR CONFIG_ROM_END_OFFSET KCONFIG)
dt_chosen(code_flash TARGET ${image} PROPERTY "zephyr,code-partition")
dt_chosen(code_flash TARGET ${image} PROPERTY "fw-to-relocate")
if("${code_flash}" STREQUAL "")
dt_chosen(code_flash TARGET ${image} PROPERTY "zephyr,code-partition")
endif()

dt_partition_addr(code_addr PATH "${code_flash}" TARGET ${image} REQUIRED ABSOLUTE)
dt_reg_size(code_size TARGET ${image} PATH ${code_flash})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -855,6 +855,11 @@ Other samples
* Added a new testing step demonstrating how to calculate event propagation statistics.
Also added the related test preset for the :file:`calc_stats.py` script (:file:`nrf/scripts/nrf_profiler/stats_nordic_presets/app_event_manager_profiler_tracer.json`).

* Added:

* The :ref:`idle_relocated_tcm_sample` sample to demonstrate how to relocate the firmware to the TCM memory at boot time.
The sample also uses the ``radio_loader`` sample image (located in :file:`nrf/samples/nrf54h20/radio_loader`), which cannot be tested as a standalone sample, to relocate the firmware from the MRAM to the TCM memory at boot time.

Drivers
=======

Expand Down
1 change: 1 addition & 0 deletions doc/nrf/samples/other.rst
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,4 @@ This section lists single |NCS| samples for various uses that are not part of ot
../../../tests/benchmarks/multicore/*/README
../../../samples/zephyr/smp_svr_mini_boot/README
../../../samples/basic/*/README
../../../samples/nrf54h20/*/README
19 changes: 19 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})

if(NOT SYSBUILD)
message(FATAL_ERROR
" This is a multi-image application that should be built using sysbuild.\n"
" Add --sysbuild argument to west build command to prepare all the images.")
endif()

project(idle)

target_sources(app PRIVATE src/main.c)
24 changes: 24 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/Kconfig.sysbuild
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# Copyright (c) 2025 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#

source "share/sysbuild/Kconfig"

choice NETCORE

default NETCORE_CUSTOM_RADIO

config NETCORE_CUSTOM_RADIO
bool "Custom radio"
help
Use custom radio.

endchoice

config NETCORE_IMAGE_NAME
default "remote_rad" if NETCORE_CUSTOM_RADIO

config NETCORE_IMAGE_PATH
default "$(ZEPHYR_NRF_MODULE_DIR)/samples/nrf54h20/idle_relocated_tcm/remote" if NETCORE_CUSTOM_RADIO
230 changes: 230 additions & 0 deletions samples/nrf54h20/idle_relocated_tcm/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
.. _idle_relocated_tcm_sample:

Multicore idle test with firmware relocated to radio core TCM
#############################################################

.. contents::
:local:
:depth: 2

The test benchmarks the idle behavior of an application that runs on multiple cores.
It demonstrates a radio loader pattern where the radio core firmware is loaded from MRAM into TCM (Tightly Coupled Memory) at runtime.

Requirements
************

The test supports the following development kit:

.. table-from-rows:: /includes/sample_board_rows.txt
:header: heading
:rows: nrf54h20dk_nrf54h20_cpuapp

Overview
********

This test demonstrates how to build a multicore idle application with :ref:`configuration_system_overview_sysbuild` using a two-stage boot process for the radio core:

* Radio Loader - A small bootloader that runs on the radio core, copies firmware from MRAM to TCM, and jumps to it.
* Remote Firmware - The actual application that runs from TCM after being loaded.

The test automatically relocates the remote firmware binary to the correct MRAM address during build time, ensuring it can be loaded by the radio loader.

Architecture
============

The system uses the following memory layout:

* **MRAM (Non-volatile):**

* ``cpurad_loader_partition`` @ 0x92000 - Contains the radio loader (8 KB)
* ``cpurad_loaded_fw`` @ 0x94000 - Contains the remote firmware binary (128 KB)

* **TCM (Volatile, fast execution):**

* ``cpurad_ram0`` @ 0x23000000 - Code execution region (128 KB)
* ``cpurad_data_ram`` @ 0x23020000 - Data region (64 KB)

Additional files
================

The test comes with the following additional files:

* :file:`sysbuild.conf` - Enables the radio loader by setting ``CONFIG_NRF_RADIO_LOADER=y``.
* :file:`boards/memory_map.overlay` - Shared memory map configuration for both loader and remote firmware.
* :file:`sysbuild/radio_loader/` - Radio loader configuration overrides (:file:`prj.conf`, overlay).
* :file:`sysbuild/remote_rad/` - Radio core firmware configuration overrides (:file:`prj.conf`, overlay).

Enabling the Radio Loader
*************************

The radio loader is automatically added to the build when you enable it in sysbuild configuration.

In :file:`sysbuild.conf`:

.. code-block:: kconfig

SB_CONFIG_NRF_RADIO_LOADER=y

This single configuration option:

#. Automatically adds the ``radio_loader`` application located in the :file:`nrf/samples/nrf54h20/radio_loader` folder.
#. Builds it for the CPURAD core.
#. No manual ``ExternalZephyrProject_Add()`` needed in sysbuild.

Memory map configuration
========================

The memory map is defined in :file:`boards/memory_map.overlay` and is shared between the radio loader and remote firmware to ensure consistency.

The overlay defines:

#. TCM regions:

.. code-block:: devicetree

cpurad_ram0: sram@23000000 {
compatible = "mmio-sram";
reg = <0x23000000 0x20000>; /* 128 KB for code */
};
cpurad_data_ram: sram@23020000 {
compatible = "mmio-sram";
reg = <0x23020000 0x10000>; /* 64 KB for data */
};

#. MRAM partitions:

.. code-block:: devicetree

&mram1x {
/delete-node/ partitions;

partitions {
compatible = "fixed-partitions";
#address-cells = <1>;
#size-cells = <1>;

cpurad_loader_partition: partition@92000 {
label = "cpurad_loader_partition";
reg = <0x92000 DT_SIZE_K(8)>; /* 8 KB allocated (~4 KB actual) */
};

cpurad_loaded_fw: partition@94000 {
label = "cpurad_loaded_fw";
reg = <0x94000 DT_SIZE_K(128)>; /* 128 KB fixed */
};
};
};

Automatic firmware relocation
*****************************

The remote firmware must be relocated to match the MRAM partition address where it will be stored.
This is automatically done by Zephyr's ``CONFIG_BUILD_OUTPUT_ADJUST_LMA`` feature when the devicetree chosen nodes are configured correctly.

How it works
============

Firmware relocation is handled automatically by Zephyr's build system using the ``CONFIG_BUILD_OUTPUT_ADJUST_LMA`` configuration option, which is configured in ``zephyr/soc/nordic/nrf54h/Kconfig.defconfig.nrf54h20_cpurad`` for all nRF54H20 CPURAD projects.

The configuration automatically detects the ``fw-to-relocate`` chosen node in your devicetree.
When present, it calculates the LMA adjustment to relocate firmware from MRAM to TCM.
Without this chosen node, firmware runs directly from the ``zephyr,code-partition`` location (standard XIP behavior).

Simply configure the devicetree chosen nodes correctly in your firmware's overlay:

.. code-block:: devicetree

/{
chosen {
/* VMA: where code runs (TCM) */
zephyr,code-partition = &cpurad_ram0;
zephyr,sram = &cpurad_data_ram;

/* LMA: where to load from (MRAM partition) - enables relocation */
fw-to-relocate = &cpurad_loaded_fw;
};
};

Zephyr automatically calculates the Load Memory Address (LMA) adjustment based on your chosen nodes:

**With fw-to-relocate chosen node** (for radio loader pattern):

.. code-block:: text

LMA_adjustment = fw-to-relocate address - zephyr,code-partition address
= cpurad_loaded_fw - cpurad_ram0
= 0x94000 - 0x23000000

**Without fw-to-relocate** (standard behavior):

.. code-block:: text

LMA_adjustment = zephyr,code-partition address - zephyr,sram address

The build system then adjusts the hex file so that the firmware is loaded from MRAM (``0x94000``), but runs from TCM (``0x23000000``).

Building and running
********************

.. |test path| replace:: :file:`samples/nrf54h20/idle_relocated_tcm`

.. include:: /includes/build_and_run_test.txt

Testing
=======

After programming the test to your development kit, complete the following steps to test it:

1. |connect_terminal|
#. Reset the kit.
#. Observe the console output for both cores:

* For the application core, the output should be as follows:

.. code-block:: console

*** Booting nRF Connect SDK zephyr-v3.5.0-3517-g9458a1aaf744 ***
build time: Nov 22 2025 17:00:59
Multicore idle test on [email protected]/nrf54h20/cpuapp
Multicore idle test iteration 0
Multicore idle test iteration 1
...

* For the radio core, the output should be as follows:

.. code-block:: console

*** Booting nRF Connect SDK zephyr-v3.5.0-3517-g9458a1aaf744 ***
build time: Nov 22 2025 17:00:29
Multicore idle test on [email protected]/nrf54h20/cpurad
Current PC (program counter) address: 0x23000ae0
Multicore idle test iteration 0
Multicore idle test iteration 1
...

The radio loader first loads the firmware from MRAM (``0x0e094000``) to TCM (``0x23000000``) and then jumps to the loaded firmware.
This process is transparent and happens during the early boot stage.

#. Verify the DFU process:

#. Build the firmware for the secondary app slot, increase the version number in the :file:`prj.conf` file (uncomment the line):

.. code-block:: kconfig

CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="0.0.1+0"

#. Build the firmware:

.. code-block:: console

west build -p -b nrf54h20dk/nrf54h20/cpuapp

#. Program the firmware to the secondary application slot:

.. code-block:: console

nrfutil device program --firmware build/zephyr_secondary_app.merged.hex --options chip_erase_mode=ERASE_NONE

Reset the development kit.
The firmware must boot from the secondary application slot.
Observe the change in build time in the console output.
Loading