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_ns_buffer_guid(mmram_desc): 316 return HobGuid(EfiGuid(*MM_NS_BUFFER_GUID), *mmram_desc) 317 318 319def generate_pei_mmram_memory_reserve_guid(regions): 320 # uint32t n_reserved regions, 4 bytes for padding so that array is aligned, 321 # array of mmram descriptors 322 format_str = "I4x" 323 data = [len(regions)] 324 for desc_format_str, mmram_desc in regions: 325 format_str += desc_format_str 326 data.extend(mmram_desc) 327 guid_data = (format_str, data) 328 return HobGuid(EfiGuid(*MM_PEI_MMRAM_MEMORY_RESERVE_GUID), *guid_data) 329 330 331def generate_hob_from_fdt_node(sp_fdt, hob_offset, hob_size=None): 332 """Create a HOB list binary from an SP FDT.""" 333 fv_hob = None 334 ns_buffer_hob = None 335 mmram_reserve_hob = None 336 shared_buf_hob = None 337 338 load_address = get_integer_property_value(sp_fdt, "load-address") 339 img_size = get_integer_property_value(sp_fdt, "image-size") 340 entrypoint_offset = get_integer_property_value(sp_fdt, "entrypoint-offset") 341 342 if entrypoint_offset is None: 343 entrypoint_offset = 0x0 344 if hob_offset is None: 345 hob_offset = 0x0 346 if img_size is None: 347 img_size = 0x0 348 349 if sp_fdt.exist_node("memory-regions"): 350 if sp_fdt.exist_property("xlat-granule"): 351 granule = int(sp_fdt.get_property("xlat-granule").value) 352 else: 353 # Default granule to 4K 354 granule = 0 355 memory_regions = sp_fdt.get_node("memory-regions") 356 regions = [] 357 for node in memory_regions.nodes: 358 base_addr = get_integer_property_value(node, "base-address") 359 page_count = get_integer_property_value(node, "pages-count") 360 361 if base_addr is None: 362 offset = get_integer_property_value( 363 node, "load-address-relative-offset" 364 ) 365 if offset is None: 366 # Cannot create memory descriptor without base address, so skip 367 # node if base address cannot be defined 368 continue 369 else: 370 base_addr = load_address + offset 371 372 if node.name.strip() == "heap": 373 region_state = STMM_MMRAM_REGION_STATE_HEAP 374 else: 375 region_state = STMM_MMRAM_REGION_STATE_DEFAULT 376 377 mmram_desc = generate_mmram_desc( 378 base_addr, page_count, granule, region_state 379 ) 380 381 if node.name.strip() == "ns_comm_buffer": 382 ns_buffer_hob = generate_ns_buffer_guid(mmram_desc) 383 384 regions.append(mmram_desc) 385 386 mmram_reserve_hob = generate_pei_mmram_memory_reserve_guid(regions) 387 388 fv_hob = FirmwareVolumeHob(load_address, entrypoint_offset, img_size) 389 hob_list_base = load_address + hob_offset 390 391 # TODO assuming default of 1 page allocated for HOB List 392 if hob_size is not None: 393 max_table_size = hob_size 394 else: 395 max_table_size = 1 << PAGE_SIZE_SHIFT 396 phit = HandoffInfoTable( 397 load_address, entrypoint_offset + img_size, hob_list_base, max_table_size 398 ) 399 400 # Create a HobList containing only PHIT and EndofHobList HOBs. 401 hob_list = HobList(phit) 402 403 # Add HOBs to HOB list 404 if fv_hob is not None: 405 hob_list.add(fv_hob) 406 if ns_buffer_hob is not None: 407 hob_list.add(ns_buffer_hob) 408 if mmram_reserve_hob is not None: 409 hob_list.add(mmram_reserve_hob) 410 if shared_buf_hob is not None: 411 hob_list.add(shared_buf_hob) 412 413 return hob_list 414