Skip to content

Commit f8c4da6

Browse files
committed
tests: bootloader: Add SB_CLEANUP_RAM tests
This commit adds tests to verify if the NSIB RAM cleanup functionality works as expected. Signed-off-by: Artur Hadasz <[email protected]>
1 parent dddc3f3 commit f8c4da6

File tree

13 files changed

+339
-0
lines changed

13 files changed

+339
-0
lines changed
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
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+
project(NONE)
11+
12+
FILE(GLOB app_sources src/*.c)
13+
target_sources(app PRIVATE ${app_sources})
14+
target_include_directories(app PRIVATE .)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include "../sysbuild/nrf54l15dk_nrf54l15_cpuapp_common.dtsi"
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
cmake_minimum_required(VERSION 3.20.0)
8+
9+
zephyr_library()
10+
zephyr_library_sources(src/b0_cleanup_ram_test_prepare.c)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build:
2+
cmake: zephyr
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/init.h>
8+
#include <zephyr/device.h>
9+
#include <zephyr/sys/printk.h>
10+
#include <hal/nrf_timer.h>
11+
#include <zephyr/linker/linker-defs.h>
12+
#include <zephyr/drivers/retained_mem.h>
13+
14+
#define CLEANUP_RAM_GAP_START ((uint32_t)__ramfunc_start)
15+
#define CLEANUP_RAM_GAP_END ((uint32_t) __ramfunc_end)
16+
17+
/* Only the RAM outside of the defined linker sections will be populated,
18+
* as modifying the defined linker sections could lead to NSIB malfunction.
19+
*/
20+
#define RAM_TO_POPULATE_START ((uint32_t) _image_ram_end)
21+
#define RAM_TO_POPULATE_END (CONFIG_SRAM_SIZE * 1024)
22+
#define VALUE_TO_POPULATE 0xDEADBEAF
23+
24+
/* As peripheral registers are not cleared during RAM cleanup, we can
25+
* use a register of a peripheral unused by NSIB to save data which can then
26+
* be read by the application.
27+
* We use this trick to save the start and end addresses of the RAMFUNC region.
28+
* This region is not erased during the RAM cleanup, as it contains the code responsible
29+
* for performing the RAM cleanup, so it must be skipped when checking if the RAM cleanup
30+
* has been successfully performed.
31+
* The TIMER00 is not used by NSIB, and is therefore its CC[0] and CC[1] registers
32+
* are used for this purpose.
33+
*/
34+
#define RAMFUNC_START_SAVE_REGISTER NRF_TIMER00->CC[0]
35+
#define RAMFUNC_END_SAVE_REGISTER NRF_TIMER00->CC[1]
36+
37+
#define RETAINED_DATA "RETAINED"
38+
39+
static int populate_ram(void)
40+
{
41+
RAMFUNC_START_SAVE_REGISTER = CLEANUP_RAM_GAP_START;
42+
RAMFUNC_END_SAVE_REGISTER = CLEANUP_RAM_GAP_END;
43+
44+
for (uint32_t addr = RAM_TO_POPULATE_START; addr < RAM_TO_POPULATE_END;
45+
addr += sizeof(uint32_t)) {
46+
*(uint32_t *) addr = VALUE_TO_POPULATE;
47+
}
48+
49+
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_retained_ram)
50+
const struct device *retained_mem_dev =
51+
DEVICE_DT_GET(DT_INST(0, zephyr_retained_ram));
52+
size_t retained_size;
53+
54+
if (!device_is_ready(retained_mem_dev)) {
55+
printk("Retained memory device is not ready");
56+
return -ENODEV;
57+
}
58+
59+
retained_size = retained_mem_size(retained_mem_dev);
60+
61+
if (retained_size < strlen(RETAINED_DATA)) {
62+
printk("Retained memory size is too small");
63+
return -EINVAL;
64+
}
65+
66+
retained_mem_write(retained_mem_dev, 0, RETAINED_DATA, strlen(RETAINED_DATA));
67+
#endif
68+
return 0;
69+
}
70+
71+
SYS_INIT(populate_ram, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY);
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
CONFIG_ZTEST=y
7+
CONFIG_ARM_MPU=n
8+
CONFIG_SOC_EARLY_RESET_HOOK=y
9+
10+
CONFIG_RETAINED_MEM=y
11+
CONFIG_RETAINED_MEM_ZEPHYR_RAM=y
12+
CONFIG_RETAINED_MEM_NRF_GPREGRET=n
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* Copyright (c) 2025 Nordic Semiconductor ASA
3+
*
4+
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
*/
6+
7+
#include <zephyr/ztest.h>
8+
#include <hal/nrf_timer.h>
9+
#include <zephyr/drivers/retained_mem.h>
10+
11+
/* As peripheral registers are not cleared during RAM cleanup, we can
12+
* use a register of a peripheral unused by NSIB to save data which can then
13+
* be read by the application.
14+
* We use this trick to save the start and end addresses of the RAMFUNC region.
15+
* This region is not cleared during the RAM cleanup, as it contains the code responsible
16+
* for performing the RAM cleanup, so it must be skipped when checking if the RAM cleanup
17+
* has been successfully performed.
18+
* The TIMER00 is not used by NSIB, and is therefore its CC[0] and CC[1] registers
19+
* are used for this purpose.
20+
*/
21+
#define RAMFUNC_START_SAVE_REGISTER NRF_TIMER00->CC[0]
22+
#define RAMFUNC_END_SAVE_REGISTER NRF_TIMER00->CC[1]
23+
24+
/* Using 2-byte magic values allows to load the whole value with movw */
25+
#define RAM_CLEANUP_SUCCESS_MAGIC 0x5678
26+
#define RAM_CLEANUP_FAILURE_MAGIC 0x4321
27+
28+
static uint32_t __noinit ram_cleanup_result;
29+
static uint32_t __noinit uncleared_address;
30+
static uint32_t __noinit uncleared_value;
31+
32+
#define EXPECTED_RETAINED_DATA "RETAINED"
33+
34+
/**
35+
* This hook needs to have the attribute __attribute__((naked)),
36+
* as otherwise the stack would be modified when calling it, leading to
37+
* test failure, as part of the RAM in which the stack resides wouldn't
38+
* be zeroed.
39+
*/
40+
__attribute__((naked)) void soc_early_reset_hook(void)
41+
{
42+
__asm__ volatile (
43+
/* Load zero (value used to clear memory) into r0 */
44+
" mov r0, #0\n"
45+
/* Explicitly clear the ram_cleanup_result variable */
46+
" mov r2, %6\n"
47+
" str r0, [r2]\n"
48+
/* Load the location of the saved ram func start address to r1 */
49+
" mov r1, %0\n"
50+
/* Load the ram func start address to r2 */
51+
" ldr r2, [r1]\n"
52+
/* Load the location of the saved ram func end address to r1 */
53+
" mov r1, %1\n"
54+
/* Load the ram func end address to r3*/
55+
" ldr r3, [r1]\n"
56+
/* Clear memory from ram func start to ram func end.
57+
* The area is skipped during the RAM cleanup,
58+
* so it is not cleared.
59+
* Simply cleaning it up here is quicker
60+
* than verifying if the current address is within the gap
61+
* in each iteration of the loop.
62+
*/
63+
"ram_func_zero_loop:\n"
64+
" cmp r2, r3\n"
65+
" bge ram_func_zero_loop_done\n"
66+
" str r0, [r2]\n"
67+
/* Increment the address by 4 (word size) */
68+
" add r2, r2, #4\n"
69+
" b ram_func_zero_loop\n"
70+
"ram_func_zero_loop_done:\n"
71+
/* Verify that all of the RAM memory has been cleared */
72+
/* Load SRAM base address to r1 */
73+
" mov r1, %2\n"
74+
/* Load SRAM size to r2 */
75+
" mov r2, %3\n"
76+
/* Calculate SRAM end address (base + size) */
77+
" add r2, r1, r2\n"
78+
"verify_ram_loop:\n"
79+
" cmp r1, r2\n"
80+
" bge verify_ram_done\n"
81+
/* Load value at current address and verify if it equals 0 */
82+
" ldr r3, [r1]\n"
83+
" cmp r3, #0\n"
84+
" bne ram_cleanup_failed\n"
85+
/* Increment the address by 4 (word size) */
86+
" add r1, r1, #4\n"
87+
" b verify_ram_loop\n"
88+
"ram_cleanup_failed:\n"
89+
/* Load RAM_CLEANUP_FAILURE_MAGIC into r0 */
90+
" movw r0, %4\n"
91+
/* Save the uncleared address in uncleared_address variable */
92+
" mov r2, %7\n"
93+
" str r1, [r2]\n"
94+
/* Save the uncleared value in uncleared_value variable */
95+
" mov r2, %8\n"
96+
" str r3, [r2]\n"
97+
" b verification_done\n"
98+
"verify_ram_done:\n"
99+
/* Load RAM_CLEANUP_SUCCESS_MAGIC */
100+
" movw r0, %5\n"
101+
"verification_done:\n"
102+
/* Store result in ram_cleanup_result variable */
103+
" mov r2, %6\n"
104+
" str r0, [r2]\n"
105+
/* __attribute__((naked)) requires manual branching */
106+
" bx lr\n"
107+
:
108+
: "r" (&RAMFUNC_START_SAVE_REGISTER),
109+
"r" (&RAMFUNC_END_SAVE_REGISTER),
110+
"r" (CONFIG_SRAM_BASE_ADDRESS),
111+
"r" (CONFIG_SRAM_SIZE * 1024),
112+
"i" (RAM_CLEANUP_FAILURE_MAGIC),
113+
"i" (RAM_CLEANUP_SUCCESS_MAGIC),
114+
"r" (&ram_cleanup_result),
115+
"r" (&uncleared_address),
116+
"r" (&uncleared_value)
117+
: "r0", "r1", "r2", "r3", "lr", "memory"
118+
);
119+
}
120+
121+
ZTEST(b0_ram_cleanup, test_ram_cleanup)
122+
{
123+
zassert_true((ram_cleanup_result == RAM_CLEANUP_SUCCESS_MAGIC) ||
124+
(ram_cleanup_result == RAM_CLEANUP_FAILURE_MAGIC),
125+
"RAM cleanup result should be either success or failure");
126+
zassert_equal(ram_cleanup_result, RAM_CLEANUP_SUCCESS_MAGIC,
127+
"Uncleared word detected at address %p, value 0x%x", uncleared_address,
128+
uncleared_value);
129+
130+
#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_retained_ram)
131+
const struct device *retained_mem_dev =
132+
DEVICE_DT_GET(DT_INST(0, zephyr_retained_ram));
133+
size_t retained_size;
134+
uint8_t buffer[strlen(EXPECTED_RETAINED_DATA)+1];
135+
136+
zassert_true(device_is_ready(retained_mem_dev), "Retained memory device is not ready");
137+
138+
retained_size = retained_mem_size(retained_mem_dev);
139+
140+
zassert_true(retained_size >= strlen(EXPECTED_RETAINED_DATA),
141+
"Retained memory size is too small");
142+
143+
retained_mem_read(retained_mem_dev, 0, buffer, strlen(EXPECTED_RETAINED_DATA));
144+
zassert_mem_equal(buffer, EXPECTED_RETAINED_DATA, strlen(EXPECTED_RETAINED_DATA),
145+
"Retained data is not correct");
146+
#endif
147+
}
148+
149+
ZTEST_SUITE(b0_ram_cleanup, NULL, NULL, NULL, NULL, NULL);
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
# Adding a module to b0 is needed as a way to inject source code into
8+
# the non-default sysbuild image (in this case b0).
9+
set(b0_EXTRA_ZEPHYR_MODULES ${CMAKE_CURRENT_LIST_DIR}/modules/b0_cleanup_ram_test_prepare CACHE INTERNAL "")
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
SB_CONFIG_SECURE_BOOT_APPCORE=y
8+
SB_CONFIG_SECURE_BOOT_GENERATE_DEFAULT_KMU_KEYFILE=y
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#
2+
# Copyright (c) 2025 Nordic Semiconductor ASA
3+
#
4+
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
5+
#
6+
7+
CONFIG_SB_CLEANUP_RAM=y
8+
CONFIG_RETAINED_MEM=y
9+
CONFIG_RETAINED_MEM_ZEPHYR_RAM=y
10+
CONFIG_RETAINED_MEM_NRF_GPREGRET=n

0 commit comments

Comments
 (0)