blob: 356d9a20f2991151838083b30064b6546050975c [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
Simon Glass301e8032013-05-16 13:53:28 +00006import os
Simon Glass77b42672017-08-05 10:28:40 -06007import pytest
Simon Glass301e8032013-05-16 13:53:28 +00008import struct
Simon Glass77b42672017-08-05 10:28:40 -06009import u_boot_utils as util
Simon Glass301e8032013-05-16 13:53:28 +000010
11# Define a base ITS which we can adjust using % and a dictionary
12base_its = '''
13/dts-v1/;
14
15/ {
16 description = "Chrome OS kernel image with one or more FDT blobs";
17 #address-cells = <1>;
18
19 images {
20 kernel@1 {
21 data = /incbin/("%(kernel)s");
22 type = "kernel";
23 arch = "sandbox";
24 os = "linux";
Julius Wernerb1307f82019-07-24 19:37:55 -070025 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000026 load = <0x40000>;
27 entry = <0x8>;
28 };
Karl Apsite657fd2d2015-05-21 09:52:50 -040029 kernel@2 {
30 data = /incbin/("%(loadables1)s");
31 type = "kernel";
32 arch = "sandbox";
33 os = "linux";
34 compression = "none";
35 %(loadables1_load)s
36 entry = <0x0>;
37 };
Simon Glass301e8032013-05-16 13:53:28 +000038 fdt@1 {
39 description = "snow";
Julius Wernerb1307f82019-07-24 19:37:55 -070040 data = /incbin/("%(fdt)s");
Simon Glass301e8032013-05-16 13:53:28 +000041 type = "flat_dt";
42 arch = "sandbox";
43 %(fdt_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070044 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000045 signature@1 {
46 algo = "sha1,rsa2048";
47 key-name-hint = "dev";
48 };
49 };
50 ramdisk@1 {
51 description = "snow";
52 data = /incbin/("%(ramdisk)s");
53 type = "ramdisk";
54 arch = "sandbox";
55 os = "linux";
56 %(ramdisk_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070057 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000058 };
Karl Apsite657fd2d2015-05-21 09:52:50 -040059 ramdisk@2 {
60 description = "snow";
61 data = /incbin/("%(loadables2)s");
62 type = "ramdisk";
63 arch = "sandbox";
64 os = "linux";
65 %(loadables2_load)s
66 compression = "none";
67 };
Simon Glass301e8032013-05-16 13:53:28 +000068 };
69 configurations {
70 default = "conf@1";
71 conf@1 {
72 kernel = "kernel@1";
73 fdt = "fdt@1";
74 %(ramdisk_config)s
Karl Apsite657fd2d2015-05-21 09:52:50 -040075 %(loadables_config)s
Simon Glass301e8032013-05-16 13:53:28 +000076 };
77 };
78};
79'''
80
81# Define a base FDT - currently we don't use anything in this
82base_fdt = '''
83/dts-v1/;
84
85/ {
86 model = "Sandbox Verified Boot Test";
87 compatible = "sandbox";
88
Simon Glass0edd82e2016-02-24 09:14:44 -070089 reset@0 {
90 compatible = "sandbox,reset";
91 };
92
Simon Glass301e8032013-05-16 13:53:28 +000093};
94'''
95
Robert P. J. Dayb28c5fc2017-03-13 06:50:55 -040096# 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
Simon Glass301e8032013-05-16 13:53:28 +000098# we expect 'bootm' to write things. Then quit.
99base_script = '''
Simon Glass6d07d632018-11-15 18:44:02 -0700100host load hostfs 0 %(fit_addr)x %(fit)s
Simon Glass301e8032013-05-16 13:53:28 +0000101fdt addr %(fit_addr)x
102bootm start %(fit_addr)x
103bootm loados
Simon Glass6d07d632018-11-15 18:44:02 -0700104host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
105host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
106host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
107host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
108host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
Simon Glass301e8032013-05-16 13:53:28 +0000109'''
110
Simon Glass77b42672017-08-05 10:28:40 -0600111@pytest.mark.boardspec('sandbox')
112@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2d26bf62017-09-18 11:11:49 -0600113@pytest.mark.requiredtool('dtc')
Simon Glass77b42672017-08-05 10:28:40 -0600114def test_fit(u_boot_console):
Simon Glassbde67122017-08-05 10:28:39 -0600115 def make_fname(leaf):
116 """Make a temporary filename
117
118 Args:
119 leaf: Leaf name of file to create (within temporary directory)
120 Return:
121 Temporary filename
122 """
Simon Glassbde67122017-08-05 10:28:39 -0600123
Simon Glass77b42672017-08-05 10:28:40 -0600124 return os.path.join(cons.config.build_dir, leaf)
Simon Glassbde67122017-08-05 10:28:39 -0600125
126 def filesize(fname):
127 """Get the size of a file
128
129 Args:
130 fname: Filename to check
131 Return:
132 Size of file in bytes
133 """
134 return os.stat(fname).st_size
135
136 def read_file(fname):
137 """Read the contents of a file
138
139 Args:
140 fname: Filename to read
141 Returns:
142 Contents of file as a string
143 """
Paul Burton57bf9be2017-09-14 14:34:48 -0700144 with open(fname, 'rb') as fd:
Simon Glassbde67122017-08-05 10:28:39 -0600145 return fd.read()
146
147 def make_dtb():
148 """Make a sample .dts file and compile it to a .dtb
149
150 Returns:
151 Filename of .dtb file created
152 """
153 src = make_fname('u-boot.dts')
154 dtb = make_fname('u-boot.dtb')
155 with open(src, 'w') as fd:
Tom Rinifd31fc12019-10-24 11:59:21 -0400156 fd.write(base_fdt)
Simon Glass77b42672017-08-05 10:28:40 -0600157 util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
Simon Glassbde67122017-08-05 10:28:39 -0600158 return dtb
159
160 def make_its(params):
161 """Make a sample .its file with parameters embedded
162
163 Args:
164 params: Dictionary containing parameters to embed in the %() strings
165 Returns:
166 Filename of .its file created
167 """
168 its = make_fname('test.its')
169 with open(its, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700170 print(base_its % params, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600171 return its
172
173 def make_fit(mkimage, params):
174 """Make a sample .fit file ready for loading
175
176 This creates a .its script with the selected parameters and uses mkimage to
177 turn this into a .fit image.
178
179 Args:
180 mkimage: Filename of 'mkimage' utility
181 params: Dictionary containing parameters to embed in the %() strings
182 Return:
183 Filename of .fit file created
184 """
185 fit = make_fname('test.fit')
186 its = make_its(params)
Simon Glass77b42672017-08-05 10:28:40 -0600187 util.run_and_log(cons, [mkimage, '-f', its, fit])
Simon Glassbde67122017-08-05 10:28:39 -0600188 with open(make_fname('u-boot.dts'), 'w') as fd:
Tom Rinifd31fc12019-10-24 11:59:21 -0400189 fd.write(base_fdt)
Simon Glassbde67122017-08-05 10:28:39 -0600190 return fit
191
192 def make_kernel(filename, text):
193 """Make a sample kernel with test data
194
195 Args:
196 filename: the name of the file you want to create
197 Returns:
198 Full path and filename of the kernel it created
199 """
200 fname = make_fname(filename)
201 data = ''
202 for i in range(100):
203 data += 'this %s %d is unlikely to boot\n' % (text, i)
204 with open(fname, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700205 print(data, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600206 return fname
207
208 def make_ramdisk(filename, text):
209 """Make a sample ramdisk with test data
210
211 Returns:
212 Filename of ramdisk created
213 """
214 fname = make_fname(filename)
215 data = ''
216 for i in range(100):
217 data += '%s %d was seldom used in the middle ages\n' % (text, i)
218 with open(fname, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700219 print(data, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600220 return fname
221
Julius Wernerb1307f82019-07-24 19:37:55 -0700222 def make_compressed(filename):
223 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
224 return filename + '.gz'
225
Simon Glassbde67122017-08-05 10:28:39 -0600226 def find_matching(text, match):
227 """Find a match in a line of text, and return the unmatched line portion
228
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
231 match text.
232
233 Once we find a match, we discard the match string itself from the line,
234 and return what remains.
235
236 TODO: If this function becomes more generally useful, we could change it
237 to use regex and return groups.
238
239 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600240 text: Text to check (list of strings, one for each command issued)
Simon Glassbde67122017-08-05 10:28:39 -0600241 match: String to search for
242 Return:
243 String containing unmatched portion of line
244 Exceptions:
245 ValueError: If match is not found
246
Simon Glass77b42672017-08-05 10:28:40 -0600247 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
Simon Glassbde67122017-08-05 10:28:39 -0600248 '10'
Simon Glass77b42672017-08-05 10:28:40 -0600249 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
Simon Glassbde67122017-08-05 10:28:39 -0600250 Traceback (most recent call last):
251 ...
252 ValueError: Test aborted
Simon Glass77b42672017-08-05 10:28:40 -0600253 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
Simon Glassbde67122017-08-05 10:28:39 -0600254 '20'
Simon Glass77b42672017-08-05 10:28:40 -0600255 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
256 'third_line:')
257 '30'
Simon Glassbde67122017-08-05 10:28:39 -0600258 """
Simon Glass77b42672017-08-05 10:28:40 -0600259 __tracebackhide__ = True
260 for line in '\n'.join(text).splitlines():
Simon Glassbde67122017-08-05 10:28:39 -0600261 pos = line.find(match)
262 if pos != -1:
263 return line[:pos] + line[pos + len(match):]
264
Simon Glass77b42672017-08-05 10:28:40 -0600265 pytest.fail("Expected '%s' but not found in output")
Simon Glassbde67122017-08-05 10:28:39 -0600266
Simon Glass77b42672017-08-05 10:28:40 -0600267 def check_equal(expected_fname, actual_fname, failure_msg):
268 """Check that a file matches its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600269
Julius Wernerbddd9852019-08-02 15:52:28 -0700270 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
273 expected data.
274
Simon Glassbde67122017-08-05 10:28:39 -0600275 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600276 expected_fname: Filename containing expected contents
277 actual_fname: Filename containing actual contents
278 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600279 """
Simon Glass77b42672017-08-05 10:28:40 -0600280 expected_data = read_file(expected_fname)
281 actual_data = read_file(actual_fname)
Julius Wernerbddd9852019-08-02 15:52:28 -0700282 if len(expected_data) < len(actual_data):
283 actual_data = actual_data[:len(expected_data)]
Simon Glass77b42672017-08-05 10:28:40 -0600284 assert expected_data == actual_data, failure_msg
Simon Glassbde67122017-08-05 10:28:39 -0600285
Simon Glass77b42672017-08-05 10:28:40 -0600286 def check_not_equal(expected_fname, actual_fname, failure_msg):
287 """Check that a file does not match its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600288
289 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600290 expected_fname: Filename containing expected contents
291 actual_fname: Filename containing actual contents
292 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600293 """
Simon Glass77b42672017-08-05 10:28:40 -0600294 expected_data = read_file(expected_fname)
295 actual_data = read_file(actual_fname)
296 assert expected_data != actual_data, failure_msg
Simon Glasscc447722014-12-02 13:17:32 -0700297
Simon Glass77b42672017-08-05 10:28:40 -0600298 def run_fit_test(mkimage):
Simon Glassbde67122017-08-05 10:28:39 -0600299 """Basic sanity check of FIT loading in U-Boot
Simon Glass301e8032013-05-16 13:53:28 +0000300
Simon Glassbde67122017-08-05 10:28:39 -0600301 TODO: Almost everything:
302 - hash algorithms - invalid hash/contents should be detected
303 - signature algorithms - invalid sig/contents should be detected
304 - compression
305 - checking that errors are detected like:
306 - image overwriting
307 - missing images
308 - invalid configurations
309 - incorrect os/arch/type fields
310 - empty data
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
316 """
Simon Glassbde67122017-08-05 10:28:39 -0600317 # 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')
Julius Wernerb1307f82019-07-24 19:37:55 -0700324 fdt = make_fname('u-boot.dtb')
Simon Glassbde67122017-08-05 10:28:39 -0600325 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')
Simon Glass301e8032013-05-16 13:53:28 +0000329
Simon Glassbde67122017-08-05 10:28:39 -0600330 # Set up basic parameters with default values
331 params = {
332 'fit_addr' : 0x1000,
Simon Glass301e8032013-05-16 13:53:28 +0000333
Simon Glassbde67122017-08-05 10:28:39 -0600334 'kernel' : kernel,
335 'kernel_out' : kernel_out,
336 'kernel_addr' : 0x40000,
337 'kernel_size' : filesize(kernel),
Simon Glass301e8032013-05-16 13:53:28 +0000338
Julius Wernerb1307f82019-07-24 19:37:55 -0700339 'fdt' : fdt,
Simon Glassbde67122017-08-05 10:28:39 -0600340 'fdt_out' : fdt_out,
341 'fdt_addr' : 0x80000,
342 'fdt_size' : filesize(control_dtb),
343 'fdt_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000344
Simon Glassbde67122017-08-05 10:28:39 -0600345 'ramdisk' : ramdisk,
346 'ramdisk_out' : ramdisk_out,
347 'ramdisk_addr' : 0xc0000,
348 'ramdisk_size' : filesize(ramdisk),
349 'ramdisk_load' : '',
350 'ramdisk_config' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000351
Simon Glassbde67122017-08-05 10:28:39 -0600352 'loadables1' : loadables1,
353 'loadables1_out' : loadables1_out,
354 'loadables1_addr' : 0x100000,
355 'loadables1_size' : filesize(loadables1),
356 'loadables1_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000357
Simon Glassbde67122017-08-05 10:28:39 -0600358 'loadables2' : loadables2,
359 'loadables2_out' : loadables2_out,
360 'loadables2_addr' : 0x140000,
361 'loadables2_size' : filesize(loadables2),
362 'loadables2_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000363
Simon Glassbde67122017-08-05 10:28:39 -0600364 'loadables_config' : '',
Julius Wernerb1307f82019-07-24 19:37:55 -0700365 'compression' : 'none',
Simon Glassbde67122017-08-05 10:28:39 -0600366 }
Simon Glass301e8032013-05-16 13:53:28 +0000367
Simon Glassbde67122017-08-05 10:28:39 -0600368 # Make a basic FIT and a script to load it
369 fit = make_fit(mkimage, params)
370 params['fit'] = fit
371 cmd = base_script % params
Simon Glass301e8032013-05-16 13:53:28 +0000372
Simon Glassbde67122017-08-05 10:28:39 -0600373 # First check that we can load a kernel
374 # We could perhaps reduce duplication with some loss of readability
Simon Glass77b42672017-08-05 10:28:40 -0600375 cons.config.dtb = control_dtb
376 cons.restart_uboot()
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')
Simon Glass301e8032013-05-16 13:53:28 +0000384
Simon Glassbde67122017-08-05 10:28:39 -0600385 # Find out the offset in the FIT where U-Boot has found the FDT
Simon Glass77b42672017-08-05 10:28:40 -0600386 line = find_matching(output, 'Booting using the fdt blob at ')
Simon Glassbde67122017-08-05 10:28:39 -0600387 fit_offset = int(line, 16) - params['fit_addr']
388 fdt_magic = struct.pack('>L', 0xd00dfeed)
389 data = read_file(fit)
Simon Glass301e8032013-05-16 13:53:28 +0000390
Simon Glassbde67122017-08-05 10:28:39 -0600391 # Now find where it actually is in the FIT (skip the first word)
392 real_fit_offset = data.find(fdt_magic, 4)
Simon Glass77b42672017-08-05 10:28:40 -0600393 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))
Simon Glass301e8032013-05-16 13:53:28 +0000396
Simon Glassbde67122017-08-05 10:28:39 -0600397 # Now a kernel and an FDT
Simon Glass77b42672017-08-05 10:28:40 -0600398 with cons.log.section('Kernel + FDT load'):
Simon Glassbde67122017-08-05 10:28:39 -0600399 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
400 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600401 cons.restart_uboot()
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')
Simon Glass301e8032013-05-16 13:53:28 +0000407
Simon Glassbde67122017-08-05 10:28:39 -0600408 # Try a ramdisk
Simon Glass77b42672017-08-05 10:28:40 -0600409 with cons.log.section('Kernel + FDT + Ramdisk load'):
Simon Glassbde67122017-08-05 10:28:39 -0600410 params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
411 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
412 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600413 cons.restart_uboot()
414 output = cons.run_command_list(cmd.splitlines())
415 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
Simon Glass301e8032013-05-16 13:53:28 +0000416
Simon Glassbde67122017-08-05 10:28:39 -0600417 # Configuration with some Loadables
Simon Glass77b42672017-08-05 10:28:40 -0600418 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
Simon Glassbde67122017-08-05 10:28:39 -0600419 params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
Simon Glass77b42672017-08-05 10:28:40 -0600420 params['loadables1_load'] = ('load = <%#x>;' %
421 params['loadables1_addr'])
422 params['loadables2_load'] = ('load = <%#x>;' %
423 params['loadables2_addr'])
Simon Glassbde67122017-08-05 10:28:39 -0600424 fit = make_fit(mkimage, params)
Simon Glass77b42672017-08-05 10:28:40 -0600425 cons.restart_uboot()
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')
Karl Apsite657fd2d2015-05-21 09:52:50 -0400431
Julius Wernerb1307f82019-07-24 19:37:55 -0700432 # 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)
439 cons.restart_uboot()
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')
Julius Wernerbddd9852019-08-02 15:52:28 -0700443 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
444 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
Julius Wernerb1307f82019-07-24 19:37:55 -0700445
446
Simon Glass77b42672017-08-05 10:28:40 -0600447 cons = u_boot_console
448 try:
449 # We need to use our own device tree file. Remember to restore it
450 # afterwards.
451 old_dtb = cons.config.dtb
452 mkimage = cons.config.build_dir + '/tools/mkimage'
453 run_fit_test(mkimage)
454 finally:
455 # Go back to the original U-Boot with the correct dtb.
456 cons.config.dtb = old_dtb
457 cons.restart_uboot()