Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions scripts/ci/check_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,18 @@ 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
"GEN_UICR_PROTECTEDMEM",
"GEN_UICR_PROTECTEDMEM_SIZE_BYTES",
"GEN_UICR_SECONDARY",
"GEN_UICR_SECONDARY_GENERATE_PERIPHCONF",
"GEN_UICR_SECONDARY_PROCESSOR_VALUE",
"GEN_UICR_SECONDARY_WDTSTART",
"GEN_UICR_SECONDARY_WDTSTART_CRV",
"GEN_UICR_SECONDARY_WDTSTART_INSTANCE_CODE",
"GEN_UICR_SECURESTORAGE",
"GEN_UICR_WDTSTART",
"GEN_UICR_WDTSTART_CRV",
"GEN_UICR_WDTSTART_INSTANCE_CODE", # 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",
Expand Down
7 changes: 7 additions & 0 deletions soc/nordic/common/uicr/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,10 @@ config NRF_PERIPHCONF_GENERATE_ENTRIES
help
Generate a C file containing PERIPHCONF entries based on the
device configuration in the devicetree.

config IS_IRONSIDE_SE_SECONDARY_IMAGE
bool "Ironside SE secondary image indicator (informative only, do not change)"
help
This Kconfig is set by sysbuild to indicate that this image is a
secondary firmware for Ironside SE. This is used by the UICR generation
system to determine which PERIPHCONF partition to use.
269 changes: 268 additions & 1 deletion soc/nordic/common/uicr/gen_uicr.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import argparse
import ctypes as c
import sys
from itertools import groupby
from itertools import groupby, pairwise
from typing import NamedTuple

from elftools.elf.elffile import ELFFile
from intelhex import IntelHex
Expand All @@ -25,10 +26,20 @@
ENABLED_VALUE = 0xFFFF_FFFF
DISABLED_VALUE = 0xBD23_28A8

KB_4 = 4096


class ScriptError(RuntimeError): ...


class PartitionInfo(NamedTuple):
"""Information about a partition for secure storage validation."""

address: int
size: int
name: str


class PeriphconfEntry(c.LittleEndianStructure):
_pack_ = 1
_fields_ = [
Expand Down Expand Up @@ -198,6 +209,105 @@ class Uicr(c.LittleEndianStructure):
]


def validate_secure_storage_partitions(args: argparse.Namespace) -> None:
"""
Validate that secure storage partitions are laid out correctly.

Args:
args: Parsed command line arguments containing partition information

Raises:
ScriptError: If validation fails
"""
# Expected order: cpuapp_crypto_partition, cpurad_crypto_partition,
# cpuapp_its_partition, cpurad_its_partition
partitions = [
PartitionInfo(
args.cpuapp_crypto_address, args.cpuapp_crypto_size, "cpuapp_crypto_partition"
),
PartitionInfo(
args.cpurad_crypto_address, args.cpurad_crypto_size, "cpurad_crypto_partition"
),
PartitionInfo(args.cpuapp_its_address, args.cpuapp_its_size, "cpuapp_its_partition"),
PartitionInfo(args.cpurad_its_address, args.cpurad_its_size, "cpurad_its_partition"),
]

# Filter out zero-sized partitions (missing partitions)
present_partitions = [p for p in partitions if p.size > 0]

# Require at least one subpartition to be present
if not present_partitions:
raise ScriptError(
"At least one secure storage subpartition must be defined. "
"Define one or more of: cpuapp_crypto_partition, cpurad_crypto_partition, "
"cpuapp_its_partition, cpurad_its_partition"
)

# Check 4KB alignment for secure storage start address
if args.securestorage_address % 4096 != 0:
raise ScriptError(
f"Secure storage address {args.securestorage_address:#x} must be aligned to 4KB "
f"(4096 bytes)"
)

# Check 4KB alignment for secure storage size
if args.securestorage_size % 4096 != 0:
raise ScriptError(
f"Secure storage size {args.securestorage_size} bytes must be aligned to 4KB "
f"(4096 bytes)"
)

