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/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)