Skip to content

Commit 56b6e57

Browse files
jonathannilsenfabiobaltieri
authored andcommitted
soc: nordic: add IronSide SE compatible UICR support
Add support for generating UICR and associated artifacts in a format compatible with IronSide SE, to be used for Nordic SoCs in the Haltium family. The main feature added with this is the ability to configure certain global domain peripherals that are managed by the secure domain through setting UICR.PERIPHCONF. This register points at a blob of (register address, register value) pairs which are loaded into the peripherals by IronSide SE ahead of the application boot. The added helper macros in uicr.h can be used to add register configurations to the PERIPHCONF. Entries added through these macros are then extracted by a script, post-processed and placed in a blob located at specific part of MRAM. A default PERIPHCONF configuration has been added for the nrf54h20 soc to support the standard BLE use case (matching the configuration in the soc devicetree). Signed-off-by: Jonathan Nilsen <[email protected]>
1 parent b43ae17 commit 56b6e57

File tree

8 files changed

+665
-0
lines changed

8 files changed

+665
-0
lines changed

boards/nordic/nrf54h20dk/nrf54h20dk_nrf54h20-memory_map.dtsi

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,5 +162,9 @@
162162
storage_partition: partition@1a4000 {
163163
reg = <0x1a4000 DT_SIZE_K(40)>;
164164
};
165+
166+
periphconf_partition: partition@1ae000 {
167+
reg = <0x1ae000 DT_SIZE_K(8)>;
168+
};
165169
};
166170
};

soc/nordic/common/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33

44
add_subdirectory_ifdef(CONFIG_RISCV_CORE_NORDIC_VPR vpr)
55

6+
if(CONFIG_NRF_PERIPHCONF_SECTION OR CONFIG_NRF_HALTIUM_GENERATE_UICR)
7+
add_subdirectory(uicr)
8+
endif()
9+
610
# Let SystemInit() be called in place of soc_reset_hook() by default.
711
zephyr_linker_symbol(SYMBOL soc_reset_hook EXPR "@SystemInit@")
812

soc/nordic/common/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,4 @@ source "subsys/logging/Kconfig.template.log_config"
4949
endif # MRAM_LATENCY
5050

5151
rsource "vpr/Kconfig"
52+
rsource "uicr/Kconfig"

