diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi index b6a53955c60..e14426c59fc 100644 --- a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi @@ -166,5 +166,13 @@ periphconf_partition: partition@1ae000 { reg = <0x1ae000 DT_SIZE_K(8)>; }; + + secondary_partition: partition@1b0000 { + reg = <0x1b0000 DT_SIZE_K(64)>; + }; + + secondary_periphconf_partition: partition@1c0000 { + reg = <0x1c0000 DT_SIZE_K(8)>; + }; }; }; diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index b0f4ccebb1e..251cbc97a58 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -1231,6 +1231,9 @@ def check_no_undef_outside_kconfig(self, kconf): "FOO_LOG_LEVEL", "FOO_SETTING_1", "FOO_SETTING_2", + "GEN_UICR_GENERATE_PERIPHCONF", # Used in specialized build tool, not part of main Kconfig + "GEN_UICR_SECONDARY", # Used in specialized build tool, not part of main Kconfig + "GEN_UICR_SECONDARY_GENERATE_PERIPHCONF", # Used in specialized build tool, not part of main Kconfig "HEAP_MEM_POOL_ADD_SIZE_", # Used as an option matching prefix "HUGETLBFS", # Linux, in boards/xtensa/intel_adsp_cavs25/doc "IAR_BUFFERED_WRITE", diff --git a/scripts/west_commands/runners/nrf_common.py b/scripts/west_commands/runners/nrf_common.py index a7401084ef3..8a69c65bb2b 100644 --- a/scripts/west_commands/runners/nrf_common.py +++ b/scripts/west_commands/runners/nrf_common.py @@ -433,26 +433,6 @@ def program_hex(self): core='Application', ) - if self.build_conf.getboolean("CONFIG_NRF_HALTIUM_GENERATE_UICR"): - zephyr_build_dir = Path(self.cfg.build_dir) / 'zephyr' - - self.op_program( - str(zephyr_build_dir / 'uicr.hex'), - 'ERASE_NONE', - None, - defer=True, - core='Application', - ) - - if self.build_conf.getboolean("CONFIG_NRF_HALTIUM_UICR_PERIPHCONF"): - self.op_program( - str(zephyr_build_dir / 'periphconf.hex'), - 'ERASE_NONE', - None, - defer=True, - core='Application', - ) - if not self.erase and regtool_generated_uicr: self.exec_op('erase', core=core, kind='uicr') else: diff --git a/soc/nordic/Kconfig.sysbuild b/soc/nordic/Kconfig.sysbuild index a726d20c464..040a04203bf 100644 --- a/soc/nordic/Kconfig.sysbuild +++ b/soc/nordic/Kconfig.sysbuild @@ -4,5 +4,6 @@ config HAS_NORDIC_VPR_LAUNCHER_IMAGE bool +rsource "common/uicr/Kconfig.sysbuild" rsource "common/vpr/Kconfig.sysbuild" orsource "*/Kconfig.sysbuild" diff --git a/soc/nordic/common/CMakeLists.txt b/soc/nordic/common/CMakeLists.txt index 825be4842fb..04f0c1a3219 100644 --- a/soc/nordic/common/CMakeLists.txt +++ b/soc/nordic/common/CMakeLists.txt @@ -3,9 +3,7 @@ add_subdirectory_ifdef(CONFIG_RISCV_CORE_NORDIC_VPR vpr) -if(CONFIG_NRF_PERIPHCONF_SECTION OR CONFIG_NRF_HALTIUM_GENERATE_UICR) - add_subdirectory(uicr) -endif() +add_subdirectory(uicr) # Let SystemInit() be called in place of soc_reset_hook() by default. zephyr_linker_symbol(SYMBOL soc_reset_hook EXPR "@SystemInit@") diff --git a/soc/nordic/common/uicr/CMakeLists.txt b/soc/nordic/common/uicr/CMakeLists.txt index 0bde6b47f57..1ec3a35c566 100644 --- a/soc/nordic/common/uicr/CMakeLists.txt +++ b/soc/nordic/common/uicr/CMakeLists.txt @@ -4,33 +4,3 @@ if(CONFIG_NRF_PERIPHCONF_SECTION) zephyr_linker_sources(SECTIONS uicr.ld) endif() - -if(CONFIG_NRF_HALTIUM_GENERATE_UICR) - if(CONFIG_NRF_PERIPHCONF_SECTION) - set(in_periphconf_elf_arg - --in-periphconf-elf $ - ) - endif() - - if(CONFIG_NRF_HALTIUM_UICR_PERIPHCONF) - set(periphconf_hex_file ${PROJECT_BINARY_DIR}/periphconf.hex) - set(out_periphconf_hex_arg - --out-periphconf-hex ${periphconf_hex_file} - ) - list(APPEND optional_byproducts ${periphconf_hex_file}) - endif() - - set(uicr_hex_file ${PROJECT_BINARY_DIR}/uicr.hex) - set_property(GLOBAL APPEND PROPERTY extra_post_build_commands - COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${ZEPHYR_BASE}/scripts/dts/python-devicetree/src - ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/gen_uicr.py - --in-config ${DOTCONFIG} - --in-edt-pickle ${EDT_PICKLE} - ${in_periphconf_elf_arg} - ${out_periphconf_hex_arg} - --out-uicr-hex ${uicr_hex_file} - ) - set_property(GLOBAL APPEND PROPERTY extra_post_build_byproducts - ${uicr_hex_file} ${optional_byproducts} - ) -endif() diff --git a/soc/nordic/common/uicr/Kconfig b/soc/nordic/common/uicr/Kconfig index f132510a7a5..3c4c6c8219b 100644 --- a/soc/nordic/common/uicr/Kconfig +++ b/soc/nordic/common/uicr/Kconfig @@ -1,29 +1,9 @@ # Copyright (c) 2025 Nordic Semiconductor ASA # SPDX-License-Identifier: Apache-2.0 -config NRF_HALTIUM_GENERATE_UICR - bool "Generate UICR file" - depends on SOC_NRF54H20_CPUAPP - default y - help - Generate UICR HEX file. - -if NRF_HALTIUM_GENERATE_UICR - -config NRF_HALTIUM_UICR_PERIPHCONF - bool "Initialize global domain peripherals" - default y - help - Generates a blob containing static global domain peripheral initialization - values extracted from the build artifacts, and configures UICR.PERIPHCONF - to point at the blob. The initialization values are then loaded ahead of - ahead of the application boot. - -endif - config NRF_PERIPHCONF_SECTION bool "Populate global peripheral initialization section" - default y if SOC_NRF54H20_CPUAPP + default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD depends on LINKER_DEVNULL_SUPPORT imply LINKER_DEVNULL_MEMORY help diff --git a/soc/nordic/common/uicr/Kconfig.sysbuild b/soc/nordic/common/uicr/Kconfig.sysbuild new file mode 100644 index 00000000000..eb885beaaaf --- /dev/null +++ b/soc/nordic/common/uicr/Kconfig.sysbuild @@ -0,0 +1,9 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +config NRF_HALTIUM_GENERATE_UICR + bool "Generate UICR file" + depends on SOC_SERIES_NRF54HX + default y + help + Generate UICR HEX file. diff --git a/soc/nordic/common/uicr/gen_uicr.py b/soc/nordic/common/uicr/gen_uicr.py index 5888786e928..78422e4312c 100644 --- a/soc/nordic/common/uicr/gen_uicr.py +++ b/soc/nordic/common/uicr/gen_uicr.py @@ -7,11 +7,7 @@ import argparse import ctypes as c -import math -import pickle -import re import sys -from collections import defaultdict from itertools import groupby from elftools.elf.elffile import ELFFile @@ -25,11 +21,6 @@ # Must match the name used in the linker script. PERIPHCONF_SECTION = "uicr_periphconf_entry" -# Expected nodelabel of the UICR devicetree node, used to extract its location from the devicetree. -UICR_NODELABEL = "uicr" -# Nodelabel of the PERIPHCONF devicetree node, used to extract its location from the devicetree. -PERIPHCONF_NODELABEL = "periphconf_partition" - # Common values for representing enabled/disabled in the UICR format. ENABLED_VALUE = 0xFFFF_FFFF DISABLED_VALUE = 0xBD23_28A8 @@ -75,23 +66,38 @@ class Protectedmem(c.LittleEndianStructure): ] -class Recovery(c.LittleEndianStructure): +class Wdtstart(c.LittleEndianStructure): _pack_ = 1 _fields_ = [ ("ENABLE", c.c_uint32), - ("PROCESSOR", c.c_uint32), - ("INITSVTOR", c.c_uint32), - ("SIZE4KB", c.c_uint32), + ("INSTANCE", c.c_uint32), + ("CRV", c.c_uint32), ] -class Its(c.LittleEndianStructure): +class SecurestorageCrypto(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("APPLICATIONSIZE1KB", c.c_uint32), + ("RADIOCORESIZE1KB", c.c_uint32), + ] + + +class SecurestorageIts(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("APPLICATIONSIZE1KB", c.c_uint32), + ("RADIOCORESIZE1KB", c.c_uint32), + ] + + +class Securestorage(c.LittleEndianStructure): _pack_ = 1 _fields_ = [ ("ENABLE", c.c_uint32), ("ADDRESS", c.c_uint32), - ("APPLICATIONSIZE", c.c_uint32), - ("RADIOCORESIZE", c.c_uint32), + ("CRYPTO", SecurestorageCrypto), + ("ITS", SecurestorageIts), ] @@ -113,6 +119,64 @@ class Mpcconf(c.LittleEndianStructure): ] +class SecondaryTrigger(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("RESETREAS", c.c_uint32), + ("RESERVED", c.c_uint32), + ] + + +class SecondaryProtectedmem(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("SIZE4KB", c.c_uint32), + ] + + +class SecondaryWdtstart(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("INSTANCE", c.c_uint32), + ("CRV", c.c_uint32), + ] + + +class SecondaryPeriphconf(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("ADDRESS", c.c_uint32), + ("MAXCOUNT", c.c_uint32), + ] + + +class SecondaryMpcconf(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("ADDRESS", c.c_uint32), + ("MAXCOUNT", c.c_uint32), + ] + + +class Secondary(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("PROCESSOR", c.c_uint32), + ("TRIGGER", SecondaryTrigger), + ("ADDRESS", c.c_uint32), + ("PROTECTEDMEM", SecondaryProtectedmem), + ("WDTSTART", SecondaryWdtstart), + ("PERIPHCONF", SecondaryPeriphconf), + ("MPCCONF", SecondaryMpcconf), + ] + + class Uicr(c.LittleEndianStructure): _pack_ = 1 _fields_ = [ @@ -123,11 +187,14 @@ class Uicr(c.LittleEndianStructure): ("APPROTECT", Approtect), ("ERASEPROTECT", c.c_uint32), ("PROTECTEDMEM", Protectedmem), - ("RECOVERY", Recovery), - ("ITS", Its), - ("RESERVED2", c.c_uint32 * 7), + ("WDTSTART", Wdtstart), + ("RESERVED2", c.c_uint32), + ("SECURESTORAGE", Securestorage), + ("RESERVED3", c.c_uint32 * 5), ("PERIPHCONF", Periphconf), ("MPCCONF", Mpcconf), + ("SECONDARY", Secondary), + ("PADDING", c.c_uint32 * 15), ] @@ -141,18 +208,6 @@ def main() -> None: "peripherals, and to protect the device in various ways." ), ) - parser.add_argument( - "--in-config", - required=True, - type=argparse.FileType("r"), - help="Path to the .config file from the application build", - ) - parser.add_argument( - "--in-edt-pickle", - required=True, - type=argparse.FileType("rb"), - help="Path to the edt.pickle file from the application build", - ) parser.add_argument( "--in-periphconf-elf", dest="in_periphconf_elfs", @@ -165,70 +220,204 @@ def main() -> None: "by ascending address and cleared of duplicate entries." ), ) + parser.add_argument( + "--out-merged-hex", + required=True, + type=argparse.FileType("w", encoding="utf-8"), + help="Path to write the merged UICR+PERIPHCONF HEX file to", + ) parser.add_argument( "--out-uicr-hex", required=True, type=argparse.FileType("w", encoding="utf-8"), - help="Path to write the generated UICR HEX file to", + help="Path to write the UICR-only HEX file to", ) parser.add_argument( "--out-periphconf-hex", + type=argparse.FileType("w", encoding="utf-8"), + help="Path to write the PERIPHCONF-only HEX file to", + ) + parser.add_argument( + "--periphconf-address", + default=None, + type=lambda s: int(s, 0), + help="Absolute flash address of the PERIPHCONF partition (decimal or 0x-prefixed hex)", + ) + parser.add_argument( + "--periphconf-size", + default=None, + type=lambda s: int(s, 0), + help="Size in bytes of the PERIPHCONF partition (decimal or 0x-prefixed hex)", + ) + parser.add_argument( + "--uicr-address", + required=True, + type=lambda s: int(s, 0), + help="Absolute flash address of the UICR region (decimal or 0x-prefixed hex)", + ) + parser.add_argument( + "--secondary", + action="store_true", + help="Enable secondary firmware support in UICR", + ) + parser.add_argument( + "--secondary-address", + default=None, + type=lambda s: int(s, 0), + help="Absolute flash address of the secondary firmware (decimal or 0x-prefixed hex)", + ) + parser.add_argument( + "--secondary-periphconf-address", + default=None, + type=lambda s: int(s, 0), + help=( + "Absolute flash address of the secondary PERIPHCONF partition " + "(decimal or 0x-prefixed hex)" + ), + ) + parser.add_argument( + "--secondary-periphconf-size", default=None, + type=lambda s: int(s, 0), + help="Size in bytes of the secondary PERIPHCONF partition (decimal or 0x-prefixed hex)", + ) + parser.add_argument( + "--in-secondary-periphconf-elf", + dest="in_secondary_periphconf_elfs", + default=[], + action="append", + type=argparse.FileType("rb"), + help=( + "Path to an ELF file to extract secondary PERIPHCONF data from. " + "Can be provided multiple times. The secondary PERIPHCONF data from each ELF file " + "is combined in a single list which is sorted by ascending address and cleared " + "of duplicate entries." + ), + ) + parser.add_argument( + "--out-secondary-periphconf-hex", type=argparse.FileType("w", encoding="utf-8"), - help="Path to write the generated PERIPHCONF HEX file to", + help="Path to write the secondary PERIPHCONF-only HEX file to", ) args = parser.parse_args() try: + # Validate argument dependencies + if args.out_periphconf_hex: + if args.periphconf_address is None: + raise ScriptError( + "--periphconf-address is required when --out-periphconf-hex is used" + ) + if args.periphconf_size is None: + raise ScriptError("--periphconf-size is required when --out-periphconf-hex is used") + + # Validate secondary argument dependencies + if args.secondary and args.secondary_address is None: + raise ScriptError("--secondary-address is required when --secondary is used") + + if args.out_secondary_periphconf_hex: + if args.secondary_periphconf_address is None: + raise ScriptError( + "--secondary-periphconf-address is required when " + "--out-secondary-periphconf-hex is used" + ) + if args.secondary_periphconf_size is None: + raise ScriptError( + "--secondary-periphconf-size is required when " + "--out-secondary-periphconf-hex is used" + ) + init_values = DISABLED_VALUE.to_bytes(4, "little") * (c.sizeof(Uicr) // 4) uicr = Uicr.from_buffer_copy(init_values) uicr.VERSION.MAJOR = UICR_FORMAT_VERSION_MAJOR uicr.VERSION.MINOR = UICR_FORMAT_VERSION_MINOR - kconfig_str = args.in_config.read() - kconfig = parse_kconfig(kconfig_str) - - edt = pickle.load(args.in_edt_pickle) + # Process periphconf data first and configure UICR completely before creating hex objects + periphconf_hex = IntelHex() + secondary_periphconf_hex = IntelHex() - try: - periphconf_partition = edt.label2node[PERIPHCONF_NODELABEL] - except LookupError as e: - raise ScriptError( - "Failed to find a PERIPHCONF partition in the devicetree. " - f"Expected a DT node with label '{PERIPHCONF_NODELABEL}'." - ) from e + if args.out_periphconf_hex: + periphconf_combined = extract_and_combine_periphconfs(args.in_periphconf_elfs) - flash_base_address = periphconf_partition.flash_controller.regs[0].addr - periphconf_address = flash_base_address + periphconf_partition.regs[0].addr - periphconf_size = periphconf_partition.regs[0].size + padding_len = args.periphconf_size - len(periphconf_combined) + periphconf_final = periphconf_combined + bytes([0xFF for _ in range(padding_len)]) - periphconf_combined = extract_and_combine_periphconfs(args.in_periphconf_elfs) - padding_len = periphconf_size - len(periphconf_combined) - periphconf_final = periphconf_combined + bytes([0xFF for _ in range(padding_len)]) + # Add periphconf data to periphconf hex object + periphconf_hex.frombytes(periphconf_final, offset=args.periphconf_address) - if kconfig.get("CONFIG_NRF_HALTIUM_UICR_PERIPHCONF") == "y": + # Configure UICR with periphconf settings uicr.PERIPHCONF.ENABLE = ENABLED_VALUE - uicr.PERIPHCONF.ADDRESS = periphconf_address - uicr.PERIPHCONF.MAXCOUNT = math.floor(periphconf_size / 8) + uicr.PERIPHCONF.ADDRESS = args.periphconf_address + + # MAXCOUNT is given in number of 8-byte peripheral + # configuration entries and periphconf_size is given in + # bytes. When setting MAXCOUNT based on the + # periphconf_size we must first assert that + # periphconf_size has not been misconfigured. + if args.periphconf_size % 8 != 0: + raise ScriptError( + f"args.periphconf_size was {args.periphconf_size}, but must be divisible by 8" + ) + + uicr.PERIPHCONF.MAXCOUNT = args.periphconf_size // 8 + + # Handle secondary firmware configuration + if args.secondary: + uicr.SECONDARY.ENABLE = ENABLED_VALUE + uicr.SECONDARY.ADDRESS = args.secondary_address - try: - uicr_node = edt.label2node[UICR_NODELABEL] - except LookupError as e: - raise ScriptError( - "Failed to find UICR node in the devicetree. " - f"Expected a DT node with label '{UICR_NODELABEL}'." - ) from e + # Handle secondary periphconf if provided + if args.out_secondary_periphconf_hex: + secondary_periphconf_combined = extract_and_combine_periphconfs( + args.in_secondary_periphconf_elfs + ) + + padding_len = args.secondary_periphconf_size - len(secondary_periphconf_combined) + secondary_periphconf_final = secondary_periphconf_combined + bytes( + [0xFF for _ in range(padding_len)] + ) + + # Add secondary periphconf data to secondary periphconf hex object + secondary_periphconf_hex.frombytes( + secondary_periphconf_final, offset=args.secondary_periphconf_address + ) + + # Configure UICR with secondary periphconf settings + uicr.SECONDARY.PERIPHCONF.ENABLE = ENABLED_VALUE + uicr.SECONDARY.PERIPHCONF.ADDRESS = args.secondary_periphconf_address + + # MAXCOUNT is given in number of 8-byte peripheral + # configuration entries and secondary_periphconf_size is given in + # bytes. When setting MAXCOUNT based on the + # secondary_periphconf_size we must first assert that + # secondary_periphconf_size has not been misconfigured. + if args.secondary_periphconf_size % 8 != 0: + raise ScriptError( + f"args.secondary_periphconf_size was {args.secondary_periphconf_size}, " + f"but must be divisible by 8" + ) + + uicr.SECONDARY.PERIPHCONF.MAXCOUNT = args.secondary_periphconf_size // 8 + # Create UICR hex object with final UICR data uicr_hex = IntelHex() - uicr_hex.frombytes(bytes(uicr), offset=uicr_node.regs[0].addr) + uicr_hex.frombytes(bytes(uicr), offset=args.uicr_address) - uicr_hex.write_hex_file(args.out_uicr_hex) + # Create merged hex by combining UICR and periphconf hex objects + merged_hex = IntelHex() + merged_hex.fromdict(uicr_hex.todict()) - if args.out_periphconf_hex is not None: - periphconf_hex = IntelHex() - periphconf_hex.frombytes(periphconf_final, offset=periphconf_address) + if args.out_periphconf_hex: periphconf_hex.write_hex_file(args.out_periphconf_hex) + merged_hex.fromdict(periphconf_hex.todict()) + + if args.out_secondary_periphconf_hex: + secondary_periphconf_hex.write_hex_file(args.out_secondary_periphconf_hex) + merged_hex.fromdict(secondary_periphconf_hex.todict()) + + merged_hex.write_hex_file(args.out_merged_hex) + uicr_hex.write_hex_file(args.out_uicr_hex) except ScriptError as e: print(f"Error: {e!s}") @@ -270,16 +459,5 @@ def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes return bytes(final_periphconf) -def parse_kconfig(content: str) -> dict[str, str | None]: - result = defaultdict(None) - match_iter = re.finditer( - r"^(?P(SB_)?CONFIG_[^=\s]+)=(?P[^\s#])+$", content, re.MULTILINE - ) - for match in match_iter: - result[match["config"]] = match["value"] - - return result - - if __name__ == "__main__": main() diff --git a/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt new file mode 100644 index 00000000000..1163ff8e21f --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# The code in this CMakeLists.txt constructs the arguments for gen_uicr.py +# and creates a flashable zephyr.hex file containing UICR data (no C code compiled) +# + +cmake_minimum_required(VERSION 3.20.0) + +# Instead of adding all of Zephyr we add just the subset that is +# required to generate uicr.hex. +# +# The generation of uicr.hex is configured by this image, so we +# include modules from zephyr_default up until kconfig. + +find_package(Zephyr + COMPONENTS zephyr_default:kconfig + REQUIRED HINTS $ENV{ZEPHYR_BASE} + ) + +project(uicr) + +# Function to parse a Kconfig value from a .config file +function(parse_kconfig_value config_file config_name output_var) + file(STRINGS ${config_file} config_lines ENCODING "UTF-8") + foreach(line ${config_lines}) + if("${line}" MATCHES "^${config_name}=\"(.*)\"$") + set(${output_var} "${CMAKE_MATCH_1}" PARENT_SCOPE) + return() + endif() + endforeach() +endfunction() + +# Function to compute partition absolute address and size from devicetree +function(compute_partition_address_and_size partition_nodelabel output_address_var output_size_var) + dt_nodelabel(partition_path NODELABEL ${partition_nodelabel} REQUIRED) + dt_reg_addr(partition_offset PATH ${partition_path} REQUIRED) + dt_reg_size(partition_size PATH ${partition_path} REQUIRED) + + # Calculate absolute partition address + math(EXPR partition_address "${CONFIG_FLASH_BASE_ADDRESS} + ${partition_offset}" OUTPUT_FORMAT HEXADECIMAL) + + # Set output variables in parent scope + set(${output_address_var} ${partition_address} PARENT_SCOPE) + set(${output_size_var} ${partition_size} PARENT_SCOPE) +endfunction() + +# Use CMAKE_VERBOSE_MAKEFILE to silence an unused-variable warning. +if(CMAKE_VERBOSE_MAKEFILE) +endif() + +set(periphconf_args) +set(periphconf_elfs) +set(merged_hex_file ${APPLICATION_BINARY_DIR}/zephyr/${CONFIG_KERNEL_BIN_NAME}.hex) +set(secondary_periphconf_elfs) +set(uicr_hex_file ${APPLICATION_BINARY_DIR}/zephyr/uicr.hex) +set(periphconf_hex_file ${APPLICATION_BINARY_DIR}/zephyr/periphconf.hex) +set(secondary_periphconf_hex_file ${APPLICATION_BINARY_DIR}/zephyr/secondary_periphconf.hex) + +# Get UICR absolute address from this image's devicetree +dt_nodelabel(uicr_path NODELABEL "uicr" REQUIRED) +dt_reg_addr(UICR_ADDRESS PATH ${uicr_path} REQUIRED) + +if(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) + # gen_uicr.py parses all zephyr.elf files. To find these files (which + # have not been built yet) we scan sibling build directories for + # zephyr.dts + get_filename_component(SYSBUILD_DIR ${APPLICATION_BINARY_DIR} DIRECTORY) + file(GLOB _siblings LIST_DIRECTORIES true "${SYSBUILD_DIR}/*") + foreach(_dir ${_siblings}) + get_filename_component(_name ${_dir} NAME) + if(_name STREQUAL "uicr") + # This image is an exception to the rule. It has a zephyr.dts, but + # no zephyr.elf + continue() + endif() + + if(EXISTS ${_dir}/zephyr/zephyr.dts) + # Read CONFIG_KERNEL_BIN_NAME from the sibling's .config file + parse_kconfig_value(${_dir}/zephyr/.config CONFIG_KERNEL_BIN_NAME kernel_bin_name) + set(kernel_elf_path ${_dir}/zephyr/${kernel_bin_name}.elf) + + # Check if this is secondary firmware by looking for the marker file + if(EXISTS ${_dir}/is_secondary_firmware.txt) + list(APPEND secondary_periphconf_elfs ${kernel_elf_path}) + else() + list(APPEND periphconf_elfs ${kernel_elf_path}) + endif() + endif() + endforeach() + + # Compute PERIPHCONF absolute address and size from this image's devicetree + compute_partition_address_and_size("periphconf_partition" PERIPHCONF_ADDRESS PERIPHCONF_SIZE) + + # Set up periphconf arguments for gen_uicr.py + list(APPEND periphconf_args --periphconf-address ${PERIPHCONF_ADDRESS}) + list(APPEND periphconf_args --periphconf-size ${PERIPHCONF_SIZE}) + list(APPEND periphconf_args --out-periphconf-hex ${periphconf_hex_file}) + + foreach(elf ${periphconf_elfs}) + list(APPEND periphconf_args --in-periphconf-elf ${elf}) + endforeach() +endif(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) + +if(CONFIG_GEN_UICR_SECONDARY) + set(secondary_args --secondary) + + # Compute SECONDARY partition absolute address from this image's devicetree + compute_partition_address_and_size("secondary_partition" SECONDARY_ADDRESS SECONDARY_SIZE) + + list(APPEND secondary_args + --secondary-address ${SECONDARY_ADDRESS} + ) + + if(CONFIG_GEN_UICR_SECONDARY_GENERATE_PERIPHCONF) + # Compute SECONDARY_PERIPHCONF absolute address and size from this image's devicetree + compute_partition_address_and_size("secondary_periphconf_partition" SECONDARY_PERIPHCONF_ADDRESS SECONDARY_PERIPHCONF_SIZE) + + list(APPEND secondary_args --secondary-periphconf-address ${SECONDARY_PERIPHCONF_ADDRESS}) + list(APPEND secondary_args --secondary-periphconf-size ${SECONDARY_PERIPHCONF_SIZE}) + list(APPEND secondary_args --out-secondary-periphconf-hex ${secondary_periphconf_hex_file}) + + foreach(elf ${secondary_periphconf_elfs}) + list(APPEND secondary_args --in-secondary-periphconf-elf ${elf}) + endforeach() + endif() +endif() + +# Generate hex files (merged, uicr-only, periphconf-only, and secondary-periphconf-only) +add_custom_command( + OUTPUT ${merged_hex_file} ${uicr_hex_file} ${periphconf_hex_file} ${secondary_periphconf_hex_file} + COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${ZEPHYR_BASE}/scripts/dts/python-devicetree/src + ${PYTHON_EXECUTABLE} ${ZEPHYR_BASE}/soc/nordic/common/uicr/gen_uicr.py + --uicr-address ${UICR_ADDRESS} + --out-merged-hex ${merged_hex_file} + --out-uicr-hex ${uicr_hex_file} + ${periphconf_args} + ${secondary_args} + DEPENDS ${periphconf_elfs} ${secondary_periphconf_elfs} + WORKING_DIRECTORY ${APPLICATION_BINARY_DIR} + COMMENT "Using gen_uicr.py to generate ${merged_hex_file}, ${uicr_hex_file}, ${periphconf_hex_file}, and ${secondary_periphconf_hex_file} from ${periphconf_elfs} ${secondary_periphconf_elfs}" +) + +# Add zephyr subdirectory to handle flash configuration with correct paths +add_subdirectory(zephyr) + +add_custom_target(gen_uicr ALL DEPENDS ${merged_hex_file} ${uicr_hex_file} ${periphconf_hex_file} ${secondary_periphconf_hex_file}) diff --git a/soc/nordic/common/uicr/gen_uicr/Kconfig b/soc/nordic/common/uicr/gen_uicr/Kconfig new file mode 100644 index 00000000000..41d31db6464 --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/Kconfig @@ -0,0 +1,26 @@ +# Copyright (c) 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +menu "UICR generator options" + +config GEN_UICR_GENERATE_PERIPHCONF + bool "Generate PERIPHCONF hex alongside UICR" + default y + help + When enabled, the UICR generator will populate the + periphconf_partition partition. + +config GEN_UICR_SECONDARY + bool "Enable UICR.SECONDARY.ENABLE" + +config GEN_UICR_SECONDARY_GENERATE_PERIPHCONF + bool "Generate SECONDARY.PERIPHCONF hex alongside UICR" + default y + depends on GEN_UICR_SECONDARY + help + When enabled, the UICR generator will populate the + secondary_periphconf_partition partition. + +endmenu + +source "Kconfig.zephyr" diff --git a/soc/nordic/common/uicr/gen_uicr/prj.conf b/soc/nordic/common/uicr/gen_uicr/prj.conf new file mode 100644 index 00000000000..b2a4ba59104 --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/prj.conf @@ -0,0 +1 @@ +# nothing here diff --git a/soc/nordic/common/uicr/gen_uicr/zephyr/CMakeLists.txt b/soc/nordic/common/uicr/gen_uicr/zephyr/CMakeLists.txt new file mode 100644 index 00000000000..1d020664016 --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/zephyr/CMakeLists.txt @@ -0,0 +1,22 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Flash configuration for UICR domain +# This subdirectory ensures runners.yaml is generated in the correct location + +# Manually include board configuration to enable automatic runners.yaml generation +include(${BOARD_DIR}/board.cmake OPTIONAL) + +# Create the runners_yaml_props_target that flash system expects +add_custom_target(runners_yaml_props_target) + +# Set hex_file property to point to zephyr.hex in this directory +set_target_properties(runners_yaml_props_target PROPERTIES + hex_file "zephyr.hex" +) + +# Override the runners.yaml path to use CMAKE_CURRENT_BINARY_DIR instead of PROJECT_BINARY_DIR +# This ensures runners.yaml is generated at build/uicr/zephyr/ where west expects it +set(PROJECT_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}) + +# Include flash support to automatically generate runners.yaml +include(${ZEPHYR_BASE}/cmake/flash/CMakeLists.txt) diff --git a/soc/nordic/common/uicr/sysbuild.cmake b/soc/nordic/common/uicr/sysbuild.cmake new file mode 100644 index 00000000000..9cafd261036 --- /dev/null +++ b/soc/nordic/common/uicr/sysbuild.cmake @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: Apache-2.0 + +# Add UICR generator as a utility image +ExternalZephyrProject_Add( + APPLICATION uicr + SOURCE_DIR ${CMAKE_CURRENT_LIST_DIR}/gen_uicr +) + +# Ensure UICR is configured and built after the default image so EDT/ELFs exist. +sysbuild_add_dependencies(CONFIGURE uicr ${DEFAULT_IMAGE}) + +add_dependencies(uicr ${DEFAULT_IMAGE}) +if(DEFINED image) + add_dependencies(uicr ${image}) +endif() diff --git a/soc/nordic/ironside/CMakeLists.txt b/soc/nordic/ironside/CMakeLists.txt index 98e721541d7..ea7f68207a8 100644 --- a/soc/nordic/ironside/CMakeLists.txt +++ b/soc/nordic/ironside/CMakeLists.txt @@ -6,6 +6,7 @@ zephyr_library() zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_CALL call.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_BOOT_REPORT boot_report.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_CPUCONF_SERVICE cpuconf.c) +zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_BOOTMODE_SERVICE bootmode.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_TDD_SERVICE tdd.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_UPDATE_SERVICE update.c) zephyr_library_sources_ifdef(CONFIG_NRF_IRONSIDE_DVFS_SERVICE dvfs.c) diff --git a/soc/nordic/ironside/Kconfig b/soc/nordic/ironside/Kconfig index 3136e3bd5f4..ce4878b7b18 100644 --- a/soc/nordic/ironside/Kconfig +++ b/soc/nordic/ironside/Kconfig @@ -56,6 +56,12 @@ config NRF_IRONSIDE_BOOT_REPORT help Support for parsing the Boot Report populated by Nordic IronSide firmware. +config NRF_IRONSIDE_BOOTMODE_SERVICE + bool "IronSide boot mode service" + select NRF_IRONSIDE_CALL + help + Service used to reboot into secondary firmware boot mode. + config NRF_IRONSIDE_DVFS_SERVICE bool "IronSide DVFS service" depends on SOC_NRF54H20_CPUAPP diff --git a/soc/nordic/ironside/bootmode.c b/soc/nordic/ironside/bootmode.c new file mode 100644 index 00000000000..92ca5e31265 --- /dev/null +++ b/soc/nordic/ironside/bootmode.c @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include +#include +#include + +#define BOOT_MODE_SECONDARY (0x1) + +BUILD_ASSERT(IRONSIDE_BOOTMODE_SERVICE_NUM_ARGS <= NRF_IRONSIDE_CALL_NUM_ARGS); + +int ironside_bootmode_secondary_reboot(const uint8_t *msg, size_t msg_size) +{ + int err; + struct ironside_call_buf *buf; + uint8_t *buf_msg; + + if (msg_size > IRONSIDE_BOOTMODE_SERVICE_MSG_MAX_SIZE) { + return -IRONSIDE_BOOTMODE_ERROR_MESSAGE_TOO_LARGE; + } + + buf = ironside_call_alloc(); + + buf->id = IRONSIDE_CALL_ID_BOOTMODE_SERVICE_V1; + + buf->args[IRONSIDE_BOOTMODE_SERVICE_MODE_IDX] = BOOT_MODE_SECONDARY; + + buf_msg = (uint8_t *)&buf->args[IRONSIDE_BOOTMODE_SERVICE_MSG_0_IDX]; + + memset(buf_msg, 0, IRONSIDE_BOOTMODE_SERVICE_MSG_MAX_SIZE); + + if (msg_size > 0) { + memcpy(buf_msg, msg, msg_size); + } + + ironside_call_dispatch(buf); + + if (buf->status == IRONSIDE_CALL_STATUS_RSP_SUCCESS) { + err = buf->args[IRONSIDE_BOOTMODE_SERVICE_RETCODE_IDX]; + } else { + err = buf->status; + } + + ironside_call_release(buf); + + return err; +} diff --git a/soc/nordic/ironside/include/nrf_ironside/bootmode.h b/soc/nordic/ironside/include/nrf_ironside/bootmode.h new file mode 100644 index 00000000000..4da46f33dd6 --- /dev/null +++ b/soc/nordic/ironside/include/nrf_ironside/bootmode.h @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2025 Nordic Semiconductor ASA + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_BOOTMODE_H_ +#define ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_BOOTMODE_H_ + +#include +#include +#include + +/** + * @name Boot mode service error codes. + * @{ + */ + +/** Invalid/unsupported boot mode transition. */ +#define IRONSIDE_BOOTMODE_ERROR_UNSUPPORTED_MODE (1) +/** Failed to reboot into the boot mode due to other activity preventing a reset. */ +#define IRONSIDE_BOOTMODE_ERROR_BUSY (2) +/** The boot message is too large to fit in the buffer. */ +#define IRONSIDE_BOOTMODE_ERROR_MESSAGE_TOO_LARGE (3) + +/** + * @} + */ + +/* IronSide call identifiers with implicit versions. */ +#define IRONSIDE_CALL_ID_BOOTMODE_SERVICE_V1 5 + +enum { + IRONSIDE_BOOTMODE_SERVICE_MODE_IDX, + IRONSIDE_BOOTMODE_SERVICE_MSG_0_IDX, + IRONSIDE_BOOTMODE_SERVICE_MSG_1_IDX, + IRONSIDE_BOOTMODE_SERVICE_MSG_2_IDX, + IRONSIDE_BOOTMODE_SERVICE_MSG_3_IDX, + /* The last enum value is reserved for the number of arguments */ + IRONSIDE_BOOTMODE_SERVICE_NUM_ARGS, +}; + +/* Maximum size of the message parameter. */ +#define IRONSIDE_BOOTMODE_SERVICE_MSG_MAX_SIZE (4 * sizeof(uint32_t)) + +/* Index of the return code within the service buffer. */ +#define IRONSIDE_BOOTMODE_SERVICE_RETCODE_IDX (0) + +/** + * @brief Request a reboot into the secondary firmware boot mode. + * + * This invokes the IronSide SE boot mode service to restart the system into the secondary boot + * mode. In this mode, the secondary configuration defined in UICR is applied instead of the + * primary one. The system immediately reboots without a reply if the request succeeds. + * + * The given message data is passed to the boot report of the CPU booted in the secondary boot mode. + * + * @note This function does not return if the request is successful. + * @note The device will boot into the secondary firmware instead of primary firmware. + * @note The request does not fail if the secondary firmware is not defined. + * + * @param msg A message that can be placed in the cpu's boot report. + * @param msg_size Size of the message in bytes. + * + * @retval 0 on success. + * @retval -IRONSIDE_BOOTMODE_ERROR_UNSUPPORTED_MODE if the secondary boot mode is unsupported. + * @retval -IRONSIDE_BOOTMODE_ERROR_BUSY if the reboot was blocked. + * @retval -IRONSIDE_BOOTMODE_ERROR_MESSAGE_TOO_LARGE if msg_size is greater than + * IRONSIDE_BOOTMODE_SERVICE_MSG_MAX_SIZE. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). + */ +int ironside_bootmode_secondary_reboot(const uint8_t *msg, size_t msg_size); + +#endif /* ZEPHYR_SOC_NORDIC_IRONSIDE_INCLUDE_NRF_IRONSIDE_BOOTMODE_H_ */ diff --git a/soc/nordic/ironside/include/nrf_ironside/cpuconf.h b/soc/nordic/ironside/include/nrf_ironside/cpuconf.h index 49e562b1369..b112396704a 100644 --- a/soc/nordic/ironside/include/nrf_ironside/cpuconf.h +++ b/soc/nordic/ironside/include/nrf_ironside/cpuconf.h @@ -63,10 +63,10 @@ BUILD_ASSERT(IRONSIDE_CPUCONF_NUM_ARGS <= NRF_IRONSIDE_CALL_NUM_ARGS); * If the given msg_size is less than that, the remaining bytes are set to zero. * * @retval 0 on success or if the CPU has already booted. - * @retval Positive non-0 error status if reported by IronSide call. - * @retval -IRONSIDE_CPUCONF_ERROR_WRONG_CPU if cpu is unrecognized + * @retval -IRONSIDE_CPUCONF_ERROR_WRONG_CPU if cpu is unrecognized. * @retval -IRONSIDE_CPUCONF_ERROR_MESSAGE_TOO_LARGE if msg_size is greater than * IRONSIDE_CPUCONF_SERVICE_MSG_MAX_SIZE. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). */ int ironside_cpuconf(NRF_PROCESSORID_Type cpu, const void *vector_table, bool cpu_wait, const uint8_t *msg, size_t msg_size); diff --git a/soc/nordic/ironside/include/nrf_ironside/dvfs.h b/soc/nordic/ironside/include/nrf_ironside/dvfs.h index f48c8a6344a..c47cc43a859 100644 --- a/soc/nordic/ironside/include/nrf_ironside/dvfs.h +++ b/soc/nordic/ironside/include/nrf_ironside/dvfs.h @@ -68,7 +68,19 @@ enum ironside_dvfs_oppoint { * specified value. It will block until the change is applied. * * @param dvfs_oppoint The new DVFS oppoint to set. - * @return int 0 on success, negative error code on failure. + * + * @retval 0 on success. + * @retval -IRONSIDE_DVFS_ERROR_WRONG_OPPOINT if the requested DVFS oppoint is not allowed. + * @retval -IRONSIDE_DVFS_ERROR_BUSY if waiting for mutex lock timed out, or hardware is busy. + * @retval -IRONSIDE_DVFS_ERROR_OPPOINT_DATA if there is configuration error in the DVFS service. + * @retval -IRONSIDE_DVFS_ERROR_PERMISSION if the caller does not have permission to change the DVFS + * oppoint. + * @retval -IRONSIDE_DVFS_ERROR_NO_CHANGE_NEEDED if the requested DVFS oppoint is already set. + * @retval -IRONSIDE_DVFS_ERROR_TIMEOUT if the operation timed out, possibly due to a hardware + * issue. + * @retval -IRONSIDE_DVFS_ERROR_ISR_NOT_ALLOWED if the DVFS oppoint change operation is not allowed + * in the ISR context. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). */ int ironside_dvfs_change_oppoint(enum ironside_dvfs_oppoint dvfs_oppoint); diff --git a/soc/nordic/ironside/include/nrf_ironside/tdd.h b/soc/nordic/ironside/include/nrf_ironside/tdd.h index d065b1619a6..adfb1c53a64 100644 --- a/soc/nordic/ironside/include/nrf_ironside/tdd.h +++ b/soc/nordic/ironside/include/nrf_ironside/tdd.h @@ -31,7 +31,8 @@ enum ironside_se_tdd_config { * @param config The configuration to be applied. * * @retval 0 on success. - * @retval -IRONSIDE_SE_TDD_ERROR_EINVAL on invalid argument. + * @retval -IRONSIDE_SE_TDD_SERVICE_ERROR_INVALID_CONFIG if the configuration is invalid. + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). */ int ironside_se_tdd_configure(const enum ironside_se_tdd_config config); diff --git a/soc/nordic/ironside/include/nrf_ironside/update.h b/soc/nordic/ironside/include/nrf_ironside/update.h index c0f021a91ed..b01b5df6dad 100644 --- a/soc/nordic/ironside/include/nrf_ironside/update.h +++ b/soc/nordic/ironside/include/nrf_ironside/update.h @@ -61,10 +61,10 @@ struct ironside_update_blob { * * @param update Pointer to update blob * + * @retval 0 on a successful request (although the update itself may still fail). * @retval -IRONSIDE_UPDATE_ERROR_NOT_PERMITTED if missing access to the update candidate. * @retval -IRONSIDE_UPDATE_ERROR_SICR_WRITE_FAILED if writing update parameters to SICR failed. - * @returns Positive non-0 error status if reported by IronSide call. - * @returns 0 on a successful request (although the update itself may still fail). + * @retval Positive error status if reported by IronSide call (see error codes in @ref call.h). * */ int ironside_update(const struct ironside_update_blob *update); diff --git a/soc/nordic/sysbuild.cmake b/soc/nordic/sysbuild.cmake index 03db1a5ad18..631c79d57fc 100644 --- a/soc/nordic/sysbuild.cmake +++ b/soc/nordic/sysbuild.cmake @@ -29,3 +29,7 @@ if(SB_CONFIG_VPR_LAUNCHER) sysbuild_cache_set(VAR ${image}_SNIPPET APPEND REMOVE_DUPLICATES ${launcher_snippet}) endif() + +if(SB_CONFIG_NRF_HALTIUM_GENERATE_UICR) + include(${CMAKE_CURRENT_LIST_DIR}/common/uicr/sysbuild.cmake) +endif() diff --git a/tests/drivers/comparator/gpio_loopback/testcase.yaml b/tests/drivers/comparator/gpio_loopback/testcase.yaml index bf0e7d127a0..ad25bd224b0 100644 --- a/tests/drivers/comparator/gpio_loopback/testcase.yaml +++ b/tests/drivers/comparator/gpio_loopback/testcase.yaml @@ -18,6 +18,7 @@ tests: - frdm_ke15z drivers.comparator.gpio_loopback.nrf_comp: extra_args: + - SNIPPET_ROOT="." - SNIPPET="gpio_loopback_nrf_comp" platform_allow: - nrf5340dk/nrf5340/cpuapp @@ -27,6 +28,7 @@ tests: - ophelia4ev/nrf54l15/cpuapp drivers.comparator.gpio_loopback.nrf_lpcomp: extra_args: + - SNIPPET_ROOT="." - SNIPPET="gpio_loopback_nrf_lpcomp" platform_allow: - nrf5340dk/nrf5340/cpuapp