feat(sptool): python version of the sptool

To cope with the changes/design decisions in the implementation of
boot protocol, from FF-A v1.1 specification in the S-EL2 SPM, we have
changed the format of the sp pkg header.
These changes need to be reflected in the sptool, used for packaging
the SP binary, and the SP's FF-A manifest. Now the SP pkg can
contain the boot information blob as defined by the FF-A specification.
To cater for these changes, bring to the TF-A project an equivalent to
the tool used in the Hafnium project.

Signed-off-by: J-Alves <joao.alves@arm.com>
Change-Id: I046f5d6e3c2ef0ba6c87f65302e127dedef34c28
diff --git a/tools/sptool/sptool.py b/tools/sptool/sptool.py
new file mode 100755
index 0000000..ae7df92
--- /dev/null
+++ b/tools/sptool/sptool.py
@@ -0,0 +1,145 @@
+#!/usr/bin/python3
+# Copyright (c) 2022, Arm Limited. All rights reserved.
+#
+# SPDX-License-Identifier: BSD-3-Clause
+
+#
+# Copyright 2022 The Hafnium Authors.
+#
+# Use of this source code is governed by a BSD-style
+# license that can be found in the LICENSE file or at
+# https://opensource.org/licenses/BSD-3-Clause.
+
+"""
+Script which generates a Secure Partition package.
+https://trustedfirmware-a.readthedocs.io/en/latest/components/secure-partition-manager.html#secure-partition-packages
+"""
+
+import argparse
+from collections import namedtuple
+import sys
+from shutil import copyfileobj
+import os
+
+HF_PAGE_SIZE = 0x1000 # bytes
+HEADER_ELEMENT_BYTES = 4 # bytes
+MANIFEST_IMAGE_SPLITTER=':'
+PM_OFFSET_DEFAULT = "0x1000"
+IMG_OFFSET_DEFAULT = "0x4000"
+
+def split_dtb_bin(i : str):
+    return i.split(MANIFEST_IMAGE_SPLITTER)
+
+def align_to_page(n):
+    return HF_PAGE_SIZE * \
+          (round(n / HF_PAGE_SIZE) + \
+           (1 if n % HF_PAGE_SIZE else 0))
+
+def to_bytes(value):
+    return int(value).to_bytes(HEADER_ELEMENT_BYTES, 'little')
+
+class SpPkg:
+    def __init__(self, pm_path : str, img_path : str, pm_offset: int,
+                 img_offset: int):
+        if not os.path.isfile(pm_path) or not os.path.isfile(img_path):
+            raise Exception(f"Parameters should be path.  \
+                              manifest: {pm_path}; img: {img_path}")
+        self.pm_path = pm_path
+        self.img_path = img_path
+        self._SpPkgHeader = namedtuple("SpPkgHeader",
+                             ("magic", "version",
+                              "pm_offset", "pm_size",
+                              "img_offset", "img_size"))
+
+        if pm_offset >= img_offset:
+            raise ValueError("pm_offset must be smaller than img_offset")
+
+        is_hfpage_aligned = lambda val : val % HF_PAGE_SIZE == 0
+        if not is_hfpage_aligned(pm_offset) or not is_hfpage_aligned(img_offset):
+           raise ValueError(f"Offsets provided need to be page aligned: pm-{pm_offset}, img-{img_offset}")
+
+        if img_offset - pm_offset < self.pm_size:
+            raise ValueError(f"pm_offset and img_offset do not fit the specified file:{pm_path})")
+
+        self.pm_offset = pm_offset
+        self.img_offset = img_offset
+
+    def __str__(self):
+        return \
+        f'''--SP package Info--
+        header:{self.header}
+        pm: {self.pm_path}
+        img: {self.img_path}
+        '''
+
+    @property
+    def magic(self):
+        return "SPKG".encode()
+
+    @property
+    def version(self):
+        return 0x2
+
+    @property
+    def pm_size(self):
+        return os.path.getsize(self.pm_path)
+
+    @property
+    def img_size(self):
+        return os.path.getsize(self.img_path)
+
+    @property
+    def header(self):
+        return self._SpPkgHeader(
+                self.magic,
+                self.version,
+                self.pm_offset,
+                self.pm_size,
+                self.img_offset,
+                self.img_size)
+
+    @property
+    def header_size(self):
+        return len(self._SpPkgHeader._fields)
+
+    def generate(self, f_out : str):
+        with open(f_out, "wb+") as output:
+            for h in self.header:
+                to_write = h if type(h) is bytes else to_bytes(h)
+                output.write(to_write)
+            output.seek(self.pm_offset)
+            with open(self.pm_path, "rb") as pm:
+                copyfileobj(pm, output)
+            output.seek(self.img_offset)
+            with open(self.img_path, "rb") as img:
+                copyfileobj(img, output)
+
+def Main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument("-i", required=True,
+                        help="path to partition's image and manifest separated by a colon.")
+    parser.add_argument("--pm-offset", required=False, default=PM_OFFSET_DEFAULT,
+                        help="set partitition manifest offset.")
+    parser.add_argument("--img-offset", required=False, default=IMG_OFFSET_DEFAULT,
+                        help="set partition image offset.")
+    parser.add_argument("-o", required=True, help="set output file path.")
+    parser.add_argument("-v", required=False, action="store_true",
+                        help="print package information.")
+    args = parser.parse_args()
+
+    if not os.path.exists(os.path.dirname(args.o)):
+        raise Exception("Provide a valid output file path!\n")
+
+    image_path, manifest_path = split_dtb_bin(args.i)
+    pm_offset = int(args.pm_offset, 0)
+    img_offset = int(args.img_offset, 0)
+    pkg = SpPkg(manifest_path, image_path, pm_offset, img_offset)
+    pkg.generate(args.o)
+
+    if args.v is True:
+        print(pkg)
+
+    return 0
+
+if __name__ == "__main__":
+    sys.exit(Main())