Skip to content

Commit 2287cc7

Browse files
committed
samples: radio_loader: Add radio core firmware loader
Add radio_loader sample for nRF54H20 that implements a two-stage boot process for the radio core. The loader copies firmware from MRAM to TCM (Tightly Coupled Memory) at boot time and jumps to the loaded firmware. The loader uses devicetree chosen nodes to specify source and destination memory regions: - zephyr,loaded-fw-src: Source partition in MRAM - zephyr,loaded-fw-dst: Destination region in TCM JIRA: NCSDK-36461 Signed-off-by: Jan Zyczkowski <[email protected]>
1 parent ffc451e commit 2287cc7

File tree

9 files changed

+275
-0
lines changed

9 files changed

+275
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
cmake_minimum_required(VERSION 3.20.0)
8+
9+
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
10+
11+
project(loader)
12+
13+
target_sources(app PRIVATE src/main.c)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
8+
/* Dummy chosen node to satisfy the build */
9+
/{
10+
chosen {
11+
zephyr,loaded-fw-src = &cpuapp_slot0_partition;
12+
zephyr,loaded-fw-dst = &cpurad_ram0;
13+
};
14+
};
15+
16+
&cpuapp_slot0_partition {
17+
reg = <0x92000 DT_SIZE_K(128)>;
18+
};
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
# ==============================================================================
8+
# Optimized minimal configuration for radio_loader
9+
# ==============================================================================
10+
# This loader only runs SYS_INIT(load_and_jump_to_firmware, EARLY, 0) which:
11+
# 1. Copies firmware from MRAM to TCM using memcpy()
12+
# 2. Jumps to the loaded firmware's reset handler
13+
# No Zephyr services, threading, or drivers are needed.
14+
#
15+
# Memory footprint target: < 4 KB
16+
17+
# ==============================================================================
18+
# Power Management
19+
# ==============================================================================
20+
CONFIG_PM=n
21+
CONFIG_PM_DEVICE=n
22+
23+
# ==============================================================================
24+
# Kernel - Threading Disabled
25+
# ==============================================================================
26+
# We never reach main() or start the scheduler, so disable all threading
27+
CONFIG_MULTITHREADING=n
28+
CONFIG_MAIN_STACK_SIZE=512
29+
CONFIG_THREAD_STACK_INFO=n
30+
31+
# Disable kernel features that require threading/scheduler
32+
CONFIG_EVENTS=n
33+
CONFIG_POLL=n
34+
CONFIG_TIMESLICING=n
35+
36+
# ==============================================================================
37+
# Console, Debug, and Logging
38+
# ==============================================================================
39+
# No console output needed - loader jumps immediately to firmware
40+
CONFIG_CONSOLE=n
41+
CONFIG_UART_CONSOLE=n
42+
CONFIG_SERIAL=n
43+
CONFIG_PRINTK=n
44+
CONFIG_EARLY_CONSOLE=n
45+
CONFIG_LOG=n
46+
47+
# Banners and debug features
48+
CONFIG_NCS_BOOT_BANNER=n
49+
CONFIG_BOOT_BANNER=n
50+
CONFIG_ERRNO=n
51+
52+
# ==============================================================================
53+
# Device Drivers
54+
# ==============================================================================
55+
# No peripheral drivers needed - we only use memcpy and jump
56+
CONFIG_GPIO=n
57+
CONFIG_PINCTRL=n
58+
CONFIG_I2C=n
59+
CONFIG_SPI=n
60+
CONFIG_WATCHDOG=n
61+
62+
# ==============================================================================
63+
# Interrupt Management
64+
# ==============================================================================
65+
CONFIG_DYNAMIC_INTERRUPTS=n
66+
CONFIG_IRQ_OFFLOAD=n
67+
CONFIG_GEN_IRQ_VECTOR_TABLE=n
68+
CONFIG_GEN_ISR_TABLES=n
69+
CONFIG_GEN_SW_ISR_TABLE=n
70+
71+
# ==============================================================================
72+
# Hardware Protection
73+
# ==============================================================================
74+
CONFIG_HW_STACK_PROTECTION=n
75+
CONFIG_ARM_MPU=n
76+
77+
# ==============================================================================
78+
# Security and Crypto
79+
# ==============================================================================
80+
# No crypto needed for simple memory copy operation
81+
CONFIG_NRF_SECURITY=n
82+
CONFIG_MBEDTLS_PSA_CRYPTO_C=n
83+
CONFIG_PSA_CRYPTO_DRIVER_OBERON=n
84+
CONFIG_PSA_CRYPTO=n
85+
CONFIG_PSA_SSF_CRYPTO_CLIENT=n
86+
87+
# ==============================================================================
88+
# Memory Optimization
89+
# ==============================================================================
90+
CONFIG_HEAP_MEM_POOL_SIZE=0
91+
CONFIG_SYS_HEAP_RUNTIME_STATS=n
92+
93+
# Use nano printf for minimal footprint (only used if PRINTK somehow gets enabled)
94+
CONFIG_CBPRINTF_NANO=y
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/kernel.h>
8+
9+
/* The loader uses devicetree chosen nodes to specify source
10+
* and destination memory regions:
11+
* - zephyr,loaded-fw-src: Source partition in NVM
12+
* - zephyr,loaded-fw-dst: Destination region in RAM
13+
*/
14+
15+
#define LOADED_FW_NVM_NODE DT_CHOSEN(zephyr_loaded_fw_src)
16+
#define LOADED_FW_NVM_PARTITION_NODE DT_PARENT(DT_PARENT(LOADED_FW_NVM_NODE))
17+
#define LOADED_FW_NVM_ADDR (DT_REG_ADDR(LOADED_FW_NVM_NODE) + \
18+
DT_REG_ADDR(LOADED_FW_NVM_PARTITION_NODE))
19+
#define LOADED_FW_NVM_SIZE DT_REG_SIZE(LOADED_FW_NVM_NODE)
20+
21+
#define LOADED_FW_RAM_NODE DT_CHOSEN(zephyr_loaded_fw_dst)
22+
#define LOADED_FW_RAM_ADDR DT_REG_ADDR(LOADED_FW_RAM_NODE)
23+
#define LOADED_FW_RAM_SIZE DT_REG_SIZE(LOADED_FW_RAM_NODE)
24+
25+
/* Verify devicetree configuration at build time */
26+
BUILD_ASSERT(DT_NODE_EXISTS(DT_CHOSEN(zephyr_loaded_fw_src)),
27+
"Missing chosen node: zephyr,loaded-fw-src");
28+
BUILD_ASSERT(DT_NODE_EXISTS(DT_CHOSEN(zephyr_loaded_fw_dst)),
29+
"Missing chosen node: zephyr,loaded-fw-dst");
30+
BUILD_ASSERT(LOADED_FW_NVM_SIZE <= LOADED_FW_RAM_SIZE,
31+
"Firmware size exceeds available TCM RAM");
32+
33+
/**
34+
* @brief Copy firmware from MRAM to TCM and jump to it
35+
*
36+
* This function runs as SYS_INIT(EARLY, 0) before main() and the scheduler.
37+
* It copies the firmware from MRAM to TCM for optimal performance, then
38+
* transfers execution to the loaded firmware's reset handler.
39+
*
40+
* This function never returns - execution transfers to the loaded firmware.
41+
*
42+
* @return 0 on success (never reached), -1 on failure (never reached)
43+
*/
44+
static int load_and_jump_to_firmware(void)
45+
{
46+
/* Copy firmware from MRAM to TCM */
47+
memcpy((void *)LOADED_FW_RAM_ADDR, (void *)LOADED_FW_NVM_ADDR, LOADED_FW_NVM_SIZE);
48+
49+
/* Extract reset handler from ARM Cortex-M vector table (entry 1) */
50+
uint32_t *vector_table = (uint32_t *)LOADED_FW_RAM_ADDR;
51+
typedef void reset_handler_t(void);
52+
reset_handler_t *reset_handler = (reset_handler_t *)(vector_table[1]);
53+
54+
/* Jump to loaded firmware - this never returns */
55+
reset_handler();
56+
57+
/* Should never reach here */
58+
return -1;
59+
}
60+
61+
SYS_INIT(load_and_jump_to_firmware, EARLY, 0);
62+
63+
/**
64+
* @brief Main function - should never be reached
65+
*
66+
* If we reach main(), the firmware load and jump failed.
67+
* This indicates a critical error in the loader.
68+
*/
69+
int main(void)
70+
{
71+
#ifdef CONFIG_PRINTK
72+
printk("ERROR: Firmware jump failed!\n");
73+
#endif
74+
while (1) {
75+
/* Hang here if jump fails */
76+
}
77+
return -1;
78+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
common:
2+
sysbuild: true
3+
tags:
4+
- ci_build
5+
- ci_samples_nrf54h20
6+
7+
tests:
8+
radio_loader.nrf54h20dk_cpurad:
9+
platform_allow:
10+
- nrf54h20dk/nrf54h20/cpurad
11+
integration_platforms:
12+
- nrf54h20dk/nrf54h20/cpurad

sysbuild/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,6 +1040,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/appcore.cmake)
10401040
include(${CMAKE_CURRENT_LIST_DIR}/netcore.cmake)
10411041
include(${CMAKE_CURRENT_LIST_DIR}/flprcore.cmake)
10421042
include(${CMAKE_CURRENT_LIST_DIR}/pprcore.cmake)
1043+
include(${CMAKE_CURRENT_LIST_DIR}/radioloader.cmake)
10431044
include(${CMAKE_CURRENT_LIST_DIR}/secureboot.cmake)
10441045
include(${CMAKE_CURRENT_LIST_DIR}/mcuboot.cmake)
10451046

