blob: 37a1315f0a4b7c81b34d8497e39f6752d01207d0 [file] [log] [blame]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +01001#!/usr/bin/env python3
2
3""" tfm_builder.py:
4
5 Build wrapping class that builds a specific tfm configuration """
6
7from __future__ import print_function
8
9__copyright__ = """
10/*
11 * Copyright (c) 2018-2019, Arm Limited. All rights reserved.
12 *
13 * SPDX-License-Identifier: BSD-3-Clause
14 *
15 */
16 """
17__author__ = "Minos Galanakis"
18__email__ = "minos.galanakis@linaro.org"
19__project__ = "Trusted Firmware-M Open CI"
20__status__ = "stable"
21__version__ = "1.0"
22
23import os
24from .utils import *
25import shutil
26from .structured_task import structuredTask
27
28
29class TFM_Builder(structuredTask):
30 """ Wrap around tfm cmake system and spawn a thread to build the project.
31 """
32 _tfb_build_params = ["TARGET_PLATFORM",
33 "COMPILER",
34 "PROJ_CONFIG",
35 "CMAKE_BUILD_TYPE",
36 "WITH_MCUBOOT"
37 ]
38
39 _tfb_build_template = ("cmake -G \"Unix Makefiles\" -DPROJ_CONFIG=`"
40 "readlink -f %(PROJ_CONFIG)s.cmake` "
41 "-DTARGET_PLATFORM=%(TARGET_PLATFORM)s "
42 "-DCOMPILER=%(COMPILER)s "
43 "-DCMAKE_BUILD_TYPE=%(CMAKE_BUILD_TYPE)s "
44 "-DBL2=%(WITH_MCUBOOT)s "
45 "%(TFM_ROOT)s")
46
47 def __init__(self,
48 name, # Proccess name
49 tfm_dir, # TFM root directory
50 work_dir, # Current working directory(ie logs)
51 cfg_dict, # Input config dictionary of the following form
52 # input_dict = {"PROJ_CONFIG": "ConfigRegression",
53 # "TARGET_PLATFORM": "MUSCA_A",
54 # "COMPILER": "ARMCLANG",
55 # "CMAKE_BUILD_TYPE": "Debug"}
56 install=False, # Install library after build
57 build_threads=4, # Number of CPU thrads used in build
58 silent=False): # Silence stdout ouptut
59
60 self._tfb_cfg = cfg_dict
61 self._tfb_build_threads = build_threads
62 self._tfb_install = install
63 self._tfb_silent = silent
64 self._tfb_binaries = []
65
66 # Required by other methods, always set working directory first
67 self._tfb_work_dir = os.path.abspath(os.path.expanduser(work_dir))
68
69 self._tfb_tfm_dir = os.path.abspath(os.path.expanduser(tfm_dir))
70 # Entries will be filled after sanity test on cfg_dict dring pre_exec
71 self._tfb_build_dir = None
72 self._tfb_log_f = None
73 super(TFM_Builder, self).__init__(name=name)
74
75 def mute(self):
76 self._tfb_silent = True
77
78 def log(self):
79 """ Print and return the contents of log file """
80 with open(self._tfb_log_f, "r") as F:
81 log = F.read()
82 print(log)
83 return log
84
85 def report(self):
86 """Return the report on the job """
87 return self.unstash("Build Report")
88
89 def pre_eval(self):
90 """ Tests that need to be run in set-up state """
91
92 # Test that all required entries exist in config
93 diff = list(set(self._tfb_build_params) - set(self._tfb_cfg.keys()))
94 if diff:
95 print("Cound't find require build entry: %s in config" % diff)
96 return False
97 # TODO check validity of passed config values
98 # TODO test detection of srec
99 # self.srec_path = shutil.which("srec_cat")
100 return True
101
102 def pre_exec(self, eval_ret):
103 """ Create all required directories, files if they do not exist """
104
105 self._tfb_build_dir = os.path.join(self._tfb_work_dir,
106 self.get_name())
107 # Ensure we have a clean build directory
108 shutil.rmtree(self._tfb_build_dir, ignore_errors=True)
109
110 self._tfb_cfg["TFM_ROOT"] = self._tfb_tfm_dir
111
112 # Append the path for the config
113 self._tfb_cfg["PROJ_CONFIG"] = os.path.join(self._tfb_tfm_dir,
Minos Galanakis0a152ea2019-06-24 11:10:07 +0100114 "configs",
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100115 self._tfb_cfg[("PROJ_"
116 "CONFIG")])
117
118 # Log will be placed in work directory, named as the build dir
119 self._tfb_log_f = "%s.log" % self._tfb_build_dir
120
121 # Confirm that the work/build directory exists
122 for p in [self._tfb_work_dir, self._tfb_build_dir]:
123 if not os.path.exists(p):
124 os.makedirs(p)
125
126 # Calcuate a list of expected binaries
127 binaries = []
128
129 # If install is asserted pick the iems from the appropriate location
130 if self._tfb_install:
131
132 fvp_path = os.path.join(self._tfb_build_dir,
133 "install", "outputs", "fvp")
134 platform_path = os.path.join(self._tfb_build_dir,
135 "install",
136 "outputs",
137 self._tfb_cfg["TARGET_PLATFORM"])
138
139 # Generate a list of binaries included in both directories
140 common_bin_list = ["tfm_%s.%s" % (s, e) for s in ["s", "ns"]
141 for e in ["bin", "axf"]]
142 if self._tfb_cfg["WITH_MCUBOOT"]:
143 common_bin_list += ["mcuboot.%s" % e for e in ["bin", "axf"]]
144
145 # When building with bootloader extra binaries are expected
146 binaries += [os.path.join(platform_path, b) for b in
David Vincze55f69c22019-09-04 14:45:19 +0200147 ["tfm_sign.bin"]]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100148 binaries += [os.path.join(fvp_path, b) for b in
David Vincze55f69c22019-09-04 14:45:19 +0200149 ["tfm_s_ns_signed.bin"]]
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100150
151 binaries += [os.path.join(p, b) for p in [fvp_path, platform_path]
152 for b in common_bin_list]
153
154 # Add Musca required binaries
155 if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
156 binaries += [os.path.join(platform_path,
157 "musca_firmware.hex")]
158
159 self._tfb_binaries = binaries
160
161 else:
162 binaries += [os.path.join(self._tfb_build_dir, "app", "tfm_ns")]
Edison Aiae966b72019-08-01 10:34:28 +0800163 binaries += [os.path.join(self._tfb_build_dir, "app",
Minos Galanakisf4ca6ac2017-12-11 02:39:21 +0100164 "secure_fw", "tfm_s")]
165 if self._tfb_cfg["WITH_MCUBOOT"]:
166 binaries += [os.path.join(self._tfb_build_dir,
167 "bl2", "ext", "mcuboot", "mcuboot")]
168
169 ext = ['.bin', '.axf']
170 self._tfb_binaries = ["%s%s" % (n, e) for n in binaries
171 for e in ext]
172
173 # Add Musca required binaries
174 if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
175 self._tfb_binaries += [os.path.join(self._tfb_build_dir,
176 "tfm_sign.bin")]
177 self._tfb_binaries += [os.path.join(self._tfb_build_dir,
178 "musca_firmware.hex")]
179
180 def get_binaries(self,
181 bootl=None,
182 bin_s=None,
183 bin_ns=None,
184 bin_sign=None,
185 filt=None):
186 """ Return the absolute location of binaries (from config)
187 if they exist. Can add a filter parameter which will only
188 consider entries with /filter/ in their path as a directory """
189 ret_boot = None
190 ret_bin_ns = None
191 ret_bin_s = None
192 ret_bin_sign = None
193
194 # Apply filter as a /filter/ string to the binary list
195 filt = "/" + filt + "/" if filter else None
196 binaries = list(filter(lambda x: filt in x, self._tfb_binaries)) \
197 if filt else self._tfb_binaries
198
199 for obj_file in binaries:
200 fname = os.path.split(obj_file)[-1]
201 if bootl:
202 if fname == bootl:
203 ret_boot = obj_file
204 continue
205 if bin_s:
206 if fname == bin_s:
207 ret_bin_s = obj_file
208 continue
209
210 if bin_ns:
211 if fname == bin_ns:
212 ret_bin_ns = obj_file
213 continue
214 if bin_sign:
215 if fname == bin_sign:
216 ret_bin_sign = obj_file
217 continue
218 return [ret_boot, ret_bin_s, ret_bin_ns, ret_bin_sign]
219
220 def task_exec(self):
221 """ Main tasks """
222
223 # Mark proccess running as status
224 self.set_status(-1)
225 # Go to build directory
226 os.chdir(self._tfb_build_dir)
227 # Compile the build commands
228 cmake_cmd = self._tfb_build_template % self._tfb_cfg
229 build_cmd = "cmake --build ./ -- -j %s" % self._tfb_build_threads
230
231 # Pass the report to later stages
232 rep = {"build_cmd": "%s" % build_cmd,
233 "cmake_cmd": "%s" % cmake_cmd}
234 self.stash("Build Report", rep)
235
236 # Calll camke to configure the project
237 if not subprocess_log(cmake_cmd,
238 self._tfb_log_f,
239 prefix=cmake_cmd,
240 silent=self._tfb_silent):
241 # Build it
242 if subprocess_log(build_cmd,
243 self._tfb_log_f,
244 append=True,
245 prefix=build_cmd,
246 silent=self._tfb_silent):
247 raise Exception("Build Failed please check log: %s" %
248 self._tfb_log_f)
249 else:
250 raise Exception("Cmake Failed please check log: %s" %
251 self._tfb_log_f)
252
253 if self._tfb_install:
254 install_cmd = "cmake --build ./ -- -j install"
255 if subprocess_log(install_cmd,
256 self._tfb_log_f,
257 append=True,
258 prefix=install_cmd,
259 silent=self._tfb_silent):
260 raise Exception(("Make install Failed."
261 " please check log: %s") % self._tfb_log_f)
262 if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
263 boot_f, s_bin, ns_bin, sns_signed_bin = self.get_binaries(
264 bootl="mcuboot.bin",
265 bin_s="tfm_s.bin",
266 bin_ns="tfm_ns.bin",
267 bin_sign="tfm_sign.bin",
268 filt="MUSCA_A")
269 self.convert_to_hex(boot_f, sns_signed_bin)
270 self._t_stop()
271
272 def sign_img(self, secure_bin, non_secure_bin):
273 """Join a secure and non secure image and sign them"""
274
275 imgtool_dir = os.path.join(self._tfb_tfm_dir,
276 "bl2/ext/mcuboot/scripts/")
277 flash_layout = os.path.join(self._tfb_tfm_dir,
278 "platform/ext/target/musca_a/"
279 "partition/flash_layout.h")
280 sign_cert = os.path.join(self._tfb_tfm_dir,
281 "bl2/ext/mcuboot/root-rsa-2048.pem")
282 sns_unsigned_bin = os.path.join(self._tfb_build_dir,
283 "sns_unsigned.bin")
284 sns_signed_bin = os.path.join(self._tfb_build_dir, "sns_signed.bin")
285
286 # Early versions of the tool hard relative imports, run from its dir
287 os.chdir(imgtool_dir)
288 assemble_cmd = ("python3 assemble.py -l %(layout)s -s %(s)s "
289 "-n %(ns)s -o %(sns)s") % {"layout": flash_layout,
290 "s": secure_bin,
291 "ns": non_secure_bin,
292 "sns": sns_unsigned_bin
293 }
294 sign_cmd = ("python3 imgtool.py sign -k %(cert)s --align 1 -v "
295 "1.0 -H 0x400 --pad 0x30000 "
296 "%(sns)s %(sns_signed)s") % {"cert": sign_cert,
297 "sns": sns_unsigned_bin,
298 "sns_signed": sns_signed_bin
299 }
300 run_proccess(assemble_cmd)
301 run_proccess(sign_cmd)
302 # Return to build directory
303 os.chdir(self._tfb_build_dir)
304 return sns_signed_bin
305
306 def convert_to_hex(self,
307 boot_bin,
308 sns_signed_bin,
309 qspi_base=0x200000,
310 boot_size=0x10000):
311 """Convert a signed image to an intel hex format with mcuboot """
312 if self._tfb_install:
313 platform_path = os.path.join(self._tfb_build_dir,
314 "install",
315 "outputs",
316 self._tfb_cfg["TARGET_PLATFORM"])
317 firmware_hex = os.path.join(platform_path, "musca_firmware.hex")
318 else:
319 firmware_hex = os.path.join(self._tfb_build_dir,
320 "musca_firmware.hex")
321
322 img_offset = qspi_base + boot_size
323 merge_cmd = ("srec_cat %(boot)s -Binary -offset 0x%(qspi_offset)x "
324 "%(sns_signed)s -Binary -offset 0x%(img_offset)x "
325 "-o %(hex)s -Intel") % {"boot": boot_bin,
326 "sns_signed": sns_signed_bin,
327 "hex": firmware_hex,
328 "qspi_offset": qspi_base,
329 "img_offset": img_offset
330 }
331 run_proccess(merge_cmd)
332 return
333
334 def post_eval(self):
335 """ Verify that the artefacts exist """
336 print("%s Post eval" % self.get_name())
337
338 ret_eval = False
339 rep = self.unstash("Build Report")
340 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
341 self._tfb_binaries))
342
343 if len(missing_binaries):
344 print("ERROR: Could not locate the following binaries:")
345 print("\n".join(missing_binaries))
346
347 # Update the artifacts to not include missing ones
348 artf = [n for n in self._tfb_binaries if n not in missing_binaries]
349 # TODO update self._tfb_binaries
350 ret_eval = False
351 else:
352 print("SUCCESS: Produced binaries:")
353 print("\n".join(self._tfb_binaries))
354 ret_eval = True
355
356 artf = self._tfb_binaries
357
358 # Add artefact related information to report
359 rep["log"] = self._tfb_log_f
360 rep["missing_artefacts"] = missing_binaries
361 rep["artefacts"] = artf
362
363 rep["status"] = "Success" if ret_eval else "Failed"
364 self.stash("Build Report", rep)
365 return ret_eval
366
367 def post_exec(self, eval_ret):
368 """ """
369
370 if eval_ret:
371 print("TFM Builder %s was Successful" % self.get_name())
372 else:
373 print("TFM Builder %s was UnSuccessful" % self.get_name())