Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
4 changes: 4 additions & 0 deletions scripts/ci/check_compliance.py
Original file line number Diff line number Diff line change
Expand Up @@ -1301,8 +1301,12 @@ 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_PROTECTEDMEM", # Used in specialized build tool, not part of main Kconfig
"GEN_UICR_PROTECTEDMEM_SIZE_BYTES", # 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_SECONDARY_PROCESSOR_VALUE", # Used in specialized build tool, not part of main Kconfig
"GEN_UICR_SECURESTORAGE", # 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.
227 changes: 226 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,81 @@ 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(
"--secondary",
action="store_true",
Expand All @@ -266,6 +451,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 +518,45 @@ 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

# 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 +590,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
Loading
Loading