diff --git a/boards/nordic/nrf54h20dk/Kconfig.defconfig b/boards/nordic/nrf54h20dk/Kconfig.defconfig index 069ed7b6eba6a..84c01ff777fa0 100644 --- a/boards/nordic/nrf54h20dk/Kconfig.defconfig +++ b/boards/nordic/nrf54h20dk/Kconfig.defconfig @@ -4,7 +4,7 @@ config HW_STACK_PROTECTION default ARCH_HAS_STACK_PROTECTION -if BOARD_NRF54H20DK_NRF54H20_CPUAPP +if BOARD_NRF54H20DK_NRF54H20_CPUAPP || BOARD_NRF54H20DK_NRF54H20_CPUAPP_SECONDARY config BT_HCI_IPC default y if BT @@ -31,7 +31,7 @@ config FLASH_LOAD_SIZE endif # !USE_DT_CODE_PARTITION -endif # BOARD_NRF54H20DK_NRF54H20_CPUAPP +endif # BOARD_NRF54H20DK_NRF54H20_CPUAPP || BOARD_NRF54H20DK_NRF54H20_CPUAPP_SECONDARY if BOARD_NRF54H20DK_NRF54H20_CPURAD diff --git a/boards/nordic/nrf54h20dk/Kconfig.nrf54h20dk b/boards/nordic/nrf54h20dk/Kconfig.nrf54h20dk index 62ad7a0d21c19..7aecd3a744436 100644 --- a/boards/nordic/nrf54h20dk/Kconfig.nrf54h20dk +++ b/boards/nordic/nrf54h20dk/Kconfig.nrf54h20dk @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 config BOARD_NRF54H20DK - select SOC_NRF54H20_CPUAPP if BOARD_NRF54H20DK_NRF54H20_CPUAPP + select SOC_NRF54H20_CPUAPP if BOARD_NRF54H20DK_NRF54H20_CPUAPP || BOARD_NRF54H20DK_NRF54H20_CPUAPP_SECONDARY select SOC_NRF54H20_CPURAD if BOARD_NRF54H20DK_NRF54H20_CPURAD select SOC_NRF54H20_CPUPPR if (BOARD_NRF54H20DK_NRF54H20_CPUPPR || \ BOARD_NRF54H20DK_NRF54H20_CPUPPR_XIP) diff --git a/boards/nordic/nrf54h20dk/board.yml b/boards/nordic/nrf54h20dk/board.yml index 2d3d40c20e0bb..78a851976ccb1 100644 --- a/boards/nordic/nrf54h20dk/board.yml +++ b/boards/nordic/nrf54h20dk/board.yml @@ -5,6 +5,8 @@ board: socs: - name: nrf54h20 variants: + - name: secondary + cpucluster: cpuapp - name: xip cpucluster: cpuppr - name: xip diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi index b6a53955c60e3..8b262a21cfd76 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)>; }; + + cpuapp_secondary_partition: partition@1b0000 { + reg = <0x1b0000 DT_SIZE_K(312)>; + }; + + secondary_periphconf_partition: partition@1fe000 { + reg = <0x1fe000 DT_SIZE_K(8)>; + }; }; }; diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary.dts b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary.dts new file mode 100644 index 0000000000000..5d51773cf9af6 --- /dev/null +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary.dts @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "nrf54h20dk_nrf54h20_cpuapp.dts" + +/ { + chosen { + zephyr,code-partition = &cpuapp_secondary_partition; + }; +}; diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_0_9_0.yaml b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_0_9_0.yaml new file mode 100644 index 0000000000000..b8514952de363 --- /dev/null +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_0_9_0.yaml @@ -0,0 +1,25 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +identifier: nrf54h20dk/nrf54h20/cpuapp/secondary +name: nRF54H20-DK-nRF54H20-Application-Secondary (revision 0.9.0) +type: mcu +arch: arm +toolchain: + - gnuarmemb + - zephyr +sysbuild: true +ram: 760 +flash: 312 +supported: + - adc + - can + - counter + - gpio + - i2c + - i2s + - pwm + - retained_mem + - spi + - watchdog + - usbd diff --git a/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_defconfig b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_defconfig new file mode 100644 index 0000000000000..e852da843c174 --- /dev/null +++ b/boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20_cpuapp_secondary_defconfig @@ -0,0 +1,5 @@ +# Copyright (c) 2024 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +# Use devicetree code partition instead of fallback boot partition +CONFIG_USE_DT_CODE_PARTITION=y diff --git a/soc/nordic/common/uicr/gen_uicr.py b/soc/nordic/common/uicr/gen_uicr.py index 50f96eab566f7..6f2fd4ad7da28 100644 --- a/soc/nordic/common/uicr/gen_uicr.py +++ b/soc/nordic/common/uicr/gen_uicr.py @@ -13,6 +13,7 @@ import sys from collections import defaultdict from itertools import groupby +from pprint import pprint from elftools.elf.elffile import ELFFile from intelhex import IntelHex @@ -25,11 +26,16 @@ UICR_NODELABEL = "uicr" # Nodelabel of the PERIPHCONF devicetree node, used to extract its location from the devicetree. PERIPHCONF_NODELABEL = "periphconf_partition" +# Nodelabel of the SECONDARY PERIPHCONF devicetree node, used to extract its location from the devicetree. +SECONDARY_PERIPHCONF_NODELABEL = "secondary_periphconf_partition" # Common values for representing enabled/disabled in the UICR format. ENABLED_VALUE = 0xFFFF_FFFF DISABLED_VALUE = 0xBD23_28A8 +# Enum values for representing PROCESSOR in the UICR format. +PROCESSOR_APPLICATION = 0xBD23_28A8 +PROCESSOR_RADIO = 0x1730_C77F class ScriptError(RuntimeError): ... @@ -62,17 +68,6 @@ class Protectedmem(c.LittleEndianStructure): ("SIZE4KB", c.c_uint32), ] - -class Recovery(c.LittleEndianStructure): - _pack_ = 1 - _fields_ = [ - ("ENABLE", c.c_uint32), - ("PROCESSOR", c.c_uint32), - ("INITSVTOR", c.c_uint32), - ("SIZE4KB", c.c_uint32), - ] - - class Its(c.LittleEndianStructure): _pack_ = 1 _fields_ = [ @@ -101,6 +96,28 @@ class Mpcconf(c.LittleEndianStructure): ] +class SecondaryTrigger(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("RESETREAS", c.c_uint32), + ] + + +class Secondary(c.LittleEndianStructure): + _pack_ = 1 + _fields_ = [ + ("ENABLE", c.c_uint32), + ("PROCESSOR", c.c_uint32), + ("SECONDARY_TRIGGER", SecondaryTrigger), + ("ADDRESS", c.c_uint32), + ("SIZE4KB", c.c_uint32), + ("PROTECTEDMEM", Protectedmem), + ("PERIPHCONF", Periphconf), + ("MPCCONF", Mpcconf), + ] + + class Uicr(c.LittleEndianStructure): _pack_ = 1 _fields_ = [ @@ -111,11 +128,13 @@ class Uicr(c.LittleEndianStructure): ("APPROTECT", Approtect), ("ERASEPROTECT", c.c_uint32), ("PROTECTEDMEM", Protectedmem), - ("RECOVERY", Recovery), + ("RESERVED2", c.c_uint32 * 4), ("ITS", Its), - ("RESERVED2", c.c_uint32 * 7), + ("RESERVED3", c.c_uint32 * 7), ("PERIPHCONF", Periphconf), ("MPCCONF", Mpcconf), + ("SECONDARY", Secondary), + ("RESERVED4", c.c_uint32 * 2), ] @@ -135,12 +154,22 @@ def main() -> None: type=argparse.FileType("r"), help="Path to the .config file from the application build", ) + parser.add_argument( + "--in-secondary-config", + type=argparse.FileType("r"), + help="Path to the .config file from the secondary 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-secondary-edt-pickle", + type=argparse.FileType("rb"), + help="Path to the edt.pickle file from the secondary application build", + ) parser.add_argument( "--in-periphconf-elf", dest="in_periphconf_elfs", @@ -153,6 +182,19 @@ def main() -> None: "by ascending address and cleared of duplicate entries." ), ) + 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 PERIPHCONF data from. Can be provided multiple times. " + "The PERIPHCONF data from each ELF file is combined in a single list which is sorted " + "by ascending address and cleared of duplicate entries. This is used for the secondary " + "application." + ), + ) parser.add_argument( "--out-uicr-hex", required=True, @@ -165,37 +207,55 @@ def main() -> None: type=argparse.FileType("w", encoding="utf-8"), help="Path to write the generated PERIPHCONF HEX file to", ) + parser.add_argument( + "--out-secondary-periphconf-hex", + default=None, + type=argparse.FileType("w", encoding="utf-8"), + help="Path to write the generated secondary PERIPHCONF HEX file to", + ) + parser.add_argument( + "--verbose", + action="store_true", + help="Enable verbose output including UICR structure contents", + ) args = parser.parse_args() try: init_values = DISABLED_VALUE.to_bytes(4, "little") * (c.sizeof(Uicr) // 4) uicr = Uicr.from_buffer_copy(init_values) - kconfig_str = args.in_config.read() - kconfig = parse_kconfig(kconfig_str) - + kconfig = parse_kconfig(args.in_config.read()) edt = pickle.load(args.in_edt_pickle) - 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 + handle_periphconf( + uicr=uicr, edt=edt, kconfig=kconfig, is_primary=True, in_periphconf_elfs=args.in_periphconf_elfs, out_periphconf_hex=args.out_periphconf_hex + ) + + if args.in_secondary_edt_pickle: + if not args.in_secondary_config: + raise ScriptError( + "Secondary configuration file is required when secondary edt.pickle is provided." + ) - 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 + secondary_edt = pickle.load(args.in_secondary_edt_pickle) + secondary_kconfig = parse_kconfig(args.in_secondary_config.read()) + handle_periphconf( + uicr=uicr, + edt=secondary_edt, + kconfig=secondary_kconfig, + is_primary=False, + in_periphconf_elfs=args.in_secondary_periphconf_elfs, + out_periphconf_hex=args.out_secondary_periphconf_hex, + ) - 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)]) + uicr.SECONDARY.ENABLE = ENABLED_VALUE + uicr.SECONDARY.PROCESSOR = PROCESSOR_APPLICATION + + # TODO - This should be zephyr chosen code partition + code_partition = edt.label2node["cpuapp_secondary_partition"] + flash_base_address = code_partition.flash_controller.regs[0].addr + uicr.SECONDARY.ADDRESS = flash_base_address + code_partition.regs[0].addr - if kconfig.get("CONFIG_NRF_HALTIUM_UICR_PERIPHCONF") == "y": - uicr.PERIPHCONF.ENABLE = ENABLED_VALUE - uicr.PERIPHCONF.ADDRESS = periphconf_address - uicr.PERIPHCONF.MAXCOUNT = math.floor(periphconf_size / 8) try: uicr_node = edt.label2node[UICR_NODELABEL] @@ -208,18 +268,50 @@ def main() -> None: uicr_hex = IntelHex() uicr_hex.frombytes(bytes(uicr), offset=uicr_node.regs[0].addr) + if args.verbose: + pretty_print_uicr(uicr) uicr_hex.write_hex_file(args.out_uicr_hex) - - if args.out_periphconf_hex is not None: - periphconf_hex = IntelHex() - periphconf_hex.frombytes(periphconf_final, offset=periphconf_address) - periphconf_hex.write_hex_file(args.out_periphconf_hex) - except ScriptError as e: print(f"Error: {e!s}") sys.exit(1) +def handle_periphconf(uicr, edt, kconfig, is_primary, in_periphconf_elfs, out_periphconf_hex): + try: + if is_primary: + periphconf_partition = edt.label2node[PERIPHCONF_NODELABEL] + else: + periphconf_partition = edt.label2node[SECONDARY_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 + + 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 + + periphconf_combined = extract_and_combine_periphconfs(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": + if is_primary: + uicr.PERIPHCONF.ENABLE = ENABLED_VALUE + uicr.PERIPHCONF.ADDRESS = periphconf_address + uicr.PERIPHCONF.MAXCOUNT = math.floor(periphconf_size / 8) + else: + uicr.SECONDARY.PERIPHCONF.ENABLE = ENABLED_VALUE + uicr.SECONDARY.PERIPHCONF.ADDRESS = periphconf_address + uicr.SECONDARY.PERIPHCONF.MAXCOUNT = math.floor(periphconf_size / 8) + + if out_periphconf_hex is not None: + periphconf_hex = IntelHex() + periphconf_hex.frombytes(periphconf_final, offset=periphconf_address) + periphconf_hex.write_hex_file(out_periphconf_hex) + + def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes: combined_periphconf = [] @@ -266,5 +358,36 @@ def parse_kconfig(content: str) -> dict[str, str | None]: return result +def pretty_print_uicr(uicr: Uicr) -> None: + """Pretty print the UICR structure contents.""" + print("=" * 60) + print("UICR STRUCTURE CONTENTS") + print("=" * 60) + + def struct_to_dict(struct): + """Convert ctypes structure to dict recursively.""" + result = {} + for field_name, _ in struct._fields_: + value = getattr(struct, field_name) + if hasattr(value, '_fields_'): # Nested structure + result[field_name] = struct_to_dict(value) + elif hasattr(value, '__getitem__') and hasattr(value, '__len__'): # Array + try: + result[field_name] = [f"0x{v:08X}" if isinstance(v, int) and v > 255 else v for v in value] + except: + result[field_name] = str(value) + else: + # Format large integers as hex + if isinstance(value, int) and value > 255: + result[field_name] = f"0x{value:08X}" + else: + result[field_name] = value + return result + + uicr_dict = struct_to_dict(uicr) + pprint(uicr_dict, width=100, depth=None) + print("=" * 60) + + if __name__ == "__main__": main()