From 7f0e404c878871b07b120ad34e4d1cdd1af0241a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Thu, 21 Aug 2025 13:56:22 +0200 Subject: [PATCH 01/10] [nrf fromtree] tests: drivers: comparator: gpio_loopback: Update SNIPPET_ROOT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When SNIPPET is added to the build, it is added to all images. But not all images have the test directory added to their SNIPPET_ROOT, so we also update the SNIPPET_ROOT. This allows, for instance, the uicr image of nrf54H to find this snippet. Ref: NCSDK-NONE Signed-off-by: Sebastian Bøe (cherry picked from commit 5f9d20cf38fd9e7a15641b874781d52fb2757cac) --- tests/drivers/comparator/gpio_loopback/testcase.yaml | 2 ++ 1 file changed, 2 insertions(+) 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 From e0deeb5b90774ab7dc816f3b79287f6455163571 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Thu, 21 Aug 2025 12:15:29 +0200 Subject: [PATCH 02/10] [nrf fromtree] soc: nordic: uicr: Reorganize how gen_uicr.py is invoked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reorganize how gen_uicr.py is invoked. Instead of invoking it from one of the Zephyr images we invoke it from a new special Zephyr image called uicr. This uicr Zephyr image is flashed in the same way as normal Zephyr images so special handling in the runner is no longer necessary. Also, we simplify gen_uicr.py by moving parsing of Kconfig/DT from gen_uicr.py to CMake. Signed-off-by: Sebastian Bøe (cherry picked from commit ac851ca1601b385e38a1f6062d24dc407664dfaf) --- scripts/ci/check_compliance.py | 1 + scripts/west_commands/runners/nrf_common.py | 20 --- soc/nordic/Kconfig.sysbuild | 1 + soc/nordic/common/CMakeLists.txt | 4 +- soc/nordic/common/uicr/CMakeLists.txt | 30 ---- soc/nordic/common/uicr/Kconfig | 22 +-- soc/nordic/common/uicr/Kconfig.sysbuild | 9 ++ soc/nordic/common/uicr/gen_uicr.py | 134 +++++++++--------- .../common/uicr/gen_uicr/CMakeLists.txt | 99 +++++++++++++ soc/nordic/common/uicr/gen_uicr/Kconfig | 14 ++ soc/nordic/common/uicr/gen_uicr/prj.conf | 1 + .../uicr/gen_uicr/zephyr/CMakeLists.txt | 22 +++ soc/nordic/common/uicr/sysbuild.cmake | 11 ++ soc/nordic/sysbuild.cmake | 4 + 14 files changed, 231 insertions(+), 141 deletions(-) create mode 100644 soc/nordic/common/uicr/Kconfig.sysbuild create mode 100644 soc/nordic/common/uicr/gen_uicr/CMakeLists.txt create mode 100644 soc/nordic/common/uicr/gen_uicr/Kconfig create mode 100644 soc/nordic/common/uicr/gen_uicr/prj.conf create mode 100644 soc/nordic/common/uicr/gen_uicr/zephyr/CMakeLists.txt create mode 100644 soc/nordic/common/uicr/sysbuild.cmake diff --git a/scripts/ci/check_compliance.py b/scripts/ci/check_compliance.py index b0f4ccebb1e..0e10d161895 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -1231,6 +1231,7 @@ 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 "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..9914a8515a8 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 @@ -141,18 +132,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,71 +144,103 @@ 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", - default=None, type=argparse.FileType("w", encoding="utf-8"), - help="Path to write the generated PERIPHCONF HEX file to", + 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)", ) 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") + 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) + # Process periphconf data first and configure UICR completely before creating hex objects + periphconf_hex = IntelHex() - edt = pickle.load(args.in_edt_pickle) + if args.out_periphconf_hex: + periphconf_combined = extract_and_combine_periphconfs(args.in_periphconf_elfs) - 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 + padding_len = args.periphconf_size - len(periphconf_combined) + periphconf_final = periphconf_combined + bytes([0xFF for _ in range(padding_len)]) - 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 + # Add periphconf data to periphconf hex object + periphconf_hex.frombytes(periphconf_final, offset=args.periphconf_address) - 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)]) - - 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" + ) - 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 + uicr.PERIPHCONF.MAXCOUNT = args.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()) + + 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}") sys.exit(1) @@ -270,16 +281,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..e0002e4e847 --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt @@ -0,0 +1,99 @@ +# 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 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(uicr_hex_file ${APPLICATION_BINARY_DIR}/zephyr/uicr.hex) +set(periphconf_hex_file ${APPLICATION_BINARY_DIR}/zephyr/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) + list(APPEND periphconf_elfs ${_dir}/zephyr/zephyr.elf) + 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) + +# Generate hex files (merged, uicr-only, and periphconf-only) +add_custom_command( + OUTPUT ${merged_hex_file} ${uicr_hex_file} ${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} + DEPENDS ${periphconf_elfs} + WORKING_DIRECTORY ${APPLICATION_BINARY_DIR} + COMMENT "Using gen_uicr.py to generate ${merged_hex_file} from ${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}) diff --git a/soc/nordic/common/uicr/gen_uicr/Kconfig b/soc/nordic/common/uicr/gen_uicr/Kconfig new file mode 100644 index 00000000000..9d483e737d4 --- /dev/null +++ b/soc/nordic/common/uicr/gen_uicr/Kconfig @@ -0,0 +1,14 @@ +# 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 emit periphconf.hex in addition to uicr.hex. + +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..167ac7c64a2 --- /dev/null +++ b/soc/nordic/common/uicr/sysbuild.cmake @@ -0,0 +1,11 @@ +# 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}) 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() From da8a9dcf442c231b419d7fe8245cb5cd06c793a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Fri, 5 Sep 2025 16:34:27 +0200 Subject: [PATCH 03/10] [nrf fromtree] soc: noric: nrf54h20: Fix custom CONFIG_KERNEL_BIN_NAME bug MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix bug where users were unable to name their binary Bøe when building for nrf54h20. Signed-off-by: Sebastian Bøe (cherry picked from commit 180f1f8917ea3f4f6534d5f0fb8b20ef7a0626b1) --- soc/nordic/common/uicr/gen_uicr/CMakeLists.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt index e0002e4e847..28893f5a671 100644 --- a/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt +++ b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt @@ -19,6 +19,17 @@ find_package(Zephyr 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) @@ -62,7 +73,9 @@ if(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) endif() if(EXISTS ${_dir}/zephyr/zephyr.dts) - list(APPEND periphconf_elfs ${_dir}/zephyr/zephyr.elf) + # Read CONFIG_KERNEL_BIN_NAME from the sibling's .config file + parse_kconfig_value(${_dir}/zephyr/.config CONFIG_KERNEL_BIN_NAME kernel_bin_name) + list(APPEND periphconf_elfs ${_dir}/zephyr/${kernel_bin_name}.elf) endif() endforeach() From f3da694ca77ee74d52503060f0ea62c187dfad74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Fri, 5 Sep 2025 16:36:08 +0200 Subject: [PATCH 04/10] [nrf fromtree] soc: nordic: Update UICR format MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Update the C struct for UICR to the latest revision. Signed-off-by: Sebastian Bøe (cherry picked from commit 67b0e045ebe10154cecd751ec9e6d5910c88f10a) --- soc/nordic/common/uicr/gen_uicr.py | 96 ++++++++++++++++++++++++++---- 1 file changed, 86 insertions(+), 10 deletions(-) diff --git a/soc/nordic/common/uicr/gen_uicr.py b/soc/nordic/common/uicr/gen_uicr.py index 9914a8515a8..4e3b7095478 100644 --- a/soc/nordic/common/uicr/gen_uicr.py +++ b/soc/nordic/common/uicr/gen_uicr.py @@ -66,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 SecurestorageCrypto(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("APPLICATIONSIZE1KB", c.c_uint32), + ("RADIOCORESIZE1KB", c.c_uint32), ] -class Its(c.LittleEndianStructure): +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), ] @@ -104,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_ = [ @@ -114,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), ] From 12561b4527969006f9666dd7bc97c62f57657722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Fri, 5 Sep 2025 16:36:46 +0200 Subject: [PATCH 05/10] [nrf fromtree] soc: nordic: gen_uicr: Support secondary firmware MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add support for secondary firmware in gen_uicr.py. Signed-off-by: Sebastian Bøe (cherry picked from commit 3648cd87d4a378cd91812614e2174e74772f46be) --- .../nrf54h20dk_nrf54h20-memory_map.dtsi | 8 ++ scripts/ci/check_compliance.py | 2 + soc/nordic/common/uicr/gen_uicr.py | 104 +++++++++++++++++- .../common/uicr/gen_uicr/CMakeLists.txt | 46 +++++++- soc/nordic/common/uicr/gen_uicr/Kconfig | 14 ++- 5 files changed, 166 insertions(+), 8 deletions(-) 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 0e10d161895..251cbc97a58 100755 --- a/scripts/ci/check_compliance.py +++ b/scripts/ci/check_compliance.py @@ -1232,6 +1232,8 @@ def check_no_undef_outside_kconfig(self, kconf): "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/soc/nordic/common/uicr/gen_uicr.py b/soc/nordic/common/uicr/gen_uicr.py index 4e3b7095478..78422e4312c 100644 --- a/soc/nordic/common/uicr/gen_uicr.py +++ b/soc/nordic/common/uicr/gen_uicr.py @@ -255,6 +255,50 @@ def main() -> None: 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 secondary PERIPHCONF-only HEX file to", + ) args = parser.parse_args() try: @@ -267,6 +311,22 @@ def main() -> None: 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) @@ -275,6 +335,7 @@ def main() -> None: # Process periphconf data first and configure UICR completely before creating hex objects periphconf_hex = IntelHex() + secondary_periphconf_hex = IntelHex() if args.out_periphconf_hex: periphconf_combined = extract_and_combine_periphconfs(args.in_periphconf_elfs) @@ -301,6 +362,44 @@ def main() -> None: 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 + + # 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=args.uicr_address) @@ -311,9 +410,12 @@ def main() -> None: 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) diff --git a/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt index 28893f5a671..1163ff8e21f 100644 --- a/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt +++ b/soc/nordic/common/uicr/gen_uicr/CMakeLists.txt @@ -51,8 +51,10 @@ 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) @@ -75,7 +77,14 @@ if(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) 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) - list(APPEND periphconf_elfs ${_dir}/zephyr/${kernel_bin_name}.elf) + 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() @@ -92,21 +101,46 @@ if(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) endforeach() endif(CONFIG_GEN_UICR_GENERATE_PERIPHCONF) -# Generate hex files (merged, uicr-only, and periphconf-only) +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} + 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} - DEPENDS ${periphconf_elfs} + ${secondary_args} + DEPENDS ${periphconf_elfs} ${secondary_periphconf_elfs} WORKING_DIRECTORY ${APPLICATION_BINARY_DIR} - COMMENT "Using gen_uicr.py to generate ${merged_hex_file} from ${periphconf_elfs}" + 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}) +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 index 9d483e737d4..41d31db6464 100644 --- a/soc/nordic/common/uicr/gen_uicr/Kconfig +++ b/soc/nordic/common/uicr/gen_uicr/Kconfig @@ -7,7 +7,19 @@ config GEN_UICR_GENERATE_PERIPHCONF bool "Generate PERIPHCONF hex alongside UICR" default y help - When enabled, the UICR generator will emit periphconf.hex in addition to uicr.hex. + 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 From 3054fca383244fb57b816cf355119ae7c38fcc66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Wed, 10 Sep 2025 11:53:12 +0200 Subject: [PATCH 06/10] [nrf fromtree] soc: nordic: ironside: Add bootmode service MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added support for the IronSide bootmode service which allows requesting a reboot into secondary firmware boot mode. In this mode, the secondary configuration defined in UICR is applied instead of the primary one. The service provides the ironside_bootmode_secondary_reboot() function that can pass message data to the boot report of the CPU booted in the secondary boot mode. Signed-off-by: Sebastian Bøe (cherry picked from commit 377a18caeef5028edb72289de7ca6e01375979e4) --- soc/nordic/ironside/CMakeLists.txt | 1 + soc/nordic/ironside/Kconfig | 6 ++ soc/nordic/ironside/bootmode.c | 51 +++++++++++++ .../ironside/include/nrf_ironside/bootmode.h | 73 +++++++++++++++++++ 4 files changed, 131 insertions(+) create mode 100644 soc/nordic/ironside/bootmode.c create mode 100644 soc/nordic/ironside/include/nrf_ironside/bootmode.h 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_ */ From 487fbb25b50ad1fa1762df178f56385dd066ee78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Wed, 10 Sep 2025 11:54:21 +0200 Subject: [PATCH 07/10] [nrf fromtree] soc: nordic: ironside: Clean up error code docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clean up error code docs. Signed-off-by: Sebastian Bøe (cherry picked from commit 363bad0705aa7f1c31b54e252faf3e2c8de46178) --- soc/nordic/ironside/include/nrf_ironside/cpuconf.h | 4 ++-- soc/nordic/ironside/include/nrf_ironside/dvfs.h | 14 +++++++++++++- soc/nordic/ironside/include/nrf_ironside/tdd.h | 3 ++- soc/nordic/ironside/include/nrf_ironside/update.h | 4 ++-- 4 files changed, 19 insertions(+), 6 deletions(-) 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); From 15ff2a0520ab03ddac66b543ab5818804a60bf53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20B=C3=B8e?= Date: Wed, 10 Sep 2025 13:41:12 +0200 Subject: [PATCH 08/10] [nrf fromtree] soc: nordic: uicr: Fix dependency issue MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Although not reproducible locally, it has been observed in CI that the uicr image will not always be the last image to be run. To ensure it is the last image to be run we have it depend on the 'image' image when defined. The uicr image is generated based on all other images in the build and must therefore run last. Signed-off-by: Sebastian Bøe (cherry picked from commit 32c67762568c1aeb4484c2abf621f3f11f0ccf78) --- soc/nordic/common/uicr/sysbuild.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/soc/nordic/common/uicr/sysbuild.cmake b/soc/nordic/common/uicr/sysbuild.cmake index 167ac7c64a2..9cafd261036 100644 --- a/soc/nordic/common/uicr/sysbuild.cmake +++ b/soc/nordic/common/uicr/sysbuild.cmake @@ -8,4 +8,8 @@ ExternalZephyrProject_Add( # 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() From 28cae3a8bd8a1078bbdaa71d9f941ed794d106d5 Mon Sep 17 00:00:00 2001 From: Jonathan Nilsen Date: Tue, 9 Sep 2025 15:29:25 +0200 Subject: [PATCH 09/10] [nrf fromtree] soc: nordic: nrf54h: generate PERIPHCONF entries based on devicetree Add build system support for populating the PERIPHCONF (global domain peripheral configuration), based on nodes and properties found in the devicetree. This should make it so all samples and tests that were broken by the move to IronSide SE now function correctly without workarounds or manual steps. When enabled, a new python script called gen_periphconf_entries.py is run when building. The script iterates over nodes and properties in the devicetree and generates a C file called periphconf_entries_generated.c in the build directory, which is added as a source file. The C file uses the macros from uicr.h to configure the global domain according to the devicetree. The PERIPHCONF entry generation is enabled by default when building for nrf54h20dk/nrf54h20/cpuapp and nrf54h20dk/nrf54h20/cpurad. It will also be used on nrf9280 soon, therefore it is placed in the common uicr directory. This new feature does the same job as nrf-regtool did when building for nrf54h20 before, and is compatible by the bindings that were used by nrf-regtool. Signed-off-by: Jonathan Nilsen (cherry picked from commit 1cb9408f1934ad72db630a92b3c3ff10b7f6fd7b) --- soc/nordic/common/uicr/CMakeLists.txt | 16 + soc/nordic/common/uicr/Kconfig | 9 + .../common/uicr/gen_periphconf_entries.py | 658 ++++++++ soc/nordic/common/uicr/gen_uicr.py | 33 + soc/nordic/common/uicr/periphconf/__init__.py | 6 + soc/nordic/common/uicr/periphconf/builder.py | 1460 +++++++++++++++++ 6 files changed, 2182 insertions(+) create mode 100644 soc/nordic/common/uicr/gen_periphconf_entries.py create mode 100644 soc/nordic/common/uicr/periphconf/__init__.py create mode 100644 soc/nordic/common/uicr/periphconf/builder.py diff --git a/soc/nordic/common/uicr/CMakeLists.txt b/soc/nordic/common/uicr/CMakeLists.txt index 1ec3a35c566..9d5deec52d5 100644 --- a/soc/nordic/common/uicr/CMakeLists.txt +++ b/soc/nordic/common/uicr/CMakeLists.txt @@ -4,3 +4,19 @@ if(CONFIG_NRF_PERIPHCONF_SECTION) zephyr_linker_sources(SECTIONS uicr.ld) endif() + +if(CONFIG_NRF_PERIPHCONF_GENERATE_ENTRIES) + set(periphconf_entries_c_file ${PROJECT_BINARY_DIR}/periphconf_entries_generated.c) + execute_process( + COMMAND + ${CMAKE_COMMAND} -E env ZEPHYR_BASE=${ZEPHYR_BASE} + ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/gen_periphconf_entries.py + --soc ${CONFIG_SOC} + --in-edt-pickle ${EDT_PICKLE} + --out-periphconf-source ${periphconf_entries_c_file} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMAND_ERROR_IS_FATAL ANY + ) + zephyr_sources(${periphconf_entries_c_file}) + message(STATUS "Generated ${periphconf_entries_c_file} from ${EDT_PICKLE}") +endif() diff --git a/soc/nordic/common/uicr/Kconfig b/soc/nordic/common/uicr/Kconfig index 3c4c6c8219b..73b5e5cf2d1 100644 --- a/soc/nordic/common/uicr/Kconfig +++ b/soc/nordic/common/uicr/Kconfig @@ -9,3 +9,12 @@ config NRF_PERIPHCONF_SECTION help Include static global domain peripheral initialization values from the build in a dedicated section in the devnull region. + +config NRF_PERIPHCONF_GENERATE_ENTRIES + bool "Generate PERIPHCONF entries source file" + default y if SOC_NRF54H20_CPUAPP || SOC_NRF54H20_CPURAD + depends on NRF_PERIPHCONF_SECTION + depends on NRF_PLATFORM_HALTIUM + help + Generate a C file containing PERIPHCONF entries based on the + device configuration in the devicetree. diff --git a/soc/nordic/common/uicr/gen_periphconf_entries.py b/soc/nordic/common/uicr/gen_periphconf_entries.py new file mode 100644 index 00000000000..e70e56acc1f --- /dev/null +++ b/soc/nordic/common/uicr/gen_periphconf_entries.py @@ -0,0 +1,658 @@ +""" +Copyright (c) 2025 Nordic Semiconductor ASA +SPDX-License-Identifier: Apache-2.0 +""" + +from __future__ import annotations + +import argparse +import enum +import os +import pickle +import sys +from pathlib import Path +from typing import Any + +try: + ZEPHYR_BASE = Path(os.environ["ZEPHYR_BASE"]).resolve() +except KeyError: + sys.exit("Set the environment variable 'ZEPHYR_BASE' to point to the zephyr root directory") + +# Add packages that are located in zephyr itself to the python path so we can import them below +sys.path.insert(0, str(ZEPHYR_BASE / "scripts/dts/python-devicetree/src")) +sys.path.insert(0, str(ZEPHYR_BASE / "soc/nordic/common/uicr")) + +from periphconf.builder import ( + Ctrlsel, + FixedPPIMap, + GpiosProp, + Node, + NrfCompChannel, + NrfFun, + NrfPsel, + NrfSaadcChannel, + PeriphconfBuilder, + ProcessorId, + SocLookupTables, + dt_processor_id, +) + +# These peripherals are special cases that don't fit the general rules we use to generate +# PERIPHCONF entries based on the devicetree. +NODELABEL_TO_KWARGS = { + # Channel links on this node do not result in PPIB connections, because the *-links properties + # are ambiguous. Instead, the PPIB connections are configured by the DPPICs connected to this. + "dppic130": {"add_ppib_channel_links": False}, + # The interrupts for this node are not managed in IRQMAP. + "canpll": {"has_irq_mapping": False}, +} + + +def get_additional_node_kwargs(node: Node) -> dict[str, Any]: + additional_kwargs = {} + for label in node.labels: + additional_kwargs.update(NODELABEL_TO_KWARGS.get(label, {})) + return additional_kwargs + + +class Soc(enum.Enum): + """Names of SoCs supported by this script""" + + NRF54H20 = "nrf54h20" + NRF9280 = "nrf9280" + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + allow_abbrev=False, + description=( + "Generate a C source file containing entries for the PERIPHCONF blob based on the " + "device configuration in devicetree." + ), + ) + parser.add_argument( + "-v", + "--verbose", + default=0, + action="count", + help="Print verbose output such as debug information.", + ) + parser.add_argument( + "--soc", + required=True, + choices=[soc.value for soc in Soc], + help=( + "SoC to generate PERIPHCONF macros for. " + "Used to look up soc specific hardware information" + ), + ) + parser.add_argument( + "--in-edt-pickle", + type=argparse.FileType("rb"), + required=True, + help="Path to the pickled edtlib.EDT object with devicetree contents.", + ) + parser.add_argument( + "--out-periphconf-source", + type=argparse.FileType("w", encoding="utf-8"), + required=True, + help="Path to write the generated PERIPHCONF C source file to.", + ) + return parser.parse_args() + + +def main() -> None: + args = parse_args() + dt = pickle.load(args.in_edt_pickle) + processor = dt_processor_id(dt) + lookup_tables = lookup_tables_get(Soc(args.soc)) + builder = PeriphconfBuilder(dt, lookup_tables) + + # Application local peripherals + if processor == ProcessorId.APPLICATION: + for node in dt.label2node["cpuapp_peripherals"].children.values(): + builder.add_local_peripheral_cfg(node, **get_additional_node_kwargs(node)) + + # Radio local peripherals + if processor == ProcessorId.RADIOCORE: + for node in dt.label2node["cpurad_peripherals"].children.values(): + builder.add_local_peripheral_cfg(node, **get_additional_node_kwargs(node)) + + # Global domain peripherals + for node in dt.label2node["global_peripherals"].children.values(): + builder.add_global_peripheral_cfg(node, **get_additional_node_kwargs(node)) + + # Add pins referenced by 'gpios' properties on non-peripheral nodes, for example + # buttons and leds. We only add SPU configurations for these and not CTRLSEL, + # to avoid false CTRLSEL conflicts for things like PWM leds. + for node in dt.nodes: + builder.add_gpio_spu_permissions(node) + + script_name = Path(__file__).resolve().relative_to(ZEPHYR_BASE) + generated_by = f"Generated by {script_name}" + + generated_source = builder.build_generated_source(generated_by) + args.out_periphconf_source.write(generated_source) + + +def lookup_tables_get(soc: Soc) -> SocLookupTables: + if soc == Soc.NRF54H20: + ctrlsel_lookup = { + # CAN120 + 0x5F8D_8000: { + # P2 + NrfPsel(fun=NrfFun.CAN_TX, port=2, pin=9): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.CAN_RX, port=2, pin=8): Ctrlsel.CAN_PWM_I3C, + # P9 + NrfPsel(fun=NrfFun.CAN_TX, port=9, pin=5): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.CAN_RX, port=9, pin=4): Ctrlsel.CAN, + }, + # PWM120 + 0x5F8E_4000: { + # P2 + NrfPsel(fun=NrfFun.PWM_OUT0, port=2, pin=4): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=2, pin=5): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=2, pin=6): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=2, pin=7): Ctrlsel.CAN_PWM_I3C, + # P6 + NrfPsel(fun=NrfFun.PWM_OUT0, port=6, pin=6): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=6, pin=7): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=6, pin=8): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=6, pin=9): Ctrlsel.CAN_PWM_I3C, + # P7 + NrfPsel(fun=NrfFun.PWM_OUT0, port=7, pin=0): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=7, pin=1): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=7, pin=6): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=7, pin=7): Ctrlsel.CAN_PWM_I3C, + }, + # PWM130 + 0x5F9A_4000: { + # P9 + NrfPsel(fun=NrfFun.PWM_OUT0, port=9, pin=2): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=9, pin=3): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=9, pin=4): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=9, pin=5): Ctrlsel.CAN_PWM_I3C, + }, + # SPIM130/SPIS130/TWIM130/TWIS130/UARTE130 + 0x5F9A_5000: { + # SPIM mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=9, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=9, pin=2): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=9, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=9, pin=4): Ctrlsel.SERIAL0, + # SPIS mappings + NrfPsel(fun=NrfFun.SPIS_MISO, port=9, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_MOSI, port=9, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_CSN, port=9, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_SCK, port=9, pin=4): Ctrlsel.SERIAL0, + # TWIM mappings + NrfPsel(fun=NrfFun.TWIM_SDA, port=9, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.TWIM_SCL, port=9, pin=4): Ctrlsel.SERIAL0, + # TWIS mappings + NrfPsel(fun=NrfFun.TWIS_SDA, port=9, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.TWIS_SCL, port=9, pin=4): Ctrlsel.SERIAL0, + # UARTÈ mappings + NrfPsel(fun=NrfFun.UART_TX, port=9, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=9, pin=4): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.UART_CTS, port=9, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=9, pin=3): Ctrlsel.SERIAL0, + }, + # SPIM131/SPIS131/TWIM131/TWIS131/UARTE131 + 0x5F9A_6000: { + # SPIM mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=9, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.SPIM_MISO, port=9, pin=2): Ctrlsel.CAN_TDM_SERIAL2, + GpiosProp(name="cs-gpios", port=9, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.SPIM_SCK, port=9, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + # SPIS mappings + NrfPsel(fun=NrfFun.SPIS_MISO, port=9, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.SPIS_MOSI, port=9, pin=2): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.SPIS_CSN, port=9, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.SPIS_SCK, port=9, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + # TWIM mappings + NrfPsel(fun=NrfFun.TWIM_SDA, port=9, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TWIM_SCL, port=9, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + # TWIS mappings + NrfPsel(fun=NrfFun.TWIS_SDA, port=9, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TWIS_SCL, port=9, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + # UARTÈ mappings + NrfPsel(fun=NrfFun.UART_TX, port=9, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.UART_RX, port=9, pin=1): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_CTS, port=9, pin=2): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.UART_RTS, port=9, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + }, + # VPR121 (FLPR) + 0x5F8D_4000: { + # P1 + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=8): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=9): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=10): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=11): Ctrlsel.VPR_GRC, + # P2 + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=0): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=1): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=2): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=3): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=4): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=5): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=6): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=7): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=8): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=9): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=10): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=11): Ctrlsel.VPR_GRC, + # P6 + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=0): Ctrlsel.VPR_GRC, + # (pin 1-2 are not connected with VIO) + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=3): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=4): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=5): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=6): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=7): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=8): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=9): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=10): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=11): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=12): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=6, pin=13): Ctrlsel.VPR_GRC, + # P7 + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=0): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=1): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=2): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=3): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=4): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=5): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=6): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=7, pin=7): Ctrlsel.VPR_GRC, + # P9 + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=0): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=1): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=2): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=3): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=4): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=9, pin=5): Ctrlsel.VPR_GRC, + }, + # SPIS120 + 0x5F8E_5000: { + NrfPsel(fun=NrfFun.SPIS_MISO, port=6, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_MOSI, port=6, pin=4): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_CSN, port=6, pin=9): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_SCK, port=6, pin=0): Ctrlsel.SERIAL0, + }, + # SPIM120/UARTE120 + 0x5F8E_6000: { + # SPIM P6 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=6, pin=8): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=6, pin=7): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=6, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=6, pin=1): Ctrlsel.SERIAL0, + # SPIM P7 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=7, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=7, pin=6): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=7, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=7, pin=3): Ctrlsel.SERIAL0, + # SPIM P2 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=2, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=2, pin=5): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=2, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=2, pin=3): Ctrlsel.SERIAL0, + # UARTÈ P6 mappings + NrfPsel(fun=NrfFun.UART_TX, port=6, pin=8): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_CTS, port=6, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=6, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=6, pin=5): Ctrlsel.SERIAL0, + # UARTÈ P7 mappings + NrfPsel(fun=NrfFun.UART_TX, port=7, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_CTS, port=7, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=7, pin=4): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=7, pin=5): Ctrlsel.SERIAL0, + # UARTÈ P2 mappings + NrfPsel(fun=NrfFun.UART_TX, port=2, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_CTS, port=2, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=2, pin=4): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=2, pin=7): Ctrlsel.SERIAL0, + }, + # SPIM121 + 0x5F8E_7000: { + # SPIM P6 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=6, pin=13): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=6, pin=12): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=6, pin=10): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=6, pin=2): Ctrlsel.SERIAL0, + # SPIM P7 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=7, pin=1): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIS_MISO, port=7, pin=1): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIM_MISO, port=7, pin=0): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIS_MOSI, port=7, pin=0): Ctrlsel.EXMIF_RADIO_SERIAL1, + GpiosProp(name="cs-gpios", port=7, pin=4): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIS_CSN, port=7, pin=4): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIM_SCK, port=7, pin=2): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.SPIS_SCK, port=7, pin=2): Ctrlsel.EXMIF_RADIO_SERIAL1, + # SPIM P2 mappings + NrfPsel(fun=NrfFun.SPIM_MOSI, port=2, pin=11): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=2, pin=10): Ctrlsel.SERIAL0, + GpiosProp(name="cs-gpios", port=2, pin=8): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=2, pin=2): Ctrlsel.SERIAL0, + }, + # EXMIF + 0x5F09_5000: { + NrfPsel(fun=NrfFun.EXMIF_CK, port=6, pin=0): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_RWDS, port=6, pin=2): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_CS0, port=6, pin=3): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ7, port=6, pin=4): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ1, port=6, pin=5): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ6, port=6, pin=6): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ0, port=6, pin=7): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ5, port=6, pin=8): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ3, port=6, pin=9): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ2, port=6, pin=10): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_DQ4, port=6, pin=11): Ctrlsel.EXMIF_RADIO_SERIAL1, + NrfPsel(fun=NrfFun.EXMIF_CS1, port=6, pin=13): Ctrlsel.EXMIF_RADIO_SERIAL1, + }, + # VPR130 (PPR) + 0x5F90_8000: { + # P0 + NrfPsel(fun=NrfFun.IGNORE, port=0, pin=4): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=0, pin=5): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=0, pin=6): Ctrlsel.VPR_GRC, + NrfPsel(fun=NrfFun.IGNORE, port=0, pin=7): Ctrlsel.VPR_GRC, + }, + # TDM130 + 0x5F99_2000: { + # TDM P1 mappings + NrfPsel(fun=NrfFun.TDM_MCK, port=1, pin=2): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_M, port=1, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_S, port=1, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDOUT, port=1, pin=4): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDIN, port=1, pin=5): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_M, port=1, pin=6): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_S, port=1, pin=6): Ctrlsel.CAN_TDM_SERIAL2, + # TDM P2 mappings + NrfPsel(fun=NrfFun.TDM_MCK, port=2, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_M, port=2, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_S, port=2, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDOUT, port=2, pin=9): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDIN, port=2, pin=10): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_M, port=2, pin=11): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_S, port=2, pin=11): Ctrlsel.CAN_TDM_SERIAL2, + }, + # TDM131 + 0x5F99_7000: { + # TDM P1 mappings + NrfPsel(fun=NrfFun.TDM_MCK, port=1, pin=0): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_M, port=1, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_S, port=1, pin=1): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDOUT, port=1, pin=9): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDIN, port=1, pin=10): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_M, port=1, pin=11): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_S, port=1, pin=11): Ctrlsel.CAN_TDM_SERIAL2, + # TDM P2 mappings + NrfPsel(fun=NrfFun.TDM_MCK, port=2, pin=2): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_M, port=2, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SCK_S, port=2, pin=3): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDOUT, port=2, pin=4): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_SDIN, port=2, pin=6): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_M, port=2, pin=7): Ctrlsel.CAN_TDM_SERIAL2, + NrfPsel(fun=NrfFun.TDM_FSYNC_S, port=2, pin=7): Ctrlsel.CAN_TDM_SERIAL2, + }, + # GPIOTE0 (RAD) + 0x5302_7000: { + # P1 + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=4): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=5): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=6): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=7): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=8): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=9): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=10): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=1, pin=11): Ctrlsel.CAN, + # P2 + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=0): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=1): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=2): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=3): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=4): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=5): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=6): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=7): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=8): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=9): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=10): Ctrlsel.CAN, + NrfPsel(fun=NrfFun.IGNORE, port=2, pin=11): Ctrlsel.CAN, + }, + } + elif soc == Soc.NRF9280: + ctrlsel_lookup = { + # PWM120 + 0x5F8E_4000: { + # P2 + NrfPsel(fun=NrfFun.PWM_OUT0, port=2, pin=0): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=2, pin=1): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=2, pin=2): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=2, pin=3): Ctrlsel.CAN_PWM_I3C, + # P6 + NrfPsel(fun=NrfFun.PWM_OUT0, port=6, pin=0): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT0, port=6, pin=6): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=6, pin=1): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=6, pin=7): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=6, pin=2): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=6, pin=8): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=6, pin=3): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=6, pin=9): Ctrlsel.CAN_PWM_I3C, + }, + # SPIM120/UARTE120 + 0x5F8E_6000: { + # SPIM P2 mappings + GpiosProp(name="cs-gpios", port=2, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=2, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MOSI, port=2, pin=4): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=2, pin=0): Ctrlsel.SERIAL0, + # SPIM P6 mappings + GpiosProp(name="cs-gpios", port=6, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=6, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MOSI, port=6, pin=8): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=6, pin=1): Ctrlsel.SERIAL0, + # UARTE P2 mappings + NrfPsel(fun=NrfFun.UART_CTS, port=2, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=2, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=2, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_TX, port=2, pin=4): Ctrlsel.SERIAL0, + # UARTE P6 mappings + NrfPsel(fun=NrfFun.UART_CTS, port=6, pin=7): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=6, pin=5): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=6, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_TX, port=6, pin=8): Ctrlsel.SERIAL0, + }, + # SPIM121 + 0x5F8E_7000: { + # P2 + GpiosProp(name="cs-gpios", port=2, pin=6): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=2, pin=8): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MOSI, port=2, pin=9): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=2, pin=1): Ctrlsel.SERIAL0, + # P6 + GpiosProp(name="cs-gpios", port=6, pin=10): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=6, pin=12): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MOSI, port=6, pin=13): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=6, pin=2): Ctrlsel.SERIAL0, + }, + # PWM130 + 0x5F9A_4000: { + NrfPsel(fun=NrfFun.PWM_OUT0, port=9, pin=2): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT1, port=9, pin=3): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT2, port=9, pin=4): Ctrlsel.CAN_PWM_I3C, + NrfPsel(fun=NrfFun.PWM_OUT3, port=9, pin=5): Ctrlsel.CAN_PWM_I3C, + }, + # SPIM130/SPIS130/TWIM130/TWIS130/UARTE130 + 0x5F9A_5000: { + # SPIM mappings + GpiosProp(name="cs-gpios", port=9, pin=1): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MISO, port=9, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_MOSI, port=9, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIM_SCK, port=9, pin=0): Ctrlsel.SERIAL0, + # SPIS mappings + NrfPsel(fun=NrfFun.SPIS_CSN, port=9, pin=1): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_MISO, port=9, pin=3): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_MOSI, port=9, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.SPIS_SCK, port=9, pin=0): Ctrlsel.SERIAL0, + # TWIM mappings + NrfPsel(fun=NrfFun.TWIM_SCL, port=9, pin=0): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.TWIM_SDA, port=9, pin=3): Ctrlsel.SERIAL0, + # UARTE mappings + NrfPsel(fun=NrfFun.UART_CTS, port=9, pin=2): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RTS, port=9, pin=1): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_RX, port=9, pin=4): Ctrlsel.SERIAL0, + NrfPsel(fun=NrfFun.UART_TX, port=9, pin=3): Ctrlsel.SERIAL0, + }, + } + else: + raise NotImplementedError(f"No CTRLSEL table exists for soc {soc}") + + # Entries below this are common to all supported socs at the time of writing. + + adc_channel_pin_lookup = { + # SAADC + 0x5F98_2000: { + NrfSaadcChannel.AIN0: (1, 0), + NrfSaadcChannel.AIN1: (1, 1), + NrfSaadcChannel.AIN2: (1, 2), + NrfSaadcChannel.AIN3: (1, 3), + NrfSaadcChannel.AIN4: (1, 4), + NrfSaadcChannel.AIN5: (1, 5), + NrfSaadcChannel.AIN6: (1, 6), + NrfSaadcChannel.AIN7: (1, 7), + NrfSaadcChannel.AIN8: (9, 0), + NrfSaadcChannel.AIN9: (9, 1), + NrfSaadcChannel.AIN10: (9, 2), + NrfSaadcChannel.AIN11: (9, 3), + NrfSaadcChannel.AIN12: (9, 4), + NrfSaadcChannel.AIN13: (9, 5), + } + } + comp_channel_pin_lookup = { + # COMP/LPCOMP + 0x5F98_3000: { + NrfCompChannel.AIN0: (1, 0), + NrfCompChannel.AIN1: (1, 1), + NrfCompChannel.AIN2: (1, 2), + NrfCompChannel.AIN3: (1, 3), + NrfCompChannel.AIN4: (1, 4), + NrfCompChannel.AIN5: (1, 5), + NrfCompChannel.AIN6: (1, 6), + NrfCompChannel.AIN7: (1, 7), + NrfCompChannel.AIN8: (9, 0), + NrfCompChannel.AIN9: (9, 1), + } + } + spu_instances = [ + ("SPU110", 0x5F08_0000), + ("SPU111", 0x5F09_0000), + ("SPU120", 0x5F8C_0000), + ("SPU121", 0x5F8D_0000), + ("SPU122", 0x5F8E_0000), + ("SPU130", 0x5F90_0000), + ("SPU131", 0x5F92_0000), + ("SPU132", 0x5F98_0000), + ("SPU133", 0x5F99_0000), + ("SPU134", 0x5F9A_0000), + ("SPU135", 0x5F9B_0000), + ("SPU136", 0x5F9C_0000), + ("SPU137", 0x5F9D_0000), + ] + dppics = { + "DPPIC120": 0x5F8E_1000, + "DPPIC130": 0x5F92_2000, + "DPPIC131": 0x5F98_1000, + "DPPIC132": 0x5F99_1000, + "DPPIC133": 0x5F9A_1000, + "DPPIC134": 0x5F9B_1000, + "DPPIC135": 0x5F9C_1000, + "DPPIC136": 0x5F9D_1000, + } + ppib_instances = [ + ("PPIB110", 0x5F09_8000), + ("PPIB120", 0x5F8E_E000), + ("PPIB121", 0x5F8E_F000), + ("PPIB130", 0x5F92_5000), + ("PPIB131", 0x5F92_6000), + ("PPIB132", 0x5F98_D000), + ("PPIB133", 0x5F99_D000), + ("PPIB134", 0x5F9A_D000), + ("PPIB135", 0x5F9B_D000), + ("PPIB136", 0x5F9C_D000), + ("PPIB137", 0x5F9D_D000), + ] + ppib_name_to_addr = dict(ppib_instances) + dppic_to_ppib_connections = { + dppics["DPPIC120"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB121"], + channel_map=range(0, 8), + ), + dppics["DPPIC131"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB132"], + channel_map=range(0, 8), + ), + dppics["DPPIC132"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB133"], + channel_map=range(0, 8), + ), + dppics["DPPIC133"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB134"], + channel_map=range(0, 8), + ), + dppics["DPPIC134"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB135"], + channel_map=range(0, 8), + ), + dppics["DPPIC135"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB136"], + channel_map=range(0, 8), + ), + dppics["DPPIC136"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB137"], + channel_map=range(0, 8), + ), + } + ppib_to_ppib_connections = { + ppib_name_to_addr["PPIB132"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB130"], + channel_map=range(0, 8), + ), + ppib_name_to_addr["PPIB133"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB130"], + channel_map=range(8, 16), + ), + ppib_name_to_addr["PPIB134"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB130"], + channel_map=range(16, 24), + ), + ppib_name_to_addr["PPIB135"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB130"], + channel_map=range(24, 32), + ), + ppib_name_to_addr["PPIB136"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB131"], + channel_map=range(0, 8), + ), + ppib_name_to_addr["PPIB137"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB131"], + channel_map=range(8, 16), + ), + ppib_name_to_addr["PPIB121"]: FixedPPIMap( + connected_to=ppib_name_to_addr["PPIB131"], + channel_map=range(16, 24), + ), + } + return SocLookupTables( + ctrlsel_lookup=ctrlsel_lookup, + adc_channel_pin_lookup=adc_channel_pin_lookup, + comp_channel_pin_lookup=comp_channel_pin_lookup, + dppic_to_ppib_connections=dppic_to_ppib_connections, + ppib_to_ppib_connections=ppib_to_ppib_connections, + spu_instances=spu_instances, + ppib_instances=ppib_instances, + ) + + +if __name__ == "__main__": + main() diff --git a/soc/nordic/common/uicr/gen_uicr.py b/soc/nordic/common/uicr/gen_uicr.py index 78422e4312c..ce69cd7e80a 100644 --- a/soc/nordic/common/uicr/gen_uicr.py +++ b/soc/nordic/common/uicr/gen_uicr.py @@ -426,6 +426,7 @@ def main() -> None: def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes: combined_periphconf = [] + ipcmap_index = 0 for in_file in elf_files: elf = ELFFile(in_file) @@ -436,6 +437,7 @@ def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes conf_section_data = conf_section.data() num_entries = len(conf_section_data) // PERIPHCONF_ENTRY_SIZE periphconf = (PeriphconfEntry * num_entries).from_buffer_copy(conf_section_data) + ipcmap_index = adjust_ipcmap_entries(periphconf, offset_index=ipcmap_index) combined_periphconf.extend(periphconf) combined_periphconf.sort(key=lambda e: e.regptr) @@ -459,5 +461,36 @@ def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes return bytes(final_periphconf) +# This workaround is currently needed to avoid conflicts in IPCMAP whenever more than +# one image uses IPCMAP, because at the moment each image has no way of knowing which +# IPCMAP channel indices it should use for the configuration it generates locally. +# +# What the workaround does is adjust all IPCMAP entries found in the periphconf by the +# given index offset. +# +# The workaround assumes that IPCMAP entries are allocated sequentially starting from 0 +# in each image, it will probably not work for arbitrary IPCMAP entries. +def adjust_ipcmap_entries(periphconf: c.Array[PeriphconfEntry], offset_index: int) -> int: + max_ipcmap_index = offset_index + + for entry in sorted(periphconf, key=lambda e: e.regptr): + if IPCMAP_CHANNEL_START_ADDR <= entry.regptr < IPCMAP_CHANNEL_END_ADDR: + entry.regptr += offset_index * IPCMAP_CHANNEL_SIZE + entry_ipcmap_index = (entry.regptr - IPCMAP_CHANNEL_START_ADDR) // IPCMAP_CHANNEL_SIZE + max_ipcmap_index = max(max_ipcmap_index, entry_ipcmap_index) + + return max_ipcmap_index + 1 + + +# Size of each IPCMAP.CHANNEL[i] +IPCMAP_CHANNEL_SIZE = 8 +# Number of entries in IPCMAP.CHANNEL +IPCMAP_CHANNEL_COUNT = 16 +# Address of IPCMAP.CHANNEL[0] +IPCMAP_CHANNEL_START_ADDR = 0x5F92_3000 + 256 * 4 +# Address of IPCMAP.CHANNEL[channel count] + 1 +IPCMAP_CHANNEL_END_ADDR = IPCMAP_CHANNEL_START_ADDR + IPCMAP_CHANNEL_SIZE * IPCMAP_CHANNEL_COUNT + + if __name__ == "__main__": main() diff --git a/soc/nordic/common/uicr/periphconf/__init__.py b/soc/nordic/common/uicr/periphconf/__init__.py new file mode 100644 index 00000000000..fe8f6fbacec --- /dev/null +++ b/soc/nordic/common/uicr/periphconf/__init__.py @@ -0,0 +1,6 @@ +""" +Copyright (c) 2025 Nordic Semiconductor ASA +SPDX-License-Identifier: Apache-2.0 +""" + +# empty diff --git a/soc/nordic/common/uicr/periphconf/builder.py b/soc/nordic/common/uicr/periphconf/builder.py new file mode 100644 index 00000000000..98c2bdfb0a0 --- /dev/null +++ b/soc/nordic/common/uicr/periphconf/builder.py @@ -0,0 +1,1460 @@ +""" +Copyright (c) 2025 Nordic Semiconductor ASA +SPDX-License-Identifier: Apache-2.0 +""" + +from __future__ import annotations + +import enum +import logging +import re +from collections.abc import Iterable, Iterator, Mapping +from dataclasses import dataclass +from functools import cached_property +from itertools import islice +from typing import ( + Any, + Literal, + NamedTuple, +) + +# edtlib classes used in this module. +# Defined this way as placeholders so type checking for these could be added later. +EDT = Any +Node = Any +PinCtrl = Any +Property = Any + +# Status values that indicate ownership either by the processor or a child processor +STATUS_OWNED = ("okay", "reserved") + +# Compatibles of global peripherals that should not be assigned to current core. +SKIP_SPU_PERIPH_PERM_COMPATS = { + "nordic,nrf-bellboard-tx", + "nordic,nrf-bellboard-rx", + "nordic,nrf-clic", + "nordic,nrf-dppic-global", + "nordic,nrf-gpio", + "nordic,nrf-gpiote", + "nordic,nrf-grtc", + "nordic,nrf-ipct-global", + "nordic,nrf-temp", + "nordic,nrf-vevif-task-tx", + "nordic,nrf-vevif-task-rx", +} + +# Compatibles of global peripherals that should be assigned to the current core but do not have DMA +NO_DMA_COMPATS = { + "nordic,nrf-auxpll", + "nordic,nrf-comp", + "nordic,nrf-egu", + "nordic,nrf-exmif", + "nordic,nrf-lpcomp", + "nordic,nrf-qdec", + "nordic,nrf-timer", + "nordic,nrf-rtc", + "nordic,nrf-wdt", + # VPRs have a PERM DMASEC setting, but PPR applications currently do not work as intended when + # setting it to secure. + "nordic,nrf-vpr-coprocessor", +} + + +class BuilderError(RuntimeError): + """Generic runtime error raised by the PeriphconfBuilder""" + + ... + + +class BadDevicetreeError(BuilderError): + """Error raised when the devicetree is misconfigured + or if assumptions about the devicetree are not met. + """ + + ... + + +def _init_logger() -> logging.Logger: + formatter = logging.Formatter("{message}", style="{") + handler = logging.StreamHandler() + handler.setFormatter(formatter) + + logger = logging.getLogger("periphconf") + logger.setLevel(logging.ERROR) + logger.addHandler(handler) + + return logger + + +# Logger for the script +log = _init_logger() + + +class PeriphconfBuilder: + def __init__( + self, + dt: EDT, + lookup_tables: SocLookupTables, + ) -> None: + """Builder class used to generate a PERIPHCONF C source file based on the devicetree. + + :param dt: Devicetree object. + :param lookup_tables: Lookup table object containing soc-specific information. + """ + + self._dt = dt + self._hw_tables = lookup_tables + self._processor_id = dt_processor_id(dt) + self._owner_id = self._processor_id.default_owner_id + + self._macros = [] + self._ipcmap_idx = 0 + self._generated_ipcmaps = set() + + self._nodelabel_lookup = { + dt_reg_addr(node): node.labels[0] for node in dt.nodes if node.regs and node.labels + } + + def build_generated_source(self, header_line: str | None = None) -> str: + """Generate a C source file containing all the macros generated through + calls to the API. + """ + source_lines = [] + + if header_line: + source_lines.extend([f"/* {header_line} */", ""]) + + source_lines.extend( + [ + "#include ", + "#include ", + "", + ] + ) + + for macro in sorted(self._macros): + source_lines.append(macro.c_render(self._nodelabel_lookup)) + + return "\n".join(source_lines) + + def add_local_peripheral_cfg( + self, + node_or_nodelabel: Node | str, + /, + ) -> None: + """Generate macros for populating PERIPHCONF based on the properties of a local domain + devicetree node. + + :param node_or_nodelabel: node or label of the node to process. + """ + self._add_peripheral_cfg(node_or_nodelabel, is_global=False, add_gpios=False) + + def add_global_peripheral_cfg( + self, + node_or_nodelabel: Node | str, + /, + *, + has_irq_mapping: bool = True, + add_ppib_channel_links: bool = True, + ) -> None: + """Generate macros for populating PERIPHCONF based on the properties of the given + devicetree node located in a global domain. + + :param node_or_nodelabel: node or label of the node to process. + :param has_irq_mapping: configure IRQMAP to map the peripheral IRQs to the processor the + devicetree belongs to, if interrupts are specified on the node. + :param add_ppib_channel_links: add PPIB connections based on the channel link properties of + the node. + """ + self._add_peripheral_cfg( + node_or_nodelabel, + is_global=True, + has_irq_mapping=has_irq_mapping, + add_ppib_channel_links=add_ppib_channel_links, + ) + + def _add_peripheral_cfg( + self, + node_or_nodelabel: Node | str, + is_global: bool = True, + has_irq_mapping: bool = True, + add_ppib_channel_links: bool = True, + add_gpios: bool = True, + ) -> None: + if isinstance(node_or_nodelabel, str): + node = self._dt.label2node[node_or_nodelabel] + else: + node = node_or_nodelabel + + if node.status not in STATUS_OWNED: + return + + if is_global: + has_skip_spu_periph_perm_compat = any( + compat in SKIP_SPU_PERIPH_PERM_COMPATS for compat in node.compats + ) + if not has_skip_spu_periph_perm_compat: + has_no_dma_compat = any(compat in NO_DMA_COMPATS for compat in node.compats) + self._add_global_peripheral_spu_permissions(node, has_dma=not has_no_dma_compat) + if has_irq_mapping: + self._add_global_peripheral_irq_mapping(node) + + if "nordic,nrf-gpiote" in node.compats: + self._add_nrf_gpiote_spu_permissions(node) + + if "nordic,nrf-dppic-global" in node.compats: + self._add_nrf_dppic_spu_permissions(node) + if add_ppib_channel_links: + self._add_nrf_dppic_ppib_links(node) + + if "nordic,nrf-ipct-global" in node.compats: + self._add_nrf_ipct_global_spu_permissions(node) + self._add_nrf_ipct_ipcmap_channel_links(node) + + if "nordic,nrf-grtc" in node.compats: + self._add_nrf_grtc_spu_permissions(node) + + if "nordic,nrf-saadc" in node.compats: + self._add_nrf_saadc_channel_pin_spu_permissions(node) + + if "nordic,nrf-comp" in node.compats or "nordic,nrf-lpcomp" in node.compats: + self._add_nrf_comp_lpcomp_channel_pin_spu_permissions(node) + + self._add_peripheral_pinctrls_spu_permissions_and_ctrlsel(node) + + if "nordic,nrf-ipct-local" in node.compats: + self._add_nrf_ipct_ipcmap_channel_links(node) + + if add_gpios: + self._add_peripheral_gpios_spu_permissions_and_ctrlsel(node) + + def add_gpio_spu_permissions(self, node_or_nodelabel: Node | str, /) -> None: + """Generate macros for populating SPU FEATURE.GPIO[n].PIN[m] registers based on + gpios properties on the given devicetree node. + + CTRLSEL is not set for these properties; it is assumed that the default CTRLSEL + value (GPIO) is the correct CTRLSEL for these pins. + + :param node_or_nodelabel: node or label of the node to process. + """ + if isinstance(node_or_nodelabel, str): + node = self._dt.label2node[node_or_nodelabel] + else: + node = node_or_nodelabel + + if node.status not in STATUS_OWNED: + return + + self._add_peripheral_gpios_spu_permissions_and_ctrlsel(node, skip_ctrlsel=True) + + def _add_global_peripheral_spu_permissions( + self, + node: Node, + has_dma: bool = True, + ) -> None: + """Adds an SPU PERIPH[n].PERM entry for the peripheral described by the node.""" + addr = dt_reg_addr(node) + secure = dt_node_is_secure(node) + if has_dma: + dma_secure = secure + else: + # If there is no DMA then the DMASEC field is not writable and reads as zero. + # Set to zero to avoid readback errors. + dma_secure = False + periph_label = node.labels[0] + + spu_address = get_spu_addr_for_periph(addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + parsed_addr = Address(addr) + periph_slave_index = parsed_addr.slave_index + + self._macros.append( + MacroCall( + "UICR_SPU_PERIPH_PERM_SET", + [ + Address(spu_address), + periph_slave_index, + secure, + dma_secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: {periph_label} permissions", + ) + ) + + def _add_global_peripheral_irq_mapping(self, node: Node) -> None: + """Adds an IRQMAP[n].SINK entry for each interrupt on the + peripheral described by the node. + """ + periph_identifier = dt_node_identifier(node) + get_irqn_from_dt = bool(node.labels) + + for i, interrupt in enumerate(node.interrupts): + interrupt_processors = set() + interrupt_ctrl = interrupt.controller + + interrupt_processors.update(dt_node_processors_from_labels(interrupt_ctrl)) + + if not interrupt_processors: + interrupt_processors.update(dt_node_processors_from_labels(node)) + + if not interrupt_processors: + raise BuilderError( + f"No unique processor ID could be found based on interrupt controllers " + f"or nodelabels for peripheral node {node.path}" + ) + if len(interrupt_processors) > 1: + raise BuilderError( + f"Peripheral node {node.path} corresponds to multiple processors " + f"({interrupt_processors}), which is not supported." + ) + + irq_processor = next(iter(interrupt_processors)) + + if get_irqn_from_dt: + macro_irqn = f"DT_IRQN_BY_IDX(DT_NODELABEL({node.labels[0]}), {i})" + else: + macro_irqn = str(interrupt.data["irq"]) + + self._macros.append( + MacroCall( + "UICR_IRQMAP_IRQ_SINK_SET", + [ + macro_irqn, + irq_processor.c_enum, + ], + comment=f"{periph_identifier} IRQ => {irq_processor.name}", + ) + ) + + def _add_nrf_gpiote_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.GPIOTE[n].CH[m] entries for configured channels on the + GPIOTE peripheral described by the node. + """ + addr = dt_reg_addr(node) + instance_name = dt_node_identifier(node) + spu_address = get_spu_addr_for_periph(addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + + for num, secure in dt_split_channels_get(node): + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_GPIOTE_CH_SET", + [ + Address(spu_address), + 0, + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: {instance_name} ch. {num} permissions", + ) + ) + + def _add_nrf_dppic_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.DPPIC[n].CH[m] and SPU FEATURE.DPPIC[n].CHG[m] entries for + configured channels and channel groups on the DPPIC peripheral described by the node. + """ + addr = dt_reg_addr(node) + channels = dt_split_channels_get(node) + channel_groups = dt_split_channels_get( + node, + owned_name="owned-channel-groups", + nonsecure_name="nonsecure-channel-groups", + ) + + instance_name = dt_node_identifier(node) + spu_address = get_spu_addr_for_periph(addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + + for num, secure in channels: + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_DPPIC_CH_SET", + [ + Address(spu_address), + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: {instance_name} ch. {num} permissions", + ) + ) + + for num, secure in channel_groups: + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_DPPIC_CHG_SET", + [ + Address(spu_address), + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: {instance_name} ch. group {num} permissions", + ) + ) + + def _add_nrf_dppic_ppib_links(self, node: Node) -> None: + """Adds PPIB SUBSCRIBE_SEND[n] and PPIB PUBLISH_RECEIVE[n] entries for + configured "source" and "sink" channels on the DPPIC peripheral described by the node. + """ + addr = dt_reg_addr(node, secure=True) + source_channels = dt_array_prop(node, "source-channels", []) + sink_channels = dt_array_prop(node, "sink-channels", []) + + for num in source_channels: + self._link_dppi_channels(addr, num, direction="source") + + for num in sink_channels: + self._link_dppi_channels(addr, num, direction="sink") + + def _link_dppi_channels( + self, + dppic_addr: int, + channel_num: int, + direction: Literal["source"] | Literal["sink"], + ) -> None: + local_ppib_addr, local_ppib_ch_map = self._hw_tables.dppic_to_ppib_connections[dppic_addr] + local_ppib_ch = local_ppib_ch_map[channel_num] + + remote_ppib_addr, remote_ppib_ch_map = self._hw_tables.ppib_to_ppib_connections[ + local_ppib_addr + ] + remote_ppib_ch = remote_ppib_ch_map[local_ppib_ch] + + if direction == "source": + sub_ppib_addr, sub_ppib_ch = local_ppib_addr, local_ppib_ch + pub_ppib_addr, pub_ppib_ch = remote_ppib_addr, remote_ppib_ch + else: + sub_ppib_addr, sub_ppib_ch = remote_ppib_addr, remote_ppib_ch + pub_ppib_addr, pub_ppib_ch = local_ppib_addr, local_ppib_ch + + sub_ppib_name = self._hw_tables.ppib_addr_to_name[sub_ppib_addr] + pub_ppib_name = self._hw_tables.ppib_addr_to_name[pub_ppib_addr] + + self._macros.append( + MacroCall( + "UICR_PPIB_SUBSCRIBE_SEND_ENABLE", + [ + Address(sub_ppib_addr), + sub_ppib_ch, + ], + comment=( + f"SUB: {sub_ppib_name} ch. {sub_ppib_ch} => {pub_ppib_name} ch. {pub_ppib_ch}" + ), + ) + ) + self._macros.append( + MacroCall( + "UICR_PPIB_PUBLISH_RECEIVE_ENABLE", + [ + Address(pub_ppib_addr), + pub_ppib_ch, + ], + comment=( + f"PUB: {sub_ppib_name} ch. {sub_ppib_ch} => {pub_ppib_name} ch. {pub_ppib_ch}" + ), + ) + ) + + def _add_nrf_ipct_global_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.IPCT[n].CH[m] entries for configured channels + on the IPCT peripheral described by the node. + """ + addr = dt_reg_addr(node) + instance_name = dt_node_identifier(node) + spu_address = get_spu_addr_for_periph(addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + + for num, secure in dt_split_channels_get(node): + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_IPCT_CH_SET", + [ + Address(spu_address), + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: {instance_name} ch. {num} permissions", + ) + ) + + def _add_nrf_ipct_ipcmap_channel_links(self, node: Node) -> None: + """Adds IPCMAP CHANNEL[n].SOURCE and CHANNEL[n].SINK entries for "sink" and "source" + channels on the IPCT peripheral described by the node. + """ + source_channel_links = dt_array_prop(node, "source-channel-links", []) + if len(source_channel_links) % 3 != 0: + raise BadDevicetreeError() + + sink_channel_links = dt_array_prop(node, "sink-channel-links", []) + if len(sink_channel_links) % 3 != 0: + raise BadDevicetreeError() + + node_domain_raw = dt_prop(node, "global-domain-id", None) + if node_domain_raw is None: + addr = dt_reg_addr(node) + try: + node_domain = Address(addr).domain + except ValueError as e: + raise BadDevicetreeError( + f"Failed to determine domain ID for address 0x{addr:08x} " + f"specified on node {node.path}" + ) from e + else: + node_domain = DomainId(node_domain_raw) + + for source_ch, sink_domain_raw, sink_ch in batched(source_channel_links, 3): + sink_domain = DomainId(sink_domain_raw) + self._link_ipct_channel(node_domain, source_ch, sink_domain, sink_ch) + + for sink_ch, source_domain_raw, source_ch in batched(sink_channel_links, 3): + source_domain = DomainId(source_domain_raw) + self._link_ipct_channel(source_domain, source_ch, node_domain, sink_ch) + + def _link_ipct_channel( + self, + source_domain: DomainId, + source_ch: int, + sink_domain: DomainId, + sink_ch: int, + ) -> None: + # Setting "source-channel-links" on one end and "sink-channel-links" on the other + # leads to duplicated entries without this check. + link_args = ( + source_domain.c_enum, + source_ch, + sink_domain.c_enum, + sink_ch, + ) + if link_args in self._generated_ipcmaps: + log.debug(f"Skip duplicate IPCMAP entry: {link_args}") + return + self._generated_ipcmaps.add(link_args) + + self._macros.append( + MacroCall( + "UICR_IPCMAP_CHANNEL_CFG", + [self._ipcmap_idx, *link_args], + comment=( + f"{source_domain.name} IPCT ch. {source_ch} => " + f"{sink_domain.name} IPCT ch. {sink_ch}" + ), + ) + ) + self._ipcmap_idx += 1 + + def _add_nrf_grtc_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.GRTC[n].CC[m] entries for configured channels + on the GRTC peripheral described by the node. + """ + grtc_addr = dt_reg_addr(node) + spu_address = get_spu_addr_for_periph(grtc_addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + + for num, secure in dt_split_channels_get(node): + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_GRTC_CC_SET", + [ + Address(spu_address), + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: GRTC CC{num} permissions", + ) + ) + + def _add_peripheral_gpios_spu_permissions_and_ctrlsel( + self, + node: Node, + skip_ctrlsel: bool = False, + ) -> None: + """Adds SPU FEATURE.GPIO[n].PIN[m] and GPIO PIN_CNF[n] entries for + pins used in phandle-array properties called 'gpios' or ending in '-gpios' + found on the peripheral node. + """ + for name in node.props: + if not re.fullmatch(r"^(.+-)?gpios$", name): + continue + + if node.props[name].type != "phandle-array": + log.debug(f"skipping *-gpios prop {name} in {node.path} (not a phandle-array)") + continue + + for entry in dt_array_prop(node, name): + gpio_node = entry.controller + if "nordic,nrf-gpio" not in gpio_node.compats: + continue + + port = gpio_node.props["port"].val + num = entry.data["pin"] + secure = dt_node_is_secure(gpio_node) + if not skip_ctrlsel: + ctrlsel = self._hw_tables.lookup_ctrlsel_for_property( + node.props[name], (port, num) + ) + else: + ctrlsel = None + self._configure_gpio_pin(entry.controller, num, secure, ctrlsel) + + def _add_peripheral_pinctrls_spu_permissions_and_ctrlsel(self, node: Node) -> None: + """Adds SPU FEATURE.GPIO[n].PIN[m] and GPIO PIN_CNF[n] entries for + pins used in pinctrl properties referenced by the peripheral node. + """ + secure = dt_node_is_secure(node) + for pinctrl in node.pinctrls: + for config_node in pinctrl.conf_nodes: + for group_node in config_node.children.values(): + for i, psel_val in enumerate(dt_array_prop(group_node, "psels")): + psel = NrfPsel.from_raw(psel_val) + + if psel.is_disconnected(): + # Pin is unused and should be ignored + continue + + gpio_node = find_gpio_node_by_port( + node.edt, + psel.port, + err_suffix=f" (referenced by {group_node.path}:psels[{i}])", + ) + num = psel.pin + ctrlsel = self._hw_tables.lookup_ctrlsel_for_pinctrl(pinctrl, psel) + self._configure_gpio_pin(gpio_node, num, secure, ctrlsel) + + def _add_nrf_saadc_channel_pin_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.GPIO[n].PIN[m] and GPIO PIN_CNF[n] entries for + pins corresponding to the channel properties on the ADC peripheral node. + """ + saadc_addr = dt_reg_addr(node, secure=True) + if saadc_addr not in self._hw_tables.adc_channel_pin_lookup: + return + + channel_pins = self._hw_tables.adc_channel_pin_lookup[saadc_addr] + secure = dt_node_is_secure(node) + + for name, child in node.children.items(): + if not name.startswith("channel"): + continue + + for prop_name in ["zephyr,input-positive", "zephyr,input-negative"]: + prop_val = dt_prop(child, prop_name, None) + if prop_val in channel_pins: + port, num = channel_pins[prop_val] + gpio_node = find_gpio_node_by_port(node.edt, port) + self._configure_gpio_pin(gpio_node, num, secure, CTRLSEL_DEFAULT) + + def _add_nrf_comp_lpcomp_channel_pin_spu_permissions(self, node: Node) -> None: + """Adds SPU FEATURE.GPIO[n].PIN[m] and GPIO PIN_CNF[n] entries for + pins corresponding to the channel properties on the COMP/LPCOMP peripheral node. + """ + comp_addr = dt_reg_addr(node, secure=True) + if comp_addr not in self._hw_tables.comp_channel_pin_lookup: + return + + channel_pins = self._hw_tables.comp_channel_pin_lookup[comp_addr] + secure = dt_node_is_secure(node) + + for prop_name in ["psel", "extrefsel"]: + prop_val = dt_prop(node, prop_name, None) + if prop_val in channel_pins: + port, num = channel_pins[prop_val] + gpio_node = find_gpio_node_by_port(node.edt, port) + self._configure_gpio_pin(gpio_node, num, secure, CTRLSEL_DEFAULT) + + def _configure_gpio_pin( + self, + gpio_node: Node, + num: int, + secure: bool, + ctrlsel: int | None, + ) -> None: + gpio_addr = dt_reg_addr(gpio_node) + gpio_port = dt_prop(gpio_node, "port") + spu_address = get_spu_addr_for_periph(gpio_addr) + spu_name = self._hw_tables.spu_addr_to_name[spu_address] + + self._macros.append( + MacroCall( + "UICR_SPU_FEATURE_GPIO_PIN_SET", + [ + Address(spu_address), + gpio_port, + num, + secure, + self._owner_id.c_enum, + ], + comment=f"{spu_name}: P{gpio_port}.{num} permissions", + ) + ) + + if ctrlsel is not None: + ctrlsel_int = int(ctrlsel) + self._macros.append( + MacroCall( + "UICR_GPIO_PIN_CNF_CTRLSEL_SET", + [ + Address(gpio_addr), + num, + ctrlsel_int, + ], + comment=f"P{gpio_port}.{num} CTRLSEL = {ctrlsel_int}", + ) + ) + + +def find_gpio_node_by_port(dt: EDT, port: int, err_suffix: str = "") -> Node: + """Find the GPIO node in the devicetree with the given port.""" + for gpio_node in dt.compat2nodes["nordic,nrf-gpio"]: + if gpio_node.props["port"].val == port: + return gpio_node + raise BadDevicetreeError(f"Failed to find Nordic GPIO node with port {port}{err_suffix}") + + +@dataclass(order=True) +class MacroCall: + name: str + args: list + comment: str | None = None + + def c_render(self, nodelabel_lookup: dict[int, str]) -> str: + """Render the macro as C code""" + str_args = [] + for arg in self.args: + if isinstance(arg, Address): + if int(arg) in nodelabel_lookup: + str_args.append(c_dt_reg_addr_by_label(nodelabel_lookup[int(arg)])) + else: + str_args.append(c_hex_addr(int(arg))) + elif isinstance(arg, bool): + str_args.append(c_bool(arg)) + elif hasattr(arg, "c_enum"): + str_args.append(arg.c_enum) + else: + str_args.append(str(arg)) + + comment = f"/* {self.comment} */\n" if self.comment else "" + return f"{comment}{self.name}({', '.join(str_args)});" + + +def c_hex_addr(address: int) -> str: + """Format address as a C 32-bit hex literal.""" + return f"0x{address:08x}UL" + + +def c_bool(value: bool) -> str: + """Format value as a C bool literal.""" + return "true" if value else "false" + + +def c_dt_reg_addr_by_label(nodelabel: str) -> str: + """Format a peripheral address using devicetree macros to get the address based on node label""" + return f"DT_REG_ADDR(DT_NODELABEL({nodelabel}))" + + +# Equivalent to itertools.batched(), using the recipe provided in the docs. +def batched(iterable: Iterable, n: int, *, strict: bool = False) -> Iterator: + if n < 1: + raise ValueError("n must be at least one") + iterator = iter(iterable) + while batch := tuple(islice(iterator, n)): + if strict and len(batch) != n: + raise ValueError("batched(): incomplete batch") + yield batch + + +# Sentinel used to represent no provided default value in the functions below +class NoDefault: ... + + +NO_DEFAULT = NoDefault + + +def dt_reg_addr( + node: Node, + index: int = 0, + secure: bool = False, + default: int | type[NoDefault] = NO_DEFAULT, +) -> int: + """Get a register address and property identifier for a node.""" + try: + addr = node.regs[index].addr + except IndexError: + if isinstance(default, int): + addr = default + else: + raise + if secure: + return int(Address(addr).as_secure()) + return addr + + +def dt_reg_size(node: Node, index: int = 0) -> int: + """Get a register size and property identifier for a node.""" + return node.regs[index].size + + +def dt_prop(node: Node, name: str, default: Any = NO_DEFAULT) -> Any: + """Get the property value and identfier of a property. + Optionally returns a default value. + """ + try: + prop = node.props[name] + except KeyError: + if default != NO_DEFAULT: + return default + raise + + return prop.val + + +def dt_node_identifier(node: Node, default_to_path: bool = True) -> str: + """Get a string that identifies the node. + Returns the first nodelabel if it exists, otherwise defaults to the node path. + If default_to_path is False, exits with an error instead of defaulting. + """ + if node.labels: + return node.labels[0] + elif default_to_path: + return node.path + raise BuilderError(f"Expected a nodelabel on {node}, but the node has no label") + + +def dt_array_prop( + node: Node, + name: str, + default: list[Any] | type[NO_DEFAULT] = NO_DEFAULT, +) -> list[Any]: + """Get the member values and identifiers of an array property. + Optionally returns a default value. + """ + try: + prop = node.props[name] + except KeyError: + if not isinstance(default, type): + return list(default) + raise + + return list(prop.val) + + +def dt_node_processors_from_labels(node: Node) -> list[ProcessorId]: + """Deduce a processor ID from a list of devicetree nodelabels.""" + substring_processor = {cpu.zephyr_name: cpu for cpu in ProcessorId.__members__.values()} + processors = set() + for substring, processor_id in substring_processor.items(): + if any(substring in label for label in node.labels): + processors.add(processor_id) + return list(processors) + + +def dt_split_channels_get( + node: Node, + owned_name: str = "owned-channels", + nonsecure_name: str = "nonsecure-channels", +) -> list[tuple[int, bool]]: + """Parse 'split channels' properties.""" + owned = [] + owned.extend(dt_array_prop(node, owned_name, default=[])) + owned.extend(dt_array_prop(node, f"child-{owned_name}", default=[])) + + sec_lookup = {} + if nonsecure_name in node.props: + nonsecure = dt_array_prop(node, nonsecure_name) + sec_lookup.update(dict.fromkeys(nonsecure, False)) + + default_sec = dt_node_is_secure(node) + channels = [] + for ch in owned: + sec = sec_lookup.setdefault(ch, default_sec) + channels.append((ch, sec)) + + return channels + + +def dt_node_is_secure(node: Node) -> bool: + if node.bus_node is not None and node.bus_node.regs: + addr = dt_reg_addr(node.bus_node) + elif node.regs: + addr = dt_reg_addr(node) + else: + raise ValueError( + f"Failed to determine security of {node.path} " + "from the address of its bus node or itself" + ) + return Address(addr).security + + +def dt_processor_id(devicetree: EDT) -> ProcessorId: + """Get processor information from a domain's devicetree.""" + cpus = [ + node + for node in devicetree.get_node("/cpus").children.values() + if node.name.startswith("cpu@") + ] + if len(cpus) != 1: + raise RuntimeError( + f"Expected exactly 1 'cpu' node, but devicetree contained {len(cpus)} nodes" + ) + + try: + return ProcessorId(cpus[0].regs[0].addr) + except Exception as e: + raise RuntimeError( + f"Devicetree 'cpu' node has invalid Processor ID {cpus[0].regs[0].addr}" + ) from e + + +@dataclass(frozen=True) +class NrfPsel: + """Decoded NRF_PSEL values.""" + + fun: NrfFun + port: int + pin: int + + @classmethod + def from_raw(cls, psel_value: int) -> NrfPsel: + """Decode a raw NRF_PSEL encoded int value to its individual parts.""" + port, pin = divmod(psel_value & (~NRF_PSEL_FUN_MASK), NRF_PSEL_GPIO_PIN_COUNT) + fun = (psel_value & NRF_PSEL_FUN_MASK) >> NRF_PSEL_FUN_POS + return NrfPsel(fun=NrfFun(fun), port=port, pin=pin) + + def is_disconnected(self) -> bool: + """True if the value represents a disconnected pin""" + return (self.port * NRF_PSEL_GPIO_PIN_COUNT + self.pin) == NRF_PSEL_PIN_MASK + + +# # Bit position of the function bits in the pinctrl pin value encoded from NRF_PSEL() +NRF_PSEL_FUN_POS = 24 +# # Mask for the function bits in the pinctrl pin value encoded from NRF_PSEL() +NRF_PSEL_FUN_MASK = 0xFF << NRF_PSEL_FUN_POS +# Number of pins per port used in NRF_PSEL() +NRF_PSEL_GPIO_PIN_COUNT = 32 +# Mask for the port, pin bits in the pinctrl pin value encoded from NRF_PSEL() +NRF_PSEL_PIN_MASK = 0x1FF + + +@dataclass(frozen=True) +class GpiosProp: + """CTRLSEL lookup table entry for special *-gpios properties used in some peripheral + bindings, which are in some cases used instead of pinctrl. + """ + + name: str + port: int + pin: int + + +@enum.unique +class Ctrlsel(int, enum.Enum): + """ + Enumeration of GPIO.PIN_CNF[n].CTRLSEL values. + The list here may not be exhaustive. + """ + + GPIO = 0 + VPR_GRC = 1 + CAN_PWM_I3C = 2 + SERIAL0 = 3 + EXMIF_RADIO_SERIAL1 = 4 + CAN_TDM_SERIAL2 = 5 + CAN = 6 + TND = 7 + + +# Default CTRLSEL value indicating that CTRLSEL should not be used +CTRLSEL_DEFAULT = Ctrlsel.GPIO + + +class NrfFun(int, enum.Enum): + """Pin functions used with pinctrl, see include/zephyr/dt-bindings/pinctrl/nrf-pinctrl.h + Only the functions relevant for CTRLSEL deduction have been included. + """ + + UART_TX = 0 + UART_RX = 1 + UART_RTS = 2 + UART_CTS = 3 + SPIM_SCK = 4 + SPIM_MOSI = 5 + SPIM_MISO = 6 + SPIS_SCK = 7 + SPIS_MOSI = 8 + SPIS_MISO = 9 + SPIS_CSN = 10 + TWIM_SCL = 11 + TWIM_SDA = 12 + PWM_OUT0 = 22 + PWM_OUT1 = 23 + PWM_OUT2 = 24 + PWM_OUT3 = 25 + EXMIF_CK = 35 + EXMIF_DQ0 = 36 + EXMIF_DQ1 = 37 + EXMIF_DQ2 = 38 + EXMIF_DQ3 = 39 + EXMIF_DQ4 = 40 + EXMIF_DQ5 = 41 + EXMIF_DQ6 = 42 + EXMIF_DQ7 = 43 + EXMIF_CS0 = 44 + EXMIF_CS1 = 45 + CAN_TX = 46 + CAN_RX = 47 + TWIS_SCL = 48 + TWIS_SDA = 49 + EXMIF_RWDS = 50 + GRTC_CLKOUT_FAST = 55 + GRTC_CLKOUT_32K = 56 + TDM_SCK_M = 71 + TDM_SCK_S = 72 + TDM_FSYNC_M = 73 + TDM_FSYNC_S = 74 + TDM_SDIN = 75 + TDM_SDOUT = 76 + TDM_MCK = 77 + + # Value used to ignore the function field and only check (port, pin) + IGNORE = -1 + # Fallback for unknown NrfFun values, assumes that pin CTRLSEL should be set to GPIO + ASSUMED_GPIO = -2 + + @classmethod + def _missing_(cls, value: Any) -> NrfFun: + return cls.ASSUMED_GPIO + + +class NrfSaadcChannel(int, enum.Enum): + """Identifiers representing SAADC channels. + See include/zephyr/dt-bindings/adc/nrf-saadc-haltium.h. + """ + + AIN0 = 1 + AIN1 = 2 + AIN2 = 3 + AIN3 = 4 + AIN4 = 5 + AIN5 = 6 + AIN6 = 7 + AIN7 = 8 + AIN8 = 9 + AIN9 = 10 + AIN10 = 11 + AIN11 = 12 + AIN12 = 13 + AIN13 = 14 + + +class NrfCompChannel(str, enum.Enum): + """Identifiers representing COMP/LPCOMP channels. + See dts/bindings/comparator/nordic,nrf-comp.yaml. + """ + + AIN0 = "AIN0" + AIN1 = "AIN1" + AIN2 = "AIN2" + AIN3 = "AIN3" + AIN4 = "AIN4" + AIN5 = "AIN5" + AIN6 = "AIN6" + AIN7 = "AIN7" + AIN8 = "AIN8" + AIN9 = "AIN9" + + +class FixedPPIMap(NamedTuple): + """A DPPIC-PPIB or PPIB-PPIB channel mapping. + These connections are fixed in the hardware. + """ + + connected_to: int + channel_map: range + + +@dataclass +class SocLookupTables: + """Container for various hardware info needed to generate PERIPHCONF.""" + + ctrlsel_lookup: dict[int, dict[NrfPsel | GpiosProp, Ctrlsel]] + adc_channel_pin_lookup: dict[int, dict[NrfSaadcChannel, tuple[int, int]]] + comp_channel_pin_lookup: dict[int, dict[NrfCompChannel, tuple[int, int]]] + dppic_to_ppib_connections: dict[int, FixedPPIMap] + ppib_to_ppib_connections: dict[int, FixedPPIMap] + spu_instances: list[tuple[str, int]] + ppib_instances: list[tuple[str, int]] + + @cached_property + def spu_addr_to_name(self) -> Mapping[int, str]: + return {addr: name for name, addr in self.spu_instances} + + @cached_property + def ppib_addr_to_name(self) -> Mapping[int, str]: + return {addr: name for name, addr in self.ppib_instances} + + def lookup_ctrlsel_for_property(self, prop: Property, psel: tuple[int, int]) -> Ctrlsel | None: + """Find the appopriate CTRLSEL value for a given gpios property.""" + if not prop.node.regs: + # Only nodes with registers can be looked up + return None + + periph_addr = dt_reg_addr(prop.node, secure=True) + gpios_prop = GpiosProp(name=prop.name, port=psel[0], pin=psel[1]) + return self._lookup_ctrlsel(periph_addr, gpios_prop) + + def lookup_ctrlsel_for_pinctrl(self, prop: PinCtrl, psel: NrfPsel) -> Ctrlsel | None: + """Find the appopriate CTRLSEL value for a given pinctrl.""" + if psel.fun == NrfFun.ASSUMED_GPIO: + # We map unsupported values to GPIO CTRLSEL + return CTRLSEL_DEFAULT + + periph_addr = dt_reg_addr(prop.node, secure=True) + return self._lookup_ctrlsel(periph_addr, psel) + + def _lookup_ctrlsel( + self, + periph_addr: int, + prop_or_psel: NrfPsel | GpiosProp, + ) -> Ctrlsel | None: + ctrlsel = None + + if periph_addr in self.ctrlsel_lookup: + ident_lut = self.ctrlsel_lookup[periph_addr] + if prop_or_psel in ident_lut: + ctrlsel = ident_lut[prop_or_psel] + elif isinstance(prop_or_psel, NrfPsel): + # Check if this entry is enumerated with "ignored" function + sub_entry_no_fun = NrfPsel( + fun=NrfFun.IGNORE, port=prop_or_psel.port, pin=prop_or_psel.pin + ) + ctrlsel = ident_lut.get(sub_entry_no_fun, None) + + log.debug(f"periph_addr=0x{periph_addr:09_x}, {prop_or_psel=} -> {ctrlsel=}") + + return ctrlsel + + +def get_spu_addr_for_periph(periph_addr: int) -> int: + """Get the address of the SPU instance governing the permissions for the peripheral + at the given address. + """ + address = Address(periph_addr) + + # The common rule is that the SPU instance sits at the start of the address space for + # the bus of the peripheral. + address.security = True + address.slave_index = 0 + address.address_space = 0 + + # However, some buses are special due to having > 16 slaves and need special handling. + address.bus = SPU_ADDRESS_BUS_REMAPPING.get(address.bus, address.bus) + + return int(address) + + +SPU_ADDRESS_BUS_REMAPPING = { + # Both of these bus IDs represent APB32 and should use the same SPU instance with bus ID 146. + 147: 146, +} + + +@enum.unique +class DomainId(enum.IntEnum): + """Domain IDs.""" + + RESERVED = 0 + SECURE = 1 + APPLICATION = 2 + RADIOCORE = 3 + CELLCORE = 4 + GLOBALFAST = 12 + GLOBALSLOW = 13 + GLOBAL_ = 14 + GLOBAL = 15 + + @property + def c_enum(self) -> str: + return f"NRF_DOMAIN_{self.name.upper()}" + + +@enum.unique +class OwnerId(enum.IntEnum): + """Owner IDs.""" + + NONE = 0 + SECURE = 1 + APPLICATION = 2 + RADIOCORE = 3 + CELL = 4 + SYSCTRL = 8 + + @property + def c_enum(self) -> str: + return f"NRF_OWNER_{self.name.upper()}" + + +@enum.unique +class ProcessorId(enum.IntEnum): + """Processor IDs.""" + + SECURE = 1 + APPLICATION = 2 + RADIOCORE = 3 + CELLCORE = 4 + SYSCTRL = 12 + PPR = 13 + FLPR = 14 + + @property + def zephyr_name(self) -> str: + """Name used in zephyr to denote the processor with this ID.""" + match self: + case ProcessorId.SECURE: + return "cpusec" + case ProcessorId.APPLICATION: + return "cpuapp" + case ProcessorId.RADIOCORE: + return "cpurad" + case ProcessorId.CELLCORE: + return "cpucell" + case ProcessorId.SYSCTRL: + return "cpusys" + case ProcessorId.PPR: + return "cpuppr" + case ProcessorId.FLPR: + return "cpuflpr" + + @property + def default_owner_id(self) -> OwnerId: + """Default owner ID associated with this ID (the ID used by accesses from the processor).""" + match self: + case ProcessorId.SECURE: + return OwnerId.SECURE + case ProcessorId.APPLICATION: + return OwnerId.APPLICATION + case ProcessorId.RADIOCORE: + return OwnerId.RADIOCORE + case ProcessorId.CELLCORE: + return OwnerId.CELL + case ProcessorId.SYSCTRL: + return OwnerId.SYSCTRL + case ProcessorId.PPR: + return OwnerId.APPLICATION + case ProcessorId.FLPR: + return OwnerId.APPLICATION + + @property + def c_enum(self) -> str: + return f"NRF_PROCESSOR_{self.name.upper()}" + + +@enum.unique +class AddressRegion(enum.IntEnum): + """Address regions, defined by Address Format of the data sheet.""" + + PROGRAM = 0 + DATA = 1 + PERIPHERAL = 2 + EXT_XIP = 3 + EXT_XIP_ENCRYPTED = 4 + STM = 5 + CPU = 7 + + @classmethod + def from_address(cls, address: int) -> AddressRegion: + """Get the address region of an address.""" + return Address(address).region + + +# Regions that have domain ID and security fields +HAS_DOMAIN_SECURITY = [ + AddressRegion.PROGRAM, + AddressRegion.DATA, + AddressRegion.PERIPHERAL, + AddressRegion.STM, +] + +# Regions that have the peripheral address format +HAS_PERIPH_BITS = [ + AddressRegion.PERIPHERAL, + AddressRegion.STM, +] + + +class Address: + """Helper for working with addresses.""" + + def __init__(self, value: int | Address = 0) -> None: + self._val = int(value) + + def __repr__(self) -> str: + if self.region in HAS_DOMAIN_SECURITY: + domain_sec_str = ( + f", domain={self.domain.name} ({int(self.domain)}), security={self.security}" + ) + else: + domain_sec_str = "" + + if self.region in HAS_PERIPH_BITS: + periph_bits_str = ( + f", bus={self.bus} (0b{self.bus:09_b}), " + f"slave_index={self.slave_index} (0b{self.slave_index:09_b})" + ) + else: + periph_bits_str = "" + + field_str = ( + f"region={self.region.name} ({int(self.region)}){domain_sec_str}{periph_bits_str}, " + f"address_space=0x{self.address_space:_x}" + ) + + return f"{type(self).__name__}({field_str})" + + def __str__(self) -> str: + return repr(self) + + def as_secure(self) -> Address: + addr = Address(self) + addr.security = True + return addr + + @property + def region(self) -> AddressRegion: + """Address region.""" + return AddressRegion(get_field(self._val, ADDRESS_REGION_POS, ADDRESS_REGION_MASK)) + + @region.setter + def region(self, new: int) -> None: + self._val = update_field(self._val, new, ADDRESS_REGION_POS, ADDRESS_REGION_MASK) + + @property + def security(self) -> bool: + """Address security (only present in some regions).""" + self._check_has_security() + return bool(get_field(self._val, ADDRESS_SECURITY_POS, ADDRESS_SECURITY_MASK)) + + @security.setter + def security(self, new: bool) -> None: + self._check_has_security() + self._val = update_field(self._val, int(new), ADDRESS_SECURITY_POS, ADDRESS_SECURITY_MASK) + + def _check_has_security(self) -> None: + self._check_region_has_field(HAS_DOMAIN_SECURITY, "security bit") + + @property + def domain(self) -> DomainId: + """Address domain ID (only present in some regions).""" + self._check_has_domain_id() + return DomainId(get_field(self._val, ADDRESS_DOMAIN_POS, ADDRESS_DOMAIN_MASK)) + + @domain.setter + def domain(self, new: DomainId | int) -> None: + self._check_has_domain_id() + self._val = update_field(self._val, new, ADDRESS_DOMAIN_POS, ADDRESS_DOMAIN_MASK) + + def _check_has_domain_id(self) -> None: + self._check_region_has_field(HAS_DOMAIN_SECURITY, "domain ID") + + @property + def bus(self) -> int: + """Bus ID (only present in some regions).""" + self._check_has_bus() + return get_field(self._val, ADDRESS_BUS_POS, ADDRESS_BUS_MASK) + + @bus.setter + def bus(self, new: int) -> None: + self._check_has_bus() + self._val = update_field(self._val, new, ADDRESS_BUS_POS, ADDRESS_BUS_MASK) + + def _check_has_bus(self) -> None: + self._check_region_has_field(HAS_PERIPH_BITS, "Peripheral/APB bus number") + + @property + def slave_index(self) -> int: + """Slave index (only present in some regions).""" + self._check_has_slave_index() + return get_field(self._val, ADDRESS_SLAVE_POS, ADDRESS_SLAVE_MASK) + + @slave_index.setter + def slave_index(self, new: int) -> None: + self._check_has_slave_index() + self._val = update_field(self._val, new, ADDRESS_SLAVE_POS, ADDRESS_SLAVE_MASK) + + def _check_has_slave_index(self) -> None: + self._check_region_has_field(HAS_PERIPH_BITS, "Peripheral/APB slave index") + + @property + def address_space(self) -> int: + """Internal address space address (semantics depend on the region).""" + match self.region: + case AddressRegion.PROGRAM | AddressRegion.DATA: + return get_field(self._val, ADDRESS_SPACE_POS, ADDRESS_PROGRAM_DATA_SPACE_MASK) + case AddressRegion.PERIPHERAL | AddressRegion.STM: + return get_field(self._val, ADDRESS_SPACE_POS, ADDRESS_PERIPHERAL_SPACE_MASK) + case _: + return get_field(self._val, ADDRESS_SPACE_POS, ADDRESS_DEFAULT_SPACE_MASK) + + @address_space.setter + def address_space(self, new: int) -> None: + match self.region: + case AddressRegion.PROGRAM | AddressRegion.DATA: + self._val = update_field( + self._val, new, ADDRESS_SPACE_POS, ADDRESS_PROGRAM_DATA_SPACE_MASK + ) + case AddressRegion.PERIPHERAL | AddressRegion.STM: + self._val = update_field( + self._val, new, ADDRESS_SPACE_POS, ADDRESS_PERIPHERAL_SPACE_MASK + ) + case _: + self._val = update_field( + self._val, new, ADDRESS_SPACE_POS, ADDRESS_DEFAULT_SPACE_MASK + ) + + def _check_region_has_field(self, valid_regions: list[AddressRegion], field_name: str) -> None: + if self.region not in valid_regions: + raise ValueError(f"{field_name} is not defined for address region {self.region.name}") + + def __eq__(self, other) -> bool: + return int(self) == int(other) + + def __lt__(self, other: Address | int) -> bool: + return int(self) < int(other) + + def __int__(self) -> int: + return self._val + + +ADDRESS_REGION_POS = 29 +ADDRESS_REGION_MASK = 0x7 << ADDRESS_REGION_POS +ADDRESS_SECURITY_POS = 28 +ADDRESS_SECURITY_MASK = 0x1 << ADDRESS_SECURITY_POS +ADDRESS_DOMAIN_POS = 24 +ADDRESS_DOMAIN_MASK = 0xF << ADDRESS_DOMAIN_POS +ADDRESS_BUS_POS = 16 +ADDRESS_BUS_MASK = 0xFF << ADDRESS_BUS_POS +ADDRESS_SLAVE_POS = 12 +ADDRESS_SLAVE_MASK = 0xF << ADDRESS_SLAVE_POS +ADDRESS_PERIPHID_POS = 12 +ADDRESS_PERIPHID_MASK = 0x7FF << ADDRESS_PERIPHID_POS +ADDRESS_SPACE_POS = 0 +ADDRESS_PROGRAM_DATA_SPACE_MASK = 0xFF_FFFF +ADDRESS_PERIPHERAL_SPACE_MASK = 0xFFF +ADDRESS_DEFAULT_SPACE_MASK = 0x1FFF_FFFF + + +def peripheral_id_get(periph_address: int) -> int: + """Get the peripheral ID of a peripheral address.""" + return get_field(periph_address, ADDRESS_PERIPHID_POS, ADDRESS_PERIPHID_MASK) + + +def get_field(value: int, field_pos: int, field_mask: int) -> int: + """Get the value of a field in a bitfield.""" + return (value & field_mask) >> field_pos + + +def update_field(value: int, field_new: int, field_pos: int, field_mask: int) -> int: + """Update a field in a bitfield.""" + return (value & ~field_mask) | ((field_new << field_pos) & field_mask) From 6de68198b55f221defa71c7607e1e011299a258a Mon Sep 17 00:00:00 2001 From: Jonathan Nilsen Date: Tue, 16 Sep 2025 16:25:22 +0200 Subject: [PATCH 10/10] [nrf fromtree] soc: nordic: uicr: print cmake warning when used without sysbuild Because generation and programming of UICR + PERIPHCONF artifacts depend on the 'uicr' image which in turn must be included by Sysbuild, many if not most nrf54h20 applications will need to be built using Sysbuild to function as intended. To make this known to the user, print a CMake warning whenever CONFIG_NRF_PERIPHCONF_SECTION=y but Sysbuild is not being used. Signed-off-by: Jonathan Nilsen (cherry picked from commit 6b93eacb6c376f209e72ebb60fce0ddc32dddb6d) --- soc/nordic/common/uicr/CMakeLists.txt | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/soc/nordic/common/uicr/CMakeLists.txt b/soc/nordic/common/uicr/CMakeLists.txt index 9d5deec52d5..d350d8d3f58 100644 --- a/soc/nordic/common/uicr/CMakeLists.txt +++ b/soc/nordic/common/uicr/CMakeLists.txt @@ -20,3 +20,11 @@ if(CONFIG_NRF_PERIPHCONF_GENERATE_ENTRIES) zephyr_sources(${periphconf_entries_c_file}) message(STATUS "Generated ${periphconf_entries_c_file} from ${EDT_PICKLE}") endif() + +if(CONFIG_NRF_PERIPHCONF_SECTION AND NOT SYSBUILD) + message(WARNING "CONFIG_NRF_PERIPHCONF_SECTION is enabled, but Sysbuild is not being used. " + "The global peripheral configuration will not be applied unless artifacts " + "are generated manually/externally. To enable automatic generation, build with " + "Sysbuild and ensure that SB_CONFIG_NRF_HALTIUM_GENERATE_UICR=y." + ) +endif()