Add tools for exporting SP memory regions
Create a Python script and CMake functions for parsing the SP ELF file
and exporting the memory region information into the SP manifest file.
Signed-off-by: Imre Kis <imre.kis@arm.com>
Change-Id: I800bc2c85fa11416843b71958a796a7b5321bd2d
diff --git a/requirements.txt b/requirements.txt
index 7017f69..3e00abc 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,5 +1,5 @@
#
-# Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
+# Copyright (c) 2020-2022, Arm Limited and Contributors. All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
#
@@ -13,5 +13,8 @@
# Include packages needed for build test tool
-r tools/b-test/requirements.txt
+# Include packages needed for custom Python tools
+-r tools/python/requirements.txt
+
# Include c-picker needed for firmware-test-builder
git+https://git.trustedfirmware.org/TS/trusted-services.git@topics/c-picker
diff --git a/tools/cmake/common/ExportMemoryRegionsToManifest.cmake b/tools/cmake/common/ExportMemoryRegionsToManifest.cmake
new file mode 100644
index 0000000..affd746
--- /dev/null
+++ b/tools/cmake/common/ExportMemoryRegionsToManifest.cmake
@@ -0,0 +1,46 @@
+#-------------------------------------------------------------------------------
+# Copyright (c) 2021-2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+#-------------------------------------------------------------------------------
+
+#[===[.rst:
+.. cmake:command:: export_memory_regions_to_manifest
+
+ Exports the memory regions from an ELF format SP into a manifest file fragment.
+
+ .. code:: cmake
+
+ export_memory_regions_to_manifest(TARGET NAME RES)
+
+ INPUTS:
+
+ ``TARGET``
+ Build target
+
+ ``NAME``
+ The UUID of the SP as a string.
+
+ ``RES``
+ The name of the SP.
+
+#]===]
+function(export_memory_regions_to_manifest)
+ set(options)
+ set(oneValueArgs TARGET NAME RES)
+ set(multiValueArgs)
+ cmake_parse_arguments(MY "${options}" "${oneValueArgs}"
+ "${multiValueArgs}" ${ARGN} )
+
+ find_package(Python3 REQUIRED COMPONENTS Interpreter)
+
+ add_custom_command(
+ TARGET ${MY_TARGET} POST_BUILD
+ COMMAND ${Python3_EXECUTABLE} ${TS_ROOT}/tools/python/elf_segments_to_manifest.py
+ $<TARGET_FILE:${MY_TARGET}>
+ $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME})
+ if (MY_RES)
+ set(${MY_RES} $<TARGET_FILE_DIR:${MY_TARGET}>/${MY_NAME} PARENT_SCOPE)
+ endif()
+endfunction()
diff --git a/tools/cmake/common/ExportSp.cmake b/tools/cmake/common/ExportSp.cmake
index f324327..f22e9cb 100644
--- a/tools/cmake/common/ExportSp.cmake
+++ b/tools/cmake/common/ExportSp.cmake
@@ -10,7 +10,13 @@
.. code:: cmake
- export_sp(SP_UUID <uuid> SP_NAME <name> MK_IN <.mk path> DTS_IN <DTS path> JSON_IN <JSON path>)
+ export_sp(
+ SP_UUID <uuid> SP_NAME
+ <name> MK_IN <.mk path>
+ DTS_IN <DTS path>
+ DTS_MEM_REGIONS <Memory region manifest path>
+ JSON_IN <JSON path>
+ )
INPUTS:
@@ -26,13 +32,16 @@
``DTS_IN``
Manifest file template
+ `DTS_MEM_REGIONS`
+ Optional, Memory region manifest file
+
``JSON_IN``
Optional, SP layout JSON file template for TF-A
#]===]
function (export_sp)
set(options)
- set(oneValueArgs SP_UUID SP_NAME MK_IN DTS_IN JSON_IN)
+ set(oneValueArgs SP_UUID SP_NAME MK_IN DTS_IN DTS_MEM_REGIONS JSON_IN)
set(multiValueArgs)
cmake_parse_arguments(EXPORT "${options}" "${oneValueArgs}"
"${multiValueArgs}" ${ARGN} )
@@ -74,8 +83,12 @@
configure_file(${EXPORT_DTS_IN} ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_SP_UUID}.dts @ONLY NEWLINE_STYLE UNIX)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_SP_UUID}.dts DESTINATION ${TS_ENV}/manifest)
+ if (DEFINED EXPORT_DTS_MEM_REGIONS)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_DTS_MEM_REGIONS} DESTINATION ${TS_ENV}/manifest)
+ endif()
+
if (DEFINED EXPORT_JSON_IN)
configure_file(${EXPORT_JSON_IN} ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_SP_NAME}.json @ONLY NEWLINE_STYLE UNIX)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${EXPORT_SP_NAME}.json DESTINATION ${TS_ENV}/json)
endif()
-endfunction()
\ No newline at end of file
+endfunction()
diff --git a/tools/python/elf_segments_to_manifest.py b/tools/python/elf_segments_to_manifest.py
new file mode 100644
index 0000000..5528164
--- /dev/null
+++ b/tools/python/elf_segments_to_manifest.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: BSD-3-Clause
+#
+# Copyright (c) 2021-2022, Arm Limited. All rights reserved.
+
+"""
+This module's goal is to take an ELF file as an input and extract the memory
+regions that need to be configured on load. The memory region description is put
+into the manifest file so the SPMC set up the memory layout when loading a
+binary format SP.
+"""
+
+from enum import IntFlag
+from math import ceil
+from elftools.elf.elffile import ELFFile
+from elftools.elf.constants import P_FLAGS
+
+
+class ElfSegmentsToManifest:
+ """
+ The class loads an ELF file and builds up an internal represention of its
+ memory layout then it can write this information into a manifest file.
+ """
+ PAGE_SIZE = 4096
+
+ class Region:
+ """ Describes a memory region and its attributes. """
+
+ class ManifestMemAttr(IntFlag):
+ """ Type for describing the memory flags of a region. """
+ R = 0x01
+ W = 0x02
+ X = 0x04
+
+ def get_attr(self):
+ """ Queries the value of the attributes in manifest format. """
+ return self.value
+
+ def __str__(self):
+ return ",".join([str(f.name) for f in __class__ if f in self])
+
+ @staticmethod
+ def from_p_flags(p_flags):
+ """ Creates an instanced initialized by p_flags. """
+ instance = 0
+ instance |= __class__.R if p_flags & P_FLAGS.PF_R else 0
+ instance |= __class__.W if p_flags & P_FLAGS.PF_W else 0
+ instance |= __class__.X if p_flags & P_FLAGS.PF_X else 0
+ return instance
+
+ class LoadFlags(IntFlag):
+ """ Type for describing the memory load flags of a region. """
+ NO_BITS = 0x01
+
+ def get_flags(self):
+ """ Queries the value of the flags in manifest format. """
+ return self.value
+
+ def is_compatible(self, other):
+ """ Return true of the other flags can be merged with self. """
+ return (self & self.NO_BITS) == (other & self.NO_BITS)
+
+ def __str__(self):
+ return ",".join([str(f.name) for f in __class__ if f in self])
+
+ def __init__(self, segment, section):
+ segment = segment.segment
+ sec_h = section.header
+ self.start_address = sec_h.sh_addr
+ self.end_address = sec_h.sh_addr + sec_h.sh_size
+ self.attributes = self.ManifestMemAttr.from_p_flags(segment.header.p_flags)
+ self.load_flags = self.LoadFlags(0)
+ self.load_flags |= self.LoadFlags.NO_BITS if sec_h.sh_type == "SHT_NOBITS" else 0
+ self.sections = [section.name]
+
+ def is_compatible_region(self, region):
+ """ Checks if the other region has compatible attributes/flags. """
+ return self.load_flags.is_compatible(region.load_flags)
+
+ def append_region(self, region):
+ """ Extends the region by the other region. """
+ self.end_address = region.end_address
+ self.sections += region.sections
+
+ def write_manifest(self, load_base_addr, manifest_file):
+ """
+ Writes the region into the manifest file. The address is adjusted by load_base_address.
+ """
+ manifest_file.write(f"{self.generate_region_name(load_base_addr)} {{\n")
+ manifest_file.write(f"\t/* {self.generate_section_list()} */\n")
+ manifest_file.write(f"\t{self.serialize_offset(load_base_addr)}\n")
+ manifest_file.write(f"\t{self.serialize_pages_count()}\n")
+ manifest_file.write(f"\t{self.serialize_attributes()}\n")
+ manifest_file.write(f"\t{self.serialize_load_flags()}\n")
+ manifest_file.write("};\n")
+
+ def generate_region_name(self, load_base_addr):
+ """ Generates a name for the region using the region_[start address] pattern. """
+ return f"region_{self.start_address - load_base_addr:x}"
+
+ def generate_section_list(self):
+ """ Lists the name of member sections of the region. """
+ return ", ".join(self.sections)
+
+ def serialize_offset(self, load_base_addr):
+ """ Calculates and outputs the offset of the region in manifest format. """
+ base = self.start_address - load_base_addr
+ end = self.end_address - load_base_addr
+ high, low = (base >> 32) & 0xffffffff, base & 0xffffffff
+ return f"load-address-relative-offset = <0x{high:x} 0x{low:x}>;\t" + \
+ f"/* 0x{base:x} - 0x{end:x} */"
+
+ def serialize_pages_count(self):
+ """ Calculates and outputs the page count of the region in manifest format. """
+ region_length = self.end_address - self.start_address
+ pages_count = ceil(region_length / ElfSegmentsToManifest.PAGE_SIZE)
+ return f"pages-count = <{pages_count}>;\t/* {region_length} bytes */"
+
+ def serialize_attributes(self):
+ """ Generates the memory region attribute value in manifest format. """
+ return f"attributes = <{self.attributes.get_attr()}>;\t/* {self.attributes} */"
+
+ def serialize_load_flags(self):
+ """ Generates the memory region load flags value in manifest format. """
+ return f"load-flags = <{self.load_flags.get_flags()}>;\t/* {self.load_flags} */"
+
+ class Segment:
+ """ Stores a segment and its sections. Able to produce a region list. """
+ def __init__(self, segment, sections):
+ def is_aligned(segment):
+ return segment.header.p_align == ElfSegmentsToManifest.PAGE_SIZE
+ assert is_aligned(segment), "Segments must be 4k aligned, check LD script"
+ self.segment = segment
+ self.sections = [s for s in sections if self.segment.section_in_segment(s)]
+ self.regions = []
+ self.merge_sections_to_regions()
+
+ def get_load_address(self):
+ """ Queries the load address of the region. """
+ return self.segment.header.p_vaddr
+
+ def merge_sections_to_regions(self):
+ """ Merges consecutive sections with comptabile attributes/flags into regions. """
+ current_region = None
+ for section in self.sections:
+ region = ElfSegmentsToManifest.Region(self, section)
+ if current_region and current_region.is_compatible_region(region):
+ current_region.append_region(region)
+ else:
+ self.regions.append(region)
+ current_region = region
+
+ def write_manifest(self, load_base_addr, manifest_file):
+ """ Writes the regions into the manifest file. """
+ for region in self.regions:
+ region.write_manifest(load_base_addr, manifest_file)
+
+ def __init__(self):
+ self.segments = []
+ self.load_base_addr = None
+
+ def read_elf(self, elf_file_fp):
+ """ Reads and parses the sections and segments of the ELF file. """
+ elf_file = ELFFile(elf_file_fp)
+ segments = elf_file.iter_segments()
+
+ def is_load(segment):
+ return segment.header.p_type == "PT_LOAD"
+ self.segments = [self.Segment(s, elf_file.iter_sections()) for s in segments if is_load(s)]
+ self.load_base_addr = min([s.get_load_address() for s in self.segments])
+
+ def write_manifest(self, manifest_file):
+ """ Writes the memory regions of each segment into the manifest. """
+ for segment in self.segments:
+ segment.write_manifest(self.load_base_addr, manifest_file)
+
+
+if __name__ == "__main__":
+ import sys
+
+ ELF_SEGMENTS_TO_MANIFEST = ElfSegmentsToManifest()
+
+ with open(sys.argv[1], "rb") as fp:
+ ELF_SEGMENTS_TO_MANIFEST.read_elf(fp)
+
+ with open(sys.argv[2], "wt", encoding="ascii") as fp:
+ ELF_SEGMENTS_TO_MANIFEST.write_manifest(fp)
diff --git a/tools/python/requirements.txt b/tools/python/requirements.txt
new file mode 100644
index 0000000..81bf605
--- /dev/null
+++ b/tools/python/requirements.txt
@@ -0,0 +1,7 @@
+#
+# Copyright (c) 2022, Arm Limited and Contributors. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+#
+
+pyelftools==0.28