blob: 37a1315f0a4b7c81b34d8497e39f6752d01207d0 [file] [log] [blame]
#!/usr/bin/env python3
""" tfm_builder.py:
Build wrapping class that builds a specific tfm configuration """
from __future__ import print_function
__copyright__ = """
/*
* Copyright (c) 2018-2019, Arm Limited. All rights reserved.
*
* SPDX-License-Identifier: BSD-3-Clause
*
*/
"""
__author__ = "Minos Galanakis"
__email__ = "minos.galanakis@linaro.org"
__project__ = "Trusted Firmware-M Open CI"
__status__ = "stable"
__version__ = "1.0"
import os
from .utils import *
import shutil
from .structured_task import structuredTask
class TFM_Builder(structuredTask):
""" Wrap around tfm cmake system and spawn a thread to build the project.
"""
_tfb_build_params = ["TARGET_PLATFORM",
"COMPILER",
"PROJ_CONFIG",
"CMAKE_BUILD_TYPE",
"WITH_MCUBOOT"
]
_tfb_build_template = ("cmake -G \"Unix Makefiles\" -DPROJ_CONFIG=`"
"readlink -f %(PROJ_CONFIG)s.cmake` "
"-DTARGET_PLATFORM=%(TARGET_PLATFORM)s "
"-DCOMPILER=%(COMPILER)s "
"-DCMAKE_BUILD_TYPE=%(CMAKE_BUILD_TYPE)s "
"-DBL2=%(WITH_MCUBOOT)s "
"%(TFM_ROOT)s")
def __init__(self,
name, # Proccess name
tfm_dir, # TFM root directory
work_dir, # Current working directory(ie logs)
cfg_dict, # Input config dictionary of the following form
# input_dict = {"PROJ_CONFIG": "ConfigRegression",
# "TARGET_PLATFORM": "MUSCA_A",
# "COMPILER": "ARMCLANG",
# "CMAKE_BUILD_TYPE": "Debug"}
install=False, # Install library after build
build_threads=4, # Number of CPU thrads used in build
silent=False): # Silence stdout ouptut
self._tfb_cfg = cfg_dict
self._tfb_build_threads = build_threads
self._tfb_install = install
self._tfb_silent = silent
self._tfb_binaries = []
# Required by other methods, always set working directory first
self._tfb_work_dir = os.path.abspath(os.path.expanduser(work_dir))
self._tfb_tfm_dir = os.path.abspath(os.path.expanduser(tfm_dir))
# Entries will be filled after sanity test on cfg_dict dring pre_exec
self._tfb_build_dir = None
self._tfb_log_f = None
super(TFM_Builder, self).__init__(name=name)
def mute(self):
self._tfb_silent = True
def log(self):
""" Print and return the contents of log file """
with open(self._tfb_log_f, "r") as F:
log = F.read()
print(log)
return log
def report(self):
"""Return the report on the job """
return self.unstash("Build Report")
def pre_eval(self):
""" Tests that need to be run in set-up state """
# Test that all required entries exist in config
diff = list(set(self._tfb_build_params) - set(self._tfb_cfg.keys()))
if diff:
print("Cound't find require build entry: %s in config" % diff)
return False
# TODO check validity of passed config values
# TODO test detection of srec
# self.srec_path = shutil.which("srec_cat")
return True
def pre_exec(self, eval_ret):
""" Create all required directories, files if they do not exist """
self._tfb_build_dir = os.path.join(self._tfb_work_dir,
self.get_name())
# Ensure we have a clean build directory
shutil.rmtree(self._tfb_build_dir, ignore_errors=True)
self._tfb_cfg["TFM_ROOT"] = self._tfb_tfm_dir
# Append the path for the config
self._tfb_cfg["PROJ_CONFIG"] = os.path.join(self._tfb_tfm_dir,
"configs",
self._tfb_cfg[("PROJ_"
"CONFIG")])
# Log will be placed in work directory, named as the build dir
self._tfb_log_f = "%s.log" % self._tfb_build_dir
# Confirm that the work/build directory exists
for p in [self._tfb_work_dir, self._tfb_build_dir]:
if not os.path.exists(p):
os.makedirs(p)
# Calcuate a list of expected binaries
binaries = []
# If install is asserted pick the iems from the appropriate location
if self._tfb_install:
fvp_path = os.path.join(self._tfb_build_dir,
"install", "outputs", "fvp")
platform_path = os.path.join(self._tfb_build_dir,
"install",
"outputs",
self._tfb_cfg["TARGET_PLATFORM"])
# Generate a list of binaries included in both directories
common_bin_list = ["tfm_%s.%s" % (s, e) for s in ["s", "ns"]
for e in ["bin", "axf"]]
if self._tfb_cfg["WITH_MCUBOOT"]:
common_bin_list += ["mcuboot.%s" % e for e in ["bin", "axf"]]
# When building with bootloader extra binaries are expected
binaries += [os.path.join(platform_path, b) for b in
["tfm_sign.bin"]]
binaries += [os.path.join(fvp_path, b) for b in
["tfm_s_ns_signed.bin"]]
binaries += [os.path.join(p, b) for p in [fvp_path, platform_path]
for b in common_bin_list]
# Add Musca required binaries
if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
binaries += [os.path.join(platform_path,
"musca_firmware.hex")]
self._tfb_binaries = binaries
else:
binaries += [os.path.join(self._tfb_build_dir, "app", "tfm_ns")]
binaries += [os.path.join(self._tfb_build_dir, "app",
"secure_fw", "tfm_s")]
if self._tfb_cfg["WITH_MCUBOOT"]:
binaries += [os.path.join(self._tfb_build_dir,
"bl2", "ext", "mcuboot", "mcuboot")]
ext = ['.bin', '.axf']
self._tfb_binaries = ["%s%s" % (n, e) for n in binaries
for e in ext]
# Add Musca required binaries
if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
self._tfb_binaries += [os.path.join(self._tfb_build_dir,
"tfm_sign.bin")]
self._tfb_binaries += [os.path.join(self._tfb_build_dir,
"musca_firmware.hex")]
def get_binaries(self,
bootl=None,
bin_s=None,
bin_ns=None,
bin_sign=None,
filt=None):
""" Return the absolute location of binaries (from config)
if they exist. Can add a filter parameter which will only
consider entries with /filter/ in their path as a directory """
ret_boot = None
ret_bin_ns = None
ret_bin_s = None
ret_bin_sign = None
# Apply filter as a /filter/ string to the binary list
filt = "/" + filt + "/" if filter else None
binaries = list(filter(lambda x: filt in x, self._tfb_binaries)) \
if filt else self._tfb_binaries
for obj_file in binaries:
fname = os.path.split(obj_file)[-1]
if bootl:
if fname == bootl:
ret_boot = obj_file
continue
if bin_s:
if fname == bin_s:
ret_bin_s = obj_file
continue
if bin_ns:
if fname == bin_ns:
ret_bin_ns = obj_file
continue
if bin_sign:
if fname == bin_sign:
ret_bin_sign = obj_file
continue
return [ret_boot, ret_bin_s, ret_bin_ns, ret_bin_sign]
def task_exec(self):
""" Main tasks """
# Mark proccess running as status
self.set_status(-1)
# Go to build directory
os.chdir(self._tfb_build_dir)
# Compile the build commands
cmake_cmd = self._tfb_build_template % self._tfb_cfg
build_cmd = "cmake --build ./ -- -j %s" % self._tfb_build_threads
# Pass the report to later stages
rep = {"build_cmd": "%s" % build_cmd,
"cmake_cmd": "%s" % cmake_cmd}
self.stash("Build Report", rep)
# Calll camke to configure the project
if not subprocess_log(cmake_cmd,
self._tfb_log_f,
prefix=cmake_cmd,
silent=self._tfb_silent):
# Build it
if subprocess_log(build_cmd,
self._tfb_log_f,
append=True,
prefix=build_cmd,
silent=self._tfb_silent):
raise Exception("Build Failed please check log: %s" %
self._tfb_log_f)
else:
raise Exception("Cmake Failed please check log: %s" %
self._tfb_log_f)
if self._tfb_install:
install_cmd = "cmake --build ./ -- -j install"
if subprocess_log(install_cmd,
self._tfb_log_f,
append=True,
prefix=install_cmd,
silent=self._tfb_silent):
raise Exception(("Make install Failed."
" please check log: %s") % self._tfb_log_f)
if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
boot_f, s_bin, ns_bin, sns_signed_bin = self.get_binaries(
bootl="mcuboot.bin",
bin_s="tfm_s.bin",
bin_ns="tfm_ns.bin",
bin_sign="tfm_sign.bin",
filt="MUSCA_A")
self.convert_to_hex(boot_f, sns_signed_bin)
self._t_stop()
def sign_img(self, secure_bin, non_secure_bin):
"""Join a secure and non secure image and sign them"""
imgtool_dir = os.path.join(self._tfb_tfm_dir,
"bl2/ext/mcuboot/scripts/")
flash_layout = os.path.join(self._tfb_tfm_dir,
"platform/ext/target/musca_a/"
"partition/flash_layout.h")
sign_cert = os.path.join(self._tfb_tfm_dir,
"bl2/ext/mcuboot/root-rsa-2048.pem")
sns_unsigned_bin = os.path.join(self._tfb_build_dir,
"sns_unsigned.bin")
sns_signed_bin = os.path.join(self._tfb_build_dir, "sns_signed.bin")
# Early versions of the tool hard relative imports, run from its dir
os.chdir(imgtool_dir)
assemble_cmd = ("python3 assemble.py -l %(layout)s -s %(s)s "
"-n %(ns)s -o %(sns)s") % {"layout": flash_layout,
"s": secure_bin,
"ns": non_secure_bin,
"sns": sns_unsigned_bin
}
sign_cmd = ("python3 imgtool.py sign -k %(cert)s --align 1 -v "
"1.0 -H 0x400 --pad 0x30000 "
"%(sns)s %(sns_signed)s") % {"cert": sign_cert,
"sns": sns_unsigned_bin,
"sns_signed": sns_signed_bin
}
run_proccess(assemble_cmd)
run_proccess(sign_cmd)
# Return to build directory
os.chdir(self._tfb_build_dir)
return sns_signed_bin
def convert_to_hex(self,
boot_bin,
sns_signed_bin,
qspi_base=0x200000,
boot_size=0x10000):
"""Convert a signed image to an intel hex format with mcuboot """
if self._tfb_install:
platform_path = os.path.join(self._tfb_build_dir,
"install",
"outputs",
self._tfb_cfg["TARGET_PLATFORM"])
firmware_hex = os.path.join(platform_path, "musca_firmware.hex")
else:
firmware_hex = os.path.join(self._tfb_build_dir,
"musca_firmware.hex")
img_offset = qspi_base + boot_size
merge_cmd = ("srec_cat %(boot)s -Binary -offset 0x%(qspi_offset)x "
"%(sns_signed)s -Binary -offset 0x%(img_offset)x "
"-o %(hex)s -Intel") % {"boot": boot_bin,
"sns_signed": sns_signed_bin,
"hex": firmware_hex,
"qspi_offset": qspi_base,
"img_offset": img_offset
}
run_proccess(merge_cmd)
return
def post_eval(self):
""" Verify that the artefacts exist """
print("%s Post eval" % self.get_name())
ret_eval = False
rep = self.unstash("Build Report")
missing_binaries = list(filter(lambda x: not os.path.isfile(x),
self._tfb_binaries))
if len(missing_binaries):
print("ERROR: Could not locate the following binaries:")
print("\n".join(missing_binaries))
# Update the artifacts to not include missing ones
artf = [n for n in self._tfb_binaries if n not in missing_binaries]
# TODO update self._tfb_binaries
ret_eval = False
else:
print("SUCCESS: Produced binaries:")
print("\n".join(self._tfb_binaries))
ret_eval = True
artf = self._tfb_binaries
# Add artefact related information to report
rep["log"] = self._tfb_log_f
rep["missing_artefacts"] = missing_binaries
rep["artefacts"] = artf
rep["status"] = "Success" if ret_eval else "Failed"
self.stash("Build Report", rep)
return ret_eval
def post_exec(self, eval_ret):
""" """
if eval_ret:
print("TFM Builder %s was Successful" % self.get_name())
else:
print("TFM Builder %s was UnSuccessful" % self.get_name())