Merge changes from topic "kc/stmm" into integration

* changes:
  fix(build): run sp_mk_gen.py with poetry
  feat(sptool): add StMM memory region descriptor
  feat(sptool): specify endianness for HOB bin
  feat(fvp): increase cactus-tertiary size
  feat(sptool): include HOB file in the TL pkg
  feat(sptool): invoke the HOB list creation code
  feat(sptool): add the HOB list creation script
  chore: add fdt dependencies to poetry
This commit is contained in:
Madhukar Pappireddy 2025-03-05 20:46:36 +01:00 committed by TrustedFirmware Code Review
commit ee5915e2ff
6 changed files with 478 additions and 9 deletions

View file

@ -1658,7 +1658,8 @@ endif #(NEED_FDT)
# Add Secure Partition packages
ifeq (${NEED_SP_PKG},yes)
$(BUILD_PLAT)/sp_gen.mk: ${SP_MK_GEN} ${SP_LAYOUT_FILE} | $$(@D)/
$(q)${PYTHON} "$<" "$@" $(filter-out $<,$^) $(BUILD_PLAT) ${COT} ${SP_DTS_LIST_FRAGMENT}
$(if $(host-poetry),$(q)poetry -q install)
$(q)$(if $(host-poetry),poetry run )${PYTHON} "$<" "$@" $(filter-out $<,$^) $(BUILD_PLAT) ${COT} ${SP_DTS_LIST_FRAGMENT}
sp: $(DTBS) $(BUILD_PLAT)/sp_gen.mk $(SP_PKGS)
$(s)echo
$(s)echo "Built SP Images successfully"

View file

