Skip to content
Draft
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
15 changes: 15 additions & 0 deletions snippets/swap_uart/snippet.yml
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions snippets/swap_uart/use_uart135.overlay
Original file line number Diff line number Diff line change
@@ -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";
};
22 changes: 22 additions & 0 deletions snippets/swap_uart/use_uart136.overlay
Original file line number Diff line number Diff line change
@@ -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";
};
7 changes: 7 additions & 0 deletions tests/subsys/bootloader/upgrade/ref_smp_svr/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Original file line number Diff line number Diff line change
@@ -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() {

Check warning on line 7 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Add an explicit return statement at the end of the function.

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-nrf&issues=AZsMCOEOsk1xgbiqBF-A&open=AZsMCOEOsk1xgbiqBF-A&pullRequest=26135
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 \

Check warning on line 11 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh:11 line length of 101 exceeds 100 columns
-j uicr_periphconf_entry "$build_dir/mcuboot/zephyr/zephyr.elf" "$build_dir/mcuboot_periphconf.bin"

Check warning on line 12 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE_STRING

tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh:12 line length of 103 exceeds 100 columns

# 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"

Check warning on line 18 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE_STRING

tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh:18 line length of 105 exceeds 100 columns

# Remove duplicates
bash -c "./remove_periphconf_duplicates.py $build_dir/periphconf_cat.bin > $build_dir/periphconf_cat_nodups.bin"

Check warning on line 21 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE_STRING

tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh:21 line length of 116 exceeds 100 columns

# 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)

Check warning on line 28 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

LONG_LINE

tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh:28 line length of 115 exceeds 100 columns
original_digest=$(xxd -e -g 4 -c 8 "$build_dir/periphconf.bin" | awk '{print $2, $3}' | md5sum)

if [ "$sorted_digest" != "$original_digest" ]; then

Check failure on line 31 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Use '[[' instead of '[' for conditional tests. The '[[' construct is safer and more feature-rich.

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-nrf&issues=AZsMCOEOsk1xgbiqBF9_&open=AZsMCOEOsk1xgbiqBF9_&pullRequest=26135
echo "ERROR: Digests don't match for $build_dir!"

Check warning on line 32 in tests/subsys/bootloader/upgrade/ref_smp_svr/move_mcuboot_periphconf_first.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Redirect this error message to stderr (>&2).

See more on https://sonarcloud.io/project/issues?id=nrfconnect_sdk-nrf&issues=AZsMCOEOsk1xgbiqBF9-&open=AZsMCOEOsk1xgbiqBF9-&pullRequest=26135
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
76 changes: 76 additions & 0 deletions tests/subsys/bootloader/upgrade/ref_smp_svr/periphconf_check.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
* Copyright (c) 2025 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/

#include <zephyr/init.h>
#include <zephyr/sys/reboot.h>
#include <uicr/uicr.h>

#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 <periphconf.bin.inc>
};

/* 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);
Original file line number Diff line number Diff line change
@@ -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()

Check failure on line 14 in tests/subsys/bootloader/upgrade/ref_smp_svr/remove_periphconf_duplicates.py

View workflow job for this annotation

GitHub Actions / Run compliance checks on patch series (PR)

Python lint error (SIM115) see https://docs.astral.sh/ruff/rules/open-file-with-context-handler

tests/subsys/bootloader/upgrade/ref_smp_svr/remove_periphconf_duplicates.py:14 Use a context manager for opening files
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()
41 changes: 41 additions & 0 deletions tests/subsys/bootloader/upgrade/ref_smp_svr/testcase.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading