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