1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013, Google Inc.
4 # Sanity check of the FIT handling in U-Boot
6 from __future__ import print_function
11 import u_boot_utils as util
13 # Define a base ITS which we can adjust using % and a dictionary
18 description = "Chrome OS kernel image with one or more FDT blobs";
23 data = /incbin/("%(kernel)s");
27 compression = "%(compression)s";
32 data = /incbin/("%(loadables1)s");
42 data = /incbin/("%(fdt)s");
46 compression = "%(compression)s";
48 algo = "sha1,rsa2048";
49 key-name-hint = "dev";
54 data = /incbin/("%(ramdisk)s");
59 compression = "%(compression)s";
63 data = /incbin/("%(loadables2)s");
83 # Define a base FDT - currently we don't use anything in this
88 model = "Sandbox Verified Boot Test";
89 compatible = "sandbox";
92 compatible = "sandbox,reset";
98 # This is the U-Boot script that is run for each test. First load the FIT,
99 # then run the 'bootm' command, then save out memory from the places where
100 # we expect 'bootm' to write things. Then quit.
102 host load hostfs 0 %(fit_addr)x %(fit)s
103 fdt addr %(fit_addr)x
104 bootm start %(fit_addr)x
106 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
107 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
108 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
109 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
110 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
113 @pytest.mark.boardspec('sandbox')
114 @pytest.mark.buildconfigspec('fit_signature')
115 @pytest.mark.requiredtool('dtc')
116 def test_fit(u_boot_console):
117 def make_fname(leaf):
118 """Make a temporary filename
121 leaf: Leaf name of file to create (within temporary directory)
126 return os.path.join(cons.config.build_dir, leaf)
129 """Get the size of a file
132 fname: Filename to check
134 Size of file in bytes
136 return os.stat(fname).st_size
138 def read_file(fname):
139 """Read the contents of a file
142 fname: Filename to read
144 Contents of file as a string
146 with open(fname, 'rb') as fd:
150 """Make a sample .dts file and compile it to a .dtb
153 Filename of .dtb file created
155 src = make_fname('u-boot.dts')
156 dtb = make_fname('u-boot.dtb')
157 with open(src, 'w') as fd:
158 print(base_fdt, file=fd)
159 util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
162 def make_its(params):
163 """Make a sample .its file with parameters embedded
166 params: Dictionary containing parameters to embed in the %() strings
168 Filename of .its file created
170 its = make_fname('test.its')
171 with open(its, 'w') as fd:
172 print(base_its % params, file=fd)
175 def make_fit(mkimage, params):
176 """Make a sample .fit file ready for loading
178 This creates a .its script with the selected parameters and uses mkimage to
179 turn this into a .fit image.
182 mkimage: Filename of 'mkimage' utility
183 params: Dictionary containing parameters to embed in the %() strings
185 Filename of .fit file created
187 fit = make_fname('test.fit')
188 its = make_its(params)
189 util.run_and_log(cons, [mkimage, '-f', its, fit])
190 with open(make_fname('u-boot.dts'), 'w') as fd:
191 print(base_fdt, file=fd)
194 def make_kernel(filename, text):
195 """Make a sample kernel with test data
198 filename: the name of the file you want to create
200 Full path and filename of the kernel it created
202 fname = make_fname(filename)
205 data += 'this %s %d is unlikely to boot\n' % (text, i)
206 with open(fname, 'w') as fd:
210 def make_ramdisk(filename, text):
211 """Make a sample ramdisk with test data
214 Filename of ramdisk created
216 fname = make_fname(filename)
219 data += '%s %d was seldom used in the middle ages\n' % (text, i)
220 with open(fname, 'w') as fd:
224 def make_compressed(filename):
225 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
226 return filename + '.gz'
228 def find_matching(text, match):
229 """Find a match in a line of text, and return the unmatched line portion
231 This is used to extract a part of a line from some text. The match string
232 is used to locate the line - we use the first line that contains that
235 Once we find a match, we discard the match string itself from the line,
236 and return what remains.
238 TODO: If this function becomes more generally useful, we could change it
239 to use regex and return groups.
242 text: Text to check (list of strings, one for each command issued)
243 match: String to search for
245 String containing unmatched portion of line
247 ValueError: If match is not found
249 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
251 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
252 Traceback (most recent call last):
254 ValueError: Test aborted
255 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
257 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
261 __tracebackhide__ = True
262 for line in '\n'.join(text).splitlines():
263 pos = line.find(match)
265 return line[:pos] + line[pos + len(match):]
267 pytest.fail("Expected '%s' but not found in output")
269 def check_equal(expected_fname, actual_fname, failure_msg):
270 """Check that a file matches its expected contents
272 This is always used on out-buffers whose size is decided by the test
273 script anyway, which in some cases may be larger than what we're
274 actually looking for. So it's safe to truncate it to the size of the
278 expected_fname: Filename containing expected contents
279 actual_fname: Filename containing actual contents
280 failure_msg: Message to print on failure
282 expected_data = read_file(expected_fname)
283 actual_data = read_file(actual_fname)
284 if len(expected_data) < len(actual_data):
285 actual_data = actual_data[:len(expected_data)]
286 assert expected_data == actual_data, failure_msg
288 def check_not_equal(expected_fname, actual_fname, failure_msg):
289 """Check that a file does not match its expected contents
292 expected_fname: Filename containing expected contents
293 actual_fname: Filename containing actual contents
294 failure_msg: Message to print on failure
296 expected_data = read_file(expected_fname)
297 actual_data = read_file(actual_fname)
298 assert expected_data != actual_data, failure_msg
300 def run_fit_test(mkimage):
301 """Basic sanity check of FIT loading in U-Boot
303 TODO: Almost everything:
304 - hash algorithms - invalid hash/contents should be detected
305 - signature algorithms - invalid sig/contents should be detected
307 - checking that errors are detected like:
310 - invalid configurations
311 - incorrect os/arch/type fields
313 - images too large/small
314 - invalid FDT (e.g. putting a random binary in instead)
315 - default configuration selection
316 - bootm command line parameters should have desired effect
317 - run code coverage to make sure we are testing all the code
319 # Set up invariant files
320 control_dtb = make_dtb()
321 kernel = make_kernel('test-kernel.bin', 'kernel')
322 ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
323 loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
324 loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
325 kernel_out = make_fname('kernel-out.bin')
326 fdt = make_fname('u-boot.dtb')
327 fdt_out = make_fname('fdt-out.dtb')
328 ramdisk_out = make_fname('ramdisk-out.bin')
329 loadables1_out = make_fname('loadables1-out.bin')
330 loadables2_out = make_fname('loadables2-out.bin')
332 # Set up basic parameters with default values
337 'kernel_out' : kernel_out,
338 'kernel_addr' : 0x40000,
339 'kernel_size' : filesize(kernel),
343 'fdt_addr' : 0x80000,
344 'fdt_size' : filesize(control_dtb),
348 'ramdisk_out' : ramdisk_out,
349 'ramdisk_addr' : 0xc0000,
350 'ramdisk_size' : filesize(ramdisk),
352 'ramdisk_config' : '',
354 'loadables1' : loadables1,
355 'loadables1_out' : loadables1_out,
356 'loadables1_addr' : 0x100000,
357 'loadables1_size' : filesize(loadables1),
358 'loadables1_load' : '',
360 'loadables2' : loadables2,
361 'loadables2_out' : loadables2_out,
362 'loadables2_addr' : 0x140000,
363 'loadables2_size' : filesize(loadables2),
364 'loadables2_load' : '',
366 'loadables_config' : '',
367 'compression' : 'none',
370 # Make a basic FIT and a script to load it
371 fit = make_fit(mkimage, params)
373 cmd = base_script % params
375 # First check that we can load a kernel
376 # We could perhaps reduce duplication with some loss of readability
377 cons.config.dtb = control_dtb
379 with cons.log.section('Kernel load'):
380 output = cons.run_command_list(cmd.splitlines())
381 check_equal(kernel, kernel_out, 'Kernel not loaded')
382 check_not_equal(control_dtb, fdt_out,
383 'FDT loaded but should be ignored')
384 check_not_equal(ramdisk, ramdisk_out,
385 'Ramdisk loaded but should not be')
387 # Find out the offset in the FIT where U-Boot has found the FDT
388 line = find_matching(output, 'Booting using the fdt blob at ')
389 fit_offset = int(line, 16) - params['fit_addr']
390 fdt_magic = struct.pack('>L', 0xd00dfeed)
391 data = read_file(fit)
393 # Now find where it actually is in the FIT (skip the first word)
394 real_fit_offset = data.find(fdt_magic, 4)
395 assert fit_offset == real_fit_offset, (
396 'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
397 (fit_offset, real_fit_offset))
399 # Now a kernel and an FDT
400 with cons.log.section('Kernel + FDT load'):
401 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
402 fit = make_fit(mkimage, params)
404 output = cons.run_command_list(cmd.splitlines())
405 check_equal(kernel, kernel_out, 'Kernel not loaded')
406 check_equal(control_dtb, fdt_out, 'FDT not loaded')
407 check_not_equal(ramdisk, ramdisk_out,
408 'Ramdisk loaded but should not be')
411 with cons.log.section('Kernel + FDT + Ramdisk load'):
412 params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
413 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
414 fit = make_fit(mkimage, params)
416 output = cons.run_command_list(cmd.splitlines())
417 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
419 # Configuration with some Loadables
420 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
421 params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
422 params['loadables1_load'] = ('load = <%#x>;' %
423 params['loadables1_addr'])
424 params['loadables2_load'] = ('load = <%#x>;' %
425 params['loadables2_addr'])
426 fit = make_fit(mkimage, params)
428 output = cons.run_command_list(cmd.splitlines())
429 check_equal(loadables1, loadables1_out,
430 'Loadables1 (kernel) not loaded')
431 check_equal(loadables2, loadables2_out,
432 'Loadables2 (ramdisk) not loaded')
434 # Kernel, FDT and Ramdisk all compressed
435 with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
436 params['compression'] = 'gzip'
437 params['kernel'] = make_compressed(kernel)
438 params['fdt'] = make_compressed(fdt)
439 params['ramdisk'] = make_compressed(ramdisk)
440 fit = make_fit(mkimage, params)
442 output = cons.run_command_list(cmd.splitlines())
443 check_equal(kernel, kernel_out, 'Kernel not loaded')
444 check_equal(control_dtb, fdt_out, 'FDT not loaded')
445 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
446 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
449 cons = u_boot_console
451 # We need to use our own device tree file. Remember to restore it
453 old_dtb = cons.config.dtb
454 mkimage = cons.config.build_dir + '/tools/mkimage'
455 run_fit_test(mkimage)
457 # Go back to the original U-Boot with the correct dtb.
458 cons.config.dtb = old_dtb