xref: /rk3399_ARM-atf/tools/sptool/hob.py (revision cc594af66e05b5f863b00dfab939f53e558d9c23)
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