@ -34,7 +34,7 @@
debug_name = "cactus-primary";
load_address = <0x7000000>;
vcpu_count = <8>;
mem_size = <1048576>;
mem_size = <0x100000>;
/*
* Platform specific SiP SMC call handled at EL3. Used
* to pend an interrupt for testing purpose.
@ -46,21 +46,21 @@
debug_name = "cactus-secondary";
load_address = <0x7100000>;
vcpu_count = <8>;
mem_size = <1048576>;
mem_size = <0x100000>;
};
vm3 {
is_ffa_partition;
debug_name = "cactus-tertiary";
load_address = <0x7200000>;
vcpu_count = <1>;
mem_size = <1048576>;
mem_size = <0x300000>;
};
vm4 {
is_ffa_partition;
debug_name = "ivy";
load_address = <0x7600000>;
vcpu_count = <1>;
mem_size = <1048576>;
mem_size = <0x100000>;
};
};

15
poetry.lock generated
View file

@ -280,6 +280,17 @@ files = [
{file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"},
]
[[package]]
name = "fdt"
version = "0.3.3"
description = "Flattened Device Tree Python Module"
optional = false
python-versions = ">=3.5"
files = [
{file = "fdt-0.3.3-py3-none-any.whl", hash = "sha256:6b2fae2e8dfa38e9b0f9666aa001dd25be74e893d293a8d60001438f732e9e47"},
{file = "fdt-0.3.3.tar.gz", hash = "sha256:81a215930fef2ab8894913c4f474105bb53e14f07129fe07cb6eff2d5fdf26d2"},
]
[[package]]
name = "filelock"
version = "3.16.0"
@ -540,7 +551,7 @@ files = [
[[package]]
name = "memory"
version = "0.1.0"
description = "A tool for analysis of "
description = "A tool for analysis of static memory consumption by TF-A images"
optional = false
python-versions = "^3.8.0"
files = []
@ -1331,4 +1342,4 @@ type = ["pytest-mypy"]
[metadata]
lock-version = "2.0"
python-versions = "^3.8"
content-hash = "7574eee0a05db1d9631bb29288abfc806810906a66e087a5a32e6e3920eb4bba"
content-hash = "c78729d7072714d77b4a69d6aabccab35dcf0548f08aa440ff178bc7bf2824be"

View file

@ -25,3 +25,4 @@ sphinxcontrib-svg2pdfconverter = "^1.2.2"
[tool.poetry.group.ci.dependencies]
click = "^8.1.3"
fdt = "^0.3.0"

425
tools/sptool/hob.py Normal file
View file

@ -0,0 +1,425 @@
#!/usr/bin/python3
# Copyright (c) 2025, Arm Limited. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
import struct
EFI_HOB_HANDOFF_TABLE_VERSION = 0x000A
PAGE_SIZE_SHIFT = 12 # TODO assuming 4K page size
# HobType values of EFI_HOB_GENERIC_HEADER.
EFI_HOB_TYPE_HANDOFF = 0x0001
EFI_HOB_TYPE_MEMORY_ALLOCATION = 0x0002
EFI_HOB_TYPE_RESOURCE_DESCRIPTOR = 0x0003
EFI_HOB_TYPE_GUID_EXTENSION = 0x0004
EFI_HOB_TYPE_FV = 0x0005
EFI_HOB_TYPE_CPU = 0x0006
EFI_HOB_TYPE_MEMORY_POOL = 0x0007
EFI_HOB_TYPE_FV2 = 0x0009
EFI_HOB_TYPE_LOAD_PEIM_UNUSED = 0x000A
EFI_HOB_TYPE_UEFI_CAPSULE = 0x000B
EFI_HOB_TYPE_FV3 = 0x000C
EFI_HOB_TYPE_UNUSED = 0xFFFE
EFI_HOB_TYPE_END_OF_HOB_LIST = 0xFFFF
# GUID values
"""struct efi_guid {
uint32_t time_low;
uint16_t time_mid;
uint16_t time_hi_and_version;
uint8_t clock_seq_and_node[8];
}"""
MM_PEI_MMRAM_MEMORY_RESERVE_GUID = (
0x0703F912,
0xBF8D,
0x4E2A,
(0xBE, 0x07, 0xAB, 0x27, 0x25, 0x25, 0xC5, 0x92),
)
MM_NS_BUFFER_GUID = (
0xF00497E3,
0xBFA2,
0x41A1,
(0x9D, 0x29, 0x54, 0xC2, 0xE9, 0x37, 0x21, 0xC5),
)
# MMRAM states and capabilities
# See UEFI Platform Initialization Specification Version 1.8, IV-5.3.5
EFI_MMRAM_OPEN = 0x00000001
EFI_MMRAM_CLOSED = 0x00000002
EFI_MMRAM_LOCKED = 0x00000004
EFI_CACHEABLE = 0x00000008
EFI_ALLOCATED = 0x00000010
EFI_NEEDS_TESTING = 0x00000020
EFI_NEEDS_ECC_INITIALIZATION = 0x00000040
EFI_SMRAM_OPEN = EFI_MMRAM_OPEN
EFI_SMRAM_CLOSED = EFI_MMRAM_CLOSED
EFI_SMRAM_LOCKED = EFI_MMRAM_LOCKED
# EFI boot mode.
EFI_BOOT_WITH_FULL_CONFIGURATION = 0x00
EFI_BOOT_WITH_MINIMAL_CONFIGURATION = 0x01
EFI_BOOT_ASSUMING_NO_CONFIGURATION_CHANGES = 0x02
EFI_BOOT_WITH_FULL_CONFIGURATION_PLUS_DIAGNOSTICS = 0x03
EFI_BOOT_WITH_DEFAULT_SETTINGS = 0x04
EFI_BOOT_ON_S4_RESUME = 0x05
EFI_BOOT_ON_S5_RESUME = 0x06
EFI_BOOT_WITH_MFG_MODE_SETTINGS = 0x07
EFI_BOOT_ON_S2_RESUME = 0x10
EFI_BOOT_ON_S3_RESUME = 0x11
EFI_BOOT_ON_FLASH_UPDATE = 0x12
EFI_BOOT_IN_RECOVERY_MODE = 0x20
STMM_BOOT_MODE = EFI_BOOT_WITH_FULL_CONFIGURATION
STMM_MMRAM_REGION_STATE_DEFAULT = EFI_CACHEABLE | EFI_ALLOCATED
STMM_MMRAM_REGION_STATE_HEAP = EFI_CACHEABLE
"""`struct` python module allows user to specify endianness.
We are expecting FVP or STMM platform as target and that they will be
little-endian. See `struct` python module documentation if other endianness is
needed."""
ENDIANNESS = "<"
def struct_pack_with_endianness(format_str, *args):
return struct.pack((ENDIANNESS + format_str), *args)
def struct_calcsize_with_endianness(format_str):
return struct.calcsize(ENDIANNESS + format_str)
# Helper for fdt node property parsing
def get_integer_property_value(fdt_node, name):
if fdt_node.exist_property(name):
p = fdt_node.get_property(name)
# <u32> Device Tree value
if len(p) == 1:
return p.value
# <u64> Device Tree value represented as two 32-bit values
if len(p) == 2:
msb = p[0]
lsb = p[1]
return lsb | (msb << 32)
return None
class EfiGuid:
"""Class representing EFI GUID (Globally Unique Identifier) as described by
the UEFI Specification v2.10"""
def __init__(self, time_low, time_mid, time_hi_and_version, clock_seq_and_node):
self.time_low = time_low
self.time_mid = time_mid
self.time_hi_and_version = time_hi_and_version
self.clock_seq_and_node = clock_seq_and_node
self.format_str = "IHH8B"
def pack(self):
return struct_pack_with_endianness(
self.format_str,
self.time_low,
self.time_mid,
self.time_hi_and_version,
*self.clock_seq_and_node,
)
def __str__(self):
return f"{hex(self.time_low)}, {hex(self.time_mid)}, \
{hex(self.time_hi_and_version)}, {[hex(i) for i in self.clock_seq_and_node]}"
class HobGenericHeader:
"""Class representing the Hob Generic Header data type as described
in the UEFI Platform Initialization Specification version 1.8.
Each HOB is required to contain this header specifying the type and length
of the HOB.
"""
def __init__(self, hob_type, hob_length):
self.format_str = "HHI"
self.hob_type = hob_type
self.hob_length = struct_calcsize_with_endianness(self.format_str) + hob_length
self.reserved = 0
def pack(self):
return struct_pack_with_endianness(
self.format_str, self.hob_type, self.hob_length, self.reserved
)
def __str__(self):
return f"Hob Type: {self.hob_type} Hob Length: {self.hob_length}"
class HobGuid:
"""Class representing the Guid Extension HOB as described in the UEFI
Platform Initialization Specification version 1.8.
Allows the production of HOBs whose types are not defined by the
specification by generating a GUID for the HOB entry."""
def __init__(self, name: EfiGuid, data_format_str, data):
hob_length = struct_calcsize_with_endianness(
name.format_str
) + struct_calcsize_with_endianness(data_format_str)
self.header = HobGenericHeader(EFI_HOB_TYPE_GUID_EXTENSION, hob_length)
self.name = name
self.data = data
self.data_format_str = data_format_str
self.format_str = (
self.header.format_str + self.name.format_str + data_format_str
)
def pack(self):
return (
self.header.pack()
+ self.name.pack()
+ struct_pack_with_endianness(self.data_format_str, *self.data)
)
def __str__(self):
return f"Header: {self.header}\n Name: {self.name}\n Data: {self.data}"
class HandoffInfoTable:
"""Class representing the Handoff Info Table HOB (also known as PHIT HOB)
as described in the UEFI Platform Initialization Specification version 1.8.
Must be the first HOB in the HOB list. Contains general state
information.
For an SP, the range `memory_bottom` to `memory_top` will be the memory
range for the SP starting at the load address. `free_memory_bottom` to
`free_memory_top` indicates space where more HOB's could be added to the
HOB List."""
def __init__(self, memory_base, memory_size, free_memory_base, free_memory_size):
# header,uint32t,uint32t, uint64_t * 5
self.format_str = "II5Q"
hob_length = struct_calcsize_with_endianness(self.format_str)
self.header = HobGenericHeader(EFI_HOB_TYPE_HANDOFF, hob_length)
self.version = EFI_HOB_HANDOFF_TABLE_VERSION
self.boot_mode = STMM_BOOT_MODE
self.memory_top = memory_base + memory_size
self.memory_bottom = memory_base
self.free_memory_top = free_memory_base + free_memory_size
self.free_memory_bottom = free_memory_base + self.header.hob_length
self.hob_end = None
def set_hob_end_addr(self, hob_end_addr):
self.hob_end = hob_end_addr
def set_free_memory_bottom_addr(self, addr):
self.free_memory_bottom = addr
def pack(self):
return self.header.pack() + struct_pack_with_endianness(
self.format_str,
self.version,
self.boot_mode,
self.memory_top,
self.memory_bottom,
self.free_memory_top,
self.free_memory_bottom,
self.hob_end,
)
class FirmwareVolumeHob:
"""Class representing the Firmware Volume HOB type as described in the
UEFI Platform Initialization Specification version 1.8.
For an SP this will detail where the SP binary is located.
"""
def __init__(self, base_address, img_offset, img_size):
# header, uint64_t, uint64_t
self.data_format_str = "2Q"
hob_length = struct_calcsize_with_endianness(self.data_format_str)
self.header = HobGenericHeader(EFI_HOB_TYPE_FV, hob_length)
self.format_str = self.header.format_str + self.data_format_str
self.base_address = base_address + img_offset
self.length = img_size - img_offset
def pack(self):
return self.header.pack() + struct_pack_with_endianness(
self.data_format_str, self.base_address, self.length
)
class EndOfHobListHob:
"""Class representing the End of HOB List HOB type as described in the
UEFI Platform Initialization Specification version 1.8.
Must be the last entry in a HOB list.
"""
def __init__(self):
self.header = HobGenericHeader(EFI_HOB_TYPE_END_OF_HOB_LIST, 0)
self.format_str = ""
def pack(self):
return self.header.pack()
class HobList:
"""Class representing a HOB (Handoff Block list) based on the UEFI Platform
Initialization Sepcification version 1.8"""
def __init__(self, phit: HandoffInfoTable):
if phit is None:
raise Exception("HobList must be initialized with valid PHIT HOB")
final_hob = EndOfHobListHob()
phit.hob_end = phit.free_memory_bottom
phit.free_memory_bottom += final_hob.header.hob_length
self.hob_list = [phit, final_hob]
def add(self, hob):
if hob is not None:
if hob.header.hob_length > (
self.get_phit().free_memory_top - self.get_phit().free_memory_bottom
):
raise MemoryError(
f"Cannot add HOB of length {hob.header.hob_length}. \
Resulting table size would exceed max table size of \
{self.max_size}. Current table size: {self.size}."
)
self.hob_list.insert(-1, hob)
self.get_phit().hob_end += hob.header.hob_length
self.get_phit().free_memory_bottom += hob.header.hob_length
def get_list(self):
return self.hob_list
def get_phit(self):
if self.hob_list is not None:
if type(self.hob_list[0]) is not HandoffInfoTable:
raise Exception("First hob in list must be of type PHIT")
return self.hob_list[0]
def generate_mmram_desc(base_addr, page_count, granule, region_state):
physical_size = page_count << (PAGE_SIZE_SHIFT + (granule << 1))
physical_start = base_addr
cpu_start = base_addr
return ("4Q", (physical_start, cpu_start, physical_size, region_state))
def generate_stmm_region_descriptor(base_addr, physical_size):
region_state = STMM_MMRAM_REGION_STATE_DEFAULT
physical_start = base_addr
cpu_start = base_addr
return ("4Q", (physical_start, cpu_start, physical_size, region_state))
def generate_ns_buffer_guid(mmram_desc):
return HobGuid(EfiGuid(*MM_NS_BUFFER_GUID), *mmram_desc)
def generate_pei_mmram_memory_reserve_guid(regions):
# uint32t n_reserved regions, 4 bytes for padding so that array is aligned,
# array of mmram descriptors
format_str = "I4x"
data = [len(regions)]
for desc_format_str, mmram_desc in regions:
format_str += desc_format_str
data.extend(mmram_desc)
guid_data = (format_str, data)
return HobGuid(EfiGuid(*MM_PEI_MMRAM_MEMORY_RESERVE_GUID), *guid_data)
def generate_hob_from_fdt_node(sp_fdt, hob_offset, hob_size=None):
"""Create a HOB list binary from an SP FDT."""
fv_hob = None
ns_buffer_hob = None
mmram_reserve_hob = None
shared_buf_hob = None
load_address = get_integer_property_value(sp_fdt, "load-address")
img_size = get_integer_property_value(sp_fdt, "image-size")
entrypoint_offset = get_integer_property_value(sp_fdt, "entrypoint-offset")
if entrypoint_offset is None:
entrypoint_offset = 0x0
if hob_offset is None:
hob_offset = 0x0
if img_size is None:
img_size = 0x0
regions = []
# StMM requires the first memory region described in the
# MM_PEI_MMRAM_MEMORY_RESERVE_GUID describe the full partition layout.
regions.append(generate_stmm_region_descriptor(load_address, img_size))
if sp_fdt.exist_node("memory-regions"):
if sp_fdt.exist_property("xlat-granule"):
granule = int(sp_fdt.get_property("xlat-granule").value)
else:
# Default granule to 4K
granule = 0
memory_regions = sp_fdt.get_node("memory-regions")
for node in memory_regions.nodes:
base_addr = get_integer_property_value(node, "base-address")
page_count = get_integer_property_value(node, "pages-count")
if base_addr is None:
offset = get_integer_property_value(
node, "load-address-relative-offset"
)
if offset is None:
# Cannot create memory descriptor without base address, so skip
# node if base address cannot be defined
continue
else:
base_addr = load_address + offset
if node.name.strip() == "heap":
region_state = STMM_MMRAM_REGION_STATE_HEAP
else:
region_state = STMM_MMRAM_REGION_STATE_DEFAULT
mmram_desc = generate_mmram_desc(
base_addr, page_count, granule, region_state
)
if node.name.strip() == "ns_comm_buffer":
ns_buffer_hob = generate_ns_buffer_guid(mmram_desc)
regions.append(mmram_desc)
mmram_reserve_hob = generate_pei_mmram_memory_reserve_guid(regions)
fv_hob = FirmwareVolumeHob(load_address, entrypoint_offset, img_size)
hob_list_base = load_address + hob_offset
# TODO assuming default of 1 page allocated for HOB List
if hob_size is not None:
max_table_size = hob_size
else:
max_table_size = 1 << PAGE_SIZE_SHIFT
phit = HandoffInfoTable(
load_address, entrypoint_offset + img_size, hob_list_base, max_table_size
)
# Create a HobList containing only PHIT and EndofHobList HOBs.
hob_list = HobList(phit)
# Add HOBs to HOB list
if fv_hob is not None:
hob_list.add(fv_hob)
if ns_buffer_hob is not None:
hob_list.add(ns_buffer_hob)
if mmram_reserve_hob is not None:
hob_list.add(mmram_reserve_hob)
if shared_buf_hob is not None:
hob_list.add(shared_buf_hob)
return hob_list

View file

@ -55,10 +55,15 @@ import os
import re
import sys
import uuid
import fdt
from spactions import SpSetupActions
import hob
import struct
from hob import HobList
MAX_SP = 8
UUID_LEN = 4
HOB_OFFSET_DEFAULT=0x2000
# Some helper functions to access args propagated to the action functions in
# SpSetupActions framework.
@ -179,6 +184,28 @@ def gen_fdt_sources(sp_layout, sp, args :dict):
write_to_sp_mk_gen(f"FDT_SOURCES += {manifest_path}", args)
return args
@SpSetupActions.sp_action(exec_order=1)
def generate_hob_list(sp_layout, sp, args: dict):
'''
Generates a HOB file for the partition, if it requested it in its FF-A
manifest.
'''
with open(get_sp_manifest_full_path(sp_layout[sp], args), "r") as f:
sp_fdt = fdt.parse_dts(f.read())
if sp_fdt.exist_property('hob_list', '/boot-info'):
sp_hob_name = os.path.basename(sp + ".hob.bin")
sp_hob_name = os.path.join(args["out_dir"], f"{sp_hob_name}")
# Add to the args so it can be consumed by the TL pkg function.
sp_layout[sp]["hob_path"] = sp_hob_name
hob_list = hob.generate_hob_from_fdt_node(sp_fdt, HOB_OFFSET_DEFAULT)
with open(sp_hob_name, "wb") as h:
for block in hob_list.get_list():
h.write(block.pack())
return args
def generate_sp_pkg(sp_node, pkg, sp_img, sp_dtb):
''' Generates the rule in case SP is to be generated in an SP Pkg. '''
pm_offset = get_pm_offset(sp_node)
@ -200,11 +227,12 @@ def generate_tl_pkg(sp_node, pkg, sp_img, sp_dtb, hob_path = None):
TE_SP_BINARY = 0x103
# TE Type for the HOB List.
TE_HOB_LIST = 0x3
tlc_add_hob = f"\t$(Q)poetry run tlc add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else ""
tlc_add_hob = f"\t$(Q)$(TLCTOOL) add --entry {TE_HOB_LIST} {hob_path} {pkg}" if hob_path is not None else ""
return f'''
{pkg}: {sp_dtb} {sp_img}
\t$(Q)echo Generating {pkg}
\t$(Q)$(TLCTOOL) create --size {get_size(sp_node)} --entry {TE_FFA_MANIFEST} {sp_dtb} {pkg} --align 12
{tlc_add_hob}
\t$(Q)$(TLCTOOL) add --entry {TE_SP_BINARY} {sp_img} {pkg}
'''
@ -228,7 +256,10 @@ def gen_partition_pkg(sp_layout, sp, args :dict):
if package_type == "sp_pkg":
partition_pkg_rule = generate_sp_pkg(sp_layout[sp], pkg, sp_img, sp_dtb)
elif package_type == "tl_pkg":
partition_pkg_rule = generate_tl_pkg(sp_layout[sp], pkg, sp_img, sp_dtb)
# Conditionally provide the Hob.
hob_path = sp_layout[sp]["hob_path"] if "hob_path" in sp_layout[sp] else None
partition_pkg_rule = generate_tl_pkg(
sp_layout[sp], pkg, sp_img, sp_dtb, hob_path)
else:
raise ValueError(f"Specified invalid pkg type {package_type}")