summaryrefslogtreecommitdiffstats
path: root/meta/lib
diff options
context:
space:
mode:
Diffstat (limited to 'meta/lib')
-rw-r--r--meta/lib/oe/fitimage.py547
-rw-r--r--meta/lib/oeqa/core/decorator/data.py12
-rw-r--r--meta/lib/oeqa/files/maturin/guessing-game/Cargo.toml2
-rw-r--r--meta/lib/oeqa/selftest/cases/fitimage.py365
-rw-r--r--meta/lib/oeqa/selftest/cases/liboe.py37
-rw-r--r--meta/lib/oeqa/selftest/cases/uboot.py59
-rw-r--r--meta/lib/oeqa/utils/sshcontrol.py2
-rw-r--r--meta/lib/oeqa/utils/subprocesstweak.py13
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
8import os
9import shlex
10import subprocess
11import bb
12
13from oeqa.utils.commands import runCmd
14
15class 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
73class ItsNodeImages(ItsNode):
74 def __init__(self, parent_node):
75 super().__init__("images", parent_node)
76
77class ItsNodeConfigurations(ItsNode):
78 def __init__(self, parent_node):
79 super().__init__("configurations", parent_node)
80
81class 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
90class 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
100class 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
111class 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
117class 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
128class 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
138class 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
147class 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
527def 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
539def 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
233class 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"]
14rand = "0.8.4" 14rand = "0.8.4"
15 15
16[dependencies.pyo3] 16[dependencies.pyo3]
17version = "0.19.0" 17version = "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
19features = ["abi3-py38"] 19features = ["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
7from oeqa.selftest.case import OESelftestTestCase
8from oeqa.utils.commands import runCmd, bitbake, get_bb_vars
9import os 7import os
10import re 8import re
11import shlex 9import shlex
12import logging 10import logging
13import pprint 11import pprint
12import tempfile
13
14import oe.fitimage
15
16from oeqa.selftest.case import OESelftestTestCase
17from oeqa.utils.commands import runCmd, bitbake, get_bb_vars, get_bb_var
18
19
20class 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
15class FitImageTestCase(OESelftestTestCase): 38class 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 395class KernelFitImageBase(FitImageTestCase):
360class 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
739class 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
679KERNEL_IMAGETYPE = "Image" 756KERNEL_IMAGETYPE = "Image"
680KERNEL_IMAGETYPES += " fitImage "
681KERNEL_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
684IMAGE_FSTYPES += "cpio.gz" 759IMAGE_FSTYPES += "cpio.gz"
@@ -690,8 +765,76 @@ UBOOT_RD_ENTRYPOINT = "0x88000000"
690UBOOT_LOADADDRESS = "0x80080000" 765UBOOT_LOADADDRESS = "0x80080000"
691UBOOT_ENTRYPOINT = "0x80080000" 766UBOOT_ENTRYPOINT = "0x80080000"
692FIT_DESC = "A model description" 767FIT_DESC = "A model description"
768FIT_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 = """
789DISTRO="poky"
790MACHINE = "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
831MACHINE = "beaglebone-yocto"
832# Add a devicetree overlay which does not need kernel sources
833PREFERRED_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
716MACHINE = "beaglebone-yocto" 858MACHINE = "beaglebone-yocto"
717KERNEL_IMAGETYPES += " fitImage "
718KERNEL_CLASSES = " kernel-fitimage "
719UBOOT_SIGN_ENABLE = "1" 859UBOOT_SIGN_ENABLE = "1"
720UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" 860UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
721UBOOT_SIGN_KEYNAME = "dev" 861UBOOT_SIGN_KEYNAME = "dev"
722UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" 862UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'"
863FIT_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
767MACHINE = "beaglebone-yocto" 906MACHINE = "beaglebone-yocto"
768KERNEL_IMAGETYPES += " fitImage "
769KERNEL_CLASSES = " kernel-fitimage "
770UBOOT_SIGN_ENABLE = "1" 907UBOOT_SIGN_ENABLE = "1"
771FIT_GENERATE_KEYS = "1" 908FIT_GENERATE_KEYS = "1"
772UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys" 909UBOOT_SIGN_KEYDIR = "${TOPDIR}/signing-keys"
@@ -775,9 +912,14 @@ UBOOT_SIGN_KEYNAME = "cfg-oe-selftest"
775FIT_SIGN_INDIVIDUAL = "1" 912FIT_SIGN_INDIVIDUAL = "1"
776UBOOT_MKIMAGE_SIGN_ARGS = "-c 'a smart comment'" 913UBOOT_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"
801INITRAMFS_IMAGE = "core-image-minimal-initramfs" 943INITRAMFS_IMAGE = "core-image-minimal-initramfs"
802INITRAMFS_SCRIPTS = "" 944INITRAMFS_SCRIPTS = ""
803UBOOT_MACHINE = "am335x_evm_defconfig" 945UBOOT_MACHINE = "am335x_evm_defconfig"
804KERNEL_CLASSES = " kernel-fitimage "
805KERNEL_IMAGETYPES = "fitImage"
806UBOOT_SIGN_ENABLE = "1" 946UBOOT_SIGN_ENABLE = "1"
807UBOOT_SIGN_KEYNAME = "beaglebonekey" 947UBOOT_SIGN_KEYNAME = "beaglebonekey"
808UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" 948UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
@@ -816,11 +956,11 @@ UBOOT_ARCH = "arm"
816UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 956UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
817UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" 957UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
818UBOOT_EXTLINUX = "0" 958UBOOT_EXTLINUX = "0"
819FIT_GENERATE_KEYS = "1"
820KERNEL_IMAGETYPE_REPLACEMENT = "zImage" 959KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
821FIT_KERNEL_COMP_ALG = "none" 960FIT_KERNEL_COMP_ALG = "none"
822FIT_HASH_ALG = "sha256" 961FIT_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"
861INITRAMFS_IMAGE = "core-image-minimal-initramfs" 998INITRAMFS_IMAGE = "core-image-minimal-initramfs"
862INITRAMFS_SCRIPTS = "" 999INITRAMFS_SCRIPTS = ""
863UBOOT_MACHINE = "am335x_evm_defconfig" 1000UBOOT_MACHINE = "am335x_evm_defconfig"
864KERNEL_CLASSES = " kernel-fitimage "
865KERNEL_IMAGETYPES = "fitImage"
866UBOOT_SIGN_ENABLE = "1" 1001UBOOT_SIGN_ENABLE = "1"
867UBOOT_SIGN_KEYNAME = "beaglebonekey" 1002UBOOT_SIGN_KEYNAME = "beaglebonekey"
868UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}" 1003UBOOT_SIGN_KEYDIR ?= "${DEPLOY_DIR_IMAGE}"
@@ -874,20 +1009,124 @@ UBOOT_ARCH = "arm"
874UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000" 1009UBOOT_MKIMAGE_DTCOPTS = "-I dts -O dtb -p 2000"
875UBOOT_MKIMAGE_KERNEL_TYPE = "kernel" 1010UBOOT_MKIMAGE_KERNEL_TYPE = "kernel"
876UBOOT_EXTLINUX = "0" 1011UBOOT_EXTLINUX = "0"
877FIT_GENERATE_KEYS = "1"
878KERNEL_IMAGETYPE_REPLACEMENT = "zImage" 1012KERNEL_IMAGETYPE_REPLACEMENT = "zImage"
879FIT_KERNEL_COMP_ALG = "none" 1013FIT_KERNEL_COMP_ALG = "none"
880FIT_HASH_ALG = "sha256" 1014FIT_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
1023class 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
888class UBootFitImageTests(FitImageTestCase): 1125class 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
9import oe.path 9import oe.path
10import os 10import os
11 11
12class LibOE(OESelftestTestCase): 12class 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
106class 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
116echo Ivn fgqbhg | tr '[a-zA-Z]' '[n-za-mN-ZA-M]'
117echo Ivn fgqree | tr '[a-zA-Z]' '[n-za-mN-ZA-M]' >&2
118exit 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
8from oeqa.selftest.case import OESelftestTestCase 8from oeqa.selftest.case import OESelftestTestCase
9from oeqa.utils.commands import bitbake, runqemu 9from oeqa.utils.commands import bitbake, runqemu, get_bb_var, get_bb_vars, runCmd
10from oeqa.core.decorator.data import skipIfNotArch 10from oeqa.core.decorator.data import skipIfNotArch, skipIfNotBuildArch
11from oeqa.core.decorator import OETestTag 11from oeqa.core.decorator import OETestTag
12 12
13uboot_boot_patterns = { 13uboot_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("""
64QEMU_USE_KVM = "1"
65
66# Using u-boot in EFI mode, need ESP partition for grub/systemd-boot/kernel etc
67IMAGE_FSTYPES:pn-core-image-minimal:append = " wic"
68
69# easiest to follow genericarm64 setup with wks file, initrd and EFI loader
70INITRAMFS_IMAGE = "core-image-initramfs-boot"
71EFI_PROVIDER = "${@bb.utils.contains("DISTRO_FEATURES", "systemd", "systemd-boot", "grub-efi", d)}"
72WKS_FILE = "genericarm64.wks.in"
73
74# use wic image with ESP for u-boot, not ext4
75QB_DEFAULT_FSTYPE = "wic"
76
77PREFERRED_PROVIDER_virtual/bootloader = "u-boot"
78QB_DEFAULT_BIOS = "u-boot.bin"
79
80# let u-boot or EFI loader load kernel from ESP
81QB_DEFAULT_KERNEL = "none"
82
83# virt pci, not scsi because support not in u-boot to find ESP
84QB_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
8class OETestCalledProcessError(subprocess.CalledProcessError): 8class 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
23def errors_have_output(): 18def errors_have_output():