sysbuild/Kconfig.radioloader

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
config NRF_RADIO_LOADER
8+
bool "Radio Core Firmware Loader [EXPERIMENTAL]"
9+
depends on SOC_NRF54H20
10+
select EXPERIMENTAL
11+
help
12+
Enable the radio loader that copies firmware from MRAM to TCM
13+
(Tightly Coupled Memory) at boot time.
14+
15+
The loader runs from MRAM and:
16+
- Copies firmware from MRAM partition to TCM
17+
- Jumps to the loaded firmware in TCM for optimal performance
18+
19+
Requires devicetree memory map configuration with partitions
20+
and chosen nodes defined in the project's overlay files.
21+
22+
if NRF_RADIO_LOADER
23+
24+
config NRF_RADIO_LOADER_BOARD
25+
string
26+
default "nrf54h20dk/nrf54h20/cpurad"
27+
help
28+
Target board for the radio loader application.
29+
30+
endif # NRF_RADIO_LOADER

sysbuild/Kconfig.sysbuild

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ rsource "Kconfig.appcore"
8181
rsource "Kconfig.flprcore"
8282
rsource "Kconfig.netcore"
8383
rsource "Kconfig.pprcore"
84+
rsource "Kconfig.radioloader"
8485
rsource "Kconfig.secureboot"
8586
rsource "Kconfig.mcuboot"
8687
rsource "Kconfig.dfu"

sysbuild/radioloader.cmake

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
# Radio Loader sysbuild integration
8+
# This file handles automatic integration of the radio_loader application
9+
10+
if(SB_CONFIG_NRF_RADIO_LOADER)
11+
message(STATUS "Adding radio_loader application")
12+
message(STATUS "Board: ${SB_CONFIG_NRF_RADIO_LOADER_BOARD}")
13+
14+
# Add radio_loader as an external Zephyr project
15+
ExternalZephyrProject_Add(
16+
APPLICATION radio_loader
17+
SOURCE_DIR "${ZEPHYR_NRF_MODULE_DIR}/samples/nrf54h20/radio_loader"
18+
BOARD ${SB_CONFIG_NRF_RADIO_LOADER_BOARD}
19+
BOARD_REVISION ${BOARD_REVISION}
20+
)
21+
22+
23+
UpdateableImage_Add(APPLICATION radio_loader)
24+
# Note: Memory map configuration should be provided by the user project
25+
# in: sysbuild/radio_loader/boards/<board>.overlay
26+
# This overlay should define partitions and chosen nodes for the loader
27+
28+
endif()

0 commit comments

Comments
 (0)