From cc594af66e05b5f863b00dfab939f53e558d9c23 Mon Sep 17 00:00:00 2001 From: Kathleen Capella Date: Wed, 16 Oct 2024 18:14:33 -0400 Subject: [PATCH] feat(sptool): add the HOB list creation script Add python library to build the Handoff Block list (HOB list) for an SP at build time. Signed-off-by: Kathleen Capella Change-Id: I17d46f7ed21ce42a83f33dfdc4fad038653d1ec3 --- tools/sptool/hob.py | 396 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 396 insertions(+) create mode 100644 tools/sptool/hob.py diff --git a/tools/sptool/hob.py b/tools/sptool/hob.py new file mode 100644 index 000000000..372ec68a5 --- /dev/null +++ b/tools/sptool/hob.py @@ -0,0 +1,396 @@ +#!/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 + + +# 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( + 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(self.format_str) + hob_length + self.reserved = 0 + + def pack(self): + return struct.pack( + 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(name.format_str) + struct.calcsize(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(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(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( + 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(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( + 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_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, array of mmram descriptors + format_str = "I" + 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 + + 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") + 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