1#!/usr/bin/python3 2# Copyright (c) 2025, Arm Limited. All rights reserved. 3# 4# SPDX-License-Identifier: BSD-3-Clause 5 6import struct 7 8EFI_HOB_HANDOFF_TABLE_VERSION = 0x000A 9 10PAGE_SIZE_SHIFT = 12 # TODO assuming 4K page size 11 12# HobType values of EFI_HOB_GENERIC_HEADER. 13 14EFI_HOB_TYPE_HANDOFF = 0x0001 15EFI_HOB_TYPE_MEMORY_ALLOCATION = 0x0002 16EFI_HOB_TYPE_RESOURCE_DESCRIPTOR = 0x0003 17EFI_HOB_TYPE_GUID_EXTENSION = 0x0004 18EFI_HOB_TYPE_FV = 0x0005 19EFI_HOB_TYPE_CPU = 0x0006 20EFI_HOB_TYPE_MEMORY_POOL = 0x0007 21EFI_HOB_TYPE_FV2 = 0x0009 22EFI_HOB_TYPE_LOAD_PEIM_UNUSED = 0x000A 23EFI_HOB_TYPE_UEFI_CAPSULE = 0x000B 24EFI_HOB_TYPE_FV3 = 0x000C 25EFI_HOB_TYPE_UNUSED = 0xFFFE 26EFI_HOB_TYPE_END_OF_HOB_LIST = 0xFFFF 27 28# GUID values 29"""struct efi_guid { 30 uint32_t time_low; 31 uint16_t time_mid; 32 uint16_t time_hi_and_version; 33 uint8_t clock_seq_and_node[8]; 34}""" 35 36MM_PEI_MMRAM_MEMORY_RESERVE_GUID = ( 37 0x0703F912, 38 0xBF8D, 39 0x4E2A, 40 (0xBE, 0x07, 0xAB, 0x27, 0x25, 0x25, 0xC5, 0x92), 41) 42MM_NS_BUFFER_GUID = ( 43 0xF00497E3, 44 0xBFA2, 45 0x41A1, 46 (0x9D, 0x29, 0x54, 0xC2, 0xE9, 0x37, 0x21, 0xC5), 47) 48 49# MMRAM states and capabilities 50# See UEFI Platform Initialization Specification Version 1.8, IV-5.3.5 51EFI_MMRAM_OPEN = 0x00000001 52EFI_MMRAM_CLOSED = 0x00000002 53EFI_MMRAM_LOCKED = 0x00000004 54EFI_CACHEABLE = 0x00000008 55EFI_ALLOCATED = 0x00000010 56EFI_NEEDS_TESTING = 0x00000020 57EFI_NEEDS_ECC_INITIALIZATION = 0x00000040 58 59EFI_SMRAM_OPEN = EFI_MMRAM_OPEN 60EFI_SMRAM_CLOSED = EFI_MMRAM_CLOSED 61EFI_SMRAM_LOCKED = EFI_MMRAM_LOCKED 62 63# EFI boot mode. 64EFI_BOOT_WITH_FULL_CONFIGURATION = 0x00 65EFI_BOOT_WITH_MINIMAL_CONFIGURATION = 0x01 66EFI_BOOT_ASSUMING_NO_CONFIGURATION_CHANGES = 0x02 67EFI_BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS = 0x03 68EFI_BOOT_WITH_DEFAULT_SETTINGS = 0x04 69EFI_BOOT_ON_S4_RESUME = 0x05 70EFI_BOOT_ON_S5_RESUME = 0x06 71EFI_BOOT_WITH_MFG_MODE_SETTINGS = 0x07 72EFI_BOOT_ON_S2_RESUME = 0x10 73EFI_BOOT_ON_S3_RESUME = 0x11 74EFI_BOOT_ON_FLASH_UPDATE = 0x12 75EFI_BOOT_IN_RECOVERY_MODE = 0x20 76 77STMM_BOOT_MODE = EFI_BOOT_WITH_FULL_CONFIGURATION 78STMM_MMRAM_REGION_STATE_DEFAULT = EFI_CACHEABLE | EFI_ALLOCATED 79STMM_MMRAM_REGION_STATE_HEAP = EFI_CACHEABLE 80 81 82# Helper for fdt node property parsing 83def get_integer_property_value(fdt_node, name): 84 if fdt_node.exist_property(name): 85 p = fdt_node.get_property(name) 86 87 # <u32> Device Tree value 88 if len(p) == 1: 89 return p.value 90 # <u64> Device Tree value represented as two 32-bit values 91 if len(p) == 2: 92 msb = p[0] 93 lsb = p[1] 94 return lsb | (msb << 32) 95 return None 96 97 98class EfiGuid: 99 """Class representing EFI GUID (Globally Unique Identifier) as described by 100 the UEFI Specification v2.10""" 101 102 def __init__(self, time_low, time_mid, time_hi_and_version, clock_seq_and_node): 103 self.time_low = time_low 104 self.time_mid = time_mid 105 self.time_hi_and_version = time_hi_and_version 106 self.clock_seq_and_node = clock_seq_and_node 107 self.format_str = "IHH8B" 108 109 def pack(self): 110 return struct.pack( 111 self.format_str, 112 self.time_low, 113 self.time_mid, 114 self.time_hi_and_version, 115 *self.clock_seq_and_node, 116 ) 117 118 def __str__(self): 119 return f"{hex(self.time_low)}, {hex(self.time_mid)}, \ 120 {hex(self.time_hi_and_version)}, {[hex(i) for i in self.clock_seq_and_node]}" 121 122 123class HobGenericHeader: 124 """Class representing the Hob Generic Header data type as described 125 in the UEFI Platform Initialization Specification version 1.8. 126 127 Each HOB is required to contain this header specifying the type and length 128 of the HOB. 129 """ 130 131 def __init__(self, hob_type, hob_length): 132 self.format_str = "HHI" 133 self.hob_type = hob_type 134 self.hob_length = struct.calcsize(self.format_str) + hob_length 135 self.reserved = 0 136 137 def pack(self): 138 return struct.pack( 139 self.format_str, self.hob_type, self.hob_length, self.reserved 140 ) 141 142 def __str__(self): 143 return f"Hob Type: {self.hob_type} Hob Length: {self.hob_length}" 144 145 146class HobGuid: 147 """Class representing the Guid Extension HOB as described in the UEFI 148 Platform Initialization Specification version 1.8. 149 150 Allows the production of HOBs whose types are not defined by the 151 specification by generating a GUID for the HOB entry.""" 152 153 def __init__(self, name: EfiGuid, data_format_str, data): 154 hob_length = struct.calcsize(name.format_str) + struct.calcsize(data_format_str) 155 self.header = HobGenericHeader(EFI_HOB_TYPE_GUID_EXTENSION, hob_length) 156 self.name = name 157 self.data = data 158 self.data_format_str = data_format_str 159 self.format_str = ( 160 self.header.format_str + self.name.format_str + data_format_str 161 ) 162 163 def pack(self): 164 return ( 165 self.header.pack() 166 + self.name.pack() 167 + struct.pack(self.data_format_str, *self.data) 168 ) 169 170 def __str__(self): 171 return f"Header: {self.header}\n Name: {self.name}\n Data: {self.data}" 172 173 174class HandoffInfoTable: 175 """Class representing the Handoff Info Table HOB (also known as PHIT HOB) 176 as described in the UEFI Platform Initialization Specification version 1.8. 177 178 Must be the first HOB in the HOB list. Contains general state 179 information. 180 181 For an SP, the range `memory_bottom` to `memory_top` will be the memory 182 range for the SP starting at the load address. `free_memory_bottom` to 183 `free_memory_top` indicates space where more HOB's could be added to the 184 HOB List.""" 185 186 def __init__(self, memory_base, memory_size, free_memory_base, free_memory_size): 187 # header,uint32t,uint32t, uint64_t * 5 188 self.format_str = "II5Q" 189 hob_length = struct.calcsize(self.format_str) 190 self.header = HobGenericHeader(EFI_HOB_TYPE_HANDOFF, hob_length) 191 self.version = EFI_HOB_HANDOFF_TABLE_VERSION 192 self.boot_mode = STMM_BOOT_MODE 193 self.memory_top = memory_base + memory_size 194 self.memory_bottom = memory_base 195 self.free_memory_top = free_memory_base + free_memory_size 196 self.free_memory_bottom = free_memory_base + self.header.hob_length 197 self.hob_end = None 198 199 def set_hob_end_addr(self, hob_end_addr): 200 self.hob_end = hob_end_addr 201 202 def set_free_memory_bottom_addr(self, addr): 203 self.free_memory_bottom = addr 204 205 def pack(self): 206 return self.header.pack() + struct.pack( 207 self.format_str, 208 self.version, 209 self.boot_mode, 210 self.memory_top, 211 self.memory_bottom, 212 self.free_memory_top, 213 self.free_memory_bottom, 214 self.hob_end, 215 ) 216 217 218class FirmwareVolumeHob: 219 """Class representing the Firmware Volume HOB type as described in the 220 UEFI Platform Initialization Specification version 1.8. 221 222 For an SP this will detail where the SP binary is located. 223 """ 224 225 def __init__(self, base_address, img_offset, img_size): 226 # header, uint64_t, uint64_t 227 self.data_format_str = "2Q" 228 hob_length = struct.calcsize(self.data_format_str) 229 self.header = HobGenericHeader(EFI_HOB_TYPE_FV, hob_length) 230 self.format_str = self.header.format_str + self.data_format_str 231 self.base_address = base_address + img_offset 232 self.length = img_size - img_offset 233 234 def pack(self): 235 return self.header.pack() + struct.pack( 236 self.data_format_str, self.base_address, self.length 237 ) 238 239 240class EndOfHobListHob: 241 """Class representing the End of HOB List HOB type as described in the 242 UEFI Platform Initialization Specification version 1.8. 243 244 Must be the last entry in a HOB list. 245 """ 246 247 def __init__(self): 248 self.header = HobGenericHeader(EFI_HOB_TYPE_END_OF_HOB_LIST, 0) 249 self.format_str = "" 250 251 def pack(self): 252 return self.header.pack() 253 254 255class HobList: 256 """Class representing a HOB (Handoff Block list) based on the UEFI Platform 257 Initialization Sepcification version 1.8""" 258 259 def __init__(self, phit: HandoffInfoTable): 260 if phit is None: 261 raise Exception("HobList must be initialized with valid PHIT HOB") 262 final_hob = EndOfHobListHob() 263 phit.hob_end = phit.free_memory_bottom 264 phit.free_memory_bottom += final_hob.header.hob_length 265 self.hob_list = [phit, final_hob] 266 267 def add(self, hob): 268 if hob is not None: 269 if hob.header.hob_length > ( 270 self.get_phit().free_memory_top - self.get_phit().free_memory_bottom 271 ): 272 raise MemoryError( 273 f"Cannot add HOB of length {hob.header.hob_length}. \ 274 Resulting table size would exceed max table size of \ 275 {self.max_size}. Current table size: {self.size}." 276 ) 277 self.hob_list.insert(-1, hob) 278 self.get_phit().hob_end += hob.header.hob_length 279 self.get_phit().free_memory_bottom += hob.header.hob_length 280 281 def get_list(self): 282 return self.hob_list 283 284 def get_phit(self): 285 if self.hob_list is not None: 286 if type(self.hob_list[0]) is not HandoffInfoTable: 287 raise Exception("First hob in list must be of type PHIT") 288 return self.hob_list[0] 289 290 291def generate_mmram_desc(base_addr, page_count, granule, region_state): 292 physical_size = page_count << (PAGE_SIZE_SHIFT + (granule << 1)) 293 physical_start = base_addr 294 cpu_start = base_addr 295 296 return ("4Q", (physical_start, cpu_start, physical_size, region_state)) 297 298 299def generate_ns_buffer_guid(mmram_desc): 300 return HobGuid(EfiGuid(*MM_NS_BUFFER_GUID), *mmram_desc) 301 302 303def generate_pei_mmram_memory_reserve_guid(regions): 304 # uint32t n_reserved regions, array of mmram descriptors 305 format_str = "I" 306 data = [len(regions)] 307 for desc_format_str, mmram_desc in regions: 308 format_str += desc_format_str 309 data.extend(mmram_desc) 310 guid_data = (format_str, data) 311 return HobGuid(EfiGuid(*MM_PEI_MMRAM_MEMORY_RESERVE_GUID), *guid_data) 312 313 314def generate_hob_from_fdt_node(sp_fdt, hob_offset, hob_size=None): 315 """Create a HOB list binary from an SP FDT.""" 316 fv_hob = None 317 ns_buffer_hob = None 318 mmram_reserve_hob = None 319 shared_buf_hob = None 320 321 load_address = get_integer_property_value(sp_fdt, "load-address") 322 img_size = get_integer_property_value(sp_fdt, "image-size") 323 entrypoint_offset = get_integer_property_value(sp_fdt, "entrypoint-offset") 324 325 if entrypoint_offset is None: 326 entrypoint_offset = 0x0 327 if hob_offset is None: 328 hob_offset = 0x0 329 if img_size is None: 330 img_size = 0x0 331 332 if sp_fdt.exist_node("memory-regions"): 333 if sp_fdt.exist_property("xlat-granule"): 334 granule = int(sp_fdt.get_property("xlat-granule").value) 335 else: 336 # Default granule to 4K 337 granule = 0 338 memory_regions = sp_fdt.get_node("memory-regions") 339 regions = [] 340 for node in memory_regions.nodes: 341 base_addr = get_integer_property_value(node, "base-address") 342 page_count = get_integer_property_value(node, "pages-count") 343 344 if base_addr is None: 345 offset = get_integer_property_value( 346 node, "load-address-relative-offset" 347 ) 348 if offset is None: 349 # Cannot create memory descriptor without base address, so skip 350 # node if base address cannot be defined 351 continue 352 else: 353 base_addr = load_address + offset 354 355 if node.name.strip() == "heap": 356 region_state = STMM_MMRAM_REGION_STATE_HEAP 357 else: 358 region_state = STMM_MMRAM_REGION_STATE_DEFAULT 359 360 mmram_desc = generate_mmram_desc( 361 base_addr, page_count, granule, region_state 362 ) 363 364 if node.name.strip() == "ns_comm_buffer": 365 ns_buffer_hob = generate_ns_buffer_guid(mmram_desc) 366 367 regions.append(mmram_desc) 368 369 mmram_reserve_hob = generate_pei_mmram_memory_reserve_guid(regions) 370 371 fv_hob = FirmwareVolumeHob(load_address, entrypoint_offset, img_size) 372 hob_list_base = load_address + hob_offset 373 374 # TODO assuming default of 1 page allocated for HOB List 375 if hob_size is not None: 376 max_table_size = hob_size 377 else: 378 max_table_size = 1 << PAGE_SIZE_SHIFT 379 phit = HandoffInfoTable( 380 load_address, entrypoint_offset + img_size, hob_list_base, max_table_size 381 ) 382 383 # Create a HobList containing only PHIT and EndofHobList HOBs. 384 hob_list = HobList(phit) 385 386 # Add HOBs to HOB list 387 if fv_hob is not None: 388 hob_list.add(fv_hob) 389 if ns_buffer_hob is not None: 390 hob_list.add(ns_buffer_hob) 391 if mmram_reserve_hob is not None: 392 hob_list.add(mmram_reserve_hob) 393 if shared_buf_hob is not None: 394 hob_list.add(shared_buf_hob) 395 396 return hob_list 397