diff --git a/snippets/swap_uart/snippet.yml b/snippets/swap_uart/snippet.yml new file mode 100644 index 00000000000..1a9359c6d6c --- /dev/null +++ b/snippets/swap_uart/snippet.yml @@ -0,0 +1,15 @@ +# +# Copyright (c) 2025 Nordic Semiconductor +# +# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause +# + +name: swap-uart + +boards: + nrf54h20dk/nrf54h20/cpurad: + append: + EXTRA_DTC_OVERLAY_FILE: use_uart136.overlay + nrf54h20dk/nrf54h20/cpuapp: + append: + EXTRA_DTC_OVERLAY_FILE: use_uart135.overlay diff --git a/snippets/swap_uart/use_uart135.overlay b/snippets/swap_uart/use_uart135.overlay new file mode 100644 index 00000000000..ead18612104 --- /dev/null +++ b/snippets/swap_uart/use_uart135.overlay @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,console = &uart135; + zephyr,shell-uart = &uart135; + zephyr,uart-mcumgr = &uart135; + }; +}; + +&uart135 { + status = "okay"; + memory-regions = <&cpuapp_dma_region>; +}; + +&uart136 { + status = "disabled"; +}; diff --git a/snippets/swap_uart/use_uart136.overlay b/snippets/swap_uart/use_uart136.overlay new file mode 100644 index 00000000000..3bb10fbba52 --- /dev/null +++ b/snippets/swap_uart/use_uart136.overlay @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +/ { + chosen { + zephyr,console = &uart136; + zephyr,shell-uart = &uart136; + zephyr,uart-mcumgr = &uart136; + }; +}; + +&uart136 { + status = "okay"; + memory-regions = <&cpurad_dma_region>; +}; + +&uart135 { + status = "disabled"; +}; diff --git a/tests/subsys/bootloader/upgrade/ref_smp_svr/CMakeLists.txt b/tests/subsys/bootloader/upgrade/ref_smp_svr/CMakeLists.txt index dc3c14e504a..fccfdca3c70 100644 --- a/tests/subsys/bootloader/upgrade/ref_smp_svr/CMakeLists.txt +++ b/tests/subsys/bootloader/upgrade/ref_smp_svr/CMakeLists.txt @@ -15,3 +15,10 @@ set(smp_srv_dir ${ZEPHYR_BASE}/samples/subsys/mgmt/mcumgr/smp_svr) # This project uses orginal sdk-zephyr C source code target_sources(app PRIVATE ${smp_srv_dir}/src/main.c) target_sources_ifdef(CONFIG_MCUMGR_TRANSPORT_BT app PRIVATE ${smp_srv_dir}/src/bluetooth.c) + +zephyr_get(PERIPHCONF_BIN) +if (DEFINED PERIPHCONF_BIN) + target_sources(app PRIVATE periphconf_check.c) + message("Including local periphconf '${PERIPHCONF_BIN}'") + generate_inc_file_for_target(app ${PERIPHCONF_BIN} ${ZEPHYR_BINARY_DIR}/include/generated/periphconf.bin.inc) +endif() diff --git a/tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh b/tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh new file mode 100755 index 00000000000..5d6a2a89b06 --- /dev/null +++ b/tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh @@ -0,0 +1,61 @@ +#!/bin/bash + +# Exit immediately if a command exits with a non-zero status +set -e + +# Function to process periphconf for a given build directory +move_mcuboot_periphconf_first() { + local build_dir="$1" + + # Create a bin file with the periphconf for MCUBoot + objcopy --set-section-flags uicr_periphconf_entry=alloc,load,readonly -I elf32-little -O binary \ + -j uicr_periphconf_entry "$build_dir/mcuboot/zephyr/zephyr.elf" "$build_dir/mcuboot_periphconf.bin" + + # Convert the full periphconf.hex to bin... + objcopy -I ihex -O binary "$build_dir/uicr/zephyr/periphconf.hex" "$build_dir/periphconf.bin" + + # ... and concatenate with MCUBoot, placing MCUBoot first + cat "$build_dir/mcuboot_periphconf.bin" "$build_dir/periphconf.bin" > "$build_dir/periphconf_cat.bin" + + # Remove duplicates + bash -c "./remove_periphconf_duplicates.py $build_dir/periphconf_cat.bin > $build_dir/periphconf_cat_nodups.bin" + + # Verify that the processed periphconf matches the original when sorted, this works since the + # original is sorted by regptr address in the first place. + local sorted_digest + local original_digest + + sorted_digest=$(xxd -e -g 4 -c 8 "$build_dir/periphconf_cat_nodups.bin" | awk '{print $2, $3}' | sort | md5sum) + original_digest=$(xxd -e -g 4 -c 8 "$build_dir/periphconf.bin" | awk '{print $2, $3}' | md5sum) + + if [ "$sorted_digest" != "$original_digest" ]; then + echo "ERROR: Digests don't match for $build_dir!" + exit 1 + fi + + # Create a hex file we can program to replace the default periphconf.hex + objcopy -I binary -O ihex --change-address 0x0E1AE000 \ + "$build_dir/periphconf_cat_nodups.bin" "$build_dir/periphconf_final.hex" + + echo "Generated new periphconf with mcuboot first: $build_dir/periphconf_final.hex" +} + +# Build initial image, enable radio, disable UART for MCUBoot +west build -p -b nrf54h20dk/nrf54h20/cpuapp -d build_no_mcuboot_uart -T no_mcuboot_uart . + +# Process periphconf for the initial build (includes digest comparison) +move_mcuboot_periphconf_first "build_no_mcuboot_uart" + +# Build of image with swapped UART, used to get new periphconf +west build -p -b nrf54h20dk/nrf54h20/cpuapp -d build_swapped_uart_v1 -T swapped_uart_v1 . + +# Process periphconf for the swapped UART build +move_mcuboot_periphconf_first "build_swapped_uart_v1" + +# Generate the periphconf that will be contained in the update image +objcopy --input-target=ihex --output-target=binary build_swapped_uart_v1/periphconf_final.hex \ +build_swapped_uart_v1/periphconf_swapped_uart.bin + +# Re-build the swapped image with integrated PERIPHCONF and bumped firmware version +west build -p -b nrf54h20dk/nrf54h20/cpuapp -d build_swapped_uart_v2 -T swapped_uart_v2 . -- \ +-DPERIPHCONF_BIN=build_swapped_uart_v1/periphconf_swapped_uart.bin diff --git a/tests/subsys/bootloader/upgrade/ref_smp_svr/periphconf_check.c b/tests/subsys/bootloader/upgrade/ref_smp_svr/periphconf_check.c new file mode 100644 index 00000000000..cad3de7bb3a --- /dev/null +++ b/tests/subsys/bootloader/upgrade/ref_smp_svr/periphconf_check.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include +#include +#include + +#define PERIPHCONF_NODE DT_NODELABEL(periphconf_partition) +#define MRAM_NODE DT_GPARENT(PERIPHCONF_NODE) +#define MRAM_WORD_SIZE DT_PROP(MRAM_NODE, write_block_size) +#define GLOBAL_PERIPHCONF_ADDR DT_REG_ADDR(MRAM_NODE) + DT_REG_ADDR(PERIPHCONF_NODE) + +/* The global PERIPHCONF blob is the one pointed to by UICR.PERIPHCONF and hence the one which + * will be applied by IronSide SE. + */ +static struct uicr_periphconf_entry *global_periphconf = + (struct uicr_periphconf_entry *)(GLOBAL_PERIPHCONF_ADDR); + +/* The local PERIPHCONF contained within the image firmware blob. This could be out of sync with + * the global PERIPHCONF if a DFU has been updated with a change in PERIPHCONF. + */ +static const uint8_t local_periphconf_inc[] = { +#include +}; + +/* Since we are executing pre-kernel, we cannot use the proper MRAM driver. Hence we rely on + * the blob being MRAM word size aligned since write are committed when the last 4-byte word + * in the 16-byte MRAM word is written. + */ +BUILD_ASSERT(sizeof(local_periphconf_inc) % MRAM_WORD_SIZE == 0, + "PERIPHCONF blob is not MRAM word size aligned."); + +/* Compare the local PERIPHCONF blob with the global one. If an entry is found that differs, copy + * the remaining entries and trigger a restart. No action is taken if the blobs are equal. + */ +int check_and_copy_local_periphconf_to_global(void) +{ + const struct uicr_periphconf_entry *local_periphconf = + (struct uicr_periphconf_entry *)local_periphconf_inc; + const size_t local_periphconf_len = + sizeof(local_periphconf_inc) / sizeof(struct uicr_periphconf_entry); + + for (int i = 0; i < local_periphconf_len; i++) { + if (local_periphconf[i].regptr == 0xFFFFFFFF && + local_periphconf[i].value == 0xFFFFFFFF) { + return 0; + } else if (local_periphconf[i].regptr == global_periphconf[i].regptr && + local_periphconf[i].value == global_periphconf[i].value) { + continue; + } else { + for (int j = i; j < local_periphconf_len; j++) { + sys_write32(local_periphconf[j].regptr, + (uintptr_t)&global_periphconf[j].regptr); + sys_write32(local_periphconf[j].value, + (uintptr_t)&global_periphconf[j].value); + } + + /* IronSide applies the configuration in PERIPHCONF before booting the + * local domains, so a reboot is needed for the changes to be applied. + */ + sys_reboot(SYS_REBOOT_WARM); + + CODE_UNREACHABLE; + + return -1; + } + } + + return 0; +} + +/* Devices are initialized with PRE_KERNEL_1, and this needs to be fixed by then, so use EARLY */ +SYS_INIT(check_and_copy_local_periphconf_to_global, EARLY, 0); diff --git a/tests/subsys/bootloader/upgrade/ref_smp_svr/remove_periphconf_duplicates.py b/tests/subsys/bootloader/upgrade/ref_smp_svr/remove_periphconf_duplicates.py new file mode 100755 index 00000000000..cd36a8a651a --- /dev/null +++ b/tests/subsys/bootloader/upgrade/ref_smp_svr/remove_periphconf_duplicates.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +"""Remove duplicate periphconf entries where regptr and value are identical and print to stdout""" + +import ctypes as c +import sys + + +class PeriphconfEntry(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [("regptr", c.c_uint32), ("value", c.c_uint32)] + + +def main(): + data = open(sys.argv[1], 'rb').read() + num_entries = len(data) // c.sizeof(PeriphconfEntry) + entries = (PeriphconfEntry * num_entries).from_buffer_copy(data) + + seen = set() + unique_entries = [] + for entry in entries: + if entry.regptr == 0xFFFFFFFF: # Don't remove the padding + unique_entries.append(entry) + else: + key = (entry.regptr, entry.value) + if key not in seen: + seen.add(key) + unique_entries.append(entry) + + result = (PeriphconfEntry * len(unique_entries))(*unique_entries) + sys.stdout.buffer.write(bytes(result)) + + +if __name__ == "__main__": + main() diff --git a/tests/subsys/bootloader/upgrade/ref_smp_svr/testcase.yaml b/tests/subsys/bootloader/upgrade/ref_smp_svr/testcase.yaml index 4563a96fc36..c86b915af8a 100644 --- a/tests/subsys/bootloader/upgrade/ref_smp_svr/testcase.yaml +++ b/tests/subsys/bootloader/upgrade/ref_smp_svr/testcase.yaml @@ -10,6 +10,47 @@ common: - ci_tests_subsys_bootloader tests: + no_mcuboot_uart: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP=y + - FILE_SUFFIX=direct_xip + - CONFIG_SOC_NRF54H20_CPURAD_ENABLE=y + - ipc_radio_CONFIG_SERIAL=y + - ipc_radio_CONFIG_UART_CONSOLE=y + - ipc_radio_CONFIG_LOG=y + - mcuboot_CONFIG_SERIAL=n + + swapped_uart_v1: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP=y + - FILE_SUFFIX=direct_xip + - CONFIG_SOC_NRF54H20_CPURAD_ENABLE=y + - ipc_radio_CONFIG_SERIAL=y + - ipc_radio_CONFIG_UART_CONSOLE=y + - ipc_radio_CONFIG_LOG=y + - mcuboot_CONFIG_SERIAL=n + required_snippets: + - swap-uart + + swapped_uart_v2: + platform_allow: + - nrf54h20dk/nrf54h20/cpuapp + extra_args: + - SB_CONFIG_MCUBOOT_MODE_DIRECT_XIP=y + - FILE_SUFFIX=direct_xip + - CONFIG_SOC_NRF54H20_CPURAD_ENABLE=y + - ipc_radio_CONFIG_SERIAL=y + - ipc_radio_CONFIG_UART_CONSOLE=y + - ipc_radio_CONFIG_LOG=y + - mcuboot_CONFIG_SERIAL=n + - CONFIG_MCUBOOT_IMGTOOL_SIGN_VERSION="2.2.2+2" + required_snippets: + - swap-uart + mcuboot.upgrade.basic: platform_allow: - nrf52840dk/nrf52840