diff options
Diffstat (limited to 'meta/lib')
-rw-r--r-- | meta/lib/oe/fitimage.py | 547 | ||||
-rw-r--r-- | meta/lib/oeqa/core/decorator/data.py | 12 | ||||
-rw-r--r-- | meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml | 2 | ||||
-rw-r--r-- | meta/lib/oeqa/selftest/cases/fitimage.py | 365 | ||||
-rw-r--r-- | meta/lib/oeqa/selftest/cases/liboe.py | 37 | ||||
-rw-r--r-- | meta/lib/oeqa/selftest/cases/uboot.py | 59 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/sshcontrol.py | 2 | ||||
-rw-r--r-- | meta/lib/oeqa/utils/subprocesstweak.py | 13 |
8 files changed, 957 insertions, 80 deletions
diff --git a/meta/lib/oe/fitimage.py b/meta/lib/oe/fitimage.py new file mode 100644 index 0000000000..f303799155 --- /dev/null +++ b/meta/lib/oe/fitimage.py | |||
@@ -0,0 +1,547 @@ | |||
1 | # | ||
2 | # Copyright OpenEmbedded Contributors | ||
3 | # | ||
4 | # SPDX-License-Identifier: GPL-2.0-only | ||
5 | # | ||
6 | # This file contains common functions for the fitimage generation | ||
7 | |||
8 | import os | ||
9 | import shlex | ||
10 | import subprocess | ||
11 | import bb | ||
12 | |||
13 | from oeqa.utils.commands import runCmd | ||
14 | |||
15 | class ItsNode: | ||
16 | INDENT_SIZE = 8 | ||
17 | |||
18 | def __init__(self, name, parent_node, sub_nodes=None, properties=None): | ||
19 | self.name = name | ||
20 | self.parent_node = parent_node | ||
21 | |||
22 | self.sub_nodes = [] | ||
23 | if sub_nodes: | ||
24 | self.sub_nodes = sub_nodes | ||
25 | |||
26 | self.properties = {} | ||
27 | if properties: | ||
28 | self.properties = properties | ||
29 | |||
30 | if parent_node: | ||
31 | parent_node.add_sub_node(self) | ||
32 | |||
33 | def add_sub_node(self, sub_node): | ||
34 | self.sub_nodes.append(sub_node) | ||
35 | |||
36 | def add_property(self, key, value): | ||
37 | self.properties[key] = value | ||
38 | |||
39 | def emit(self, f, indent): | ||
40 | indent_str_name = " " * indent | ||
41 | indent_str_props = " " * (indent + self.INDENT_SIZE) | ||
42 | f.write("%s%s {\n" % (indent_str_name, self.name)) | ||
43 | for key, value in self.properties.items(): | ||
44 | bb.debug(1, "key: %s, value: %s" % (key, str(value))) | ||
45 | # Single integer: <0x12ab> | ||
46 | if isinstance(value, int): | ||
47 | f.write(indent_str_props + key + ' = <0x%x>;\n' % value) | ||
48 | # list of strings: "string1", "string2" or integers: <0x12ab 0x34cd> | ||
49 | elif isinstance(value, list): | ||
50 | if len(value) == 0: | ||
51 | f.write(indent_str_props + key + ' = "";\n') | ||
52 | elif isinstance(value[0], int): | ||
53 | list_entries = ' '.join('0x%x' % entry for entry in value) | ||
54 | f.write(indent_str_props + key + ' = <%s>;\n' % list_entries) | ||
55 | else: | ||
56 | list_entries = ', '.join('"%s"' % entry for entry in value) | ||
57 | f.write(indent_str_props + key + ' = %s;\n' % list_entries) | ||
58 | elif isinstance(value, str): | ||
59 | # path: /incbin/("path/to/file") | ||
60 | if key in ["data"] and value.startswith('/incbin/('): | ||
61 | f.write(indent_str_props + key + ' = %s;\n' % value) | ||
62 | # Integers which are already string formatted | ||
63 | elif value.startswith("<") and value.endswith(">"): | ||
64 | f.write(indent_str_props + key + ' = %s;\n' % value) | ||
65 | else: | ||
66 | f.write(indent_str_props + key + ' = "%s";\n' % value) | ||
67 | else: | ||
68 | bb.fatal("%s has unexpexted data type." % str(value)) | ||
69 | for sub_node in self.sub_nodes: | ||
70 | sub_node.emit(f, indent + self.INDENT_SIZE) | ||
71 | f.write(indent_str_name + '};\n') | ||
72 | |||
73 | class ItsNodeImages(ItsNode): | ||
74 | def __init__(self, parent_node): | ||
75 | super().__init__("images", parent_node) | ||
76 | |||
77 | class ItsNodeConfigurations(ItsNode): | ||
78 | def __init__(self, parent_node): | ||
79 | super().__init__("configurations", parent_node) | ||
80 | |||
81 | class ItsNodeHash(ItsNode): | ||
82 | def __init__(self, name, parent_node, algo, opt_props=None): | ||
83 | properties = { | ||
84 | "algo": algo | ||
85 | } | ||
86 | if opt_props: | ||
87 | properties.update(opt_props) | ||
88 | super().__init__(name, parent_node, None, properties) | ||
89 | |||
90 | class ItsImageSignature(ItsNode): | ||
91 | def __init__(self, name, parent_node, algo, keyname, opt_props=None): | ||
92 | properties = { | ||
93 | "algo": algo, | ||
94 | "key-name-hint": keyname | ||
95 | } | ||
96 | if opt_props: | ||
97 | properties.update(opt_props) | ||
98 | super().__init__(name, parent_node, None, properties) | ||
99 | |||
100 | class ItsNodeImage(ItsNode): | ||
101 | def __init__(self, name, parent_node, description, type, compression, sub_nodes=None, opt_props=None): | ||
102 | properties = { | ||
103 | "description": description, | ||
104 | "type": type, | ||
105 | "compression": compression, | ||
106 | } | ||
107 | if opt_props: | ||
108 | properties.update(opt_props) | ||
109 | super().__init__(name, parent_node, sub_nodes, properties) | ||
110 | |||
111 | class ItsNodeDtb(ItsNodeImage): | ||
112 | def __init__(self, name, parent_node, description, type, compression, | ||
113 | sub_nodes=None, opt_props=None, compatible=None): | ||
114 | super().__init__(name, parent_node, description, type, compression, sub_nodes, opt_props) | ||
115 | self.compatible = compatible | ||
116 | |||
117 | class ItsNodeDtbAlias(ItsNode): | ||
118 | """Additional Configuration Node for a DTB | ||
119 | |||
120 | Symlinks pointing to a DTB file are handled by an addtitional | ||
121 | configuration node referring to another DTB image node. | ||
122 | """ | ||
123 | def __init__(self, name, alias_name, compatible=None): | ||
124 | super().__init__(name, parent_node=None, sub_nodes=None, properties=None) | ||
125 | self.alias_name = alias_name | ||
126 | self.compatible = compatible | ||
127 | |||
128 | class ItsNodeConfigurationSignature(ItsNode): | ||
129 | def __init__(self, name, parent_node, algo, keyname, opt_props=None): | ||
130 | properties = { | ||
131 | "algo": algo, | ||
132 | "key-name-hint": keyname | ||
133 | } | ||
134 | if opt_props: | ||
135 | properties.update(opt_props) | ||
136 | super().__init__(name, parent_node, None, properties) | ||
137 | |||
138 | class ItsNodeConfiguration(ItsNode): | ||
139 | def __init__(self, name, parent_node, description, sub_nodes=None, opt_props=None): | ||
140 | properties = { | ||
141 | "description": description, | ||
142 | } | ||
143 | if opt_props: | ||
144 | properties.update(opt_props) | ||
145 | super().__init__(name, parent_node, sub_nodes, properties) | ||
146 | |||
147 | class ItsNodeRootKernel(ItsNode): | ||
148 | """Create FIT images for the kernel | ||
149 | |||
150 | Currently only a single kernel (no less or more) can be added to the FIT | ||
151 | image along with 0 or more device trees and 0 or 1 ramdisk. | ||
152 | |||
153 | If a device tree included in the FIT image, the default configuration is the | ||
154 | firt DTB. If there is no dtb present than the default configuation the kernel. | ||
155 | """ | ||
156 | def __init__(self, description, address_cells, host_prefix, arch, conf_prefix, | ||
157 | sign_enable=False, sign_keydir=None, | ||
158 | mkimage=None, mkimage_dtcopts=None, | ||
159 | mkimage_sign=None, mkimage_sign_args=None, | ||
160 | hash_algo=None, sign_algo=None, pad_algo=None, | ||
161 | sign_keyname_conf=None, | ||
162 | sign_individual=False, sign_keyname_img=None): | ||
163 | props = { | ||
164 | "description": description, | ||
165 | "#address-cells": f"<{address_cells}>" | ||
166 | } | ||
167 | super().__init__("/", None, None, props) | ||
168 | self.images = ItsNodeImages(self) | ||
169 | self.configurations = ItsNodeConfigurations(self) | ||
170 | |||
171 | self._host_prefix = host_prefix | ||
172 | self._arch = arch | ||
173 | self._conf_prefix = conf_prefix | ||
174 | |||
175 | # Signature related properties | ||
176 | self._sign_enable = sign_enable | ||
177 | self._sign_keydir = sign_keydir | ||
178 | self._mkimage = mkimage | ||
179 | self._mkimage_dtcopts = mkimage_dtcopts | ||
180 | self._mkimage_sign = mkimage_sign | ||
181 | self._mkimage_sign_args = mkimage_sign_args | ||
182 | self._hash_algo = hash_algo | ||
183 | self._sign_algo = sign_algo | ||
184 | self._pad_algo = pad_algo | ||
185 | self._sign_keyname_conf = sign_keyname_conf | ||
186 | self._sign_individual = sign_individual | ||
187 | self._sign_keyname_img = sign_keyname_img | ||
188 | self._sanitize_sign_config() | ||
189 | |||
190 | self._dtbs = [] | ||
191 | self._dtb_alias = [] | ||
192 | self._kernel = None | ||
193 | self._ramdisk = None | ||
194 | self._bootscr = None | ||
195 | self._setup = None | ||
196 | |||
197 | def _sanitize_sign_config(self): | ||
198 | if self._sign_enable: | ||
199 | if not self._hash_algo: | ||
200 | bb.fatal("FIT image signing is enabled but no hash algorithm is provided.") | ||
201 | if not self._sign_algo: | ||
202 | bb.fatal("FIT image signing is enabled but no signature algorithm is provided.") | ||
203 | if not self._pad_algo: | ||
204 | bb.fatal("FIT image signing is enabled but no padding algorithm is provided.") | ||
205 | if not self._sign_keyname_conf: | ||
206 | bb.fatal("FIT image signing is enabled but no configuration key name is provided.") | ||
207 | if self._sign_individual and not self._sign_keyname_img: | ||
208 | bb.fatal("FIT image signing is enabled for individual images but no image key name is provided.") | ||
209 | |||
210 | def write_its_file(self, itsfile): | ||
211 | with open(itsfile, 'w') as f: | ||
212 | f.write("/dts-v1/;\n\n") | ||
213 | self.emit(f, 0) | ||
214 | |||
215 | def its_add_node_image(self, image_id, description, image_type, compression, opt_props): | ||
216 | image_node = ItsNodeImage( | ||
217 | image_id, | ||
218 | self.images, | ||
219 | description, | ||
220 | image_type, | ||
221 | compression, | ||
222 | opt_props=opt_props | ||
223 | ) | ||
224 | if self._hash_algo: | ||
225 | ItsNodeHash( | ||
226 | "hash-1", | ||
227 | image_node, | ||
228 | self._hash_algo | ||
229 | ) | ||
230 | if self._sign_individual: | ||
231 | ItsImageSignature( | ||
232 | "signature-1", | ||
233 | image_node, | ||
234 | f"{self._hash_algo},{self._sign_algo}", | ||
235 | self._sign_keyname_img | ||
236 | ) | ||
237 | return image_node | ||
238 | |||
239 | def its_add_node_dtb(self, image_id, description, image_type, compression, opt_props, compatible): | ||
240 | dtb_node = ItsNodeDtb( | ||
241 | image_id, | ||
242 | self.images, | ||
243 | description, | ||
244 | image_type, | ||
245 | compression, | ||
246 | opt_props=opt_props, | ||
247 | compatible=compatible | ||
248 | ) | ||
249 | if self._hash_algo: | ||
250 | ItsNodeHash( | ||
251 | "hash-1", | ||
252 | dtb_node, | ||
253 | self._hash_algo | ||
254 | ) | ||
255 | if self._sign_individual: | ||
256 | ItsImageSignature( | ||
257 | "signature-1", | ||
258 | dtb_node, | ||
259 | f"{self._hash_algo},{self._sign_algo}", | ||
260 | self._sign_keyname_img | ||
261 | ) | ||
262 | return dtb_node | ||
263 | |||
264 | def fitimage_emit_section_kernel(self, kernel_id, kernel_path, compression, | ||
265 | load, entrypoint, mkimage_kernel_type, entrysymbol=None): | ||
266 | """Emit the fitImage ITS kernel section""" | ||
267 | if self._kernel: | ||
268 | bb.fatal("Kernel section already exists in the ITS file.") | ||
269 | if entrysymbol: | ||
270 | result = subprocess.run([self._host_prefix + "nm", "vmlinux"], capture_output=True, text=True) | ||
271 | for line in result.stdout.splitlines(): | ||
272 | parts = line.split() | ||
273 | if len(parts) == 3 and parts[2] == entrysymbol: | ||
274 | entrypoint = "<0x%s>" % parts[0] | ||
275 | break | ||
276 | kernel_node = self.its_add_node_image( | ||
277 | kernel_id, | ||
278 | "Linux kernel", | ||
279 | mkimage_kernel_type, | ||
280 | compression, | ||
281 | { | ||
282 | "data": '/incbin/("' + kernel_path + '")', | ||
283 | "arch": self._arch, | ||
284 | "os": "linux", | ||
285 | "load": f"<{load}>", | ||
286 | "entry": f"<{entrypoint}>" | ||
287 | } | ||
288 | ) | ||
289 | self._kernel = kernel_node | ||
290 | |||
291 | def fitimage_emit_section_dtb(self, dtb_id, dtb_path, dtb_loadaddress=None, | ||
292 | dtbo_loadaddress=None, add_compatible=False): | ||
293 | """Emit the fitImage ITS DTB section""" | ||
294 | load=None | ||
295 | dtb_ext = os.path.splitext(dtb_path)[1] | ||
296 | if dtb_ext == ".dtbo": | ||
297 | if dtbo_loadaddress: | ||
298 | load = dtbo_loadaddress | ||
299 | elif dtb_loadaddress: | ||
300 | load = dtb_loadaddress | ||
301 | |||
302 | opt_props = { | ||
303 | "data": '/incbin/("' + dtb_path + '")', | ||
304 | "arch": self._arch | ||
305 | } | ||
306 | if load: | ||
307 | opt_props["load"] = f"<{load}>" | ||
308 | |||
309 | # Preserve the DTB's compatible string to be added to the configuration node | ||
310 | compatible = None | ||
311 | if add_compatible: | ||
312 | compatible = get_compatible_from_dtb(dtb_path) | ||
313 | |||
314 | dtb_node = self.its_add_node_dtb( | ||
315 | "fdt-" + dtb_id, | ||
316 | "Flattened Device Tree blob", | ||
317 | "flat_dt", | ||
318 | "none", | ||
319 | opt_props, | ||
320 | compatible | ||
321 | ) | ||
322 | self._dtbs.append(dtb_node) | ||
323 | |||
324 | def fitimage_emit_section_dtb_alias(self, dtb_alias_id, dtb_path, add_compatible=False): | ||
325 | """Add a configuration node referring to another DTB""" | ||
326 | # Preserve the DTB's compatible string to be added to the configuration node | ||
327 | compatible = None | ||
328 | if add_compatible: | ||
329 | compatible = get_compatible_from_dtb(dtb_path) | ||
330 | |||
331 | dtb_id = os.path.basename(dtb_path) | ||
332 | dtb_alias_node = ItsNodeDtbAlias("fdt-" + dtb_id, dtb_alias_id, compatible) | ||
333 | self._dtb_alias.append(dtb_alias_node) | ||
334 | bb.warn(f"compatible: {compatible}, dtb_alias_id: {dtb_alias_id}, dtb_id: {dtb_id}, dtb_path: {dtb_path}") | ||
335 | |||
336 | def fitimage_emit_section_boot_script(self, bootscr_id, bootscr_path): | ||
337 | """Emit the fitImage ITS u-boot script section""" | ||
338 | if self._bootscr: | ||
339 | bb.fatal("U-boot script section already exists in the ITS file.") | ||
340 | bootscr_node = self.its_add_node_image( | ||
341 | bootscr_id, | ||
342 | "U-boot script", | ||
343 | "script", | ||
344 | "none", | ||
345 | { | ||
346 | "data": '/incbin/("' + bootscr_path + '")', | ||
347 | "arch": self._arch, | ||
348 | "type": "script" | ||
349 | } | ||
350 | ) | ||
351 | self._bootscr = bootscr_node | ||
352 | |||
353 | def fitimage_emit_section_setup(self, setup_id, setup_path): | ||
354 | """Emit the fitImage ITS setup section""" | ||
355 | if self._setup: | ||
356 | bb.fatal("Setup section already exists in the ITS file.") | ||
357 | load = "<0x00090000>" | ||
358 | entry = "<0x00090000>" | ||
359 | setup_node = self.its_add_node_image( | ||
360 | setup_id, | ||
361 | "Linux setup.bin", | ||
362 | "x86_setup", | ||
363 | "none", | ||
364 | { | ||
365 | "data": '/incbin/("' + setup_path + '")', | ||
366 | "arch": self._arch, | ||
367 | "os": "linux", | ||
368 | "load": load, | ||
369 | "entry": entry | ||
370 | } | ||
371 | ) | ||
372 | self._setup = setup_node | ||
373 | |||
374 | def fitimage_emit_section_ramdisk(self, ramdisk_id, ramdisk_path, description="ramdisk", load=None, entry=None): | ||
375 | """Emit the fitImage ITS ramdisk section""" | ||
376 | if self._ramdisk: | ||
377 | bb.fatal("Ramdisk section already exists in the ITS file.") | ||
378 | opt_props = { | ||
379 | "data": '/incbin/("' + ramdisk_path + '")', | ||
380 | "type": "ramdisk", | ||
381 | "arch": self._arch, | ||
382 | "os": "linux" | ||
383 | } | ||
384 | if load: | ||
385 | opt_props["load"] = f"<{load}>" | ||
386 | if entry: | ||
387 | opt_props["entry"] = f"<{entry}>" | ||
388 | |||
389 | ramdisk_node = self.its_add_node_image( | ||
390 | ramdisk_id, | ||
391 | description, | ||
392 | "ramdisk", | ||
393 | "none", | ||
394 | opt_props | ||
395 | ) | ||
396 | self._ramdisk = ramdisk_node | ||
397 | |||
398 | def _fitimage_emit_one_section_config(self, conf_node_name, dtb=None): | ||
399 | """Emit the fitImage ITS configuration section""" | ||
400 | opt_props = {} | ||
401 | conf_desc = [] | ||
402 | sign_entries = [] | ||
403 | |||
404 | if self._kernel: | ||
405 | conf_desc.append("Linux kernel") | ||
406 | opt_props["kernel"] = self._kernel.name | ||
407 | if self._sign_enable: | ||
408 | sign_entries.append("kernel") | ||
409 | |||
410 | if dtb: | ||
411 | conf_desc.append("FDT blob") | ||
412 | opt_props["fdt"] = dtb.name | ||
413 | if dtb.compatible: | ||
414 | opt_props["compatible"] = dtb.compatible | ||
415 | if self._sign_enable: | ||
416 | sign_entries.append("fdt") | ||
417 | |||
418 | if self._ramdisk: | ||
419 | conf_desc.append("ramdisk") | ||
420 | opt_props["ramdisk"] = self._ramdisk.name | ||
421 | if self._sign_enable: | ||
422 | sign_entries.append("ramdisk") | ||
423 | |||
424 | if self._bootscr: | ||
425 | conf_desc.append("u-boot script") | ||
426 | opt_props["bootscr"] = self._bootscr.name | ||
427 | if self._sign_enable: | ||
428 | sign_entries.append("bootscr") | ||
429 | |||
430 | if self._setup: | ||
431 | conf_desc.append("setup") | ||
432 | opt_props["setup"] = self._setup.name | ||
433 | if self._sign_enable: | ||
434 | sign_entries.append("setup") | ||
435 | |||
436 | # First added configuration is the default configuration | ||
437 | default_flag = "0" | ||
438 | if len(self.configurations.sub_nodes) == 0: | ||
439 | default_flag = "1" | ||
440 | |||
441 | conf_node = ItsNodeConfiguration( | ||
442 | conf_node_name, | ||
443 | self.configurations, | ||
444 | f"{default_flag} {', '.join(conf_desc)}", | ||
445 | opt_props=opt_props | ||
446 | ) | ||
447 | if self._hash_algo: | ||
448 | ItsNodeHash( | ||
449 | "hash-1", | ||
450 | conf_node, | ||
451 | self._hash_algo | ||
452 | ) | ||
453 | if self._sign_enable: | ||
454 | ItsNodeConfigurationSignature( | ||
455 | "signature-1", | ||
456 | conf_node, | ||
457 | f"{self._hash_algo},{self._sign_algo}", | ||
458 | self._sign_keyname_conf, | ||
459 | opt_props={ | ||
460 | "padding": self._pad_algo, | ||
461 | "sign-images": sign_entries | ||
462 | } | ||
463 | ) | ||
464 | |||
465 | def fitimage_emit_section_config(self, default_dtb_image=None): | ||
466 | if self._dtbs: | ||
467 | for dtb in self._dtbs: | ||
468 | dtb_name = dtb.name | ||
469 | if dtb.name.startswith("fdt-"): | ||
470 | dtb_name = dtb.name[len("fdt-"):] | ||
471 | self._fitimage_emit_one_section_config(self._conf_prefix + dtb_name, dtb) | ||
472 | for dtb in self._dtb_alias: | ||
473 | self._fitimage_emit_one_section_config(self._conf_prefix + dtb.alias_name, dtb) | ||
474 | else: | ||
475 | # Currently exactly one kernel is supported. | ||
476 | self._fitimage_emit_one_section_config(self._conf_prefix + "1") | ||
477 | |||
478 | default_conf = self.configurations.sub_nodes[0].name | ||
479 | if default_dtb_image and self._dtbs: | ||
480 | default_conf = self._conf_prefix + default_dtb_image | ||
481 | self.configurations.add_property('default', default_conf) | ||
482 | |||
483 | def run_mkimage_assemble(self, itsfile, fitfile): | ||
484 | cmd = [ | ||
485 | self._mkimage, | ||
486 | '-f', itsfile, | ||
487 | fitfile | ||
488 | ] | ||
489 | if self._mkimage_dtcopts: | ||
490 | cmd.insert(1, '-D') | ||
491 | cmd.insert(2, self._mkimage_dtcopts) | ||
492 | try: | ||
493 | subprocess.run(cmd, check=True, capture_output=True) | ||
494 | except subprocess.CalledProcessError as e: | ||
495 | bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}\nitsflile: {os.path.abspath(itsfile)}") | ||
496 | |||
497 | def run_mkimage_sign(self, fitfile): | ||
498 | if not self._sign_enable: | ||
499 | bb.debug(1, "FIT image signing is disabled. Skipping signing.") | ||
500 | return | ||
501 | |||
502 | # Some sanity checks because mkimage exits with 0 also without needed keys | ||
503 | sign_key_path = os.path.join(self._sign_keydir, self._sign_keyname_conf) | ||
504 | if not os.path.exists(sign_key_path + '.key') or not os.path.exists(sign_key_path + '.crt'): | ||
505 | bb.fatal("%s.key or .crt does not exist" % sign_key_path) | ||
506 | if self._sign_individual: | ||
507 | sign_key_img_path = os.path.join(self._sign_keydir, self._sign_keyname_img) | ||
508 | if not os.path.exists(sign_key_img_path + '.key') or not os.path.exists(sign_key_img_path + '.crt'): | ||
509 | bb.fatal("%s.key or .crt does not exist" % sign_key_img_path) | ||
510 | |||
511 | cmd = [ | ||
512 | self._mkimage_sign, | ||
513 | '-F', | ||
514 | '-k', self._sign_keydir, | ||
515 | '-r', fitfile | ||
516 | ] | ||
517 | if self._mkimage_dtcopts: | ||
518 | cmd.extend(['-D', self._mkimage_dtcopts]) | ||
519 | if self._mkimage_sign_args: | ||
520 | cmd.extend(shlex.split(self._mkimage_sign_args)) | ||
521 | try: | ||
522 | subprocess.run(cmd, check=True, capture_output=True) | ||
523 | except subprocess.CalledProcessError as e: | ||
524 | bb.fatal(f"Command '{' '.join(cmd)}' failed with return code {e.returncode}\nstdout: {e.stdout.decode()}\nstderr: {e.stderr.decode()}") | ||
525 | |||
526 | |||
527 | def symlink_points_below(file_or_symlink, expected_parent_dir): | ||
528 | """returns symlink destination if it points below directory""" | ||
529 | file_path = os.path.join(expected_parent_dir, file_or_symlink) | ||
530 | if not os.path.islink(file_path): | ||
531 | return None | ||
532 | |||
533 | realpath = os.path.relpath(os.path.realpath(file_path), expected_parent_dir) | ||
534 | if realpath.startswith(".."): | ||
535 | return None | ||
536 | |||
537 | return realpath | ||
538 | |||
539 | def get_compatible_from_dtb(dtb_path, fdtget_path="fdtget"): | ||
540 | compatible = None | ||
541 | cmd = [fdtget_path, "-t", "s", dtb_path, "/", "compatible"] | ||
542 | try: | ||
543 | ret = subprocess.run(cmd, check=True, capture_output=True, text=True) | ||
544 | compatible = ret.stdout.strip().split() | ||
545 | except subprocess.CalledProcessError: | ||
546 | compatible = None | ||
547 | return compatible | ||
diff --git a/meta/lib/oeqa/core/decorator/data.py b/meta/lib/oeqa/core/decorator/data.py index 5444b2cb75..0daf46334f 100644 --- a/meta/lib/oeqa/core/decorator/data.py +++ b/meta/lib/oeqa/core/decorator/data.py | |||
@@ -228,3 +228,15 @@ class skipIfNotArch(OETestDecorator): | |||
228 | arch = self.case.td['HOST_ARCH'] | 228 | arch = self.case.td['HOST_ARCH'] |
229 | if arch not in self.archs: | 229 | if arch not in self.archs: |
230 | self.case.skipTest('Test skipped on %s' % arch) | 230 | self.case.skipTest('Test skipped on %s' % arch) |
231 | |||
232 | @registerDecorator | ||
233 | class skipIfNotBuildArch(OETestDecorator): | ||
234 | """ | ||
235 | Skip test if BUILD_ARCH is not present in the tuple specified. | ||
236 | """ | ||
237 | |||
238 | attrs = ('archs',) | ||
239 | def setUpDecorator(self): | ||
240 | arch = self.case.td['BUILD_ARCH'] | ||
241 | if arch not in self.archs: | ||
242 | self.case.skipTest('Test skipped on %s' % arch) | ||
diff --git a/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml b/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml index de95025e86..a78ada2593 100644 --- a/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml +++ b/meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml | |||
@@ -14,7 +14,7 @@ crate-type = ["cdylib"] | |||
14 | rand = "0.8.4" | 14 | rand = "0.8.4" |
15 | 15 | ||
16 | [dependencies.pyo3] | 16 | [dependencies.pyo3] |
17 | version = "0.19.0" | 17 | version = "0.24.1" |
18 | # "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8 | 18 | # "abi3-py38" tells pyo3 (and maturin) to build using the stable ABI with minimum Python version 3.8 |
19 | features = ["abi3-py38"] | 19 | features = ["abi3-py38"] |
20 | 20 | ||
diff --git a/meta/lib/oeqa/selftest/cases/fitimage.py b/meta/lib/oeqa/selftest/cases/fitimage.py index b39f2622df..be291e4b0f 100644 --- a/meta/lib/oeqa/selftest/cases/fitimage.py +++ b/meta/lib/oeqa/selftest/cases/fitimage.py | |||
@@ -4,13 +4,36 @@ | |||
4 | # SPDX-License-Identifier: MIT | 4 | # SPDX-License-Identifier: MIT |
5 | # | 5 | # |
6 | 6 | ||
7 | from oeqa.selftest.case import OESelftestTestCase | ||
8 | from oeqa.utils.commands import runCmd, bitbake, get_bb_vars | ||
9 | import os | 7 | import os |
10 | import re | 8 | import re |
11 | import shlex | 9 | import shlex |
12 | import logging | 10 | import logging |
13 | import pprint | 11 | import pprint |
12 | import tempfile | ||
13 | |||
14 | import oe.fitimage | ||
15 | |||
16 | from oeqa.selftest.case import OESelftestTestCase | ||
17 | from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_bb_var | ||
18 | |||
19 | |||
20 | class BbVarsMockGenKeys: | ||
21 | def __init__(self, keydir, gen_keys="0", sign_enabled="0", keyname="", sign_ind="0", img_keyname=""): | ||
22 | self.bb_vars = { | ||
23 | 'FIT_GENERATE_KEYS': gen_keys, | ||
24 | 'FIT_KEY_GENRSA_ARGS': "-F4", | ||
25 | 'FIT_KEY_REQ_ARGS': "-batch -new", | ||
26 | 'FIT_KEY_SIGN_PKCS': "-x509", | ||
27 | 'FIT_SIGN_INDIVIDUAL': sign_ind, | ||
28 | 'FIT_SIGN_NUMBITS': "2048", | ||
29 | 'UBOOT_SIGN_ENABLE': sign_enabled, | ||
30 | 'UBOOT_SIGN_IMG_KEYNAME': img_keyname, | ||
31 | 'UBOOT_SIGN_KEYDIR': keydir, | ||
32 | 'UBOOT_SIGN_KEYNAME': keyname, | ||
33 | } | ||
34 | |||
35 | def getVar(self, var): | ||
36 | return self.bb_vars[var] | ||
14 | 37 | ||
15 | class FitImageTestCase(OESelftestTestCase): | 38 | class FitImageTestCase(OESelftestTestCase): |
16 | """Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass | 39 | """Test functions usable for testing kernel-fitimage.bbclass and uboot-sign.bbclass |
@@ -161,10 +184,23 @@ class FitImageTestCase(OESelftestTestCase): | |||
161 | 184 | ||
162 | @staticmethod | 185 | @staticmethod |
163 | def _get_dtb_files(bb_vars): | 186 | def _get_dtb_files(bb_vars): |
187 | """Return a list of devicetree names | ||
188 | |||
189 | The list should be used to check the dtb and conf nodes in the FIT image or its file. | ||
190 | In addition to the entries from KERNEL_DEVICETREE, the external devicetree and the | ||
191 | external devicetree overlay added by the test recipe bbb-dtbs-as-ext are handled as well. | ||
192 | """ | ||
164 | kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE') | 193 | kernel_devicetree = bb_vars.get('KERNEL_DEVICETREE') |
194 | all_dtbs = [] | ||
195 | dtb_symlinks = [] | ||
165 | if kernel_devicetree: | 196 | if kernel_devicetree: |
166 | return [os.path.basename(dtb) for dtb in kernel_devicetree.split()] | 197 | all_dtbs += [os.path.basename(dtb) for dtb in kernel_devicetree.split()] |
167 | return [] | 198 | # Support only the test recipe which provides 1 devicetree and 1 devicetree overlay |
199 | pref_prov_dtb = bb_vars.get('PREFERRED_PROVIDER_virtual/dtb') | ||
200 | if pref_prov_dtb == "bbb-dtbs-as-ext": | ||
201 | all_dtbs += ["am335x-bonegreen-ext.dtb", "BBORG_RELAY-00A2.dtbo"] | ||
202 | dtb_symlinks.append("am335x-bonegreen-ext-alias.dtb") | ||
203 | return (all_dtbs, dtb_symlinks) | ||
168 | 204 | ||
169 | def _is_req_dict_in_dict(self, found_dict, req_dict): | 205 | def _is_req_dict_in_dict(self, found_dict, req_dict): |
170 | """ | 206 | """ |
@@ -243,7 +279,7 @@ class FitImageTestCase(OESelftestTestCase): | |||
243 | self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4)) | 279 | self.logger.debug("sigs:\n%s\n" % pprint.pformat(sigs, indent=4)) |
244 | if req_sigvalues_config or req_sigvalues_image: | 280 | if req_sigvalues_config or req_sigvalues_image: |
245 | for its_path, values in sigs.items(): | 281 | for its_path, values in sigs.items(): |
246 | if 'conf-' in its_path: | 282 | if bb_vars.get('FIT_CONF_PREFIX', "conf-") in its_path: |
247 | reqsigvalues = req_sigvalues_config | 283 | reqsigvalues = req_sigvalues_config |
248 | else: | 284 | else: |
249 | reqsigvalues = req_sigvalues_image | 285 | reqsigvalues = req_sigvalues_image |
@@ -356,9 +392,8 @@ class FitImageTestCase(OESelftestTestCase): | |||
356 | # Verify the FIT image | 392 | # Verify the FIT image |
357 | self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir) | 393 | self._check_fitimage(bb_vars, fitimage_path, uboot_tools_bindir) |
358 | 394 | ||
359 | 395 | class KernelFitImageBase(FitImageTestCase): | |
360 | class KernelFitImageTests(FitImageTestCase): | 396 | """Test cases for the linux-yocto-fitimage recipe""" |
361 | """Test cases for the kernel-fitimage bbclass""" | ||
362 | 397 | ||
363 | def _fit_get_bb_vars(self, additional_vars=[]): | 398 | def _fit_get_bb_vars(self, additional_vars=[]): |
364 | """Retrieve BitBake variables specific to the test case. | 399 | """Retrieve BitBake variables specific to the test case. |
@@ -367,6 +402,8 @@ class KernelFitImageTests(FitImageTestCase): | |||
367 | """ | 402 | """ |
368 | internal_used = { | 403 | internal_used = { |
369 | 'DEPLOY_DIR_IMAGE', | 404 | 'DEPLOY_DIR_IMAGE', |
405 | 'FIT_CONF_DEFAULT_DTB', | ||
406 | 'FIT_CONF_PREFIX', | ||
370 | 'FIT_DESC', | 407 | 'FIT_DESC', |
371 | 'FIT_HASH_ALG', | 408 | 'FIT_HASH_ALG', |
372 | 'FIT_KERNEL_COMP_ALG', | 409 | 'FIT_KERNEL_COMP_ALG', |
@@ -376,9 +413,11 @@ class KernelFitImageTests(FitImageTestCase): | |||
376 | 'INITRAMFS_IMAGE_BUNDLE', | 413 | 'INITRAMFS_IMAGE_BUNDLE', |
377 | 'INITRAMFS_IMAGE_NAME', | 414 | 'INITRAMFS_IMAGE_NAME', |
378 | 'INITRAMFS_IMAGE', | 415 | 'INITRAMFS_IMAGE', |
416 | 'KERNEL_DEPLOYSUBDIR', | ||
379 | 'KERNEL_DEVICETREE', | 417 | 'KERNEL_DEVICETREE', |
380 | 'KERNEL_FIT_LINK_NAME', | 418 | 'KERNEL_FIT_LINK_NAME', |
381 | 'MACHINE', | 419 | 'MACHINE', |
420 | 'PREFERRED_PROVIDER_virtual/dtb', | ||
382 | 'UBOOT_ARCH', | 421 | 'UBOOT_ARCH', |
383 | 'UBOOT_ENTRYPOINT', | 422 | 'UBOOT_ENTRYPOINT', |
384 | 'UBOOT_LOADADDRESS', | 423 | 'UBOOT_LOADADDRESS', |
@@ -391,10 +430,19 @@ class KernelFitImageTests(FitImageTestCase): | |||
391 | 'UBOOT_SIGN_KEYDIR', | 430 | 'UBOOT_SIGN_KEYDIR', |
392 | 'UBOOT_SIGN_KEYNAME', | 431 | 'UBOOT_SIGN_KEYNAME', |
393 | } | 432 | } |
394 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), "virtual/kernel") | 433 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), self.kernel_recipe) |
395 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) | 434 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) |
396 | return bb_vars | 435 | return bb_vars |
397 | 436 | ||
437 | def _config_add_kernel_classes(self, config): | ||
438 | config += '# Use kernel-fit-extra-artifacts.bbclass for the creation of the vmlinux artifact' + os.linesep | ||
439 | config += 'KERNEL_CLASSES = "kernel-fit-extra-artifacts"' + os.linesep | ||
440 | return config | ||
441 | |||
442 | @property | ||
443 | def kernel_recipe(self): | ||
444 | return "linux-yocto-fitimage" | ||
445 | |||
398 | def _config_add_uboot_env(self, config): | 446 | def _config_add_uboot_env(self, config): |
399 | """Generate an u-boot environment | 447 | """Generate an u-boot environment |
400 | 448 | ||
@@ -408,7 +456,7 @@ class KernelFitImageTests(FitImageTestCase): | |||
408 | config += '# Add an u-boot script to the fitImage' + os.linesep | 456 | config += '# Add an u-boot script to the fitImage' + os.linesep |
409 | config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep | 457 | config += 'FIT_UBOOT_ENV = "%s"' % fit_uenv_file + os.linesep |
410 | config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep | 458 | config += 'FILESEXTRAPATHS:prepend := "${TOPDIR}/%s:"' % test_files_dir + os.linesep |
411 | config += 'SRC_URI:append:pn-linux-yocto = " file://${FIT_UBOOT_ENV}"' + os.linesep | 459 | config += 'SRC_URI:append:pn-%s = " file://${FIT_UBOOT_ENV}"' % self.kernel_recipe + os.linesep |
412 | 460 | ||
413 | if not os.path.isdir(test_files_dir): | 461 | if not os.path.isdir(test_files_dir): |
414 | os.makedirs(test_files_dir) | 462 | os.makedirs(test_files_dir) |
@@ -420,7 +468,7 @@ class KernelFitImageTests(FitImageTestCase): | |||
420 | 468 | ||
421 | def _bitbake_fit_image(self, bb_vars): | 469 | def _bitbake_fit_image(self, bb_vars): |
422 | """Bitbake the kernel and return the paths to the its file and the FIT image""" | 470 | """Bitbake the kernel and return the paths to the its file and the FIT image""" |
423 | bitbake("virtual/kernel") | 471 | bitbake(self.kernel_recipe) |
424 | 472 | ||
425 | # Find the right its file and the final fitImage and check if both files are available | 473 | # Find the right its file and the final fitImage and check if both files are available |
426 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | 474 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] |
@@ -439,8 +487,13 @@ class KernelFitImageTests(FitImageTestCase): | |||
439 | fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT} | 487 | fitimage_name = "fitImage" # or fitImage-${KERNEL_IMAGE_LINK_NAME}${KERNEL_IMAGE_BIN_EXT} |
440 | else: | 488 | else: |
441 | self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE') | 489 | self.fail('Invalid configuration: INITRAMFS_IMAGE_BUNDLE = "1" and not INITRAMFS_IMAGE') |
442 | fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name)) | 490 | kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR'] |
443 | fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name)) | 491 | if kernel_deploysubdir: |
492 | fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_its_name)) | ||
493 | fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, kernel_deploysubdir, fitimage_name)) | ||
494 | else: | ||
495 | fitimage_its_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_its_name)) | ||
496 | fitimage_path = os.path.realpath(os.path.join(deploy_dir_image, fitimage_name)) | ||
444 | return (fitimage_its_path, fitimage_path) | 497 | return (fitimage_its_path, fitimage_path) |
445 | 498 | ||
446 | def _get_req_its_paths(self, bb_vars): | 499 | def _get_req_its_paths(self, bb_vars): |
@@ -452,7 +505,7 @@ class KernelFitImageTests(FitImageTestCase): | |||
452 | ['/', 'images', 'kernel-1', 'signature-1'], | 505 | ['/', 'images', 'kernel-1', 'signature-1'], |
453 | ] | 506 | ] |
454 | """ | 507 | """ |
455 | dtb_files = FitImageTestCase._get_dtb_files(bb_vars) | 508 | dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) |
456 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | 509 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] |
457 | fit_uboot_env = bb_vars['FIT_UBOOT_ENV'] | 510 | fit_uboot_env = bb_vars['FIT_UBOOT_ENV'] |
458 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] | 511 | initramfs_image = bb_vars['INITRAMFS_IMAGE'] |
@@ -470,11 +523,11 @@ class KernelFitImageTests(FitImageTestCase): | |||
470 | if initramfs_image and initramfs_image_bundle != "1": | 523 | if initramfs_image and initramfs_image_bundle != "1": |
471 | images.append('ramdisk-1') | 524 | images.append('ramdisk-1') |
472 | 525 | ||
473 | # configuration nodes | 526 | # configuration nodes (one per DTB and also one per symlink) |
474 | if dtb_files: | 527 | if dtb_files: |
475 | configurations = [ 'conf-' + conf for conf in dtb_files ] | 528 | configurations = [bb_vars['FIT_CONF_PREFIX'] + conf for conf in dtb_files + dtb_symlinks] |
476 | else: | 529 | else: |
477 | configurations = [ 'conf-1' ] | 530 | configurations = [bb_vars['FIT_CONF_PREFIX'] + '1'] |
478 | 531 | ||
479 | # Create a list of paths for all image and configuration nodes | 532 | # Create a list of paths for all image and configuration nodes |
480 | req_its_paths = [] | 533 | req_its_paths = [] |
@@ -497,11 +550,11 @@ class KernelFitImageTests(FitImageTestCase): | |||
497 | its_field_check = [ | 550 | its_field_check = [ |
498 | 'description = "%s";' % bb_vars['FIT_DESC'], | 551 | 'description = "%s";' % bb_vars['FIT_DESC'], |
499 | 'description = "Linux kernel";', | 552 | 'description = "Linux kernel";', |
500 | 'data = /incbin/("linux.bin");', | ||
501 | 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";', | 553 | 'type = "' + str(bb_vars['UBOOT_MKIMAGE_KERNEL_TYPE']) + '";', |
554 | # 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal... | ||
555 | 'data = /incbin/("linux.bin");', | ||
502 | 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";', | 556 | 'arch = "' + str(bb_vars['UBOOT_ARCH']) + '";', |
503 | 'os = "linux";', | 557 | 'os = "linux";', |
504 | # 'compression = "' + str(bb_vars['FIT_KERNEL_COMP_ALG']) + '";', defined based on files in TMPDIR, not ideal... | ||
505 | 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;', | 558 | 'load = <' + str(bb_vars['UBOOT_LOADADDRESS']) + '>;', |
506 | 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;', | 559 | 'entry = <' + str(bb_vars['UBOOT_ENTRYPOINT']) + '>;', |
507 | ] | 560 | ] |
@@ -511,10 +564,14 @@ class KernelFitImageTests(FitImageTestCase): | |||
511 | its_field_check.append("load = <%s>;" % uboot_rd_loadaddress) | 564 | its_field_check.append("load = <%s>;" % uboot_rd_loadaddress) |
512 | if uboot_rd_entrypoint: | 565 | if uboot_rd_entrypoint: |
513 | its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint) | 566 | its_field_check.append("entry = <%s>;" % uboot_rd_entrypoint) |
514 | its_field_check += [ | 567 | |
515 | # 'default = "conf-1";', needs more work | 568 | fit_conf_default_dtb = bb_vars.get('FIT_CONF_DEFAULT_DTB') |
516 | 'kernel = "kernel-1";', | 569 | if fit_conf_default_dtb: |
517 | ] | 570 | fit_conf_prefix = bb_vars.get('FIT_CONF_PREFIX', "conf-") |
571 | its_field_check.append('default = "' + fit_conf_prefix + fit_conf_default_dtb + '";') | ||
572 | |||
573 | its_field_check.append('kernel = "kernel-1";') | ||
574 | |||
518 | if initramfs_image and initramfs_image_bundle != "1": | 575 | if initramfs_image and initramfs_image_bundle != "1": |
519 | its_field_check.append('ramdisk = "ramdisk-1";') | 576 | its_field_check.append('ramdisk = "ramdisk-1";') |
520 | 577 | ||
@@ -548,7 +605,7 @@ class KernelFitImageTests(FitImageTestCase): | |||
548 | 605 | ||
549 | def _get_req_sections(self, bb_vars): | 606 | def _get_req_sections(self, bb_vars): |
550 | """Generate a dictionary of expected sections in the output of dumpimage""" | 607 | """Generate a dictionary of expected sections in the output of dumpimage""" |
551 | dtb_files = FitImageTestCase._get_dtb_files(bb_vars) | 608 | dtb_files, dtb_symlinks = FitImageTestCase._get_dtb_files(bb_vars) |
552 | fit_hash_alg = bb_vars['FIT_HASH_ALG'] | 609 | fit_hash_alg = bb_vars['FIT_HASH_ALG'] |
553 | fit_sign_alg = bb_vars['FIT_SIGN_ALG'] | 610 | fit_sign_alg = bb_vars['FIT_SIGN_ALG'] |
554 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | 611 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] |
@@ -584,25 +641,36 @@ class KernelFitImageTests(FitImageTestCase): | |||
584 | } | 641 | } |
585 | # Create a configuration section for each DTB | 642 | # Create a configuration section for each DTB |
586 | if dtb_files: | 643 | if dtb_files: |
587 | for dtb in dtb_files: | 644 | for dtb in dtb_files + dtb_symlinks: |
588 | req_sections['conf-' + dtb] = { | 645 | conf_name = bb_vars['FIT_CONF_PREFIX'] + dtb |
589 | "Kernel": "kernel-1", | 646 | # Assume that DTBs with an "-alias" in its name are symlink DTBs created e.g. by the |
590 | "FDT": 'fdt-' + dtb, | 647 | # bbb-dtbs-as-ext test recipe. Make the configuration node pointing to the real DTB. |
591 | } | 648 | real_dtb = dtb.replace("-alias", "") |
649 | # dtb overlays do not refer to a kernel (yet?) | ||
650 | if dtb.endswith('.dtbo'): | ||
651 | req_sections[conf_name] = { | ||
652 | "FDT": 'fdt-' + real_dtb, | ||
653 | } | ||
654 | else: | ||
655 | req_sections[conf_name] = { | ||
656 | "Kernel": "kernel-1", | ||
657 | "FDT": 'fdt-' + real_dtb, | ||
658 | } | ||
592 | if initramfs_image and initramfs_image_bundle != "1": | 659 | if initramfs_image and initramfs_image_bundle != "1": |
593 | req_sections['conf-' + dtb]['Init Ramdisk'] = "ramdisk-1" | 660 | req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1" |
594 | else: | 661 | else: |
595 | req_sections['conf-1'] = { | 662 | conf_name = bb_vars['FIT_CONF_PREFIX'] + '1' |
663 | req_sections[conf_name] = { | ||
596 | "Kernel": "kernel-1" | 664 | "Kernel": "kernel-1" |
597 | } | 665 | } |
598 | if initramfs_image and initramfs_image_bundle != "1": | 666 | if initramfs_image and initramfs_image_bundle != "1": |
599 | req_sections['conf-1']['Init Ramdisk'] = "ramdisk-1" | 667 | req_sections[conf_name]['Init Ramdisk'] = "ramdisk-1" |
600 | 668 | ||
601 | # Add signing related properties if needed | 669 | # Add signing related properties if needed |
602 | if uboot_sign_enable == "1": | 670 | if uboot_sign_enable == "1": |
603 | for section in req_sections: | 671 | for section in req_sections: |
604 | req_sections[section]['Hash algo'] = fit_hash_alg | 672 | req_sections[section]['Hash algo'] = fit_hash_alg |
605 | if section.startswith('conf-'): | 673 | if section.startswith(bb_vars['FIT_CONF_PREFIX']): |
606 | req_sections[section]['Hash value'] = "unavailable" | 674 | req_sections[section]['Hash value'] = "unavailable" |
607 | req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) | 675 | req_sections[section]['Sign algo'] = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) |
608 | num_signatures += 1 | 676 | num_signatures += 1 |
@@ -624,18 +692,26 @@ class KernelFitImageTests(FitImageTestCase): | |||
624 | uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME'] | 692 | uboot_sign_keyname = bb_vars['UBOOT_SIGN_KEYNAME'] |
625 | uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | 693 | uboot_sign_img_keyname = bb_vars['UBOOT_SIGN_IMG_KEYNAME'] |
626 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | 694 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] |
695 | kernel_deploysubdir = bb_vars['KERNEL_DEPLOYSUBDIR'] | ||
627 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] | 696 | fit_sign_individual = bb_vars['FIT_SIGN_INDIVIDUAL'] |
628 | fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg] | 697 | fit_hash_alg_len = FitImageTestCase.MKIMAGE_HASH_LENGTHS[fit_hash_alg] |
629 | fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg] | 698 | fit_sign_alg_len = FitImageTestCase.MKIMAGE_SIGNATURE_LENGTHS[fit_sign_alg] |
630 | for section, values in sections.items(): | 699 | for section, values in sections.items(): |
631 | # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1") | 700 | # Configuration nodes are always signed with UBOOT_SIGN_KEYNAME (if UBOOT_SIGN_ENABLE = "1") |
632 | if section.startswith("conf"): | 701 | if section.startswith(bb_vars['FIT_CONF_PREFIX']): |
633 | sign_algo = values.get('Sign algo', None) | 702 | sign_algo = values.get('Sign algo', None) |
634 | req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) | 703 | req_sign_algo = "%s,%s:%s" % (fit_hash_alg, fit_sign_alg, uboot_sign_keyname) |
635 | self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section) | 704 | self.assertEqual(sign_algo, req_sign_algo, 'Signature algorithm for %s not expected value' % section) |
636 | sign_value = values.get('Sign value', None) | 705 | sign_value = values.get('Sign value', None) |
637 | self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section) | 706 | self.assertEqual(len(sign_value), fit_sign_alg_len, 'Signature value for section %s not expected length' % section) |
638 | dtb_path = os.path.join(deploy_dir_image, section.replace('conf-', '')) | 707 | dtb_file_name = section.replace(bb_vars['FIT_CONF_PREFIX'], '') |
708 | dtb_path = os.path.join(deploy_dir_image, dtb_file_name) | ||
709 | if kernel_deploysubdir: | ||
710 | dtb_path = os.path.join(deploy_dir_image, kernel_deploysubdir, dtb_file_name) | ||
711 | # External devicetrees created by devicetree.bbclass are in a subfolder and have priority | ||
712 | dtb_path_ext = os.path.join(deploy_dir_image, "devicetree", dtb_file_name) | ||
713 | if os.path.exists(dtb_path_ext): | ||
714 | dtb_path = dtb_path_ext | ||
639 | self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section) | 715 | self._verify_fit_image_signature(uboot_tools_bindir, fitimage_path, dtb_path, section) |
640 | else: | 716 | else: |
641 | # Image nodes always need a hash which gets indirectly signed by the config signature | 717 | # Image nodes always need a hash which gets indirectly signed by the config signature |
@@ -660,6 +736,8 @@ class KernelFitImageTests(FitImageTestCase): | |||
660 | self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." % | 736 | self.assertEqual(found_comments, num_signatures, "Expected %d signed and commented (%s) sections in the fitImage." % |
661 | (num_signatures, a_comment)) | 737 | (num_signatures, a_comment)) |
662 | 738 | ||
739 | class KernelFitImageRecipeTests(KernelFitImageBase): | ||
740 | """Test cases for the kernel-fitimage bbclass""" | ||
663 | 741 | ||
664 | def test_fit_image(self): | 742 | def test_fit_image(self): |
665 | """ | 743 | """ |
@@ -675,10 +753,7 @@ class KernelFitImageTests(FitImageTestCase): | |||
675 | Author: Usama Arif <usama.arif@arm.com> | 753 | Author: Usama Arif <usama.arif@arm.com> |
676 | """ | 754 | """ |
677 | config = """ | 755 | config = """ |
678 | # Enable creation of fitImage | ||
679 | KERNEL_IMAGETYPE = "Image" | 756 | KERNEL_IMAGETYPE = "Image" |
680 | KERNEL_IMAGETYPES += " fitImage " | ||
681 | KERNEL_CLASSES = " kernel-fitimage " | ||
682 | 757 | ||
683 | # RAM disk variables including load address and entrypoint for kernel and RAM disk | 758 | # RAM disk variables including load address and entrypoint for kernel and RAM disk |
684 | IMAGE_FSTYPES += "cpio.gz" | 759 | IMAGE_FSTYPES += "cpio.gz" |
@@ -690,8 +765,76 @@ UBOOT_RD_ENTRYPOINT = "0x88000000" | |||
690 | UBOOT_LOADADDRESS = "0x80080000" | 765 | UBOOT_LOADADDRESS = "0x80080000" |
691 | UBOOT_ENTRYPOINT = "0x80080000" | 766 | UBOOT_ENTRYPOINT = "0x80080000" |
692 | FIT_DESC = "A model description" | 767 | FIT_DESC = "A model description" |
768 | FIT_CONF_PREFIX = "foo-" | ||
769 | """ | ||
770 | config = self._config_add_kernel_classes(config) | ||
771 | self.write_config(config) | ||
772 | bb_vars = self._fit_get_bb_vars() | ||
773 | self._test_fitimage(bb_vars) | ||
774 | |||
775 | def test_get_compatible_from_dtb(self): | ||
776 | """Test the oe.fitimage.get_compatible_from_dtb function | ||
777 | |||
778 | 1. bitbake bbb-dtbs-as-ext | ||
779 | 2. Check if symlink_points_below returns the path to the DTB | ||
780 | 3. Check if the expected compatible string is found by get_compatible_from_dtb() | ||
781 | """ | ||
782 | DTB_RECIPE = "bbb-dtbs-as-ext" | ||
783 | DTB_FILE = "am335x-bonegreen-ext.dtb" | ||
784 | DTB_SYMLINK = "am335x-bonegreen-ext-alias.dtb" | ||
785 | DTBO_FILE = "BBORG_RELAY-00A2.dtbo" | ||
786 | EXPECTED_COMP = ["ti,am335x-bone-green", "ti,am335x-bone-black", "ti,am335x-bone", "ti,am33xx"] | ||
787 | |||
788 | config = """ | ||
789 | DISTRO="poky" | ||
790 | MACHINE = "beaglebone-yocto" | ||
693 | """ | 791 | """ |
694 | self.write_config(config) | 792 | self.write_config(config) |
793 | |||
794 | # Provide the fdtget command called by get_compatible_from_dtb | ||
795 | dtc_bindir = FitImageTestCase._setup_native('dtc-native') | ||
796 | fdtget_path = os.path.join(dtc_bindir, "fdtget") | ||
797 | self.assertExists(fdtget_path) | ||
798 | |||
799 | # bitbake an external DTB with a symlink to it and a DTB overlay | ||
800 | bitbake(DTB_RECIPE) | ||
801 | deploy_dir_image = get_bb_var("DEPLOY_DIR_IMAGE", DTB_RECIPE) | ||
802 | devicetree_dir = os.path.join(deploy_dir_image, "devicetree") | ||
803 | dtb_path = os.path.join(devicetree_dir, DTB_FILE) | ||
804 | dtb_alias_path = os.path.join(devicetree_dir, DTB_SYMLINK) | ||
805 | dtbo_file = os.path.join(devicetree_dir, DTBO_FILE) | ||
806 | self.assertExists(dtb_path) | ||
807 | self.assertExists(dtb_alias_path) | ||
808 | self.assertExists(dtbo_file) | ||
809 | |||
810 | # Test symlink_points_below | ||
811 | linked_dtb = oe.fitimage.symlink_points_below(dtb_alias_path, devicetree_dir) | ||
812 | self.assertEqual(linked_dtb, DTB_FILE) | ||
813 | |||
814 | # Check if get_compatible_from_dtb finds the expected compatible string in the DTBs | ||
815 | comp = oe.fitimage.get_compatible_from_dtb(dtb_path, fdtget_path) | ||
816 | self.assertEqual(comp, EXPECTED_COMP) | ||
817 | comp_alias = oe.fitimage.get_compatible_from_dtb(dtb_alias_path, fdtget_path) | ||
818 | self.assertEqual(comp_alias, EXPECTED_COMP) | ||
819 | # The alias is a symlink, therefore the compatible string is equal | ||
820 | self.assertEqual(comp_alias, comp) | ||
821 | |||
822 | def test_fit_image_ext_dtb_dtbo(self): | ||
823 | """ | ||
824 | Summary: Check if FIT image and Image Tree Source (its) are created correctly. | ||
825 | Expected: 1) its and FIT image are built successfully | ||
826 | 2) The its file contains also the external devicetree overlay | ||
827 | 3) Dumping the FIT image indicates the devicetree overlay | ||
828 | """ | ||
829 | config = """ | ||
830 | # Enable creation of fitImage | ||
831 | MACHINE = "beaglebone-yocto" | ||
832 | # Add a devicetree overlay which does not need kernel sources | ||
833 | PREFERRED_PROVIDER_virtual/dtb = "bbb-dtbs-as-ext" | ||
834 | """ | ||
835 | config = self._config_add_kernel_classes(config) | ||
836 | config = self._config_add_uboot_env(config) | ||
837 | self.write_config(config) | ||
695 | bb_vars = self._fit_get_bb_vars() | 838 | bb_vars = self._fit_get_bb_vars() |
696 | self._test_fitimage(bb_vars) | 839 | self._test_fitimage(bb_vars) |
697 | 840 | ||
@@ -702,8 +845,7 @@ FIT_DESC = "A model description" | |||
702 | and the configuration nodes are signed correctly. | 845 | and the configuration nodes are signed correctly. |
703 | Expected: 1) its and FIT image are built successfully | 846 | Expected: 1) its and FIT image are built successfully |
704 | 2) Scanning the its file indicates signing is enabled | 847 | 2) Scanning the its file indicates signing is enabled |
705 | as requested by UBOOT_SIGN_ENABLE (using 1 key | 848 | as requested by UBOOT_SIGN_ENABLE |
706 | generated by the test not via FIT_GENERATE_KEYS) | ||
707 | 3) Dumping the FIT image indicates signature values | 849 | 3) Dumping the FIT image indicates signature values |
708 | are present (only for the configuration nodes as | 850 | are present (only for the configuration nodes as |
709 | FIT_SIGN_INDIVIDUAL is disabled) | 851 | FIT_SIGN_INDIVIDUAL is disabled) |
@@ -714,13 +856,13 @@ FIT_DESC = "A model description" | |||
714 | config = """ | 856 | config = """ |
715 | # Enable creation of fitImage | 857 | # Enable creation of fitImage |
716 | MACHINE = "beaglebone-yocto" | 858 | MACHINE = "beaglebone-yocto" |
717 | KERNEL_IMAGETYPES += " fitImage " | ||
718 | KERNEL_CLASSES = " kernel-fitimage " | ||
719 | UBOOT_SIGN_ENABLE = "1" | 859 | UBOOT_SIGN_ENABLE = "1" |
720 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | 860 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" |
721 | UBOOT_SIGN_KEYNAME = "dev" | 861 | UBOOT_SIGN_KEYNAME = "dev" |
722 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | 862 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" |
863 | FIT_CONF_DEFAULT_DTB = "am335x-bonegreen.dtb" | ||
723 | """ | 864 | """ |
865 | config = self._config_add_kernel_classes(config) | ||
724 | config = self._config_add_uboot_env(config) | 866 | config = self._config_add_uboot_env(config) |
725 | self.write_config(config) | 867 | self.write_config(config) |
726 | 868 | ||
@@ -733,10 +875,7 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | |||
733 | 'UBOOT_SIGN_KEYDIR', | 875 | 'UBOOT_SIGN_KEYDIR', |
734 | ]) | 876 | ]) |
735 | 877 | ||
736 | # Do not use the random keys generated by FIT_GENERATE_KEYS. | ||
737 | # Using a static key is probably a more realistic scenario. | ||
738 | self._gen_signing_key(bb_vars) | 878 | self._gen_signing_key(bb_vars) |
739 | |||
740 | self._test_fitimage(bb_vars) | 879 | self._test_fitimage(bb_vars) |
741 | 880 | ||
742 | def test_sign_fit_image_individual(self): | 881 | def test_sign_fit_image_individual(self): |
@@ -745,11 +884,11 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | |||
745 | and all nodes are signed correctly. | 884 | and all nodes are signed correctly. |
746 | Expected: 1) its and FIT image are built successfully | 885 | Expected: 1) its and FIT image are built successfully |
747 | 2) Scanning the its file indicates signing is enabled | 886 | 2) Scanning the its file indicates signing is enabled |
748 | as requested by UBOOT_SIGN_ENABLE (using 2 keys | 887 | as requested by UBOOT_SIGN_ENABLE |
749 | generated via FIT_GENERATE_KEYS) | ||
750 | 3) Dumping the FIT image indicates signature values | 888 | 3) Dumping the FIT image indicates signature values |
751 | are present (including for images as enabled via | 889 | are present (including for images as enabled via |
752 | FIT_SIGN_INDIVIDUAL) | 890 | FIT_SIGN_INDIVIDUAL) |
891 | This also implies that FIT_GENERATE_KEYS = "1" works. | ||
753 | 4) Verify the FIT image contains the comments passed via | 892 | 4) Verify the FIT image contains the comments passed via |
754 | UBOOT_MKIMAGE_SIGN_ARGS once per image and per | 893 | UBOOT_MKIMAGE_SIGN_ARGS once per image and per |
755 | configuration node. | 894 | configuration node. |
@@ -765,8 +904,6 @@ UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | |||
765 | config = """ | 904 | config = """ |
766 | # Enable creation of fitImage | 905 | # Enable creation of fitImage |
767 | MACHINE = "beaglebone-yocto" | 906 | MACHINE = "beaglebone-yocto" |
768 | KERNEL_IMAGETYPES += " fitImage " | ||
769 | KERNEL_CLASSES = " kernel-fitimage " | ||
770 | UBOOT_SIGN_ENABLE = "1" | 907 | UBOOT_SIGN_ENABLE = "1" |
771 | FIT_GENERATE_KEYS = "1" | 908 | FIT_GENERATE_KEYS = "1" |
772 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" | 909 | UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" |
@@ -775,9 +912,14 @@ UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | |||
775 | FIT_SIGN_INDIVIDUAL = "1" | 912 | FIT_SIGN_INDIVIDUAL = "1" |
776 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" | 913 | UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" |
777 | """ | 914 | """ |
915 | config = self._config_add_kernel_classes(config) | ||
778 | config = self._config_add_uboot_env(config) | 916 | config = self._config_add_uboot_env(config) |
779 | self.write_config(config) | 917 | self.write_config(config) |
780 | bb_vars = self._fit_get_bb_vars() | 918 | bb_vars = self._fit_get_bb_vars() |
919 | |||
920 | # Ensure new keys are generated and FIT_GENERATE_KEYS = "1" is tested | ||
921 | bitbake("kernel-signing-keys-native -c cleansstate") | ||
922 | |||
781 | self._test_fitimage(bb_vars) | 923 | self._test_fitimage(bb_vars) |
782 | 924 | ||
783 | def test_fit_image_sign_initramfs(self): | 925 | def test_fit_image_sign_initramfs(self): |
@@ -801,8 +943,6 @@ MACHINE = "beaglebone-yocto" | |||
801 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" | 943 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" |
802 | INITRAMFS_SCRIPTS = "" | 944 | INITRAMFS_SCRIPTS = "" |
803 | UBOOT_MACHINE = "am335x_evm_defconfig" | 945 | UBOOT_MACHINE = "am335x_evm_defconfig" |
804 | KERNEL_CLASSES = " kernel-fitimage " | ||
805 | KERNEL_IMAGETYPES = "fitImage" | ||
806 | UBOOT_SIGN_ENABLE = "1" | 946 | UBOOT_SIGN_ENABLE = "1" |
807 | UBOOT_SIGN_KEYNAME = "beaglebonekey" | 947 | UBOOT_SIGN_KEYNAME = "beaglebonekey" |
808 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" | 948 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" |
@@ -816,11 +956,11 @@ UBOOT_ARCH = "arm" | |||
816 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | 956 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" |
817 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" | 957 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" |
818 | UBOOT_EXTLINUX = "0" | 958 | UBOOT_EXTLINUX = "0" |
819 | FIT_GENERATE_KEYS = "1" | ||
820 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" | 959 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" |
821 | FIT_KERNEL_COMP_ALG = "none" | 960 | FIT_KERNEL_COMP_ALG = "none" |
822 | FIT_HASH_ALG = "sha256" | 961 | FIT_HASH_ALG = "sha256" |
823 | """ | 962 | """ |
963 | config = self._config_add_kernel_classes(config) | ||
824 | config = self._config_add_uboot_env(config) | 964 | config = self._config_add_uboot_env(config) |
825 | self.write_config(config) | 965 | self.write_config(config) |
826 | 966 | ||
@@ -833,10 +973,7 @@ FIT_HASH_ALG = "sha256" | |||
833 | 'UBOOT_SIGN_KEYDIR', | 973 | 'UBOOT_SIGN_KEYDIR', |
834 | ]) | 974 | ]) |
835 | 975 | ||
836 | # Do not use the random keys generated by FIT_GENERATE_KEYS. | ||
837 | # Using a static key is probably a more realistic scenario. | ||
838 | self._gen_signing_key(bb_vars) | 976 | self._gen_signing_key(bb_vars) |
839 | |||
840 | self._test_fitimage(bb_vars) | 977 | self._test_fitimage(bb_vars) |
841 | 978 | ||
842 | def test_fit_image_sign_initramfs_bundle(self): | 979 | def test_fit_image_sign_initramfs_bundle(self): |
@@ -861,8 +998,6 @@ INITRAMFS_IMAGE_BUNDLE = "1" | |||
861 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" | 998 | INITRAMFS_IMAGE = "core-image-minimal-initramfs" |
862 | INITRAMFS_SCRIPTS = "" | 999 | INITRAMFS_SCRIPTS = "" |
863 | UBOOT_MACHINE = "am335x_evm_defconfig" | 1000 | UBOOT_MACHINE = "am335x_evm_defconfig" |
864 | KERNEL_CLASSES = " kernel-fitimage " | ||
865 | KERNEL_IMAGETYPES = "fitImage" | ||
866 | UBOOT_SIGN_ENABLE = "1" | 1001 | UBOOT_SIGN_ENABLE = "1" |
867 | UBOOT_SIGN_KEYNAME = "beaglebonekey" | 1002 | UBOOT_SIGN_KEYNAME = "beaglebonekey" |
868 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" | 1003 | UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" |
@@ -874,20 +1009,124 @@ UBOOT_ARCH = "arm" | |||
874 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" | 1009 | UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" |
875 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" | 1010 | UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" |
876 | UBOOT_EXTLINUX = "0" | 1011 | UBOOT_EXTLINUX = "0" |
877 | FIT_GENERATE_KEYS = "1" | ||
878 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" | 1012 | KERNEL_IMAGETYPE_REPLACEMENT = "zImage" |
879 | FIT_KERNEL_COMP_ALG = "none" | 1013 | FIT_KERNEL_COMP_ALG = "none" |
880 | FIT_HASH_ALG = "sha256" | 1014 | FIT_HASH_ALG = "sha256" |
881 | """ | 1015 | """ |
1016 | config = self._config_add_kernel_classes(config) | ||
882 | config = self._config_add_uboot_env(config) | 1017 | config = self._config_add_uboot_env(config) |
883 | self.write_config(config) | 1018 | self.write_config(config) |
884 | bb_vars = self._fit_get_bb_vars() | 1019 | bb_vars = self._fit_get_bb_vars() |
1020 | self._gen_signing_key(bb_vars) | ||
885 | self._test_fitimage(bb_vars) | 1021 | self._test_fitimage(bb_vars) |
886 | 1022 | ||
1023 | class FitImagePyTests(KernelFitImageBase): | ||
1024 | """Test cases for the fitimage.py module without calling bitbake""" | ||
1025 | |||
1026 | def _test_fitimage_py(self, bb_vars_overrides=None): | ||
1027 | topdir = os.path.join(os.environ['BUILDDIR']) | ||
1028 | fitimage_its_path = os.path.join(topdir, self._testMethodName + '.its') | ||
1029 | |||
1030 | # Provide variables without calling bitbake | ||
1031 | bb_vars = { | ||
1032 | # image-fitimage.conf | ||
1033 | 'FIT_DESC': "Kernel fitImage for a dummy distro", | ||
1034 | 'FIT_HASH_ALG': "sha256", | ||
1035 | 'FIT_SIGN_ALG': "rsa2048", | ||
1036 | 'FIT_PAD_ALG': "pkcs-1.5", | ||
1037 | 'FIT_GENERATE_KEYS': "0", | ||
1038 | 'FIT_SIGN_NUMBITS': "2048", | ||
1039 | 'FIT_KEY_GENRSA_ARGS': "-F4", | ||
1040 | 'FIT_KEY_REQ_ARGS': "-batch -new", | ||
1041 | 'FIT_KEY_SIGN_PKCS': "-x509", | ||
1042 | 'FIT_SIGN_INDIVIDUAL': "0", | ||
1043 | 'FIT_CONF_PREFIX': "conf-", | ||
1044 | 'FIT_SUPPORTED_INITRAMFS_FSTYPES': "cpio.lz4 cpio.lzo cpio.lzma cpio.xz cpio.zst cpio.gz ext2.gz cpio", | ||
1045 | 'FIT_CONF_DEFAULT_DTB': "", | ||
1046 | 'FIT_ADDRESS_CELLS': "1", | ||
1047 | 'FIT_UBOOT_ENV': "", | ||
1048 | # kernel.bbclass | ||
1049 | 'UBOOT_ENTRYPOINT': "0x20008000", | ||
1050 | 'UBOOT_LOADADDRESS': "0x20008000", | ||
1051 | 'INITRAMFS_IMAGE': "", | ||
1052 | 'INITRAMFS_IMAGE_BUNDLE': "", | ||
1053 | # kernel-uboot.bbclass | ||
1054 | 'FIT_KERNEL_COMP_ALG': "gzip", | ||
1055 | 'FIT_KERNEL_COMP_ALG_EXTENSION': ".gz", | ||
1056 | 'UBOOT_MKIMAGE_KERNEL_TYPE': "kernel", | ||
1057 | # uboot-config.bbclass | ||
1058 | 'UBOOT_MKIMAGE_DTCOPTS': "", | ||
1059 | 'UBOOT_MKIMAGE': "uboot-mkimage", | ||
1060 | 'UBOOT_MKIMAGE_SIGN': "uboot-mkimage", | ||
1061 | 'UBOOT_MKIMAGE_SIGN_ARGS': "", | ||
1062 | 'UBOOT_SIGN_ENABLE': "0", | ||
1063 | 'UBOOT_SIGN_KEYDIR': None, | ||
1064 | 'UBOOT_SIGN_KEYNAME': None, | ||
1065 | 'UBOOT_SIGN_IMG_KEYNAME': None, | ||
1066 | # others | ||
1067 | 'MACHINE': "qemux86-64", | ||
1068 | 'UBOOT_ARCH': "x86", | ||
1069 | 'HOST_PREFIX': "x86_64-poky-linux-" | ||
1070 | } | ||
1071 | if bb_vars_overrides: | ||
1072 | bb_vars.update(bb_vars_overrides) | ||
1073 | |||
1074 | root_node = oe.fitimage.ItsNodeRootKernel( | ||
1075 | bb_vars["FIT_DESC"], bb_vars["FIT_ADDRESS_CELLS"], | ||
1076 | bb_vars['HOST_PREFIX'], bb_vars['UBOOT_ARCH'], bb_vars["FIT_CONF_PREFIX"], | ||
1077 | oe.types.boolean(bb_vars['UBOOT_SIGN_ENABLE']), bb_vars["UBOOT_SIGN_KEYDIR"], | ||
1078 | bb_vars["UBOOT_MKIMAGE"], bb_vars["UBOOT_MKIMAGE_DTCOPTS"], | ||
1079 | bb_vars["UBOOT_MKIMAGE_SIGN"], bb_vars["UBOOT_MKIMAGE_SIGN_ARGS"], | ||
1080 | bb_vars['FIT_HASH_ALG'], bb_vars['FIT_SIGN_ALG'], bb_vars['FIT_PAD_ALG'], | ||
1081 | bb_vars['UBOOT_SIGN_KEYNAME'], | ||
1082 | oe.types.boolean(bb_vars['FIT_SIGN_INDIVIDUAL']), bb_vars['UBOOT_SIGN_IMG_KEYNAME'] | ||
1083 | ) | ||
1084 | |||
1085 | root_node.fitimage_emit_section_kernel("kernel-1", "linux.bin", "none", | ||
1086 | bb_vars.get('UBOOT_LOADADDRESS'), bb_vars.get('UBOOT_ENTRYPOINT'), | ||
1087 | bb_vars.get('UBOOT_MKIMAGE_KERNEL_TYPE'), bb_vars.get("UBOOT_ENTRYSYMBOL") | ||
1088 | ) | ||
1089 | |||
1090 | dtb_files, _ = FitImageTestCase._get_dtb_files(bb_vars) | ||
1091 | for dtb in dtb_files: | ||
1092 | root_node.fitimage_emit_section_dtb(dtb, os.path.join("a-dir", dtb), | ||
1093 | bb_vars.get("UBOOT_DTB_LOADADDRESS"), bb_vars.get("UBOOT_DTBO_LOADADDRESS")) | ||
1094 | |||
1095 | if bb_vars.get('FIT_UBOOT_ENV'): | ||
1096 | root_node.fitimage_emit_section_boot_script( | ||
1097 | "bootscr-" + bb_vars['FIT_UBOOT_ENV'], bb_vars['FIT_UBOOT_ENV']) | ||
1098 | |||
1099 | if bb_vars['MACHINE'] == "qemux86-64": # Not really the right if | ||
1100 | root_node.fitimage_emit_section_setup("setup-1", "setup1.bin") | ||
1101 | |||
1102 | if bb_vars.get('INITRAMFS_IMAGE') and bb_vars.get("INITRAMFS_IMAGE_BUNDLE") != "1": | ||
1103 | root_node.fitimage_emit_section_ramdisk("ramdisk-1", "a-dir/a-initramfs-1", | ||
1104 | "core-image-minimal-initramfs", | ||
1105 | bb_vars.get("UBOOT_RD_LOADADDRESS"), bb_vars.get("UBOOT_RD_ENTRYPOINT")) | ||
1106 | |||
1107 | root_node.fitimage_emit_section_config(bb_vars['FIT_CONF_DEFAULT_DTB']) | ||
1108 | root_node.write_its_file(fitimage_its_path) | ||
1109 | |||
1110 | self.assertExists(fitimage_its_path, "%s image tree source doesn't exist" % (fitimage_its_path)) | ||
1111 | self.logger.debug("Checking its: %s" % fitimage_its_path) | ||
1112 | self._check_its_file(bb_vars, fitimage_its_path) | ||
1113 | |||
1114 | def test_fitimage_py_default(self): | ||
1115 | self._test_fitimage_py() | ||
1116 | |||
1117 | def test_fitimage_py_default_dtb(self): | ||
1118 | bb_vars_overrides = { | ||
1119 | 'KERNEL_DEVICETREE': "one.dtb two.dtb three.dtb", | ||
1120 | 'FIT_CONF_DEFAULT_DTB': "two.dtb" | ||
1121 | } | ||
1122 | self._test_fitimage_py(bb_vars_overrides) | ||
1123 | |||
887 | 1124 | ||
888 | class UBootFitImageTests(FitImageTestCase): | 1125 | class UBootFitImageTests(FitImageTestCase): |
889 | """Test cases for the uboot-sign bbclass""" | 1126 | """Test cases for the uboot-sign bbclass""" |
890 | 1127 | ||
1128 | BOOTLOADER_RECIPE = "virtual/bootloader" | ||
1129 | |||
891 | def _fit_get_bb_vars(self, additional_vars=[]): | 1130 | def _fit_get_bb_vars(self, additional_vars=[]): |
892 | """Get bb_vars as needed by _test_sign_fit_image | 1131 | """Get bb_vars as needed by _test_sign_fit_image |
893 | 1132 | ||
@@ -929,13 +1168,13 @@ class UBootFitImageTests(FitImageTestCase): | |||
929 | 'UBOOT_SIGN_KEYDIR', | 1168 | 'UBOOT_SIGN_KEYDIR', |
930 | 'UBOOT_SIGN_KEYNAME', | 1169 | 'UBOOT_SIGN_KEYNAME', |
931 | } | 1170 | } |
932 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), "virtual/bootloader") | 1171 | bb_vars = get_bb_vars(list(internal_used | set(additional_vars)), UBootFitImageTests.BOOTLOADER_RECIPE) |
933 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) | 1172 | self.logger.debug("bb_vars: %s" % pprint.pformat(bb_vars, indent=4)) |
934 | return bb_vars | 1173 | return bb_vars |
935 | 1174 | ||
936 | def _bitbake_fit_image(self, bb_vars): | 1175 | def _bitbake_fit_image(self, bb_vars): |
937 | """Bitbake the bootloader and return the paths to the its file and the FIT image""" | 1176 | """Bitbake the bootloader and return the paths to the its file and the FIT image""" |
938 | bitbake("virtual/bootloader") | 1177 | bitbake(UBootFitImageTests.BOOTLOADER_RECIPE) |
939 | 1178 | ||
940 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] | 1179 | deploy_dir_image = bb_vars['DEPLOY_DIR_IMAGE'] |
941 | machine = bb_vars['MACHINE'] | 1180 | machine = bb_vars['MACHINE'] |
@@ -1286,9 +1525,7 @@ UBOOT_SIGN_KEYNAME = "cfg-oe-selftest" | |||
1286 | self.write_config(config) | 1525 | self.write_config(config) |
1287 | bb_vars = self._fit_get_bb_vars() | 1526 | bb_vars = self._fit_get_bb_vars() |
1288 | 1527 | ||
1289 | # Using a static key. FIT_GENERATE_KEYS = "1" does not work without kernel-fitimage.bbclass | ||
1290 | self._gen_signing_key(bb_vars) | 1528 | self._gen_signing_key(bb_vars) |
1291 | |||
1292 | self._test_fitimage(bb_vars) | 1529 | self._test_fitimage(bb_vars) |
1293 | self._check_kernel_dtb(bb_vars) | 1530 | self._check_kernel_dtb(bb_vars) |
1294 | 1531 | ||
@@ -1449,11 +1686,9 @@ FIT_SIGN_INDIVIDUAL = "1" | |||
1449 | """ | 1686 | """ |
1450 | self.write_config(config) | 1687 | self.write_config(config) |
1451 | bb_vars = self._fit_get_bb_vars() | 1688 | bb_vars = self._fit_get_bb_vars() |
1452 | |||
1453 | # Using a static key. FIT_GENERATE_KEYS = "1" does not work without kernel-fitimage.bbclass | ||
1454 | self._gen_signing_key(bb_vars) | 1689 | self._gen_signing_key(bb_vars) |
1455 | 1690 | ||
1456 | bitbake("virtual/bootloader") | 1691 | bitbake(UBootFitImageTests.BOOTLOADER_RECIPE) |
1457 | 1692 | ||
1458 | # Just check the DTB of u-boot since there is no u-boot FIT image | 1693 | # Just check the DTB of u-boot since there is no u-boot FIT image |
1459 | self._check_kernel_dtb(bb_vars) | 1694 | self._check_kernel_dtb(bb_vars) |
diff --git a/meta/lib/oeqa/selftest/cases/liboe.py b/meta/lib/oeqa/selftest/cases/liboe.py index d5ffffdcb4..930354c931 100644 --- a/meta/lib/oeqa/selftest/cases/liboe.py +++ b/meta/lib/oeqa/selftest/cases/liboe.py | |||
@@ -9,11 +9,11 @@ from oeqa.utils.commands import get_bb_var, get_bb_vars, bitbake, runCmd | |||
9 | import oe.path | 9 | import oe.path |
10 | import os | 10 | import os |
11 | 11 | ||
12 | class LibOE(OESelftestTestCase): | 12 | class CopyTreeTests(OESelftestTestCase): |
13 | 13 | ||
14 | @classmethod | 14 | @classmethod |
15 | def setUpClass(cls): | 15 | def setUpClass(cls): |
16 | super(LibOE, cls).setUpClass() | 16 | super().setUpClass() |
17 | cls.tmp_dir = get_bb_var('TMPDIR') | 17 | cls.tmp_dir = get_bb_var('TMPDIR') |
18 | 18 | ||
19 | def test_copy_tree_special(self): | 19 | def test_copy_tree_special(self): |
@@ -102,3 +102,36 @@ class LibOE(OESelftestTestCase): | |||
102 | self.assertEqual(dstcnt, len(testfiles), "Number of files in dst (%s) differs from number of files in src(%s)." % (dstcnt, srccnt)) | 102 | self.assertEqual(dstcnt, len(testfiles), "Number of files in dst (%s) differs from number of files in src(%s)." % (dstcnt, srccnt)) |
103 | 103 | ||
104 | oe.path.remove(testloc) | 104 | oe.path.remove(testloc) |
105 | |||
106 | class SubprocessTests(OESelftestTestCase): | ||
107 | |||
108 | def test_subprocess_tweak(self): | ||
109 | """ | ||
110 | Test that the string representation of | ||
111 | oeqa.utils.subprocesstweak.OETestCalledProcessError includes stdout and | ||
112 | stderr, as expected. | ||
113 | """ | ||
114 | script = """ | ||
115 | #! /bin/sh | ||
116 | echo Ivn fgqbhg | tr '[a-zA-Z]' '[n-za-mN-ZA-M]' | ||
117 | echo Ivn fgqree | tr '[a-zA-Z]' '[n-za-mN-ZA-M]' >&2 | ||
118 | exit 42 | ||
119 | """ | ||
120 | |||
121 | import subprocess | ||
122 | import unittest.mock | ||
123 | from oeqa.utils.subprocesstweak import OETestCalledProcessError | ||
124 | |||
125 | with self.assertRaises(OETestCalledProcessError) as cm: | ||
126 | with unittest.mock.patch("subprocess.CalledProcessError", OETestCalledProcessError): | ||
127 | subprocess.run(["bash", "-"], input=script, text=True, capture_output=True, check=True) | ||
128 | |||
129 | e = cm.exception | ||
130 | self.assertEqual(e.returncode, 42) | ||
131 | self.assertEqual("Via stdout\n", e.stdout) | ||
132 | self.assertEqual("Via stderr\n", e.stderr) | ||
133 | |||
134 | string = str(e) | ||
135 | self.assertIn("exit status 42", string) | ||
136 | self.assertIn("Standard Output: Via stdout", string) | ||
137 | self.assertIn("Standard Error: Via stderr", string) | ||
diff --git a/meta/lib/oeqa/selftest/cases/uboot.py b/meta/lib/oeqa/selftest/cases/uboot.py index 96da4efb06..980ea327f0 100644 --- a/meta/lib/oeqa/selftest/cases/uboot.py +++ b/meta/lib/oeqa/selftest/cases/uboot.py | |||
@@ -6,8 +6,8 @@ | |||
6 | # | 6 | # |
7 | 7 | ||
8 | from oeqa.selftest.case import OESelftestTestCase | 8 | from oeqa.selftest.case import OESelftestTestCase |
9 | from oeqa.utils.commands import bitbake, runqemu | 9 | from oeqa.utils.commands import bitbake, runqemu, get_bb_var, get_bb_vars, runCmd |
10 | from oeqa.core.decorator.data import skipIfNotArch | 10 | from oeqa.core.decorator.data import skipIfNotArch, skipIfNotBuildArch |
11 | from oeqa.core.decorator import OETestTag | 11 | from oeqa.core.decorator import OETestTag |
12 | 12 | ||
13 | uboot_boot_patterns = { | 13 | uboot_boot_patterns = { |
@@ -41,3 +41,58 @@ QEMU_USE_KVM = "False" | |||
41 | status, output = qemu.run_serial(cmd) | 41 | status, output = qemu.run_serial(cmd) |
42 | self.assertEqual(status, 1, msg=output) | 42 | self.assertEqual(status, 1, msg=output) |
43 | self.assertTrue("U-Boot" in output, msg=output) | 43 | self.assertTrue("U-Boot" in output, msg=output) |
44 | |||
45 | @skipIfNotArch(['aarch64']) | ||
46 | @skipIfNotBuildArch(['aarch64']) | ||
47 | @OETestTag("runqemu") | ||
48 | def test_boot_uboot_kvm_to_full_target(self): | ||
49 | """ | ||
50 | Tests building u-boot and booting it with QEMU and KVM. | ||
51 | Requires working KVM on build host. See "kvm-ok" output. | ||
52 | """ | ||
53 | |||
54 | runCmd("kvm-ok") | ||
55 | |||
56 | image = "core-image-minimal" | ||
57 | vars = get_bb_vars(['HOST_ARCH', 'BUILD_ARCH'], image) | ||
58 | host_arch = vars['HOST_ARCH'] | ||
59 | build_arch = vars['BUILD_ARCH'] | ||
60 | |||
61 | self.assertEqual(host_arch, build_arch, 'HOST_ARCH %s and BUILD_ARCH %s must match for KVM' % (host_arch, build_arch)) | ||
62 | |||
63 | self.write_config(""" | ||
64 | QEMU_USE_KVM = "1" | ||
65 | |||
66 | # Using u-boot in EFI mode, need ESP partition for grub/systemd-boot/kernel etc | ||
67 | IMAGE_FSTYPES:pn-core-image-minimal:append = " wic" | ||
68 | |||
69 | # easiest to follow genericarm64 setup with wks file, initrd and EFI loader | ||
70 | INITRAMFS_IMAGE = "core-image-initramfs-boot" | ||
71 | EFI_PROVIDER = "${@bb.utils.contains("DISTRO_FEATURES", "systemd", "systemd-boot", "grub-efi", d)}" | ||
72 | WKS_FILE = "genericarm64.wks.in" | ||
73 | |||
74 | # use wic image with ESP for u-boot, not ext4 | ||
75 | QB_DEFAULT_FSTYPE = "wic" | ||
76 | |||
77 | PREFERRED_PROVIDER_virtual/bootloader = "u-boot" | ||
78 | QB_DEFAULT_BIOS = "u-boot.bin" | ||
79 | |||
80 | # let u-boot or EFI loader load kernel from ESP | ||
81 | QB_DEFAULT_KERNEL = "none" | ||
82 | |||
83 | # virt pci, not scsi because support not in u-boot to find ESP | ||
84 | QB_DRIVE_TYPE = "/dev/vd" | ||
85 | """) | ||
86 | bitbake("virtual/bootloader %s" % image) | ||
87 | |||
88 | runqemu_params = get_bb_var('TEST_RUNQEMUPARAMS', image) or "" | ||
89 | with runqemu(image, ssh=False, runqemuparams='nographic kvm %s' % runqemu_params) as qemu: | ||
90 | |||
91 | # boot to target and login worked, should have been fast with kvm | ||
92 | cmd = "dmesg" | ||
93 | status, output = qemu.run_serial(cmd) | ||
94 | self.assertEqual(status, 1, msg=output) | ||
95 | # Machine is qemu | ||
96 | self.assertTrue("Machine model: linux,dummy-virt" in output, msg=output) | ||
97 | # with KVM enabled | ||
98 | self.assertTrue("KVM: hypervisor services detected" in output, msg=output) | ||
diff --git a/meta/lib/oeqa/utils/sshcontrol.py b/meta/lib/oeqa/utils/sshcontrol.py index 6c5648779a..88a61aff63 100644 --- a/meta/lib/oeqa/utils/sshcontrol.py +++ b/meta/lib/oeqa/utils/sshcontrol.py | |||
@@ -58,7 +58,7 @@ class SSHProcess(object): | |||
58 | data = os.read(self.process.stdout.fileno(), 1024) | 58 | data = os.read(self.process.stdout.fileno(), 1024) |
59 | if not data: | 59 | if not data: |
60 | self.process.poll() | 60 | self.process.poll() |
61 | if self.process.returncode is None: | 61 | if self.process.returncode is not None: |
62 | self.process.stdout.close() | 62 | self.process.stdout.close() |
63 | eof = True | 63 | eof = True |
64 | else: | 64 | else: |
diff --git a/meta/lib/oeqa/utils/subprocesstweak.py b/meta/lib/oeqa/utils/subprocesstweak.py index 3e43ed547b..1774513023 100644 --- a/meta/lib/oeqa/utils/subprocesstweak.py +++ b/meta/lib/oeqa/utils/subprocesstweak.py | |||
@@ -8,16 +8,11 @@ import subprocess | |||
8 | class OETestCalledProcessError(subprocess.CalledProcessError): | 8 | class OETestCalledProcessError(subprocess.CalledProcessError): |
9 | def __str__(self): | 9 | def __str__(self): |
10 | def strify(o): | 10 | def strify(o): |
11 | if isinstance(o, bytes): | 11 | return o.decode("utf-8", errors="replace") if isinstance(o, bytes) else o |
12 | return o.decode("utf-8", errors="replace") | ||
13 | else: | ||
14 | return o | ||
15 | 12 | ||
16 | s = "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode) | 13 | s = super().__str__() |
17 | if hasattr(self, "output") and self.output: | 14 | s = s + "\nStandard Output: " + strify(self.output) |
18 | s = s + "\nStandard Output: " + strify(self.output) | 15 | s = s + "\nStandard Error: " + strify(self.stderr) |
19 | if hasattr(self, "stderr") and self.stderr: | ||
20 | s = s + "\nStandard Error: " + strify(self.stderr) | ||
21 | return s | 16 | return s |
22 | 17 | ||
23 | def errors_have_output(): | 18 | def errors_have_output(): |