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