blob: d34736f7eebad69ca640da7455765696f7db4624 [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")]
163 if "ConfigCoreTest" in self._tfb_build_dir:
164 binaries += [os.path.join(self._tfb_build_dir,
165 "unit_test", "tfm_s")]
166 else:
167 binaries += [os.path.join(self._tfb_build_dir, "app",
168 "secure_fw", "tfm_s")]
169 if self._tfb_cfg["WITH_MCUBOOT"]:
170 binaries += [os.path.join(self._tfb_build_dir,
171 "bl2", "ext", "mcuboot", "mcuboot")]
172
173 ext = ['.bin', '.axf']
174 self._tfb_binaries = ["%s%s" % (n, e) for n in binaries
175 for e in ext]
176
177 # Add Musca required binaries
178 if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
179 self._tfb_binaries += [os.path.join(self._tfb_build_dir,
180 "tfm_sign.bin")]
181 self._tfb_binaries += [os.path.join(self._tfb_build_dir,
182 "musca_firmware.hex")]
183
184 def get_binaries(self,
185 bootl=None,
186 bin_s=None,
187 bin_ns=None,
188 bin_sign=None,
189 filt=None):
190 """ Return the absolute location of binaries (from config)
191 if they exist. Can add a filter parameter which will only
192 consider entries with /filter/ in their path as a directory """
193 ret_boot = None
194 ret_bin_ns = None
195 ret_bin_s = None
196 ret_bin_sign = None
197
198 # Apply filter as a /filter/ string to the binary list
199 filt = "/" + filt + "/" if filter else None
200 binaries = list(filter(lambda x: filt in x, self._tfb_binaries)) \
201 if filt else self._tfb_binaries
202
203 for obj_file in binaries:
204 fname = os.path.split(obj_file)[-1]
205 if bootl:
206 if fname == bootl:
207 ret_boot = obj_file
208 continue
209 if bin_s:
210 if fname == bin_s:
211 ret_bin_s = obj_file
212 continue
213
214 if bin_ns:
215 if fname == bin_ns:
216 ret_bin_ns = obj_file
217 continue
218 if bin_sign:
219 if fname == bin_sign:
220 ret_bin_sign = obj_file
221 continue
222 return [ret_boot, ret_bin_s, ret_bin_ns, ret_bin_sign]
223
224 def task_exec(self):
225 """ Main tasks """
226
227 # Mark proccess running as status
228 self.set_status(-1)
229 # Go to build directory
230 os.chdir(self._tfb_build_dir)
231 # Compile the build commands
232 cmake_cmd = self._tfb_build_template % self._tfb_cfg
233 build_cmd = "cmake --build ./ -- -j %s" % self._tfb_build_threads
234
235 # Pass the report to later stages
236 rep = {"build_cmd": "%s" % build_cmd,
237 "cmake_cmd": "%s" % cmake_cmd}
238 self.stash("Build Report", rep)
239
240 # Calll camke to configure the project
241 if not subprocess_log(cmake_cmd,
242 self._tfb_log_f,
243 prefix=cmake_cmd,
244 silent=self._tfb_silent):
245 # Build it
246 if subprocess_log(build_cmd,
247 self._tfb_log_f,
248 append=True,
249 prefix=build_cmd,
250 silent=self._tfb_silent):
251 raise Exception("Build Failed please check log: %s" %
252 self._tfb_log_f)
253 else:
254 raise Exception("Cmake Failed please check log: %s" %
255 self._tfb_log_f)
256
257 if self._tfb_install:
258 install_cmd = "cmake --build ./ -- -j install"
259 if subprocess_log(install_cmd,
260 self._tfb_log_f,
261 append=True,
262 prefix=install_cmd,
263 silent=self._tfb_silent):
264 raise Exception(("Make install Failed."
265 " please check log: %s") % self._tfb_log_f)
266 if self._tfb_cfg["TARGET_PLATFORM"] == "MUSCA_A":
267 boot_f, s_bin, ns_bin, sns_signed_bin = self.get_binaries(
268 bootl="mcuboot.bin",
269 bin_s="tfm_s.bin",
270 bin_ns="tfm_ns.bin",
271 bin_sign="tfm_sign.bin",
272 filt="MUSCA_A")
273 self.convert_to_hex(boot_f, sns_signed_bin)
274 self._t_stop()
275
276 def sign_img(self, secure_bin, non_secure_bin):
277 """Join a secure and non secure image and sign them"""
278
279 imgtool_dir = os.path.join(self._tfb_tfm_dir,
280 "bl2/ext/mcuboot/scripts/")
281 flash_layout = os.path.join(self._tfb_tfm_dir,
282 "platform/ext/target/musca_a/"
283 "partition/flash_layout.h")
284 sign_cert = os.path.join(self._tfb_tfm_dir,
285 "bl2/ext/mcuboot/root-rsa-2048.pem")
286 sns_unsigned_bin = os.path.join(self._tfb_build_dir,
287 "sns_unsigned.bin")
288 sns_signed_bin = os.path.join(self._tfb_build_dir, "sns_signed.bin")
289
290 # Early versions of the tool hard relative imports, run from its dir
291 os.chdir(imgtool_dir)
292 assemble_cmd = ("python3 assemble.py -l %(layout)s -s %(s)s "
293 "-n %(ns)s -o %(sns)s") % {"layout": flash_layout,
294 "s": secure_bin,
295 "ns": non_secure_bin,
296 "sns": sns_unsigned_bin
297 }
298 sign_cmd = ("python3 imgtool.py sign -k %(cert)s --align 1 -v "
299 "1.0 -H 0x400 --pad 0x30000 "
300 "%(sns)s %(sns_signed)s") % {"cert": sign_cert,
301 "sns": sns_unsigned_bin,
302 "sns_signed": sns_signed_bin
303 }
304 run_proccess(assemble_cmd)
305 run_proccess(sign_cmd)
306 # Return to build directory
307 os.chdir(self._tfb_build_dir)
308 return sns_signed_bin
309
310 def convert_to_hex(self,
311 boot_bin,
312 sns_signed_bin,
313 qspi_base=0x200000,
314 boot_size=0x10000):
315 """Convert a signed image to an intel hex format with mcuboot """
316 if self._tfb_install:
317 platform_path = os.path.join(self._tfb_build_dir,
318 "install",
319 "outputs",
320 self._tfb_cfg["TARGET_PLATFORM"])
321 firmware_hex = os.path.join(platform_path, "musca_firmware.hex")
322 else:
323 firmware_hex = os.path.join(self._tfb_build_dir,
324 "musca_firmware.hex")
325
326 img_offset = qspi_base + boot_size
327 merge_cmd = ("srec_cat %(boot)s -Binary -offset 0x%(qspi_offset)x "
328 "%(sns_signed)s -Binary -offset 0x%(img_offset)x "
329 "-o %(hex)s -Intel") % {"boot": boot_bin,
330 "sns_signed": sns_signed_bin,
331 "hex": firmware_hex,
332 "qspi_offset": qspi_base,
333 "img_offset": img_offset
334 }
335 run_proccess(merge_cmd)
336 return
337
338 def post_eval(self):
339 """ Verify that the artefacts exist """
340 print("%s Post eval" % self.get_name())
341
342 ret_eval = False
343 rep = self.unstash("Build Report")
344 missing_binaries = list(filter(lambda x: not os.path.isfile(x),
345 self._tfb_binaries))
346
347 if len(missing_binaries):
348 print("ERROR: Could not locate the following binaries:")
349 print("\n".join(missing_binaries))
350
351 # Update the artifacts to not include missing ones
352 artf = [n for n in self._tfb_binaries if n not in missing_binaries]
353 # TODO update self._tfb_binaries
354 ret_eval = False
355 else:
356 print("SUCCESS: Produced binaries:")
357 print("\n".join(self._tfb_binaries))
358 ret_eval = True
359
360 artf = self._tfb_binaries
361
362 # Add artefact related information to report
363 rep["log"] = self._tfb_log_f
364 rep["missing_artefacts"] = missing_binaries
365 rep["artefacts"] = artf
366
367 rep["status"] = "Success" if ret_eval else "Failed"
368 self.stash("Build Report", rep)
369 return ret_eval
370
371 def post_exec(self, eval_ret):
372 """ """
373
374 if eval_ret:
375 print("TFM Builder %s was Successful" % self.get_name())
376 else:
377 print("TFM Builder %s was UnSuccessful" % self.get_name())