# Check that the first present partition starts at the secure storage address
first_partition = present_partitions[0]
if first_partition.address != args.securestorage_address:
raise ScriptError(
f"First partition {first_partition.name} starts at {first_partition.address:#x}, "
f"but must start at secure storage address {args.securestorage_address:#x}"
)

# Check that all present partitions have sizes that are multiples of 1KB
for partition in present_partitions:
if partition.size % 1024 != 0:
raise ScriptError(
f"Partition {partition.name} has size {partition.size} bytes, but must be "
f"a multiple of 1024 bytes (1KB)"
)

# Check that partitions are in correct order and don't overlap
for curr_partition, next_partition in pairwise(present_partitions):
# Check order - partitions should be in ascending address order
if curr_partition.address >= next_partition.address:
raise ScriptError(
f"Partition {curr_partition.name} (starts at {curr_partition.address:#x}) "
f"must come before {next_partition.name} (starts at {next_partition.address:#x})"
)

# Check for overlap
curr_end = curr_partition.address + curr_partition.size
if curr_end > next_partition.address:
raise ScriptError(
f"Partition {curr_partition.name} (ends at {curr_end:#x}) overlaps with "
f"{next_partition.name} (starts at {next_partition.address:#x})"
)

# Check for gaps (should be no gaps between consecutive partitions)
if curr_end < next_partition.address:
gap = next_partition.address - curr_end
raise ScriptError(
f"Gap of {gap} bytes between {curr_partition.name} (ends at {curr_end:#x}) and "
f"{next_partition.name} (starts at {next_partition.address:#x})"
)

# Check that combined subpartition sizes equal secure_storage_partition size
total_subpartition_size = sum(p.size for p in present_partitions)
if total_subpartition_size != args.securestorage_size:
raise ScriptError(
f"Combined size of subpartitions ({total_subpartition_size} bytes) does not match "
f"secure_storage_partition size ({args.securestorage_size} bytes). "
f"The definition is not coherent."
)


