blob: f45848484eb0f332d063e7c7727d5652701945ce [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 Glassa7531902022-10-11 09:47:17 -060010import fit_util
Simon Glass301e8032013-05-16 13:53:28 +000011
12# Define a base ITS which we can adjust using % and a dictionary
13base_its = '''
14/dts-v1/;
15
16/ {
17 description = "Chrome OS kernel image with one or more FDT blobs";
18 #address-cells = <1>;
19
20 images {
Simon Glass79af75f2021-02-15 17:08:06 -070021 kernel-1 {
Simon Glass301e8032013-05-16 13:53:28 +000022 data = /incbin/("%(kernel)s");
23 type = "kernel";
24 arch = "sandbox";
25 os = "linux";
Julius Wernerb1307f82019-07-24 19:37:55 -070026 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000027 load = <0x40000>;
28 entry = <0x8>;
29 };
Simon Glass79af75f2021-02-15 17:08:06 -070030 kernel-2 {
Karl Apsite657fd2d2015-05-21 09:52:50 -040031 data = /incbin/("%(loadables1)s");
32 type = "kernel";
33 arch = "sandbox";
34 os = "linux";
35 compression = "none";
36 %(loadables1_load)s
37 entry = <0x0>;
38 };
Simon Glass79af75f2021-02-15 17:08:06 -070039 fdt-1 {
Simon Glass301e8032013-05-16 13:53:28 +000040 description = "snow";
Julius Wernerb1307f82019-07-24 19:37:55 -070041 data = /incbin/("%(fdt)s");
Simon Glass301e8032013-05-16 13:53:28 +000042 type = "flat_dt";
43 arch = "sandbox";
44 %(fdt_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070045 compression = "%(compression)s";
Simon Glass79af75f2021-02-15 17:08:06 -070046 signature-1 {
Simon Glass301e8032013-05-16 13:53:28 +000047 algo = "sha1,rsa2048";
48 key-name-hint = "dev";
49 };
50 };
Simon Glass79af75f2021-02-15 17:08:06 -070051 ramdisk-1 {
Simon Glass301e8032013-05-16 13:53:28 +000052 description = "snow";
53 data = /incbin/("%(ramdisk)s");
54 type = "ramdisk";
55 arch = "sandbox";
56 os = "linux";
57 %(ramdisk_load)s
Julius Wernerb1307f82019-07-24 19:37:55 -070058 compression = "%(compression)s";
Simon Glass301e8032013-05-16 13:53:28 +000059 };
Simon Glass79af75f2021-02-15 17:08:06 -070060 ramdisk-2 {
Karl Apsite657fd2d2015-05-21 09:52:50 -040061 description = "snow";
62 data = /incbin/("%(loadables2)s");
63 type = "ramdisk";
64 arch = "sandbox";
65 os = "linux";
66 %(loadables2_load)s
67 compression = "none";
68 };
Simon Glass301e8032013-05-16 13:53:28 +000069 };
70 configurations {
Simon Glass79af75f2021-02-15 17:08:06 -070071 default = "conf-1";
72 conf-1 {
73 kernel = "kernel-1";
74 fdt = "fdt-1";
Simon Glass301e8032013-05-16 13:53:28 +000075 %(ramdisk_config)s
Karl Apsite657fd2d2015-05-21 09:52:50 -040076 %(loadables_config)s
Simon Glass301e8032013-05-16 13:53:28 +000077 };
78 };
79};
80'''
81
82# Define a base FDT - currently we don't use anything in this
83base_fdt = '''
84/dts-v1/;
85
86/ {
Heinrich Schuchardt954ab3c2019-12-18 11:05:59 +010087 #address-cells = <1>;
88 #size-cells = <0>;
89
90 model = "Sandbox Verified Boot Test";
91 compatible = "sandbox";
Simon Glass301e8032013-05-16 13:53:28 +000092
Philippe Reynes059df562022-03-28 22:56:53 +020093 binman {
94 };
95
Simon Glass0edd82e2016-02-24 09:14:44 -070096 reset@0 {
97 compatible = "sandbox,reset";
Heinrich Schuchardt954ab3c2019-12-18 11:05:59 +010098 reg = <0>;
Simon Glass0edd82e2016-02-24 09:14:44 -070099 };
Simon Glass301e8032013-05-16 13:53:28 +0000100};
101'''
102
Robert P. J. Dayb28c5fc2017-03-13 06:50:55 -0400103# This is the U-Boot script that is run for each test. First load the FIT,
104# then run the 'bootm' command, then save out memory from the places where
Simon Glass301e8032013-05-16 13:53:28 +0000105# we expect 'bootm' to write things. Then quit.
106base_script = '''
Simon Glass6d07d632018-11-15 18:44:02 -0700107host load hostfs 0 %(fit_addr)x %(fit)s
Simon Glass301e8032013-05-16 13:53:28 +0000108fdt addr %(fit_addr)x
109bootm start %(fit_addr)x
110bootm loados
Simon Glass6d07d632018-11-15 18:44:02 -0700111host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
112host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
113host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
114host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
115host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
Simon Glass301e8032013-05-16 13:53:28 +0000116'''
117
Simon Glass77b42672017-08-05 10:28:40 -0600118@pytest.mark.boardspec('sandbox')
119@pytest.mark.buildconfigspec('fit_signature')
Stephen Warren2d26bf62017-09-18 11:11:49 -0600120@pytest.mark.requiredtool('dtc')
Simon Glass77b42672017-08-05 10:28:40 -0600121def test_fit(u_boot_console):
Simon Glassbde67122017-08-05 10:28:39 -0600122 def make_fname(leaf):
123 """Make a temporary filename
124
125 Args:
126 leaf: Leaf name of file to create (within temporary directory)
127 Return:
128 Temporary filename
129 """
Simon Glass77b42672017-08-05 10:28:40 -0600130 return os.path.join(cons.config.build_dir, leaf)
Simon Glassbde67122017-08-05 10:28:39 -0600131
132 def filesize(fname):
133 """Get the size of a file
134
135 Args:
136 fname: Filename to check
137 Return:
138 Size of file in bytes
139 """
140 return os.stat(fname).st_size
141
142 def read_file(fname):
143 """Read the contents of a file
144
145 Args:
146 fname: Filename to read
147 Returns:
148 Contents of file as a string
149 """
Paul Burton57bf9be2017-09-14 14:34:48 -0700150 with open(fname, 'rb') as fd:
Simon Glassbde67122017-08-05 10:28:39 -0600151 return fd.read()
152
Simon Glassbde67122017-08-05 10:28:39 -0600153 def make_ramdisk(filename, text):
154 """Make a sample ramdisk with test data
155
156 Returns:
157 Filename of ramdisk created
158 """
159 fname = make_fname(filename)
160 data = ''
161 for i in range(100):
162 data += '%s %d was seldom used in the middle ages\n' % (text, i)
163 with open(fname, 'w') as fd:
Paul Burtondffd56d2017-09-14 14:34:43 -0700164 print(data, file=fd)
Simon Glassbde67122017-08-05 10:28:39 -0600165 return fname
166
Julius Wernerb1307f82019-07-24 19:37:55 -0700167 def make_compressed(filename):
168 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
169 return filename + '.gz'
170
Simon Glassbde67122017-08-05 10:28:39 -0600171 def find_matching(text, match):
172 """Find a match in a line of text, and return the unmatched line portion
173
174 This is used to extract a part of a line from some text. The match string
175 is used to locate the line - we use the first line that contains that
176 match text.
177
178 Once we find a match, we discard the match string itself from the line,
179 and return what remains.
180
181 TODO: If this function becomes more generally useful, we could change it
182 to use regex and return groups.
183
184 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600185 text: Text to check (list of strings, one for each command issued)
Simon Glassbde67122017-08-05 10:28:39 -0600186 match: String to search for
187 Return:
188 String containing unmatched portion of line
189 Exceptions:
190 ValueError: If match is not found
191
Simon Glass77b42672017-08-05 10:28:40 -0600192 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
Simon Glassbde67122017-08-05 10:28:39 -0600193 '10'
Simon Glass77b42672017-08-05 10:28:40 -0600194 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
Simon Glassbde67122017-08-05 10:28:39 -0600195 Traceback (most recent call last):
196 ...
197 ValueError: Test aborted
Simon Glass77b42672017-08-05 10:28:40 -0600198 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
Simon Glassbde67122017-08-05 10:28:39 -0600199 '20'
Simon Glass77b42672017-08-05 10:28:40 -0600200 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
201 'third_line:')
202 '30'
Simon Glassbde67122017-08-05 10:28:39 -0600203 """
Simon Glass77b42672017-08-05 10:28:40 -0600204 __tracebackhide__ = True
205 for line in '\n'.join(text).splitlines():
Simon Glassbde67122017-08-05 10:28:39 -0600206 pos = line.find(match)
207 if pos != -1:
208 return line[:pos] + line[pos + len(match):]
209
Simon Glass77b42672017-08-05 10:28:40 -0600210 pytest.fail("Expected '%s' but not found in output")
Simon Glassbde67122017-08-05 10:28:39 -0600211
Simon Glass77b42672017-08-05 10:28:40 -0600212 def check_equal(expected_fname, actual_fname, failure_msg):
213 """Check that a file matches its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600214
Julius Wernerbddd9852019-08-02 15:52:28 -0700215 This is always used on out-buffers whose size is decided by the test
216 script anyway, which in some cases may be larger than what we're
217 actually looking for. So it's safe to truncate it to the size of the
218 expected data.
219
Simon Glassbde67122017-08-05 10:28:39 -0600220 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600221 expected_fname: Filename containing expected contents
222 actual_fname: Filename containing actual contents
223 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600224 """
Simon Glass77b42672017-08-05 10:28:40 -0600225 expected_data = read_file(expected_fname)
226 actual_data = read_file(actual_fname)
Julius Wernerbddd9852019-08-02 15:52:28 -0700227 if len(expected_data) < len(actual_data):
228 actual_data = actual_data[:len(expected_data)]
Simon Glass77b42672017-08-05 10:28:40 -0600229 assert expected_data == actual_data, failure_msg
Simon Glassbde67122017-08-05 10:28:39 -0600230
Simon Glass77b42672017-08-05 10:28:40 -0600231 def check_not_equal(expected_fname, actual_fname, failure_msg):
232 """Check that a file does not match its expected contents
Simon Glassbde67122017-08-05 10:28:39 -0600233
234 Args:
Simon Glass77b42672017-08-05 10:28:40 -0600235 expected_fname: Filename containing expected contents
236 actual_fname: Filename containing actual contents
237 failure_msg: Message to print on failure
Simon Glassbde67122017-08-05 10:28:39 -0600238 """
Simon Glass77b42672017-08-05 10:28:40 -0600239 expected_data = read_file(expected_fname)
240 actual_data = read_file(actual_fname)
241 assert expected_data != actual_data, failure_msg
Simon Glasscc447722014-12-02 13:17:32 -0700242
Simon Glass77b42672017-08-05 10:28:40 -0600243 def run_fit_test(mkimage):
Simon Glassbde67122017-08-05 10:28:39 -0600244 """Basic sanity check of FIT loading in U-Boot
Simon Glass301e8032013-05-16 13:53:28 +0000245
Simon Glassbde67122017-08-05 10:28:39 -0600246 TODO: Almost everything:
247 - hash algorithms - invalid hash/contents should be detected
248 - signature algorithms - invalid sig/contents should be detected
249 - compression
250 - checking that errors are detected like:
251 - image overwriting
252 - missing images
253 - invalid configurations
254 - incorrect os/arch/type fields
255 - empty data
256 - images too large/small
257 - invalid FDT (e.g. putting a random binary in instead)
258 - default configuration selection
259 - bootm command line parameters should have desired effect
260 - run code coverage to make sure we are testing all the code
261 """
Simon Glassbde67122017-08-05 10:28:39 -0600262 # Set up invariant files
Simon Glassa7531902022-10-11 09:47:17 -0600263 control_dtb = fit_util.make_dtb(cons, base_fdt, 'u-boot')
264 kernel = fit_util.make_kernel(cons, 'test-kernel.bin', 'kernel')
Simon Glassbde67122017-08-05 10:28:39 -0600265 ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
Simon Glassa7531902022-10-11 09:47:17 -0600266 loadables1 = fit_util.make_kernel(cons, 'test-loadables1.bin', 'lenrek')
Simon Glassbde67122017-08-05 10:28:39 -0600267 loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
268 kernel_out = make_fname('kernel-out.bin')
Julius Wernerb1307f82019-07-24 19:37:55 -0700269 fdt = make_fname('u-boot.dtb')
Simon Glassbde67122017-08-05 10:28:39 -0600270 fdt_out = make_fname('fdt-out.dtb')
271 ramdisk_out = make_fname('ramdisk-out.bin')
272 loadables1_out = make_fname('loadables1-out.bin')
273 loadables2_out = make_fname('loadables2-out.bin')
Simon Glass301e8032013-05-16 13:53:28 +0000274
Simon Glassbde67122017-08-05 10:28:39 -0600275 # Set up basic parameters with default values
276 params = {
277 'fit_addr' : 0x1000,
Simon Glass301e8032013-05-16 13:53:28 +0000278
Simon Glassbde67122017-08-05 10:28:39 -0600279 'kernel' : kernel,
280 'kernel_out' : kernel_out,
281 'kernel_addr' : 0x40000,
282 'kernel_size' : filesize(kernel),
Simon Glass301e8032013-05-16 13:53:28 +0000283
Julius Wernerb1307f82019-07-24 19:37:55 -0700284 'fdt' : fdt,
Simon Glassbde67122017-08-05 10:28:39 -0600285 'fdt_out' : fdt_out,
286 'fdt_addr' : 0x80000,
287 'fdt_size' : filesize(control_dtb),
288 'fdt_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000289
Simon Glassbde67122017-08-05 10:28:39 -0600290 'ramdisk' : ramdisk,
291 'ramdisk_out' : ramdisk_out,
292 'ramdisk_addr' : 0xc0000,
293 'ramdisk_size' : filesize(ramdisk),
294 'ramdisk_load' : '',
295 'ramdisk_config' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000296
Simon Glassbde67122017-08-05 10:28:39 -0600297 'loadables1' : loadables1,
298 'loadables1_out' : loadables1_out,
299 'loadables1_addr' : 0x100000,
300 'loadables1_size' : filesize(loadables1),
301 'loadables1_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000302
Simon Glassbde67122017-08-05 10:28:39 -0600303 'loadables2' : loadables2,
304 'loadables2_out' : loadables2_out,
305 'loadables2_addr' : 0x140000,
306 'loadables2_size' : filesize(loadables2),
307 'loadables2_load' : '',
Simon Glass301e8032013-05-16 13:53:28 +0000308
Simon Glassbde67122017-08-05 10:28:39 -0600309 'loadables_config' : '',
Julius Wernerb1307f82019-07-24 19:37:55 -0700310 'compression' : 'none',
Simon Glassbde67122017-08-05 10:28:39 -0600311 }
Simon Glass301e8032013-05-16 13:53:28 +0000312
Simon Glassbde67122017-08-05 10:28:39 -0600313 # Make a basic FIT and a script to load it
Simon Glassa7531902022-10-11 09:47:17 -0600314 fit = fit_util.make_fit(cons, mkimage, base_its, params)
Simon Glassbde67122017-08-05 10:28:39 -0600315 params['fit'] = fit
316 cmd = base_script % params
Simon Glass301e8032013-05-16 13:53:28 +0000317
Simon Glassbde67122017-08-05 10:28:39 -0600318 # First check that we can load a kernel
319 # We could perhaps reduce duplication with some loss of readability
Simon Glass77b42672017-08-05 10:28:40 -0600320 cons.config.dtb = control_dtb
321 cons.restart_uboot()
322 with cons.log.section('Kernel load'):
323 output = cons.run_command_list(cmd.splitlines())
324 check_equal(kernel, kernel_out, 'Kernel not loaded')
325 check_not_equal(control_dtb, fdt_out,
326 'FDT loaded but should be ignored')
327 check_not_equal(ramdisk, ramdisk_out,
328 'Ramdisk loaded but should not be')
Simon Glass301e8032013-05-16 13:53:28 +0000329
Simon Glassbde67122017-08-05 10:28:39 -0600330 # Find out the offset in the FIT where U-Boot has found the FDT
Simon Glass77b42672017-08-05 10:28:40 -0600331 line = find_matching(output, 'Booting using the fdt blob at ')
Simon Glassbde67122017-08-05 10:28:39 -0600332 fit_offset = int(line, 16) - params['fit_addr']
333 fdt_magic = struct.pack('>L', 0xd00dfeed)
334 data = read_file(fit)
Simon Glass301e8032013-05-16 13:53:28 +0000335
Simon Glassbde67122017-08-05 10:28:39 -0600336 # Now find where it actually is in the FIT (skip the first word)
337 real_fit_offset = data.find(fdt_magic, 4)
Simon Glass77b42672017-08-05 10:28:40 -0600338 assert fit_offset == real_fit_offset, (
339 'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
340 (fit_offset, real_fit_offset))
Simon Glass301e8032013-05-16 13:53:28 +0000341
Simon Glassbde67122017-08-05 10:28:39 -0600342 # Now a kernel and an FDT
Simon Glass77b42672017-08-05 10:28:40 -0600343 with cons.log.section('Kernel + FDT load'):
Simon Glassbde67122017-08-05 10:28:39 -0600344 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
Simon Glassa7531902022-10-11 09:47:17 -0600345 fit = fit_util.make_fit(cons, mkimage, base_its, params)
Simon Glass77b42672017-08-05 10:28:40 -0600346 cons.restart_uboot()
347 output = cons.run_command_list(cmd.splitlines())
348 check_equal(kernel, kernel_out, 'Kernel not loaded')
349 check_equal(control_dtb, fdt_out, 'FDT not loaded')
350 check_not_equal(ramdisk, ramdisk_out,
351 'Ramdisk loaded but should not be')
Simon Glass301e8032013-05-16 13:53:28 +0000352
Simon Glassbde67122017-08-05 10:28:39 -0600353 # Try a ramdisk
Simon Glass77b42672017-08-05 10:28:40 -0600354 with cons.log.section('Kernel + FDT + Ramdisk load'):
Simon Glass79af75f2021-02-15 17:08:06 -0700355 params['ramdisk_config'] = 'ramdisk = "ramdisk-1";'
Simon Glassbde67122017-08-05 10:28:39 -0600356 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
Simon Glassa7531902022-10-11 09:47:17 -0600357 fit = fit_util.make_fit(cons, mkimage, base_its, params)
Simon Glass77b42672017-08-05 10:28:40 -0600358 cons.restart_uboot()
359 output = cons.run_command_list(cmd.splitlines())
360 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
Simon Glass301e8032013-05-16 13:53:28 +0000361
Simon Glassbde67122017-08-05 10:28:39 -0600362 # Configuration with some Loadables
Simon Glass77b42672017-08-05 10:28:40 -0600363 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
Simon Glass79af75f2021-02-15 17:08:06 -0700364 params['loadables_config'] = 'loadables = "kernel-2", "ramdisk-2";'
Simon Glass77b42672017-08-05 10:28:40 -0600365 params['loadables1_load'] = ('load = <%#x>;' %
366 params['loadables1_addr'])
367 params['loadables2_load'] = ('load = <%#x>;' %
368 params['loadables2_addr'])
Simon Glassa7531902022-10-11 09:47:17 -0600369 fit = fit_util.make_fit(cons, mkimage, base_its, params)
Simon Glass77b42672017-08-05 10:28:40 -0600370 cons.restart_uboot()
371 output = cons.run_command_list(cmd.splitlines())
372 check_equal(loadables1, loadables1_out,
373 'Loadables1 (kernel) not loaded')
374 check_equal(loadables2, loadables2_out,
375 'Loadables2 (ramdisk) not loaded')
Karl Apsite657fd2d2015-05-21 09:52:50 -0400376
Julius Wernerb1307f82019-07-24 19:37:55 -0700377 # Kernel, FDT and Ramdisk all compressed
378 with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
379 params['compression'] = 'gzip'
380 params['kernel'] = make_compressed(kernel)
381 params['fdt'] = make_compressed(fdt)
382 params['ramdisk'] = make_compressed(ramdisk)
Simon Glassa7531902022-10-11 09:47:17 -0600383 fit = fit_util.make_fit(cons, mkimage, base_its, params)
Julius Wernerb1307f82019-07-24 19:37:55 -0700384 cons.restart_uboot()
385 output = cons.run_command_list(cmd.splitlines())
386 check_equal(kernel, kernel_out, 'Kernel not loaded')
387 check_equal(control_dtb, fdt_out, 'FDT not loaded')
Julius Wernerbddd9852019-08-02 15:52:28 -0700388 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
389 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
Julius Wernerb1307f82019-07-24 19:37:55 -0700390
391
Simon Glass77b42672017-08-05 10:28:40 -0600392 cons = u_boot_console
393 try:
394 # We need to use our own device tree file. Remember to restore it
395 # afterwards.
396 old_dtb = cons.config.dtb
397 mkimage = cons.config.build_dir + '/tools/mkimage'
398 run_fit_test(mkimage)
399 finally:
400 # Go back to the original U-Boot with the correct dtb.
401 cons.config.dtb = old_dtb
402 cons.restart_uboot()