diff --git a/Makefile b/Makefile index 4368279a2..4cba2b019 100644 --- a/Makefile +++ b/Makefile @@ -1658,7 +1658,8 @@ endif #(NEED_FDT) # Add Secure Partition packages ifeq (${NEED_SP_PKG},yes) $(BUILD_PLAT)/sp_gen.mk: ${SP_MK_GEN} ${SP_LAYOUT_FILE} | $$(@D)/ - $(q)${PYTHON} "$<" "$@" $(filter-out $<,$^) $(BUILD_PLAT) ${COT} ${SP_DTS_LIST_FRAGMENT} + $(if $(host-poetry),$(q)poetry -q install) + $(q)$(if $(host-poetry),poetry run )${PYTHON} "$<" "$@" $(filter-out $<,$^) $(BUILD_PLAT) ${COT} ${SP_DTS_LIST_FRAGMENT} sp: $(DTBS) $(BUILD_PLAT)/sp_gen.mk $(SP_PKGS) $(s)echo $(s)echo "Built SP Images successfully" diff --git a/plat/arm/board/fvp/fdts/fvp_spmc_manifest.dts b/plat/arm/board/fvp/fdts/fvp_spmc_manifest.dts index bf0e7f387..b62df1a6f 100644 --- a/plat/arm/board/fvp/fdts/fvp_spmc_manifest.dts +++ b/plat/arm/board/fvp/fdts/fvp_spmc_manifest.dts @@ -34,7 +34,7 @@ debug_name = "cactus-primary"; load_address = <0x7000000>; vcpu_count = <8>; - mem_size = <1048576>; + mem_size = <0x100000>; /* * Platform specific SiP SMC call handled at EL3. Used * to pend an interrupt for testing purpose. @@ -46,21 +46,21 @@ debug_name = "cactus-secondary"; load_address = <0x7100000>; vcpu_count = <8>; - mem_size = <1048576>; + mem_size = <0x100000>; }; vm3 { is_ffa_partition; debug_name = "cactus-tertiary"; load_address = <0x7200000>; vcpu_count = <1>; - mem_size = <1048576>; + mem_size = <0x300000>; }; vm4 { is_ffa_partition; debug_name = "ivy"; load_address = <0x7600000>; vcpu_count = <1>; - mem_size = <1048576>; + mem_size = <0x100000>; }; }; diff --git a/poetry.lock b/poetry.lock index 341a2bd03..d05e199c7 100644 --- a/poetry.lock +++ b/poetry.lock @@ -280,6 +280,17 @@ files = [ {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, ] +[[package]] +name = "fdt" +version = "0.3.3" +description = "Flattened Device Tree Python Module" +optional = false +python-versions = ">=3.5" +files = [ + {file = "fdt-0.3.3-py3-none-any.whl", hash = "sha256:6b2fae2e8dfa38e9b0f9666aa001dd25be74e893d293a8d60001438f732e9e47"}, + {file = "fdt-0.3.3.tar.gz", hash = "sha256:81a215930fef2ab8894913c4f474105bb53e14f07129fe07cb6eff2d5fdf26d2"}, +] + [[package]] name = "filelock" version = "3.16.0" @@ -540,7 +551,7 @@ files = [ [[package]] name = "memory" version = "0.1.0" -description = "A tool for analysis of " +description = "A tool for analysis of static memory consumption by TF-A images" optional = false python-versions = "^3.8.0" files = [] @@ -1331,4 +1342,4 @@ type = ["pytest-mypy"] [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "7574eee0a05db1d9631bb29288abfc806810906a66e087a5a32e6e3920eb4bba" +content-hash = "c78729d7072714d77b4a69d6aabccab35dcf0548f08aa440ff178bc7bf2824be" diff --git a/pyproject.toml b/pyproject.toml index 55f3f2a3c..88c475393 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,3 +25,4 @@ sphinxcontrib-svg2pdfconverter = "^1.2.2" [tool.poetry.group.ci.dependencies] click = "^8.1.3" +fdt = "^0.3.0" diff --git a/tools/sptool/hob.py b/tools/sptool/hob.py new file mode 100644 index 000000000..dced08650 --- /dev/null +++ b/tools/sptool/hob.py @@ -0,0 +1,425 @@ +#!/usr/bin/python3 +# Copyright (c) 2025, Arm Limited. All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +import struct + +EFI_HOB_HANDOFF_TABLE_VERSION = 0x000A + +PAGE_SIZE_SHIFT = 12 # TODO assuming 4K page size + +# HobType values of EFI_HOB_GENERIC_HEADER. + +EFI_HOB_TYPE_HANDOFF = 0x0001 +EFI_HOB_TYPE_MEMORY_ALLOCATION = 0x0002 +EFI_HOB_TYPE_RESOURCE_DESCRIPTOR = 0x0003 +EFI_HOB_TYPE_GUID_EXTENSION = 0x0004 +EFI_HOB_TYPE_FV = 0x0005 +EFI_HOB_TYPE_CPU = 0x0006 +EFI_HOB_TYPE_MEMORY_POOL = 0x0007 +EFI_HOB_TYPE_FV2 = 0x0009 +EFI_HOB_TYPE_LOAD_PEIM_UNUSED = 0x000A +EFI_HOB_TYPE_UEFI_CAPSULE = 0x000B +EFI_HOB_TYPE_FV3 = 0x000C +EFI_HOB_TYPE_UNUSED = 0xFFFE +EFI_HOB_TYPE_END_OF_HOB_LIST = 0xFFFF + +# GUID values +"""struct efi_guid { + uint32_t time_low; + uint16_t time_mid; + uint16_t time_hi_and_version; + uint8_t clock_seq_and_node[8]; +}""" + +MM_PEI_MMRAM_MEMORY_RESERVE_GUID = ( + 0x0703F912, + 0xBF8D, + 0x4E2A, + (0xBE, 0x07, 0xAB, 0x27, 0x25, 0x25, 0xC5, 0x92), +) +MM_NS_BUFFER_GUID = ( + 0xF00497E3, + 0xBFA2, + 0x41A1, + (0x9D, 0x29, 0x54, 0xC2, 0xE9, 0x37, 0x21, 0xC5), +) + +# MMRAM states and capabilities +# See UEFI Platform Initialization Specification Version 1.8, IV-5.3.5 +EFI_MMRAM_OPEN = 0x00000001 +EFI_MMRAM_CLOSED = 0x00000002 +EFI_MMRAM_LOCKED = 0x00000004 +EFI_CACHEABLE = 0x00000008 +EFI_ALLOCATED = 0x00000010 +EFI_NEEDS_TESTING = 0x00000020 +EFI_NEEDS_ECC_INITIALIZATION = 0x00000040 + +EFI_SMRAM_OPEN = EFI_MMRAM_OPEN +EFI_SMRAM_CLOSED = EFI_MMRAM_CLOSED +EFI_SMRAM_LOCKED = EFI_MMRAM_LOCKED + +# EFI boot mode. +EFI_BOOT_WITH_FULL_CONFIGURATION = 0x00 +EFI_BOOT_WITH_MINIMAL_CONFIGURATION = 0x01 +EFI_BOOT_ASSUMING_NO_CONFIGURATION_CHANGES = 0x02 +EFI_BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS = 0x03 +EFI_BOOT_WITH_DEFAULT_SETTINGS = 0x04 +EFI_BOOT_ON_S4_RESUME = 0x05 +EFI_BOOT_ON_S5_RESUME = 0x06 +EFI_BOOT_WITH_MFG_MODE_SETTINGS = 0x07 +EFI_BOOT_ON_S2_RESUME = 0x10 +EFI_BOOT_ON_S3_RESUME = 0x11 +EFI_BOOT_ON_FLASH_UPDATE = 0x12 +EFI_BOOT_IN_RECOVERY_MODE = 0x20 + +STMM_BOOT_MODE = EFI_BOOT_WITH_FULL_CONFIGURATION +STMM_MMRAM_REGION_STATE_DEFAULT = EFI_CACHEABLE | EFI_ALLOCATED +STMM_MMRAM_REGION_STATE_HEAP = EFI_CACHEABLE + +"""`struct` python module allows user to specify endianness. +We are expecting FVP or STMM platform as target and that they will be +little-endian. See `struct` python module documentation if other endianness is +needed.""" +ENDIANNESS = "<" + + +def struct_pack_with_endianness(format_str, *args): + return struct.pack((ENDIANNESS + format_str), *args) + + +def struct_calcsize_with_endianness(format_str): + return struct.calcsize(ENDIANNESS + format_str) + + +# Helper for fdt node property parsing +def get_integer_property_value(fdt_node, name): + if fdt_node.exist_property(name): + p = fdt_node.get_property(name) + + # Device Tree value + if len(p) == 1: + return p.value + # Device Tree value represented as two 32-bit values + if len(p) == 2: + msb = p[0] + lsb = p[1] + return lsb | (msb << 32) + return None + + +class EfiGuid: + """Class representing EFI GUID (Globally Unique Identifier) as described by + the UEFI Specification v2.10""" + + def __init__(self, time_low, time_mid, time_hi_and_version, clock_seq_and_node): + self.time_low = time_low + self.time_mid = time_mid + self.time_hi_and_version = time_hi_and_version + self.clock_seq_and_node = clock_seq_and_node + self.format_str = "IHH8B" + + def pack(self): + return struct_pack_with_endianness( + self.format_str, + self.time_low, + self.time_mid, + self.time_hi_and_version, + *self.clock_seq_and_node, + ) + + def __str__(self): + return f"{hex(self.time_low)}, {hex(self.time_mid)}, \ + {hex(self.time_hi_and_version)}, {[hex(i) for i in self.clock_seq_and_node]}" + + +class HobGenericHeader: + """Class representing the Hob Generic Header data type as described + in the UEFI Platform Initialization Specification version 1.8. + + Each HOB is required to contain this header specifying the type and length + of the HOB. + """ + + def __init__(self, hob_type, hob_length): + self.format_str = "HHI" + self.hob_type = hob_type + self.hob_length = struct_calcsize_with_endianness(self.format_str) + hob_length + self.reserved = 0 + + def pack(self): + return struct_pack_with_endianness( + self.format_str, self.hob_type, self.hob_length, self.reserved + ) + + def __str__(self): + return f"Hob Type: {self.hob_type} Hob Length: {self.hob_length}" + + +class HobGuid: + """Class representing the Guid Extension HOB as described in the UEFI + Platform Initialization Specification version 1.8. + + Allows the production of HOBs whose types are not defined by the + specification by generating a GUID for the HOB entry.""" + + def __init__(self, name: EfiGuid, data_format_str, data): + hob_length = struct_calcsize_with_endianness( + name.format_str + ) + struct_calcsize_with_endianness(data_format_str) + self.header = HobGenericHeader(EFI_HOB_TYPE_GUID_EXTENSION, hob_length) + self.name = name + self.data = data + self.data_format_str = data_format_str + self.format_str = ( + self.header.format_str + self.name.format_str + data_format_str + ) + + def pack(self): + return ( + self.header.pack() + + self.name.pack() + + struct_pack_with_endianness(self.data_format_str, *self.data) + ) + + def __str__(self): + return f"Header: {self.header}\n Name: {self.name}\n Data: {self.data}" + + +class HandoffInfoTable: + """Class representing the Handoff Info Table HOB (also known as PHIT HOB) + as described in the UEFI Platform Initialization Specification version 1.8. + + Must be the first HOB in the HOB list. Contains general state + information. + + For an SP, the range `memory_bottom` to `memory_top` will be the memory + range for the SP starting at the load address. `free_memory_bottom` to + `free_memory_top` indicates space where more HOB's could be added to the + HOB List.""" + + def __init__(self, memory_base, memory_size, free_memory_base, free_memory_size): + # header,uint32t,uint32t, uint64_t * 5 + self.format_str = "II5Q" + hob_length = struct_calcsize_with_endianness(self.format_str) + self.header = HobGenericHeader(EFI_HOB_TYPE_HANDOFF, hob_length) + self.version = EFI_HOB_HANDOFF_TABLE_VERSION + self.boot_mode = STMM_BOOT_MODE + self.memory_top = memory_base + memory_size + self.memory_bottom = memory_base + self.free_memory_top = free_memory_base + free_memory_size + self.free_memory_bottom = free_memory_base + self.header.hob_length + self.hob_end = None + + def set_hob_end_addr(self, hob_end_addr): + self.hob_end = hob_end_addr + + def set_free_memory_bottom_addr(self, addr): + self.free_memory_bottom = addr + + def pack(self): + return self.header.pack() + struct_pack_with_endianness( + self.format_str, + self.version, + self.boot_mode, + self.memory_top, + self.memory_bottom, + self.free_memory_top, + self.free_memory_bottom, + self.hob_end, + ) + + +class FirmwareVolumeHob: + """Class representing the Firmware Volume HOB type as described in the + UEFI Platform Initialization Specification version 1.8. + + For an SP this will detail where the SP binary is located. + """ + + def __init__(self, base_address, img_offset, img_size): + # header, uint64_t, uint64_t + self.data_format_str = "2Q" + hob_length = struct_calcsize_with_endianness(self.data_format_str) + self.header = HobGenericHeader(EFI_HOB_TYPE_FV, hob_length) + self.format_str = self.header.format_str + self.data_format_str + self.base_address = base_address + img_offset + self.length = img_size - img_offset + + def pack(self): + return self.header.pack() + struct_pack_with_endianness( + self.data_format_str, self.base_address, self.length + ) + + +class EndOfHobListHob: + """Class representing the End of HOB List HOB type as described in the + UEFI Platform Initialization Specification version 1.8. + + Must be the last entry in a HOB list. + """ + + def __init__(self): + self.header = HobGenericHeader(EFI_HOB_TYPE_END_OF_HOB_LIST, 0) + self.format_str = "" + + def pack(self): + return self.header.pack() + + +class HobList: + """Class representing a HOB (Handoff Block list) based on the UEFI Platform + Initialization Sepcification version 1.8""" + + def __init__(self, phit: HandoffInfoTable): + if phit is None: + raise Exception("HobList must be initialized with valid PHIT HOB") + final_hob = EndOfHobListHob() + phit.hob_end = phit.free_memory_bottom + phit.free_memory_bottom += final_hob.header.hob_length + self.hob_list = [phit, final_hob] + + def add(self, hob): + if hob is not None: + if hob.header.hob_length > ( + self.get_phit().free_memory_top - self.get_phit().free_memory_bottom + ): + raise MemoryError( + f"Cannot add HOB of length {hob.header.hob_length}. \ + Resulting table size would exceed max table size of \ + {self.max_size}. Current table size: {self.size}." + ) + self.hob_list.insert(-1, hob) + self.get_phit().hob_end += hob.header.hob_length + self.get_phit().free_memory_bottom += hob.header.hob_length + + def get_list(self): + return self.hob_list + + def get_phit(self): + if self.hob_list is not None: + if type(self.hob_list[0]) is not HandoffInfoTable: + raise Exception("First hob in list must be of type PHIT") + return self.hob_list[0] + + +def generate_mmram_desc(base_addr, page_count, granule, region_state): + physical_size = page_count << (PAGE_SIZE_SHIFT + (granule << 1)) + physical_start = base_addr + cpu_start = base_addr + + return ("4Q", (physical_start, cpu_start, physical_size, region_state)) + + +def generate_stmm_region_descriptor(base_addr, physical_size): + region_state = STMM_MMRAM_REGION_STATE_DEFAULT + physical_start = base_addr + cpu_start = base_addr + return ("4Q", (physical_start, cpu_start, physical_size, region_state)) + + +def generate_ns_buffer_guid(mmram_desc): + return HobGuid(EfiGuid(*MM_NS_BUFFER_GUID), *mmram_desc) + + +def generate_pei_mmram_memory_reserve_guid(regions): + # uint32t n_reserved regions, 4 bytes for padding so that array is aligned, + # array of mmram descriptors + format_str = "I4x" + data = [len(regions)] + for desc_format_str, mmram_desc in regions: + format_str += desc_format_str + data.extend(mmram_desc) + guid_data = (format_str, data) + return HobGuid(EfiGuid(*MM_PEI_MMRAM_MEMORY_RESERVE_GUID), *guid_data) + + +def generate_hob_from_fdt_node(sp_fdt, hob_offset, hob_size=None): + """Create a HOB list binary from an SP FDT.""" + fv_hob = None + ns_buffer_hob = None + mmram_reserve_hob = None + shared_buf_hob = None + + load_address = get_integer_property_value(sp_fdt, "load-address") + img_size = get_integer_property_value(sp_fdt, "image-size") + entrypoint_offset = get_integer_property_value(sp_fdt, "entrypoint-offset") + + if entrypoint_offset is None: + entrypoint_offset = 0x0 + if hob_offset is None: + hob_offset = 0x0 + if img_size is None: + img_size = 0x0 + + regions = [] + + # StMM requires the first memory region described in the + # MM_PEI_MMRAM_MEMORY_RESERVE_GUID describe the full partition layout. + regions.append(generate_stmm_region_descriptor(load_address, img_size)) + + if sp_fdt.exist_node("memory-regions"): + if sp_fdt.exist_property("xlat-granule"): + granule = int(sp_fdt.get_property("xlat-granule").value) + else: + # Default granule to 4K + granule = 0 + memory_regions = sp_fdt.get_node("memory-regions") + for node in memory_regions.nodes: + base_addr = get_integer_property_value(node, "base-address") + page_count = get_integer_property_value(node, "pages-count") + + if base_addr is None: + offset = get_integer_property_value( + node, "load-address-relative-offset" + ) + if offset is None: + # Cannot create memory descriptor without base address, so skip + # node if base address cannot be defined + continue + else: + base_addr = load_address + offset + + if node.name.strip() == "heap": + region_state = STMM_MMRAM_REGION_STATE_HEAP + else: + region_state = STMM_MMRAM_REGION_STATE_DEFAULT + + mmram_desc = generate_mmram_desc( + base_addr, page_count, granule, region_state + ) + + if node.name.strip() == "ns_comm_buffer": + ns_buffer_hob = generate_ns_buffer_guid(mmram_desc) + + regions.append(mmram_desc) + + mmram_reserve_hob = generate_pei_mmram_memory_reserve_guid(regions) + + fv_hob = FirmwareVolumeHob(load_address, entrypoint_offset, img_size) + hob_list_base = load_address + hob_offset + + # TODO assuming default of 1 page allocated for HOB List + if hob_size is not None: + max_table_size = hob_size + else: + max_table_size = 1 << PAGE_SIZE_SHIFT + phit = HandoffInfoTable( + load_address, entrypoint_offset + img_size, hob_list_base, max_table_size + ) + + # Create a HobList containing only PHIT and EndofHobList HOBs. + hob_list = HobList(phit) + + # Add HOBs to HOB list + if fv_hob is not None: + hob_list.add(fv_hob) + if ns_buffer_hob is not None: + hob_list.add(ns_buffer_hob) + if mmram_reserve_hob is not None: + hob_list.add(mmram_reserve_hob) + if shared_buf_hob is not None: + hob_list.add(shared_buf_hob) + + return hob_list diff --git a/tools/sptool/sp_mk_generator.py b/tools/sptool/sp_mk_generator.py index f80050e04..9a00c745a 100644 --- a/tools/sptool/sp_mk_generator.py +++ b/tools/sptool/sp_mk_generator.py @@ -55,10 +55,15 @@ import os import re import sys import uuid +import fdt from spactions import SpSetupActions +import hob +import struct +from hob import HobList MAX_SP = 8 UUID_LEN = 4 +HOB_OFFSET_DEFAULT=0x2000 # Some helper functions to access args propagated to the action functions in # SpSetupActions framework. @@ -179,6 +184,28 @@ def gen_fdt_sources(sp_layout, sp, args :dict): write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args) return args +@SpSetupActions.sp_action(exec_order=1) +def generate_hob_list(sp_layout, sp, args: dict): + ''' + Generates a HOB file for the partition, if it requested it in its FF-A + manifest. + ''' + with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as f: + sp_fdt = fdt.parse_dts(f.read()) + + if sp_fdt.exist_property('hob_list', '/boot-info'): + sp_hob_name = os.path.basename(sp + ".hob.bin") + sp_hob_name = os.path.join(args["out_dir"], f"{sp_hob_name}") + + # Add to the args so it can be consumed by the TL pkg function. + sp_layout[sp]["hob_path"] = sp_hob_name + hob_list = hob.generate_hob_from_fdt_node(sp_fdt, HOB_OFFSET_DEFAULT) + with open(sp_hob_name, "wb") as h: + for block in hob_list.get_list(): + h.write(block.pack()) + + return args + def generate_sp_pkg(sp_node, pkg, sp_img, sp_dtb): ''' Generates the rule in case SP is to be generated in an SP Pkg. ''' pm_offset = get_pm_offset(sp_node) @@ -200,11 +227,12 @@ def generate_tl_pkg(sp_node, pkg, sp_img, sp_dtb, hob_path = None): TE_SP_BINARY = 0x103 # TE Type for the HOB List. TE_HOB_LIST = 0x3 - tlc_add_hob = f"\t$(Q)poetry run tlc add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else "" + tlc_add_hob = f"\t$(Q)$(TLCTOOL) add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else "" return f''' {pkg}: {sp_dtb} {sp_img} \t$(Q)echo Generating {pkg} \t$(Q)$(TLCTOOL) create --size {get_size(sp_node)} --entry {TE_FFA_MANIFEST} {sp_dtb} {pkg} --align 12 +{tlc_add_hob} \t$(Q)$(TLCTOOL) add --entry {TE_SP_BINARY} {sp_img} {pkg} ''' @@ -228,7 +256,10 @@ def gen_partition_pkg(sp_layout, sp, args :dict): if package_type == "sp_pkg": partition_pkg_rule = generate_sp_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) elif package_type == "tl_pkg": - partition_pkg_rule = generate_tl_pkg(sp_layout[sp], pkg, sp_img, sp_dtb) + # Conditionally provide the Hob. + hob_path = sp_layout[sp]["hob_path"] if "hob_path" in sp_layout[sp] else None + partition_pkg_rule = generate_tl_pkg( + sp_layout[sp], pkg, sp_img, sp_dtb, hob_path) else: raise ValueError(f"Specified invalid pkg type {package_type}")