def main() -> None:
parser = argparse.ArgumentParser(
allow_abbrev=False,
Expand Down Expand Up @@ -255,6 +365,111 @@ 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(
"--securestorage",
action="store_true",
help="Enable secure storage support in UICR",
)
parser.add_argument(
"--securestorage-address",
default=None,
type=lambda s: int(s, 0),
help="Absolute flash address of the secure storage partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--securestorage-size",
default=None,
type=lambda s: int(s, 0),
help="Size in bytes of the secure storage partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpuapp-crypto-address",
default=0,
type=lambda s: int(s, 0),
help="Absolute flash address of cpuapp_crypto_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpuapp-crypto-size",
default=0,
type=lambda s: int(s, 0),
help="Size in bytes of cpuapp_crypto_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpurad-crypto-address",
default=0,
type=lambda s: int(s, 0),
help="Absolute flash address of cpurad_crypto_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpurad-crypto-size",
default=0,
type=lambda s: int(s, 0),
help="Size in bytes of cpurad_crypto_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpuapp-its-address",
default=0,
type=lambda s: int(s, 0),
help="Absolute flash address of cpuapp_its_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpuapp-its-size",
default=0,
type=lambda s: int(s, 0),
help="Size in bytes of cpuapp_its_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpurad-its-address",
default=0,
type=lambda s: int(s, 0),
help="Absolute flash address of cpurad_its_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--cpurad-its-size",
default=0,
type=lambda s: int(s, 0),
help="Size in bytes of cpurad_its_partition (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--protectedmem",
action="store_true",
help="Enable protected memory region in UICR",
)
parser.add_argument(
"--protectedmem-size-bytes",
type=int,
help="Protected memory size in bytes (must be divisible by 4096)",
)
parser.add_argument(
"--wdtstart",
action="store_true",
help="Enable watchdog timer start in UICR",
)
parser.add_argument(
"--wdtstart-instance-code",
type=lambda s: int(s, 0),
help="Watchdog timer instance code (0xBD2328A8 for WDT0, 0x1730C77F for WDT1)",
)
parser.add_argument(
"--wdtstart-crv",
type=int,
help="Initial Counter Reload Value (CRV) for watchdog timer (minimum: 0xF)",
)
parser.add_argument(
"--secondary-wdtstart",
action="store_true",
help="Enable watchdog timer start in UICR.SECONDARY",
)
parser.add_argument(
"--secondary-wdtstart-instance-code",
type=lambda s: int(s, 0),
help="Secondary watchdog timer instance code (0xBD2328A8 for WDT0, 0x1730C77F for WDT1)",
)
parser.add_argument(
"--secondary-wdtstart-crv",
type=int,
help="Secondary initial Counter Reload Value (CRV) for watchdog timer (minimum: 0xF)",
)
parser.add_argument(
"--secondary",
action="store_true",
Expand All @@ -266,6 +481,12 @@ def main() -> None:
type=lambda s: int(s, 0),
help="Absolute flash address of the secondary firmware (decimal or 0x-prefixed hex)",
)
parser.add_argument(
"--secondary-processor",
default=0xBD2328A8,
type=lambda s: int(s, 0),
help="Processor to boot for the secondary firmware ",
)
parser.add_argument(
"--secondary-periphconf-address",
default=None,
Expand Down Expand Up @@ -327,12 +548,51 @@ def main() -> None:
"--out-secondary-periphconf-hex is used"
)

# Validate secure storage argument dependencies
if args.securestorage:
if args.securestorage_address is None:
raise ScriptError(
"--securestorage-address is required when --securestorage is used"
)
if args.securestorage_size is None:
raise ScriptError("--securestorage-size is required when --securestorage is used")

# Validate partition layout
validate_secure_storage_partitions(args)

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

# Handle secure storage configuration
if args.securestorage:
uicr.SECURESTORAGE.ENABLE = ENABLED_VALUE
uicr.SECURESTORAGE.ADDRESS = args.securestorage_address

# Set partition sizes in 1KB units
uicr.SECURESTORAGE.CRYPTO.APPLICATIONSIZE1KB = args.cpuapp_crypto_size // 1024
uicr.SECURESTORAGE.CRYPTO.RADIOCORESIZE1KB = args.cpurad_crypto_size // 1024
uicr.SECURESTORAGE.ITS.APPLICATIONSIZE1KB = args.cpuapp_its_size // 1024
uicr.SECURESTORAGE.ITS.RADIOCORESIZE1KB = args.cpurad_its_size // 1024

# Handle protected memory configuration
if args.protectedmem:
if args.protectedmem_size_bytes % KB_4 != 0:
raise ScriptError(
f"Protected memory size ({args.protectedmem_size_bytes} bytes) "
f"must be divisible by {KB_4}"
)
uicr.PROTECTEDMEM.ENABLE = ENABLED_VALUE
uicr.PROTECTEDMEM.SIZE4KB = args.protectedmem_size_bytes // KB_4

# Handle WDTSTART configuration
if args.wdtstart:
uicr.WDTSTART.ENABLE = ENABLED_VALUE
uicr.WDTSTART.CRV = args.wdtstart_crv
uicr.WDTSTART.INSTANCE = args.wdtstart_instance_code

# Process periphconf data first and configure UICR completely before creating hex objects
periphconf_hex = IntelHex()
secondary_periphconf_hex = IntelHex()
Expand Down Expand Up @@ -366,6 +626,7 @@ def main() -> None:
if args.secondary:
uicr.SECONDARY.ENABLE = ENABLED_VALUE
uicr.SECONDARY.ADDRESS = args.secondary_address
uicr.SECONDARY.PROCESSOR = args.secondary_processor

# Handle secondary periphconf if provided
if args.out_secondary_periphconf_hex:
Expand Down Expand Up @@ -400,6 +661,12 @@ def main() -> None:

uicr.SECONDARY.PERIPHCONF.MAXCOUNT = args.secondary_periphconf_size // 8

# Handle secondary WDTSTART configuration
if args.secondary_wdtstart:
uicr.SECONDARY.WDTSTART.ENABLE = ENABLED_VALUE
uicr.SECONDARY.WDTSTART.CRV = args.secondary_wdtstart_crv
uicr.SECONDARY.WDTSTART.INSTANCE = args.secondary_wdtstart_instance_code

# Create UICR hex object with final UICR data
uicr_hex = IntelHex()
uicr_hex.frombytes(bytes(uicr), offset=args.uicr_address)
Expand Down
Loading
Loading