diff --git a/doc/nrf/libraries/security/bootloader/fw_info.rst b/doc/nrf/libraries/security/bootloader/fw_info.rst index 96de05069197..ad8980a3a10b 100644 --- a/doc/nrf/libraries/security/bootloader/fw_info.rst +++ b/doc/nrf/libraries/security/bootloader/fw_info.rst @@ -51,6 +51,9 @@ External APIs The firmware information structure allows for exchange of arbitrary tagged and versioned interfaces called *external APIs* (EXT_APIs). +.. note:: + EXT_APIs are currently only supported on the nRF52 and nRF53 Series devices. + An EXT_API structure is a structure consisting of a header followed by arbitrary data. The header consists of the following information: diff --git a/samples/bootloader/prj.conf b/samples/bootloader/prj.conf index 5ccb6b3f2089..39743bcc713c 100644 --- a/samples/bootloader/prj.conf +++ b/samples/bootloader/prj.conf @@ -17,11 +17,6 @@ CONFIG_LOG_DEFAULT_LEVEL=0 CONFIG_SECURE_BOOT_VALIDATION=y CONFIG_SECURE_BOOT_VALIDATION_LOG_LEVEL_INF=y CONFIG_SECURE_BOOT_STORAGE=y -CONFIG_BL_ROT_VERIFY_EXT_API_ENABLED=y -CONFIG_BL_SHA256_EXT_API_ENABLED=y -CONFIG_BL_SECP256R1_EXT_API_ENABLED=y -CONFIG_BL_VALIDATE_FW_EXT_API_ENABLED=y -CONFIG_EXT_API_PROVIDE_EXT_API_ENABLED=y CONFIG_MAIN_STACK_SIZE=2048 CONFIG_TIMEOUT_64BIT=n diff --git a/samples/bootloader/prj_minimal.conf b/samples/bootloader/prj_minimal.conf index 3b238025a52d..a5ddbe1c33c9 100644 --- a/samples/bootloader/prj_minimal.conf +++ b/samples/bootloader/prj_minimal.conf @@ -5,11 +5,6 @@ # CONFIG_B0_MIN_PARTITION_SIZE=y -CONFIG_BL_ROT_VERIFY_EXT_API_ENABLED=y -CONFIG_BL_SECP256R1_EXT_API_ENABLED=y -CONFIG_BL_SHA256_EXT_API_ENABLED=y -CONFIG_BL_VALIDATE_FW_EXT_API_ENABLED=y -CONFIG_EXT_API_PROVIDE_EXT_API_ENABLED=y CONFIG_FPROTECT=y CONFIG_FW_INFO=y CONFIG_IS_SECURE_BOOTLOADER=y diff --git a/subsys/bootloader/Kconfig b/subsys/bootloader/Kconfig index 1cb4dde61090..e5de12fc3756 100644 --- a/subsys/bootloader/Kconfig +++ b/subsys/bootloader/Kconfig @@ -101,6 +101,7 @@ config SB_BPROT_IN_DEBUG config SB_CLEANUP_RAM bool "Perform RAM cleanup" depends on !FW_INFO_PROVIDE_ENABLE + depends on !MPU_STACK_GUARD depends on CPU_CORTEX_M4 || CPU_CORTEX_M33 help Sets contents of memory to 0 before jumping to application. diff --git a/subsys/bootloader/bl_boot/bl_boot.c b/subsys/bootloader/bl_boot/bl_boot.c index a27b86006c11..8b9b0c9e1d35 100644 --- a/subsys/bootloader/bl_boot/bl_boot.c +++ b/subsys/bootloader/bl_boot/bl_boot.c @@ -175,8 +175,8 @@ static void __ramfunc jump_in(uint32_t reset) " bx r0\n" : : "r" (reset), - "i" (CONFIG_SRAM_BASE_ADDRESS), - "i" (CONFIG_SRAM_SIZE * 1024), + "r" (CONFIG_SRAM_BASE_ADDRESS), + "r" (CONFIG_SRAM_SIZE * 1024), "r" (CLEANUP_RAM_GAP_START), "r" (CLEANUP_RAM_GAP_SIZE), "i" (0) diff --git a/subsys/fw_info/Kconfig b/subsys/fw_info/Kconfig index 898feb4870c5..ccf376beea8b 100644 --- a/subsys/fw_info/Kconfig +++ b/subsys/fw_info/Kconfig @@ -123,6 +123,14 @@ config FW_INFO_PROVIDE_ENABLE help Hidden option, set if at least one *_EXT_API_ENABLED option is enabled. +config EXT_API_SUPPORTED + bool + default y if SOC_SERIES_NRF52X + default y if SOC_NRF5340_CPUAPP + default y if SOC_SERIES_NRF91X + help + External APIs are supported on the current platform. + EXT_API = EXT_API_PROVIDE id = 0x1200 flags = 0 diff --git a/subsys/fw_info/Kconfig.template.fw_info_ext_api b/subsys/fw_info/Kconfig.template.fw_info_ext_api index e1e6c267343a..dd59eb1d514a 100644 --- a/subsys/fw_info/Kconfig.template.fw_info_ext_api +++ b/subsys/fw_info/Kconfig.template.fw_info_ext_api @@ -41,6 +41,7 @@ config $(EXT_API)_EXT_API_AT_LEAST_REQUIRED config $(EXT_API)_EXT_API_ENABLED bool "Provide the $(EXT_API) EXT_API to other images" + default y if IS_SECURE_BOOTLOADER && EXT_API_SUPPORTED select FW_INFO_PROVIDE_ENABLE help Provide this EXT_API to other images. diff --git a/tests/subsys/bootloader/b0_ram_cleanup/CMakeLists.txt b/tests/subsys/bootloader/b0_ram_cleanup/CMakeLists.txt new file mode 100644 index 000000000000..ae6cdfb9af3a --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/CMakeLists.txt @@ -0,0 +1,14 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(b0_ram_cleanup) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) +target_include_directories(app PRIVATE .) diff --git a/tests/subsys/bootloader/b0_ram_cleanup/boards/nrf54l15dk_nrf54l15_cpuapp.overlay b/tests/subsys/bootloader/b0_ram_cleanup/boards/nrf54l15dk_nrf54l15_cpuapp.overlay new file mode 100644 index 000000000000..9122e6f29aed --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/boards/nrf54l15dk_nrf54l15_cpuapp.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../nrf54l15dk_nrf54l15_cpuapp_common.dtsi" diff --git a/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/CMakeLists.txt b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/CMakeLists.txt new file mode 100644 index 000000000000..0dbeec328eb6 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/CMakeLists.txt @@ -0,0 +1,10 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +cmake_minimum_required(VERSION 3.20.0) + +zephyr_library() +zephyr_library_sources(src/b0_cleanup_ram_test_prepare.c) diff --git a/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/src/b0_cleanup_ram_test_prepare.c b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/src/b0_cleanup_ram_test_prepare.c new file mode 100644 index 000000000000..7c3878c6df54 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/src/b0_cleanup_ram_test_prepare.c @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include +#include +#include +#include + +#define CLEANUP_RAM_GAP_START ((uint32_t)__ramfunc_start) +#define CLEANUP_RAM_GAP_END ((uint32_t) __ramfunc_end) + +/* Only the RAM outside of the defined linker sections will be populated, + * as modifying the defined linker sections could lead to NSIB malfunction. + */ +#define RAM_TO_POPULATE_START ((uint32_t) _image_ram_end) +#define RAM_TO_POPULATE_END (CONFIG_SRAM_SIZE * 1024) +#define VALUE_TO_POPULATE 0xDEADBEAF + +/* As peripheral registers are not cleared during RAM cleanup, we can + * use a register of a peripheral unused by NSIB to save data which can then + * be read by the application. + * We use this trick to save the start and end addresses of the RAMFUNC region. + * This region is not erased during the RAM cleanup, as it contains the code responsible + * for performing the RAM cleanup, so it must be skipped when checking if the RAM cleanup + * has been successfully performed. + * The TIMER00 is not used by NSIB, and is therefore its CC[0] and CC[1] registers + * are used for this purpose. + */ +#define RAMFUNC_START_SAVE_REGISTER NRF_TIMER00->CC[0] +#define RAMFUNC_END_SAVE_REGISTER NRF_TIMER00->CC[1] + +#define RETAINED_DATA "RETAINED" + +static int populate_ram(void) +{ + RAMFUNC_START_SAVE_REGISTER = CLEANUP_RAM_GAP_START; + RAMFUNC_END_SAVE_REGISTER = CLEANUP_RAM_GAP_END; + + for (uint32_t addr = RAM_TO_POPULATE_START; addr < RAM_TO_POPULATE_END; + addr += sizeof(uint32_t)) { + *(uint32_t *) addr = VALUE_TO_POPULATE; + } + +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_retained_ram) + const struct device *retained_mem_dev = + DEVICE_DT_GET(DT_INST(0, zephyr_retained_ram)); + size_t retained_size; + + if (!device_is_ready(retained_mem_dev)) { + printk("Retained memory device is not ready"); + return -ENODEV; + } + + retained_size = retained_mem_size(retained_mem_dev); + + if (retained_size < strlen(RETAINED_DATA)) { + printk("Retained memory size is too small"); + return -EINVAL; + } + + retained_mem_write(retained_mem_dev, 0, RETAINED_DATA, strlen(RETAINED_DATA)); +#endif + return 0; +} + +SYS_INIT(populate_ram, APPLICATION, CONFIG_APPLICATION_INIT_PRIORITY); diff --git a/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/zephyr/module.yml b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/zephyr/module.yml new file mode 100644 index 000000000000..eb317c3ce8a6 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/modules/b0_cleanup_ram_test_prepare/zephyr/module.yml @@ -0,0 +1,2 @@ +build: + cmake: . diff --git a/tests/subsys/bootloader/b0_ram_cleanup/nrf54l15dk_nrf54l15_cpuapp_common.dtsi b/tests/subsys/bootloader/b0_ram_cleanup/nrf54l15dk_nrf54l15_cpuapp_common.dtsi new file mode 100644 index 000000000000..214b50ae57e2 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/nrf54l15dk_nrf54l15_cpuapp_common.dtsi @@ -0,0 +1,29 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + cpuapp_sram@2003FFF0 { + compatible = "zephyr,memory-region", "mmio-sram"; + reg = <0x2003FFF0 0x10>; + zephyr,memory-region = "RetainedMem"; + status = "okay"; + + retainedmem { + compatible = "zephyr,retained-ram"; + status = "okay"; + #address-cells = <1>; + #size-cells = <1>; + + test_retention_area: test_retention_area@0 { + compatible = "zephyr,retention"; + status = "okay"; + reg = <0x0 0x10>; + prefix = [0B 01]; + checksum = <4>; + }; + }; + }; +}; diff --git a/tests/subsys/bootloader/b0_ram_cleanup/prj.conf b/tests/subsys/bootloader/b0_ram_cleanup/prj.conf new file mode 100644 index 000000000000..3fca32af7277 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/prj.conf @@ -0,0 +1,12 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# +CONFIG_ZTEST=y +CONFIG_ARM_MPU=n +CONFIG_SOC_EARLY_RESET_HOOK=y + +CONFIG_RETAINED_MEM=y +CONFIG_RETAINED_MEM_ZEPHYR_RAM=y +CONFIG_RETAINED_MEM_NRF_GPREGRET=n diff --git a/tests/subsys/bootloader/b0_ram_cleanup/src/main.c b/tests/subsys/bootloader/b0_ram_cleanup/src/main.c new file mode 100644 index 000000000000..fe6b2ace6aba --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/src/main.c @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +/* As peripheral registers are not cleared during RAM cleanup, we can + * use a register of a peripheral unused by NSIB to save data which can then + * be read by the application. + * We use this trick to save the start and end addresses of the RAMFUNC region. + * This region is not cleared during the RAM cleanup, as it contains the code responsible + * for performing the RAM cleanup, so it must be skipped when checking if the RAM cleanup + * has been successfully performed. + * The TIMER00 is not used by NSIB, and is therefore its CC[0] and CC[1] registers + * are used for this purpose. + */ +#define RAMFUNC_START_SAVE_REGISTER NRF_TIMER00->CC[0] +#define RAMFUNC_END_SAVE_REGISTER NRF_TIMER00->CC[1] + +/* Using 2-byte magic values allows to load the whole value with movw */ +#define RAM_CLEANUP_SUCCESS_MAGIC 0x5678 +#define RAM_CLEANUP_FAILURE_MAGIC 0x4321 + +static uint32_t __noinit ram_cleanup_result; +static uint32_t __noinit uncleared_address; +static uint32_t __noinit uncleared_value; + +#define EXPECTED_RETAINED_DATA "RETAINED" + +/** + * This hook needs to have the attribute __attribute__((naked)), + * as otherwise the stack would be modified when calling it, leading to + * test failure, as part of the RAM in which the stack resides wouldn't + * be zeroed. + */ +__attribute__((naked)) void soc_early_reset_hook(void) +{ + __asm__ volatile ( + /* Load zero (value used to clear memory) into r0 */ + " mov r0, #0\n" + /* Explicitly clear the ram_cleanup_result variable */ + " mov r2, %6\n" + " str r0, [r2]\n" + /* Load the location of the saved ram func start address to r1 */ + " mov r1, %0\n" + /* Load the ram func start address to r2 */ + " ldr r2, [r1]\n" + /* Load the location of the saved ram func end address to r1 */ + " mov r1, %1\n" + /* Load the ram func end address to r3*/ + " ldr r3, [r1]\n" + /* Clear memory from ram func start to ram func end. + * The area is skipped during the RAM cleanup, + * so it is not cleared. + * Simply cleaning it up here is quicker + * than verifying if the current address is within the gap + * in each iteration of the loop. + */ + "ram_func_zero_loop:\n" + " cmp r2, r3\n" + " bge ram_func_zero_loop_done\n" + " str r0, [r2]\n" + /* Increment the address by 4 (word size) */ + " add r2, r2, #4\n" + " b ram_func_zero_loop\n" + "ram_func_zero_loop_done:\n" + /* Verify that all of the RAM memory has been cleared */ + /* Load SRAM base address to r1 */ + " mov r1, %2\n" + /* Load SRAM size to r2 */ + " mov r2, %3\n" + /* Calculate SRAM end address (base + size) */ + " add r2, r1, r2\n" + "verify_ram_loop:\n" + " cmp r1, r2\n" + " bge verify_ram_done\n" + /* Load value at current address and verify if it equals 0 */ + " ldr r3, [r1]\n" + " cmp r3, #0\n" + " bne ram_cleanup_failed\n" + /* Increment the address by 4 (word size) */ + " add r1, r1, #4\n" + " b verify_ram_loop\n" + "ram_cleanup_failed:\n" + /* Load RAM_CLEANUP_FAILURE_MAGIC into r0 */ + " movw r0, %4\n" + /* Save the uncleared address in uncleared_address variable */ + " mov r2, %7\n" + " str r1, [r2]\n" + /* Save the uncleared value in uncleared_value variable */ + " mov r2, %8\n" + " str r3, [r2]\n" + " b verification_done\n" + "verify_ram_done:\n" + /* Load RAM_CLEANUP_SUCCESS_MAGIC */ + " movw r0, %5\n" + "verification_done:\n" + /* Store result in ram_cleanup_result variable */ + " mov r2, %6\n" + " str r0, [r2]\n" + /* __attribute__((naked)) requires manual branching */ + " bx lr\n" + : + : "r" (&RAMFUNC_START_SAVE_REGISTER), + "r" (&RAMFUNC_END_SAVE_REGISTER), + "r" (CONFIG_SRAM_BASE_ADDRESS), + "r" (CONFIG_SRAM_SIZE * 1024), + "i" (RAM_CLEANUP_FAILURE_MAGIC), + "i" (RAM_CLEANUP_SUCCESS_MAGIC), + "r" (&ram_cleanup_result), + "r" (&uncleared_address), + "r" (&uncleared_value) + : "r0", "r1", "r2", "r3", "lr", "memory" + ); +} + +ZTEST(b0_ram_cleanup, test_ram_cleanup) +{ + zassert_true((ram_cleanup_result == RAM_CLEANUP_SUCCESS_MAGIC) || + (ram_cleanup_result == RAM_CLEANUP_FAILURE_MAGIC), + "RAM cleanup result should be either success or failure"); + zassert_equal(ram_cleanup_result, RAM_CLEANUP_SUCCESS_MAGIC, + "Uncleared word detected at address %p, value 0x%x", uncleared_address, + uncleared_value); + +#if DT_HAS_COMPAT_STATUS_OKAY(zephyr_retained_ram) + const struct device *retained_mem_dev = + DEVICE_DT_GET(DT_INST(0, zephyr_retained_ram)); + size_t retained_size; + uint8_t buffer[strlen(EXPECTED_RETAINED_DATA)+1]; + + zassert_true(device_is_ready(retained_mem_dev), "Retained memory device is not ready"); + + retained_size = retained_mem_size(retained_mem_dev); + + zassert_true(retained_size >= strlen(EXPECTED_RETAINED_DATA), + "Retained memory size is too small"); + + retained_mem_read(retained_mem_dev, 0, buffer, strlen(EXPECTED_RETAINED_DATA)); + zassert_mem_equal(buffer, EXPECTED_RETAINED_DATA, strlen(EXPECTED_RETAINED_DATA), + "Retained data is not correct"); +#endif +} + +ZTEST_SUITE(b0_ram_cleanup, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.cmake b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.cmake new file mode 100644 index 000000000000..fe70503208eb --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.cmake @@ -0,0 +1,9 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +# Adding a module to b0 is needed as a way to inject source code into +# the non-default sysbuild image (in this case b0). +set(b0_EXTRA_ZEPHYR_MODULES ${CMAKE_CURRENT_LIST_DIR}/modules/b0_cleanup_ram_test_prepare CACHE INTERNAL "") diff --git a/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.conf b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.conf new file mode 100644 index 000000000000..1dcdb3c60c1d --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild.conf @@ -0,0 +1,8 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +SB_CONFIG_SECURE_BOOT_APPCORE=y +SB_CONFIG_SECURE_BOOT_GENERATE_DEFAULT_KMU_KEYFILE=y diff --git a/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.conf b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.conf new file mode 100644 index 000000000000..f0e45385da95 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.conf @@ -0,0 +1,10 @@ +# +# Copyright (c) 2025 Nordic Semiconductor ASA +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +CONFIG_SB_CLEANUP_RAM=y +CONFIG_RETAINED_MEM=y +CONFIG_RETAINED_MEM_ZEPHYR_RAM=y +CONFIG_RETAINED_MEM_NRF_GPREGRET=n diff --git a/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.overlay b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.overlay new file mode 100644 index 000000000000..9122e6f29aed --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/sysbuild/b0.overlay @@ -0,0 +1,7 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "../nrf54l15dk_nrf54l15_cpuapp_common.dtsi" diff --git a/tests/subsys/bootloader/b0_ram_cleanup/testcase.yaml b/tests/subsys/bootloader/b0_ram_cleanup/testcase.yaml new file mode 100644 index 000000000000..2da5eb7c5667 --- /dev/null +++ b/tests/subsys/bootloader/b0_ram_cleanup/testcase.yaml @@ -0,0 +1,12 @@ +common: + sysbuild: true + tags: + - b0 + - ci_tests_subsys_bootloader + +tests: + b0.cleanup_ram: + platform_allow: + - nrf54l15dk/nrf54l15/cpuapp + integration_platforms: + - nrf54l15dk/nrf54l15/cpuapp