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 <kathleen.capella@arm.com>
Change-Id: I17d46f7ed21ce42a83f33dfdc4fad038653d1ec3
diff --git a/tools/sptool/hob.py b/tools/sptool/hob.py
new file mode 100644
index 0000000..372ec68
--- /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)
+
+ # <u32> Device Tree value
+ if len(p) == 1:
+ return p.value
+ # <u64> 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