blob: e3210ed43fa4c4e37b5485738d4ff1bec239fb50 [file] [log] [blame]
Wolfgang Denk1a459662013-07-08 09:37:19 +02001# SPDX-License-Identifier: GPL-2.0+
Tom Rini83d290c2018-05-06 17:58:06 -04002# Copyright (c) 2013, Google Inc.
Simon Glass301e8032013-05-16 13:53:28 +00003#
Simon Glass77b42672017-08-05 10:28:40 -06004# Sanity check of the FIT handling in U-Boot
Simon Glass301e8032013-05-16 13:53:28 +00005
Paul Burtondffd56d2017-09-14 14:34:43 -07006from __future__ import print_function
7
Simon Glass301e8032013-05-16 13:53:28 +00008import os
Simon Glass77b42672017-08-05 10:28:40 -06009import pytest
Simon Glass301e8032013-05-16 13:53:28 +000010import struct
Simon Glass77b42672017-08-05 10:28:40 -060011import u_boot_utils as util
Simon Glass301e8032013-05-16 13:53:28 +000012
13# Define a base ITS which we can adjust using % and a dictionary
14base_its = '''
15/dts-v1/;
16
17/ {
18 description = "Chrome OS kernel image with one or more FDT blobs";
19 #address-cells = <1>;
20
21 images {
22 kernel@1 {
23 data = /incbin/("%(kernel)s");
24 type = "kernel";
25 arch = "sandbox";
26 os = "linux";
Julius Wernerb1307f82019-07-24 19:37:55 -070027 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000028 load = <0x40000>;
29 entry = <0x8>;
30 };
Karl Apsite657fd2d2015-05-21 09:52:50 -040031 kernel@2 {
32 data = /incbin/("%(loadables1)s");
33 type = "kernel";
34 arch = "sandbox";
35 os = "linux";
36 compression = "none";
37 %(loadables1_load)s
38 entry = <0x0>;
39 };
Simon Glass301e8032013-05-16 13:53:28 +000040 fdt@1 {
41 description = "snow";
Julius Wernerb1307f82019-07-24 19:37:55 -070042 data = /incbin/("%(fdt)s");
Simon Glass301e8032013-05-16 13:53:28 +000043 type = "flat_dt";
44 arch = "sandbox";
45 %(fdt_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070046 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000047 signature@1 {
48 algo = "sha1,rsa2048";
49 key-name-hint = "dev";
50 };
51 };
52 ramdisk@1 {
53 description = "snow";
54 data = /incbin/("%(ramdisk)s");
55 type = "ramdisk";
56 arch = "sandbox";
57 os = "linux";
58 %(ramdisk_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070059 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000060 };
Karl Apsite657fd2d2015-05-21 09:52:50 -040061 ramdisk@2 {
62 description = "snow";
63 data = /incbin/("%(loadables2)s");
64 type = "ramdisk";
65 arch = "sandbox";
66 os = "linux";
67 %(loadables2_load)s
68 compression = "none";
69 };
Simon Glass301e8032013-05-16 13:53:28 +000070 };
71 configurations {
72 default = "conf@1";
73 conf@1 {
74 kernel = "kernel@1";
75 fdt = "fdt@1";
76 %(ramdisk_config)s
Karl Apsite657fd2d2015-05-21 09:52:50 -040077 %(loadables_config)s
Simon Glass301e8032013-05-16 13:53:28 +000078 };
79 };
80};
81'''
82
83# Define a base FDT - currently we don't use anything in this
84base_fdt = '''
85/dts-v1/;
86
87/ {
88 model = "Sandbox Verified Boot Test";
89 compatible = "sandbox";
90
Simon Glass0edd82e2016-02-24 09:14:44 -070091 reset@0 {
92 compatible = "sandbox,reset";
93 };
94
Simon Glass301e8032013-05-16 13:53:28 +000095};
96'''
97
Robert P. J. Dayb28c5fc2017-03-13 06:50:55 -040098# 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
Simon Glass301e8032013-05-16 13:53:28 +0000100# we expect 'bootm' to write things. Then quit.
101base_script = '''
Simon Glass6d07d632018-11-15 18:44:02 -0700102host load hostfs 0 %(fit_addr)x %(fit)s
Simon Glass301e8032013-05-16 13:53:28 +0000103fdt addr %(fit_addr)x
104bootm start %(fit_addr)x
105bootm loados
Simon Glass6d07d632018-11-15 18:44:02 -0700106host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
107host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
108host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
109host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
110host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
Simon Glass301e8032013-05-16 13:53:28 +0000111'''
112
Simon Glass77b42672017-08-05 10:28:40 -0600113@pytest.mark.boardspec('sandbox')
114@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2d26bf62017-09-18 11:11:49 -0600115@pytest.mark.requiredtool('dtc')
Simon Glass77b42672017-08-05 10:28:40 -0600116def test_fit(u_boot_console):
Simon Glassbde67122017-08-05 10:28:39 -0600117 def make_fname(leaf):
118 """Make a temporary filename
119
120 Args:
121 leaf: Leaf name of file to create (within temporary directory)
122 Return:
123 Temporary filename
124 """
Simon Glassbde67122017-08-05 10:28:39 -0600125
Simon Glass77b42672017-08-05 10:28:40 -0600126 return os.path.join(cons.config.build_dir, leaf)
Simon Glassbde67122017-08-05 10:28:39 -0600127
128 def filesize(fname):
129 """Get the size of a file
130
131 Args:
132 fname: Filename to check
133 Return:
134 Size of file in bytes
135 """
136 return os.stat(fname).st_size
137
138 def read_file(fname):
139 """Read the contents of a file
140
141 Args:
142 fname: Filename to read
143 Returns:
144 Contents of file as a string
145 """
Paul Burton57bf9be2017-09-14 14:34:48 -0700146 with open(fname, 'rb') as fd:
Simon Glassbde67122017-08-05 10:28:39 -0600147 return fd.read()
148
149 def make_dtb():
150 """Make a sample .dts file and compile it to a .dtb
151
152 Returns:
153 Filename of .dtb file created
154 """
155 src = make_fname('u-boot.dts')
156 dtb = make_fname('u-boot.dtb')
157 with open(src, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700158 print(base_fdt, file=fd)
Simon Glass77b42672017-08-05 10:28:40 -0600159 util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
Simon Glassbde67122017-08-05 10:28:39 -0600160 return dtb
161
162 def make_its(params):
163 """Make a sample .its file with parameters embedded
164
165 Args:
166 params: Dictionary containing parameters to embed in the %() strings
167 Returns:
168 Filename of .its file created
169 """
170 its = make_fname('test.its')
171 with open(its, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700172 print(base_its % params, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600173 return its
174
175 def make_fit(mkimage, params):
176 """Make a sample .fit file ready for loading
177
178 This creates a .its script with the selected parameters and uses mkimage to
179 turn this into a .fit image.
180
181 Args:
182 mkimage: Filename of 'mkimage' utility
183 params: Dictionary containing parameters to embed in the %() strings
184 Return:
185 Filename of .fit file created
186 """
187 fit = make_fname('test.fit')
188 its = make_its(params)
Simon Glass77b42672017-08-05 10:28:40 -0600189 util.run_and_log(cons, [mkimage, '-f', its, fit])
Simon Glassbde67122017-08-05 10:28:39 -0600190 with open(make_fname('u-boot.dts'), 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700191 print(base_fdt, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600192 return fit
193
194 def make_kernel(filename, text):
195 """Make a sample kernel with test data
196
197 Args:
198 filename: the name of the file you want to create
199 Returns:
200 Full path and filename of the kernel it created
201 """
202 fname = make_fname(filename)
203 data = ''
204 for i in range(100):
205 data += 'this %s %d is unlikely to boot\n' % (text, i)
206 with open(fname, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700207 print(data, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600208 return fname
209
210 def make_ramdisk(filename, text):
211 """Make a sample ramdisk with test data
212
213 Returns:
214 Filename of ramdisk created
215 """
216 fname = make_fname(filename)
217 data = ''
218 for i in range(100):
219 data += '%s %d was seldom used in the middle ages\n' % (text, i)
220 with open(fname, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700221 print(data, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600222 return fname
223
Julius Wernerb1307f82019-07-24 19:37:55 -0700224 def make_compressed(filename):
225 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
226 return filename + '.gz'
227
Simon Glassbde67122017-08-05 10:28:39 -0600228 def find_matching(text, match):
229 """Find a match in a line of text, and return the unmatched line portion
230
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
233 match text.
234
235 Once we find a match, we discard the match string itself from the line,
236 and return what remains.
237
238 TODO: If this function becomes more generally useful, we could change it
239 to use regex and return groups.
240
241 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600242 text: Text to check (list of strings, one for each command issued)
Simon Glassbde67122017-08-05 10:28:39 -0600243 match: String to search for
244 Return:
245 String containing unmatched portion of line
246 Exceptions:
247 ValueError: If match is not found
248
Simon Glass77b42672017-08-05 10:28:40 -0600249 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
Simon Glassbde67122017-08-05 10:28:39 -0600250 '10'
Simon Glass77b42672017-08-05 10:28:40 -0600251 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
Simon Glassbde67122017-08-05 10:28:39 -0600252 Traceback (most recent call last):
253 ...
254 ValueError: Test aborted
Simon Glass77b42672017-08-05 10:28:40 -0600255 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
Simon Glassbde67122017-08-05 10:28:39 -0600256 '20'
Simon Glass77b42672017-08-05 10:28:40 -0600257 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
258 'third_line:')
259 '30'
Simon Glassbde67122017-08-05 10:28:39 -0600260 """
Simon Glass77b42672017-08-05 10:28:40 -0600261 __tracebackhide__ = True
262 for line in '\n'.join(text).splitlines():
Simon Glassbde67122017-08-05 10:28:39 -0600263 pos = line.find(match)
264 if pos != -1:
265 return line[:pos] + line[pos + len(match):]
266
Simon Glass77b42672017-08-05 10:28:40 -0600267 pytest.fail("Expected '%s' but not found in output")
Simon Glassbde67122017-08-05 10:28:39 -0600268
Simon Glass77b42672017-08-05 10:28:40 -0600269 def check_equal(expected_fname, actual_fname, failure_msg):
270 """Check that a file matches its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600271
Julius Wernerbddd9852019-08-02 15:52:28 -0700272 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
275 expected data.
276
Simon Glassbde67122017-08-05 10:28:39 -0600277 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600278 expected_fname: Filename containing expected contents
279 actual_fname: Filename containing actual contents
280 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600281 """
Simon Glass77b42672017-08-05 10:28:40 -0600282 expected_data = read_file(expected_fname)
283 actual_data = read_file(actual_fname)
Julius Wernerbddd9852019-08-02 15:52:28 -0700284 if len(expected_data) < len(actual_data):
285 actual_data = actual_data[:len(expected_data)]
Simon Glass77b42672017-08-05 10:28:40 -0600286 assert expected_data == actual_data, failure_msg
Simon Glassbde67122017-08-05 10:28:39 -0600287
Simon Glass77b42672017-08-05 10:28:40 -0600288 def check_not_equal(expected_fname, actual_fname, failure_msg):
289 """Check that a file does not match its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600290
291 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600292 expected_fname: Filename containing expected contents
293 actual_fname: Filename containing actual contents
294 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600295 """
Simon Glass77b42672017-08-05 10:28:40 -0600296 expected_data = read_file(expected_fname)
297 actual_data = read_file(actual_fname)
298 assert expected_data != actual_data, failure_msg
Simon Glasscc447722014-12-02 13:17:32 -0700299
Simon Glass77b42672017-08-05 10:28:40 -0600300 def run_fit_test(mkimage):
Simon Glassbde67122017-08-05 10:28:39 -0600301 """Basic sanity check of FIT loading in U-Boot
Simon Glass301e8032013-05-16 13:53:28 +0000302
Simon Glassbde67122017-08-05 10:28:39 -0600303 TODO: Almost everything:
304 - hash algorithms - invalid hash/contents should be detected
305 - signature algorithms - invalid sig/contents should be detected
306 - compression
307 - checking that errors are detected like:
308 - image overwriting
309 - missing images
310 - invalid configurations
311 - incorrect os/arch/type fields
312 - empty data
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
318 """
Simon Glassbde67122017-08-05 10:28:39 -0600319 # 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')
Julius Wernerb1307f82019-07-24 19:37:55 -0700326 fdt = make_fname('u-boot.dtb')
Simon Glassbde67122017-08-05 10:28:39 -0600327 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')
Simon Glass301e8032013-05-16 13:53:28 +0000331
Simon Glassbde67122017-08-05 10:28:39 -0600332 # Set up basic parameters with default values
333 params = {
334 'fit_addr' : 0x1000,
Simon Glass301e8032013-05-16 13:53:28 +0000335
Simon Glassbde67122017-08-05 10:28:39 -0600336 'kernel' : kernel,
337 'kernel_out' : kernel_out,
338 'kernel_addr' : 0x40000,
339 'kernel_size' : filesize(kernel),
Simon Glass301e8032013-05-16 13:53:28 +0000340
Julius Wernerb1307f82019-07-24 19:37:55 -0700341 'fdt' : fdt,
Simon Glassbde67122017-08-05 10:28:39 -0600342 'fdt_out' : fdt_out,
343 'fdt_addr' : 0x80000,
344 'fdt_size' : filesize(control_dtb),
345 'fdt_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000346
Simon Glassbde67122017-08-05 10:28:39 -0600347 'ramdisk' : ramdisk,
348 'ramdisk_out' : ramdisk_out,
349 'ramdisk_addr' : 0xc0000,
350 'ramdisk_size' : filesize(ramdisk),
351 'ramdisk_load' : '',
352 'ramdisk_config' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000353
Simon Glassbde67122017-08-05 10:28:39 -0600354 'loadables1' : loadables1,
355 'loadables1_out' : loadables1_out,
356 'loadables1_addr' : 0x100000,
357 'loadables1_size' : filesize(loadables1),
358 'loadables1_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000359
Simon Glassbde67122017-08-05 10:28:39 -0600360 'loadables2' : loadables2,
361 'loadables2_out' : loadables2_out,
362 'loadables2_addr' : 0x140000,
363 'loadables2_size' : filesize(loadables2),
364 'loadables2_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000365
Simon Glassbde67122017-08-05 10:28:39 -0600366 'loadables_config' : '',
Julius Wernerb1307f82019-07-24 19:37:55 -0700367 'compression' : 'none',
Simon Glassbde67122017-08-05 10:28:39 -0600368 }
Simon Glass301e8032013-05-16 13:53:28 +0000369
Simon Glassbde67122017-08-05 10:28:39 -0600370 # Make a basic FIT and a script to load it
371 fit = make_fit(mkimage, params)
372 params['fit'] = fit
373 cmd = base_script % params
Simon Glass301e8032013-05-16 13:53:28 +0000374
Simon Glassbde67122017-08-05 10:28:39 -0600375 # First check that we can load a kernel
376 # We could perhaps reduce duplication with some loss of readability
Simon Glass77b42672017-08-05 10:28:40 -0600377 cons.config.dtb = control_dtb
378 cons.restart_uboot()
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')
Simon Glass301e8032013-05-16 13:53:28 +0000386
Simon Glassbde67122017-08-05 10:28:39 -0600387 # Find out the offset in the FIT where U-Boot has found the FDT
Simon Glass77b42672017-08-05 10:28:40 -0600388 line = find_matching(output, 'Booting using the fdt blob at ')
Simon Glassbde67122017-08-05 10:28:39 -0600389 fit_offset = int(line, 16) - params['fit_addr']
390 fdt_magic = struct.pack('>L', 0xd00dfeed)
391 data = read_file(fit)
Simon Glass301e8032013-05-16 13:53:28 +0000392
Simon Glassbde67122017-08-05 10:28:39 -0600393 # Now find where it actually is in the FIT (skip the first word)
394 real_fit_offset = data.find(fdt_magic, 4)
Simon Glass77b42672017-08-05 10:28:40 -0600395 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))
Simon Glass301e8032013-05-16 13:53:28 +0000398
Simon Glassbde67122017-08-05 10:28:39 -0600399 # Now a kernel and an FDT
Simon Glass77b42672017-08-05 10:28:40 -0600400 with cons.log.section('Kernel + FDT load'):
Simon Glassbde67122017-08-05 10:28:39 -0600401 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
402 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600403 cons.restart_uboot()
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')
Simon Glass301e8032013-05-16 13:53:28 +0000409
Simon Glassbde67122017-08-05 10:28:39 -0600410 # Try a ramdisk
Simon Glass77b42672017-08-05 10:28:40 -0600411 with cons.log.section('Kernel + FDT + Ramdisk load'):
Simon Glassbde67122017-08-05 10:28:39 -0600412 params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
413 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
414 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600415 cons.restart_uboot()
416 output = cons.run_command_list(cmd.splitlines())
417 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
Simon Glass301e8032013-05-16 13:53:28 +0000418
Simon Glassbde67122017-08-05 10:28:39 -0600419 # Configuration with some Loadables
Simon Glass77b42672017-08-05 10:28:40 -0600420 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
Simon Glassbde67122017-08-05 10:28:39 -0600421 params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
Simon Glass77b42672017-08-05 10:28:40 -0600422 params['loadables1_load'] = ('load = <%#x>;' %
423 params['loadables1_addr'])
424 params['loadables2_load'] = ('load = <%#x>;' %
425 params['loadables2_addr'])
Simon Glassbde67122017-08-05 10:28:39 -0600426 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600427 cons.restart_uboot()
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')
Karl Apsite657fd2d2015-05-21 09:52:50 -0400433
Julius Wernerb1307f82019-07-24 19:37:55 -0700434 # 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)
441 cons.restart_uboot()
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')
Julius Wernerbddd9852019-08-02 15:52:28 -0700445 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
446 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
Julius Wernerb1307f82019-07-24 19:37:55 -0700447
448
Simon Glass77b42672017-08-05 10:28:40 -0600449 cons = u_boot_console
450 try:
451 # We need to use our own device tree file. Remember to restore it
452 # afterwards.
453 old_dtb = cons.config.dtb
454 mkimage = cons.config.build_dir + '/tools/mkimage'
455 run_fit_test(mkimage)
456 finally:
457 # Go back to the original U-Boot with the correct dtb.
458 cons.config.dtb = old_dtb
459 cons.restart_uboot()