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
86 model = "Sandbox Verified Boot Test";
87 compatible = "sandbox";
90 compatible = "sandbox,reset";
96 # This is the U-Boot script that is run for each test. First load the FIT,
97 # then run the 'bootm' command, then save out memory from the places where
98 # we expect 'bootm' to write things. Then quit.
100 host load hostfs 0 %(fit_addr)x %(fit)s
101 fdt addr %(fit_addr)x
102 bootm start %(fit_addr)x
104 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
105 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
106 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
107 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
108 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
111 @pytest.mark.boardspec('sandbox')
112 @pytest.mark.buildconfigspec('fit_signature')
113 @pytest.mark.requiredtool('dtc')
114 def test_fit(u_boot_console):
115 def make_fname(leaf):
116 """Make a temporary filename
119 leaf: Leaf name of file to create (within temporary directory)
124 return os.path.join(cons.config.build_dir, leaf)
127 """Get the size of a file
130 fname: Filename to check
132 Size of file in bytes
134 return os.stat(fname).st_size
136 def read_file(fname):
137 """Read the contents of a file
140 fname: Filename to read
142 Contents of file as a string
144 with open(fname, 'rb') as fd:
148 """Make a sample .dts file and compile it to a .dtb
151 Filename of .dtb file created
153 src = make_fname('u-boot.dts')
154 dtb = make_fname('u-boot.dtb')
155 with open(src, 'w') as fd:
157 util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
160 def make_its(params):
161 """Make a sample .its file with parameters embedded
164 params: Dictionary containing parameters to embed in the %() strings
166 Filename of .its file created
168 its = make_fname('test.its')
169 with open(its, 'w') as fd:
170 print(base_its % params, file=fd)
173 def make_fit(mkimage, params):
174 """Make a sample .fit file ready for loading
176 This creates a .its script with the selected parameters and uses mkimage to
177 turn this into a .fit image.
180 mkimage: Filename of 'mkimage' utility
181 params: Dictionary containing parameters to embed in the %() strings
183 Filename of .fit file created
185 fit = make_fname('test.fit')
186 its = make_its(params)
187 util.run_and_log(cons, [mkimage, '-f', its, fit])
188 with open(make_fname('u-boot.dts'), 'w') as fd:
192 def make_kernel(filename, text):
193 """Make a sample kernel with test data
196 filename: the name of the file you want to create
198 Full path and filename of the kernel it created
200 fname = make_fname(filename)
203 data += 'this %s %d is unlikely to boot\n' % (text, i)
204 with open(fname, 'w') as fd:
208 def make_ramdisk(filename, text):
209 """Make a sample ramdisk with test data
212 Filename of ramdisk created
214 fname = make_fname(filename)
217 data += '%s %d was seldom used in the middle ages\n' % (text, i)
218 with open(fname, 'w') as fd:
222 def make_compressed(filename):
223 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
224 return filename + '.gz'
226 def find_matching(text, match):
227 """Find a match in a line of text, and return the unmatched line portion
229 This is used to extract a part of a line from some text. The match string
230 is used to locate the line - we use the first line that contains that
233 Once we find a match, we discard the match string itself from the line,
234 and return what remains.
236 TODO: If this function becomes more generally useful, we could change it
237 to use regex and return groups.
240 text: Text to check (list of strings, one for each command issued)
241 match: String to search for
243 String containing unmatched portion of line
245 ValueError: If match is not found
247 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
249 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
250 Traceback (most recent call last):
252 ValueError: Test aborted
253 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
255 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
259 __tracebackhide__ = True
260 for line in '\n'.join(text).splitlines():
261 pos = line.find(match)
263 return line[:pos] + line[pos + len(match):]
265 pytest.fail("Expected '%s' but not found in output")
267 def check_equal(expected_fname, actual_fname, failure_msg):
268 """Check that a file matches its expected contents
270 This is always used on out-buffers whose size is decided by the test
271 script anyway, which in some cases may be larger than what we're
272 actually looking for. So it's safe to truncate it to the size of the
276 expected_fname: Filename containing expected contents
277 actual_fname: Filename containing actual contents
278 failure_msg: Message to print on failure
280 expected_data = read_file(expected_fname)
281 actual_data = read_file(actual_fname)
282 if len(expected_data) < len(actual_data):
283 actual_data = actual_data[:len(expected_data)]
284 assert expected_data == actual_data, failure_msg
286 def check_not_equal(expected_fname, actual_fname, failure_msg):
287 """Check that a file does not match its expected contents
290 expected_fname: Filename containing expected contents
291 actual_fname: Filename containing actual contents
292 failure_msg: Message to print on failure
294 expected_data = read_file(expected_fname)
295 actual_data = read_file(actual_fname)
296 assert expected_data != actual_data, failure_msg
298 def run_fit_test(mkimage):
299 """Basic sanity check of FIT loading in U-Boot
301 TODO: Almost everything:
302 - hash algorithms - invalid hash/contents should be detected
303 - signature algorithms - invalid sig/contents should be detected
305 - checking that errors are detected like:
308 - invalid configurations
309 - incorrect os/arch/type fields
311 - images too large/small
312 - invalid FDT (e.g. putting a random binary in instead)
313 - default configuration selection
314 - bootm command line parameters should have desired effect
315 - run code coverage to make sure we are testing all the code
317 # Set up invariant files
318 control_dtb = make_dtb()
319 kernel = make_kernel('test-kernel.bin', 'kernel')
320 ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
321 loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
322 loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
323 kernel_out = make_fname('kernel-out.bin')
324 fdt = make_fname('u-boot.dtb')
325 fdt_out = make_fname('fdt-out.dtb')
326 ramdisk_out = make_fname('ramdisk-out.bin')
327 loadables1_out = make_fname('loadables1-out.bin')
328 loadables2_out = make_fname('loadables2-out.bin')
330 # Set up basic parameters with default values
335 'kernel_out' : kernel_out,
336 'kernel_addr' : 0x40000,
337 'kernel_size' : filesize(kernel),
341 'fdt_addr' : 0x80000,
342 'fdt_size' : filesize(control_dtb),
346 'ramdisk_out' : ramdisk_out,
347 'ramdisk_addr' : 0xc0000,
348 'ramdisk_size' : filesize(ramdisk),
350 'ramdisk_config' : '',
352 'loadables1' : loadables1,
353 'loadables1_out' : loadables1_out,
354 'loadables1_addr' : 0x100000,
355 'loadables1_size' : filesize(loadables1),
356 'loadables1_load' : '',
358 'loadables2' : loadables2,
359 'loadables2_out' : loadables2_out,
360 'loadables2_addr' : 0x140000,
361 'loadables2_size' : filesize(loadables2),
362 'loadables2_load' : '',
364 'loadables_config' : '',
365 'compression' : 'none',
368 # Make a basic FIT and a script to load it
369 fit = make_fit(mkimage, params)
371 cmd = base_script % params
373 # First check that we can load a kernel
374 # We could perhaps reduce duplication with some loss of readability
375 cons.config.dtb = control_dtb
377 with cons.log.section('Kernel load'):
378 output = cons.run_command_list(cmd.splitlines())
379 check_equal(kernel, kernel_out, 'Kernel not loaded')
380 check_not_equal(control_dtb, fdt_out,
381 'FDT loaded but should be ignored')
382 check_not_equal(ramdisk, ramdisk_out,
383 'Ramdisk loaded but should not be')
385 # Find out the offset in the FIT where U-Boot has found the FDT
386 line = find_matching(output, 'Booting using the fdt blob at ')
387 fit_offset = int(line, 16) - params['fit_addr']
388 fdt_magic = struct.pack('>L', 0xd00dfeed)
389 data = read_file(fit)
391 # Now find where it actually is in the FIT (skip the first word)
392 real_fit_offset = data.find(fdt_magic, 4)
393 assert fit_offset == real_fit_offset, (
394 'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
395 (fit_offset, real_fit_offset))
397 # Now a kernel and an FDT
398 with cons.log.section('Kernel + FDT load'):
399 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
400 fit = make_fit(mkimage, params)
402 output = cons.run_command_list(cmd.splitlines())
403 check_equal(kernel, kernel_out, 'Kernel not loaded')
404 check_equal(control_dtb, fdt_out, 'FDT not loaded')
405 check_not_equal(ramdisk, ramdisk_out,
406 'Ramdisk loaded but should not be')
409 with cons.log.section('Kernel + FDT + Ramdisk load'):
410 params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
411 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
412 fit = make_fit(mkimage, params)
414 output = cons.run_command_list(cmd.splitlines())
415 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
417 # Configuration with some Loadables
418 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
419 params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
420 params['loadables1_load'] = ('load = <%#x>;' %
421 params['loadables1_addr'])
422 params['loadables2_load'] = ('load = <%#x>;' %
423 params['loadables2_addr'])
424 fit = make_fit(mkimage, params)
426 output = cons.run_command_list(cmd.splitlines())
427 check_equal(loadables1, loadables1_out,
428 'Loadables1 (kernel) not loaded')
429 check_equal(loadables2, loadables2_out,
430 'Loadables2 (ramdisk) not loaded')
432 # Kernel, FDT and Ramdisk all compressed
433 with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
434 params['compression'] = 'gzip'
435 params['kernel'] = make_compressed(kernel)
436 params['fdt'] = make_compressed(fdt)
437 params['ramdisk'] = make_compressed(ramdisk)
438 fit = make_fit(mkimage, params)
440 output = cons.run_command_list(cmd.splitlines())
441 check_equal(kernel, kernel_out, 'Kernel not loaded')
442 check_equal(control_dtb, fdt_out, 'FDT not loaded')
443 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
444 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
447 cons = u_boot_console
449 # We need to use our own device tree file. Remember to restore it
451 old_dtb = cons.config.dtb
452 mkimage = cons.config.build_dir + '/tools/mkimage'
453 run_fit_test(mkimage)
455 # Go back to the original U-Boot with the correct dtb.
456 cons.config.dtb = old_dtb