1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013, Google Inc.
4 # Sanity check of the FIT handling in U-Boot
9 import u_boot_utils as util
11 # Define a base ITS which we can adjust using % and a dictionary
16 description = "Chrome OS kernel image with one or more FDT blobs";
21 data = /incbin/("%(kernel)s");
25 compression = "%(compression)s";
30 data = /incbin/("%(loadables1)s");
40 data = /incbin/("%(fdt)s");
44 compression = "%(compression)s";
46 algo = "sha1,rsa2048";
47 key-name-hint = "dev";
52 data = /incbin/("%(ramdisk)s");
57 compression = "%(compression)s";
61 data = /incbin/("%(loadables2)s");
81 # Define a base FDT - currently we don't use anything in this
89 model = "Sandbox Verified Boot Test";
90 compatible = "sandbox";
93 compatible = "sandbox,reset";
99 # This is the U-Boot script that is run for each test. First load the FIT,
100 # then run the 'bootm' command, then save out memory from the places where
101 # we expect 'bootm' to write things. Then quit.
103 host load hostfs 0 %(fit_addr)x %(fit)s
104 fdt addr %(fit_addr)x
105 bootm start %(fit_addr)x
107 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
108 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
109 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
110 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
111 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
114 @pytest.mark.boardspec('sandbox')
115 @pytest.mark.buildconfigspec('fit_signature')
116 @pytest.mark.requiredtool('dtc')
117 def test_fit(u_boot_console):
118 def make_fname(leaf):
119 """Make a temporary filename
122 leaf: Leaf name of file to create (within temporary directory)
127 return os.path.join(cons.config.build_dir, leaf)
130 """Get the size of a file
133 fname: Filename to check
135 Size of file in bytes
137 return os.stat(fname).st_size
139 def read_file(fname):
140 """Read the contents of a file
143 fname: Filename to read
145 Contents of file as a string
147 with open(fname, 'rb') as fd:
151 """Make a sample .dts file and compile it to a .dtb
154 Filename of .dtb file created
156 src = make_fname('u-boot.dts')
157 dtb = make_fname('u-boot.dtb')
158 with open(src, 'w') as fd:
160 util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
163 def make_its(params):
164 """Make a sample .its file with parameters embedded
167 params: Dictionary containing parameters to embed in the %() strings
169 Filename of .its file created
171 its = make_fname('test.its')
172 with open(its, 'w') as fd:
173 print(base_its % params, file=fd)
176 def make_fit(mkimage, params):
177 """Make a sample .fit file ready for loading
179 This creates a .its script with the selected parameters and uses mkimage to
180 turn this into a .fit image.
183 mkimage: Filename of 'mkimage' utility
184 params: Dictionary containing parameters to embed in the %() strings
186 Filename of .fit file created
188 fit = make_fname('test.fit')
189 its = make_its(params)
190 util.run_and_log(cons, [mkimage, '-f', its, fit])
191 with open(make_fname('u-boot.dts'), 'w') as fd:
195 def make_kernel(filename, text):
196 """Make a sample kernel with test data
199 filename: the name of the file you want to create
201 Full path and filename of the kernel it created
203 fname = make_fname(filename)
206 data += 'this %s %d is unlikely to boot\n' % (text, i)
207 with open(fname, 'w') as fd:
211 def make_ramdisk(filename, text):
212 """Make a sample ramdisk with test data
215 Filename of ramdisk created
217 fname = make_fname(filename)
220 data += '%s %d was seldom used in the middle ages\n' % (text, i)
221 with open(fname, 'w') as fd:
225 def make_compressed(filename):
226 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
227 return filename + '.gz'
229 def find_matching(text, match):
230 """Find a match in a line of text, and return the unmatched line portion
232 This is used to extract a part of a line from some text. The match string
233 is used to locate the line - we use the first line that contains that
236 Once we find a match, we discard the match string itself from the line,
237 and return what remains.
239 TODO: If this function becomes more generally useful, we could change it
240 to use regex and return groups.
243 text: Text to check (list of strings, one for each command issued)
244 match: String to search for
246 String containing unmatched portion of line
248 ValueError: If match is not found
250 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
252 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
253 Traceback (most recent call last):
255 ValueError: Test aborted
256 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
258 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
262 __tracebackhide__ = True
263 for line in '\n'.join(text).splitlines():
264 pos = line.find(match)
266 return line[:pos] + line[pos + len(match):]
268 pytest.fail("Expected '%s' but not found in output")
270 def check_equal(expected_fname, actual_fname, failure_msg):
271 """Check that a file matches its expected contents
273 This is always used on out-buffers whose size is decided by the test
274 script anyway, which in some cases may be larger than what we're
275 actually looking for. So it's safe to truncate it to the size of the
279 expected_fname: Filename containing expected contents
280 actual_fname: Filename containing actual contents
281 failure_msg: Message to print on failure
283 expected_data = read_file(expected_fname)
284 actual_data = read_file(actual_fname)
285 if len(expected_data) < len(actual_data):
286 actual_data = actual_data[:len(expected_data)]
287 assert expected_data == actual_data, failure_msg
289 def check_not_equal(expected_fname, actual_fname, failure_msg):
290 """Check that a file does not match its expected contents
293 expected_fname: Filename containing expected contents
294 actual_fname: Filename containing actual contents
295 failure_msg: Message to print on failure
297 expected_data = read_file(expected_fname)
298 actual_data = read_file(actual_fname)
299 assert expected_data != actual_data, failure_msg
301 def run_fit_test(mkimage):
302 """Basic sanity check of FIT loading in U-Boot
304 TODO: Almost everything:
305 - hash algorithms - invalid hash/contents should be detected
306 - signature algorithms - invalid sig/contents should be detected
308 - checking that errors are detected like:
311 - invalid configurations
312 - incorrect os/arch/type fields
314 - images too large/small
315 - invalid FDT (e.g. putting a random binary in instead)
316 - default configuration selection
317 - bootm command line parameters should have desired effect
318 - run code coverage to make sure we are testing all the code
320 # Set up invariant files
321 control_dtb = make_dtb()
322 kernel = make_kernel('test-kernel.bin', 'kernel')
323 ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
324 loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
325 loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
326 kernel_out = make_fname('kernel-out.bin')
327 fdt = make_fname('u-boot.dtb')
328 fdt_out = make_fname('fdt-out.dtb')
329 ramdisk_out = make_fname('ramdisk-out.bin')
330 loadables1_out = make_fname('loadables1-out.bin')
331 loadables2_out = make_fname('loadables2-out.bin')
333 # Set up basic parameters with default values
338 'kernel_out' : kernel_out,
339 'kernel_addr' : 0x40000,
340 'kernel_size' : filesize(kernel),
344 'fdt_addr' : 0x80000,
345 'fdt_size' : filesize(control_dtb),
349 'ramdisk_out' : ramdisk_out,
350 'ramdisk_addr' : 0xc0000,
351 'ramdisk_size' : filesize(ramdisk),
353 'ramdisk_config' : '',
355 'loadables1' : loadables1,
356 'loadables1_out' : loadables1_out,
357 'loadables1_addr' : 0x100000,
358 'loadables1_size' : filesize(loadables1),
359 'loadables1_load' : '',
361 'loadables2' : loadables2,
362 'loadables2_out' : loadables2_out,
363 'loadables2_addr' : 0x140000,
364 'loadables2_size' : filesize(loadables2),
365 'loadables2_load' : '',
367 'loadables_config' : '',
368 'compression' : 'none',
371 # Make a basic FIT and a script to load it
372 fit = make_fit(mkimage, params)
374 cmd = base_script % params
376 # First check that we can load a kernel
377 # We could perhaps reduce duplication with some loss of readability
378 cons.config.dtb = control_dtb
380 with cons.log.section('Kernel load'):
381 output = cons.run_command_list(cmd.splitlines())
382 check_equal(kernel, kernel_out, 'Kernel not loaded')
383 check_not_equal(control_dtb, fdt_out,
384 'FDT loaded but should be ignored')
385 check_not_equal(ramdisk, ramdisk_out,
386 'Ramdisk loaded but should not be')
388 # Find out the offset in the FIT where U-Boot has found the FDT
389 line = find_matching(output, 'Booting using the fdt blob at ')
390 fit_offset = int(line, 16) - params['fit_addr']
391 fdt_magic = struct.pack('>L', 0xd00dfeed)
392 data = read_file(fit)
394 # Now find where it actually is in the FIT (skip the first word)
395 real_fit_offset = data.find(fdt_magic, 4)
396 assert fit_offset == real_fit_offset, (
397 'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
398 (fit_offset, real_fit_offset))
400 # Now a kernel and an FDT
401 with cons.log.section('Kernel + FDT load'):
402 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
403 fit = make_fit(mkimage, params)
405 output = cons.run_command_list(cmd.splitlines())
406 check_equal(kernel, kernel_out, 'Kernel not loaded')
407 check_equal(control_dtb, fdt_out, 'FDT not loaded')
408 check_not_equal(ramdisk, ramdisk_out,
409 'Ramdisk loaded but should not be')
412 with cons.log.section('Kernel + FDT + Ramdisk load'):
413 params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
414 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
415 fit = make_fit(mkimage, params)
417 output = cons.run_command_list(cmd.splitlines())
418 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
420 # Configuration with some Loadables
421 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
422 params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
423 params['loadables1_load'] = ('load = <%#x>;' %
424 params['loadables1_addr'])
425 params['loadables2_load'] = ('load = <%#x>;' %
426 params['loadables2_addr'])
427 fit = make_fit(mkimage, params)
429 output = cons.run_command_list(cmd.splitlines())
430 check_equal(loadables1, loadables1_out,
431 'Loadables1 (kernel) not loaded')
432 check_equal(loadables2, loadables2_out,
433 'Loadables2 (ramdisk) not loaded')
435 # Kernel, FDT and Ramdisk all compressed
436 with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
437 params['compression'] = 'gzip'
438 params['kernel'] = make_compressed(kernel)
439 params['fdt'] = make_compressed(fdt)
440 params['ramdisk'] = make_compressed(ramdisk)
441 fit = make_fit(mkimage, params)
443 output = cons.run_command_list(cmd.splitlines())
444 check_equal(kernel, kernel_out, 'Kernel not loaded')
445 check_equal(control_dtb, fdt_out, 'FDT not loaded')
446 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
447 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
450 cons = u_boot_console
452 # We need to use our own device tree file. Remember to restore it
454 old_dtb = cons.config.dtb
455 mkimage = cons.config.build_dir + '/tools/mkimage'
456 run_fit_test(mkimage)
458 # Go back to the original U-Boot with the correct dtb.
459 cons.config.dtb = old_dtb