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