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"""`struct` python module allows user to specify endianness. 82We are expecting FVP or STMM platform as target and that they will be 83little-endian. See `struct` python module documentation if other endianness is 84needed.""" 85ENDIANNESS = "<" 86 87 88def struct_pack_with_endianness(format_str, *args): 89 return struct.pack((ENDIANNESS + format_str), *args) 90 91 92def struct_calcsize_with_endianness(format_str): 93 return struct.calcsize(ENDIANNESS + format_str) 94 95 96# Helper for fdt node property parsing 97def get_integer_property_value(fdt_node, name): 98 if fdt_node.exist_property(name): 99 p = fdt_node.get_property(name) 100 101 # <u32> Device Tree value 102 if len(p) == 1: 103 return p.value 104 # <u64> Device Tree value represented as two 32-bit values 105 if len(p) == 2: 106 msb = p[0] 107 lsb = p[1] 108 return lsb | (msb << 32) 109 return None 110 111 112class EfiGuid: 113 """Class representing EFI GUID (Globally Unique Identifier) as described by 114 the UEFI Specification v2.10""" 115 116 def __init__(self, time_low, time_mid, time_hi_and_version, clock_seq_and_node): 117 self.time_low = time_low 118 self.time_mid = time_mid 119 self.time_hi_and_version = time_hi_and_version 120 self.clock_seq_and_node = clock_seq_and_node 121 self.format_str = "IHH8B" 122 123 def pack(self): 124 return struct_pack_with_endianness( 125 self.format_str, 126 self.time_low, 127 self.time_mid, 128 self.time_hi_and_version, 129 *self.clock_seq_and_node, 130 ) 131 132 def __str__(self): 133 return f"{hex(self.time_low)}, {hex(self.time_mid)}, \ 134 {hex(self.time_hi_and_version)}, {[hex(i) for i in self.clock_seq_and_node]}" 135 136 137class HobGenericHeader: 138 """Class representing the Hob Generic Header data type as described 139 in the UEFI Platform Initialization Specification version 1.8. 140 141 Each HOB is required to contain this header specifying the type and length 142 of the HOB. 143 """ 144 145 def __init__(self, hob_type, hob_length): 146 self.format_str = "HHI" 147 self.hob_type = hob_type 148 self.hob_length = struct_calcsize_with_endianness(self.format_str) + hob_length 149 self.reserved = 0 150 151 def pack(self): 152 return struct_pack_with_endianness( 153 self.format_str, self.hob_type, self.hob_length, self.reserved 154 ) 155 156 def __str__(self): 157 return f"Hob Type: {self.hob_type} Hob Length: {self.hob_length}" 158 159 160class HobGuid: 161 """Class representing the Guid Extension HOB as described in the UEFI 162 Platform Initialization Specification version 1.8. 163 164 Allows the production of HOBs whose types are not defined by the 165 specification by generating a GUID for the HOB entry.""" 166 167 def __init__(self, name: EfiGuid, data_format_str, data): 168 hob_length = struct_calcsize_with_endianness( 169 name.format_str 170 ) + struct_calcsize_with_endianness(data_format_str) 171 self.header = HobGenericHeader(EFI_HOB_TYPE_GUID_EXTENSION, hob_length) 172 self.name = name 173 self.data = data 174 self.data_format_str = data_format_str 175 self.format_str = ( 176 self.header.format_str + self.name.format_str + data_format_str 177 ) 178 179 def pack(self): 180 return ( 181 self.header.pack() 182 + self.name.pack() 183 + struct_pack_with_endianness(self.data_format_str, *self.data) 184 ) 185 186 def __str__(self): 187 return f"Header: {self.header}\n Name: {self.name}\n Data: {self.data}" 188 189 190class HandoffInfoTable: 191 """Class representing the Handoff Info Table HOB (also known as PHIT HOB) 192 as described in the UEFI Platform Initialization Specification version 1.8. 193 194 Must be the first HOB in the HOB list. Contains general state 195 information. 196 197 For an SP, the range `memory_bottom` to `memory_top` will be the memory 198 range for the SP starting at the load address. `free_memory_bottom` to 199 `free_memory_top` indicates space where more HOB's could be added to the 200 HOB List.""" 201 202 def __init__(self, memory_base, memory_size, free_memory_base, free_memory_size): 203 # header,uint32t,uint32t, uint64_t * 5 204 self.format_str = "II5Q" 205 hob_length = struct_calcsize_with_endianness(self.format_str) 206 self.header = HobGenericHeader(EFI_HOB_TYPE_HANDOFF, hob_length) 207 self.version = EFI_HOB_HANDOFF_TABLE_VERSION 208 self.boot_mode = STMM_BOOT_MODE 209 self.memory_top = memory_base + memory_size 210 self.memory_bottom = memory_base 211 self.free_memory_top = free_memory_base + free_memory_size 212 self.free_memory_bottom = free_memory_base + self.header.hob_length 213 self.hob_end = None 214 215 def set_hob_end_addr(self, hob_end_addr): 216 self.hob_end = hob_end_addr 217 218 def set_free_memory_bottom_addr(self, addr): 219 self.free_memory_bottom = addr 220 221 def pack(self): 222 return self.header.pack() + struct_pack_with_endianness( 223 self.format_str, 224 self.version, 225 self.boot_mode, 226 self.memory_top, 227 self.memory_bottom, 228 self.free_memory_top, 229 self.free_memory_bottom, 230 self.hob_end, 231 ) 232 233 234class FirmwareVolumeHob: 235 """Class representing the Firmware Volume HOB type as described in the 236 UEFI Platform Initialization Specification version 1.8. 237 238 For an SP this will detail where the SP binary is located. 239 """ 240 241 def __init__(self, base_address, img_offset, img_size): 242 # header, uint64_t, uint64_t 243 self.data_format_str = "2Q" 244 hob_length = struct_calcsize_with_endianness(self.data_format_str) 245 self.header = HobGenericHeader(EFI_HOB_TYPE_FV, hob_length) 246 self.format_str = self.header.format_str + self.data_format_str 247 self.base_address = base_address + img_offset 248 self.length = img_size - img_offset 249 250 def pack(self): 251 return self.header.pack() + struct_pack_with_endianness( 252 self.data_format_str, self.base_address, self.length 253 ) 254 255 256class EndOfHobListHob: 257 """Class representing the End of HOB List HOB type as described in the 258 UEFI Platform Initialization Specification version 1.8. 259 260 Must be the last entry in a HOB list. 261 """ 262 263 def __init__(self): 264 self.header = HobGenericHeader(EFI_HOB_TYPE_END_OF_HOB_LIST, 0) 265 self.format_str = "" 266 267 def pack(self): 268 return self.header.pack() 269 270 271class HobList: 272 """Class representing a HOB (Handoff Block list) based on the UEFI Platform 273 Initialization Sepcification version 1.8""" 274 275 def __init__(self, phit: HandoffInfoTable): 276 if phit is None: 277 raise Exception("HobList must be initialized with valid PHIT HOB") 278 final_hob = EndOfHobListHob() 279 phit.hob_end = phit.free_memory_bottom 280 phit.free_memory_bottom += final_hob.header.hob_length 281 self.hob_list = [phit, final_hob] 282 283 def add(self, hob): 284 if hob is not None: 285 if hob.header.hob_length > ( 286 self.get_phit().free_memory_top - self.get_phit().free_memory_bottom 287 ): 288 raise MemoryError( 289 f"Cannot add HOB of length {hob.header.hob_length}. \ 290 Resulting table size would exceed max table size of \ 291 {self.max_size}. Current table size: {self.size}." 292 ) 293 self.hob_list.insert(-1, hob) 294 self.get_phit().hob_end += hob.header.hob_length 295 self.get_phit().free_memory_bottom += hob.header.hob_length 296 297 def get_list(self): 298 return self.hob_list 299 300 def get_phit(self): 301 if self.hob_list is not None: 302 if type(self.hob_list[0]) is not HandoffInfoTable: 303 raise Exception("First hob in list must be of type PHIT") 304 return self.hob_list[0] 305 306 307def generate_mmram_desc(base_addr, page_count, granule, region_state): 308 physical_size = page_count << (PAGE_SIZE_SHIFT + (granule << 1)) 309 physical_start = base_addr 310 cpu_start = base_addr 311 312 return ("4Q", (physical_start, cpu_start, physical_size, region_state)) 313 314 315def generate_stmm_region_descriptor(base_addr, physical_size): 316 region_state = STMM_MMRAM_REGION_STATE_DEFAULT 317 physical_start = base_addr 318 cpu_start = base_addr 319 return ("4Q", (physical_start, cpu_start, physical_size, region_state)) 320 321 322def generate_ns_buffer_guid(mmram_desc): 323 return HobGuid(EfiGuid(*MM_NS_BUFFER_GUID), *mmram_desc) 324 325 326def generate_pei_mmram_memory_reserve_guid(regions): 327 # uint32t n_reserved regions, 4 bytes for padding so that array is aligned, 328 # array of mmram descriptors 329 format_str = "I4x" 330 data = [len(regions)] 331 for desc_format_str, mmram_desc in regions: 332 format_str += desc_format_str 333 data.extend(mmram_desc) 334 guid_data = (format_str, data) 335 return HobGuid(EfiGuid(*MM_PEI_MMRAM_MEMORY_RESERVE_GUID), *guid_data) 336 337 338def generate_hob_from_fdt_node(sp_fdt, hob_offset, hob_size=None): 339 """Create a HOB list binary from an SP FDT.""" 340 fv_hob = None 341 ns_buffer_hob = None 342 mmram_reserve_hob = None 343 shared_buf_hob = None 344 345 load_address = get_integer_property_value(sp_fdt, "load-address") 346 img_size = get_integer_property_value(sp_fdt, "image-size") 347 entrypoint_offset = get_integer_property_value(sp_fdt, "entrypoint-offset") 348 349 if entrypoint_offset is None: 350 entrypoint_offset = 0x0 351 if hob_offset is None: 352 hob_offset = 0x0 353 if img_size is None: 354 img_size = 0x0 355 356 regions = [] 357 358 # StMM requires the first memory region described in the 359 # MM_PEI_MMRAM_MEMORY_RESERVE_GUID describe the full partition layout. 360 regions.append(generate_stmm_region_descriptor(load_address, img_size)) 361 362 if sp_fdt.exist_node("memory-regions"): 363 if sp_fdt.exist_property("xlat-granule"): 364 granule = int(sp_fdt.get_property("xlat-granule").value) 365 else: 366 # Default granule to 4K 367 granule = 0 368 memory_regions = sp_fdt.get_node("memory-regions") 369 for node in memory_regions.nodes: 370 base_addr = get_integer_property_value(node, "base-address") 371 page_count = get_integer_property_value(node, "pages-count") 372 373 if base_addr is None: 374 offset = get_integer_property_value( 375 node, "load-address-relative-offset" 376 ) 377 if offset is None: 378 # Cannot create memory descriptor without base address, so skip 379 # node if base address cannot be defined 380 continue 381 else: 382 base_addr = load_address + offset 383 384 if node.name.strip() == "heap": 385 region_state = STMM_MMRAM_REGION_STATE_HEAP 386 else: 387 region_state = STMM_MMRAM_REGION_STATE_DEFAULT 388 389 mmram_desc = generate_mmram_desc( 390 base_addr, page_count, granule, region_state 391 ) 392 393 if node.name.strip() == "ns_comm_buffer": 394 ns_buffer_hob = generate_ns_buffer_guid(mmram_desc) 395 396 regions.append(mmram_desc) 397 398 mmram_reserve_hob = generate_pei_mmram_memory_reserve_guid(regions) 399 400 fv_hob = FirmwareVolumeHob(load_address, entrypoint_offset, img_size) 401 hob_list_base = load_address + hob_offset 402 403 # TODO assuming default of 1 page allocated for HOB List 404 if hob_size is not None: 405 max_table_size = hob_size 406 else: 407 max_table_size = 1 << PAGE_SIZE_SHIFT 408 phit = HandoffInfoTable( 409 load_address, entrypoint_offset + img_size, hob_list_base, max_table_size 410 ) 411 412 # Create a HobList containing only PHIT and EndofHobList HOBs. 413 hob_list = HobList(phit) 414 415 # Add HOBs to HOB list 416 if fv_hob is not None: 417 hob_list.add(fv_hob) 418 if ns_buffer_hob is not None: 419 hob_list.add(ns_buffer_hob) 420 if mmram_reserve_hob is not None: 421 hob_list.add(mmram_reserve_hob) 422 if shared_buf_hob is not None: 423 hob_list.add(shared_buf_hob) 424 425 return hob_list 426