soc/nordic/common/uicr/CMakeLists.txt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
if(CONFIG_NRF_PERIPHCONF_SECTION)
5+
zephyr_linker_sources(SECTIONS uicr.ld)
6+
endif()
7+
8+
if(CONFIG_NRF_HALTIUM_GENERATE_UICR)
9+
if(CONFIG_NRF_PERIPHCONF_SECTION)
10+
set(in_periphconf_elf_arg
11+
--in-periphconf-elf $<TARGET_FILE:${ZEPHYR_LINK_STAGE_EXECUTABLE}>
12+
)
13+
endif()
14+
15+
if(CONFIG_NRF_HALTIUM_UICR_PERIPHCONF)
16+
set(periphconf_hex_file ${PROJECT_BINARY_DIR}/periphconf.hex)
17+
set(out_periphconf_hex_arg
18+
--out-periphconf-hex ${periphconf_hex_file}
19+
)
20+
list(APPEND optional_byproducts ${periphconf_hex_file})
21+
endif()
22+
23+
set(uicr_hex_file ${PROJECT_BINARY_DIR}/uicr.hex)
24+
set_property(GLOBAL APPEND PROPERTY extra_post_build_commands
25+
COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${ZEPHYR_BASE}/scripts/dts/python-devicetree/src
26+
${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/gen_uicr.py
27+
--in-config ${DOTCONFIG}
28+
--in-edt-pickle ${EDT_PICKLE}
29+
${in_periphconf_elf_arg}
30+
${out_periphconf_hex_arg}
31+
--out-uicr-hex ${uicr_hex_file}
32+
)
33+
set_property(GLOBAL APPEND PROPERTY extra_post_build_byproducts
34+
${uicr_hex_file} ${optional_byproducts}
35+
)
36+
endif()

soc/nordic/common/uicr/Kconfig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright (c) 2025 Nordic Semiconductor ASA
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
config NRF_HALTIUM_GENERATE_UICR
5+
bool "Generate UICR file"
6+
depends on SOC_NRF54H20_CPUAPP
7+
default y
8+
help
9+
Generate UICR HEX file.
10+
11+
if NRF_HALTIUM_GENERATE_UICR
12+
13+
config NRF_HALTIUM_UICR_PERIPHCONF
14+
bool "Initialize global domain peripherals"
15+
default y
16+
help
17+
Generates a blob containing static global domain peripheral initialization
18+
values extracted from the build artifacts, and configures UICR.PERIPHCONF
19+
to point at the blob. The initialization values are then loaded ahead of
20+
ahead of the application boot.
21+
22+
endif
23+
24+
config NRF_PERIPHCONF_SECTION
25+
bool "Populate global peripheral initialization section"
26+
default y if SOC_NRF54H20_CPUAPP
27+
depends on LINKER_DEVNULL_SUPPORT
28+
imply LINKER_DEVNULL_MEMORY
29+
help
30+
Include static global domain peripheral initialization values from the
31+
build in a dedicated section in the devnull region.

soc/nordic/common/uicr/gen_uicr.py

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
"""
2+
Copyright (c) 2025 Nordic Semiconductor ASA
3+
SPDX-License-Identifier: Apache-2.0
4+
"""
5+
6+
from __future__ import annotations
7+
8+
import argparse
9+
import ctypes as c
10+
import math
11+
import pickle
12+
import re
13+
import sys
14+
from collections import defaultdict
15+
from itertools import groupby
16+
17+
from elftools.elf.elffile import ELFFile
18+
from intelhex import IntelHex
19+
20+
# Name of the ELF section containing PERIPHCONF entries.
21+
# Must match the name used in the linker script.
22+
PERIPHCONF_SECTION = "uicr_periphconf_entry"
23+
24+
# Expected nodelabel of the UICR devicetree node, used to extract its location from the devicetree.
25+
UICR_NODELABEL = "uicr"
26+
# Nodelabel of the PERIPHCONF devicetree node, used to extract its location from the devicetree.
27+
PERIPHCONF_NODELABEL = "periphconf_partition"
28+
29+
# Common values for representing enabled/disabled in the UICR format.
30+
ENABLED_VALUE = 0xFFFF_FFFF
31+
DISABLED_VALUE = 0xBD23_28A8
32+
33+
34+
class ScriptError(RuntimeError): ...
35+
36+
37+
class PeriphconfEntry(c.LittleEndianStructure):
38+
_pack_ = 1
39+
_fields_ = [
40+
("regptr", c.c_uint32),
41+
("value", c.c_uint32),
42+
]
43+
44+
45+
PERIPHCONF_ENTRY_SIZE = c.sizeof(PeriphconfEntry)
46+
47+
48+
class Approtect(c.LittleEndianStructure):
49+
_pack_ = 1
50+
_fields_ = [
51+
("APPLICATION", c.c_uint32),
52+
("RADIOCORE", c.c_uint32),
53+
("RESERVED", c.c_uint32),
54+
("CORESIGHT", c.c_uint32),
55+
]
56+
57+
58+
class Protectedmem(c.LittleEndianStructure):
59+
_pack_ = 1
60+
_fields_ = [
61+
("ENABLE", c.c_uint32),
62+
("SIZE4KB", c.c_uint32),
63+
]
64+
65+
66+
class Recovery(c.LittleEndianStructure):
67+
_pack_ = 1
68+
_fields_ = [
69+
("ENABLE", c.c_uint32),
70+
("PROCESSOR", c.c_uint32),
71+
("INITSVTOR", c.c_uint32),
72+
("SIZE4KB", c.c_uint32),
73+
]
74+
75+
76+
class Its(c.LittleEndianStructure):
77+
_pack_ = 1
78+
_fields_ = [
79+
("ENABLE", c.c_uint32),
80+
("ADDRESS", c.c_uint32),
81+
("APPLICATIONSIZE", c.c_uint32),
82+
("RADIOCORESIZE", c.c_uint32),
83+
]
84+
85+
86+
class Periphconf(c.LittleEndianStructure):
87+
_pack_ = 1
88+
_fields_ = [
89+
("ENABLE", c.c_uint32),
90+
("ADDRESS", c.c_uint32),
91+
("MAXCOUNT", c.c_uint32),
92+
]
93+
94+
95+
class Mpcconf(c.LittleEndianStructure):
96+
_pack_ = 1
97+
_fields_ = [
98+
("ENABLE", c.c_uint32),
99+
("ADDRESS", c.c_uint32),
100+
("MAXCOUNT", c.c_uint32),
101+
]
102+
103+
104+
class Uicr(c.LittleEndianStructure):
105+
_pack_ = 1
106+
_fields_ = [
107+
("VERSION", c.c_uint32),
108+
("RESERVED", c.c_uint32),
109+
("LOCK", c.c_uint32),
110+
("RESERVED1", c.c_uint32),
111+
("APPROTECT", Approtect),
112+
("ERASEPROTECT", c.c_uint32),
113+
("PROTECTEDMEM", Protectedmem),
114+
("RECOVERY", Recovery),
115+
("ITS", Its),
116+
("RESERVED2", c.c_uint32 * 7),
117+
("PERIPHCONF", Periphconf),
118+
("MPCCONF", Mpcconf),
119+
]
120+
121+
122+
def main() -> None:
123+
parser = argparse.ArgumentParser(
124+
allow_abbrev=False,
125+
description=(
126+
"Generate artifacts for the UICR and associated configuration blobs from application "
127+
"build outputs. User Information Configuration Registers (UICR), in the context of "
128+
"certain Nordic SoCs, are used to configure system resources, like memory and "
129+
"peripherals, and to protect the device in various ways."
130+
),
131+
)
132+
parser.add_argument(
133+
"--in-config",
134+
required=True,
135+
type=argparse.FileType("r"),
136+
help="Path to the .config file from the application build",
137+
)
138+
parser.add_argument(
139+
"--in-edt-pickle",
140+
required=True,
141+
type=argparse.FileType("rb"),
142+
help="Path to the edt.pickle file from the application build",
143+
)
144+
parser.add_argument(
145+
"--in-periphconf-elf",
146+
dest="in_periphconf_elfs",
147+
default=[],
148+
action="append",
149+
type=argparse.FileType("rb"),
150+
help=(
151+
"Path to an ELF file to extract PERIPHCONF data from. Can be provided multiple times. "
152+
"The PERIPHCONF data from each ELF file is combined in a single list which is sorted "
153+
"by ascending address and cleared of duplicate entries."
154+
),
155+
)
156+
parser.add_argument(
157+
"--out-uicr-hex",
158+
required=True,
159+
type=argparse.FileType("w", encoding="utf-8"),
160+
help="Path to write the generated UICR HEX file to",
161+
)
162+
parser.add_argument(
163+
"--out-periphconf-hex",
164+
default=None,
165+
type=argparse.FileType("w", encoding="utf-8"),
166+
help="Path to write the generated PERIPHCONF HEX file to",
167+
)
168+
args = parser.parse_args()
169+
170+
try:
171+
init_values = DISABLED_VALUE.to_bytes(4, "little") * (c.sizeof(Uicr) // 4)
172+
uicr = Uicr.from_buffer_copy(init_values)
173+
174+
kconfig_str = args.in_config.read()
175+
kconfig = parse_kconfig(kconfig_str)
176+
177+
edt = pickle.load(args.in_edt_pickle)
178+
179+
try:
180+
periphconf_partition = edt.label2node[PERIPHCONF_NODELABEL]
181+
except LookupError as e:
182+
raise ScriptError(
183+
"Failed to find a PERIPHCONF partition in the devicetree. "
184+
f"Expected a DT node with label '{PERIPHCONF_NODELABEL}'."
185+
) from e
186+
187+
flash_base_address = periphconf_partition.flash_controller.regs[0].addr
188+
periphconf_address = flash_base_address + periphconf_partition.regs[0].addr
189+
periphconf_size = periphconf_partition.regs[0].size
190+
191+
periphconf_combined = extract_and_combine_periphconfs(args.in_periphconf_elfs)
192+
padding_len = periphconf_size - len(periphconf_combined)
193+
periphconf_final = periphconf_combined + bytes([0xFF for _ in range(padding_len)])
194+
195+
if kconfig.get("CONFIG_NRF_HALTIUM_UICR_PERIPHCONF") == "y":
196+
uicr.PERIPHCONF.ENABLE = ENABLED_VALUE
197+
uicr.PERIPHCONF.ADDRESS = periphconf_address
198+
uicr.PERIPHCONF.MAXCOUNT = math.floor(periphconf_size / 8)
199+
200+
try:
201+
uicr_node = edt.label2node[UICR_NODELABEL]
202+
except LookupError as e:
203+
raise ScriptError(
204+
"Failed to find UICR node in the devicetree. "
205+
f"Expected a DT node with label '{UICR_NODELABEL}'."
206+
) from e
207+
208+
uicr_hex = IntelHex()
209+
uicr_hex.frombytes(bytes(uicr), offset=uicr_node.regs[0].addr)
210+
211+
uicr_hex.write_hex_file(args.out_uicr_hex)
212+
213+
if args.out_periphconf_hex is not None:
214+
periphconf_hex = IntelHex()
215+
periphconf_hex.frombytes(periphconf_final, offset=periphconf_address)
216+
periphconf_hex.write_hex_file(args.out_periphconf_hex)
217+
218+
except ScriptError as e:
219+
print(f"Error: {e!s}")
220+
sys.exit(1)
221+
222+
223+
def extract_and_combine_periphconfs(elf_files: list[argparse.FileType]) -> bytes:
224+
combined_periphconf = []
225+
226+
for in_file in elf_files:
227+
elf = ELFFile(in_file)
228+
conf_section = elf.get_section_by_name(PERIPHCONF_SECTION)
229+
if conf_section is None:
230+
continue
231+
232+
conf_section_data = conf_section.data()
233+
num_entries = len(conf_section_data) // PERIPHCONF_ENTRY_SIZE
234+
periphconf = (PeriphconfEntry * num_entries).from_buffer_copy(conf_section_data)
235+
combined_periphconf.extend(periphconf)
236+
237+
combined_periphconf.sort(key=lambda e: e.regptr)
238+
deduplicated_periphconf = []
239+
240+
for regptr, regptr_entries in groupby(combined_periphconf, key=lambda e: e.regptr):
241+
entries = list(regptr_entries)
242+
if len(entries) > 1:
243+
unique_values = {e.value for e in entries}
244+
if len(unique_values) > 1:
245+
raise ScriptError(
246+
f"PERIPHCONF has conflicting values for register 0x{regptr:09_x}: "
247+
+ ", ".join([f"0x{val:09_x}" for val in unique_values])
248+
)
249+
deduplicated_periphconf.append(entries[0])
250+
251+
final_periphconf = (PeriphconfEntry * len(deduplicated_periphconf))()
252+
for i, entry in enumerate(deduplicated_periphconf):
253+
final_periphconf[i] = entry
254+
255+
return bytes(final_periphconf)
256+
257+
258+
def parse_kconfig(content: str) -> dict[str, str | None]:
259+
result = defaultdict(None)
260+
match_iter = re.finditer(
261+
r"^(?P<config>(SB_)?CONFIG_[^=\s]+)=(?P<value>[^\s#])+$", content, re.MULTILINE
262+
)
263+
for match in match_iter:
264+
result[match["config"]] = match["value"]
265+
266+
return result
267+
268+
269+
if __name__ == "__main__":
270+
main()

0 commit comments

Comments
 (0)