]> git.ipfire.org Git - thirdparty/u-boot.git/blob - test/py/tests/test_fit.py
bootm: Fix flags used for bootargs string substitution
[thirdparty/u-boot.git] / test / py / tests / test_fit.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2013, Google Inc.
3 #
4 # Sanity check of the FIT handling in U-Boot
5
6 import os
7 import pytest
8 import struct
9 import u_boot_utils as util
10 import fit_util
11
12 # Define a base ITS which we can adjust using % and a dictionary
13 base_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 {
21 kernel-1 {
22 data = /incbin/("%(kernel)s");
23 type = "kernel";
24 arch = "sandbox";
25 os = "linux";
26 compression = "%(compression)s";
27 load = <0x40000>;
28 entry = <0x8>;
29 };
30 kernel-2 {
31 data = /incbin/("%(loadables1)s");
32 type = "kernel";
33 arch = "sandbox";
34 os = "linux";
35 compression = "none";
36 %(loadables1_load)s
37 entry = <0x0>;
38 };
39 fdt-1 {
40 description = "snow";
41 data = /incbin/("%(fdt)s");
42 type = "flat_dt";
43 arch = "sandbox";
44 %(fdt_load)s
45 compression = "%(compression)s";
46 signature-1 {
47 algo = "sha1,rsa2048";
48 key-name-hint = "dev";
49 };
50 };
51 ramdisk-1 {
52 description = "snow";
53 data = /incbin/("%(ramdisk)s");
54 type = "ramdisk";
55 arch = "sandbox";
56 os = "linux";
57 %(ramdisk_load)s
58 compression = "%(compression)s";
59 };
60 ramdisk-2 {
61 description = "snow";
62 data = /incbin/("%(loadables2)s");
63 type = "ramdisk";
64 arch = "sandbox";
65 os = "linux";
66 %(loadables2_load)s
67 compression = "none";
68 };
69 };
70 configurations {
71 default = "conf-1";
72 conf-1 {
73 kernel = "kernel-1";
74 fdt = "fdt-1";
75 %(ramdisk_config)s
76 %(loadables_config)s
77 };
78 };
79 };
80 '''
81
82 # Define a base FDT - currently we don't use anything in this
83 base_fdt = '''
84 /dts-v1/;
85
86 / {
87 #address-cells = <1>;
88 #size-cells = <0>;
89
90 model = "Sandbox Verified Boot Test";
91 compatible = "sandbox";
92
93 binman {
94 };
95
96 reset@0 {
97 compatible = "sandbox,reset";
98 reg = <0>;
99 };
100 };
101 '''
102
103 # 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
105 # we expect 'bootm' to write things. Then quit.
106 base_script = '''
107 host load hostfs 0 %(fit_addr)x %(fit)s
108 fdt addr %(fit_addr)x
109 bootm start %(fit_addr)x
110 bootm loados
111 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
112 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
113 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
114 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
115 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
116 '''
117
118 @pytest.mark.boardspec('sandbox')
119 @pytest.mark.buildconfigspec('fit_signature')
120 @pytest.mark.requiredtool('dtc')
121 def test_fit(u_boot_console):
122 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 """
130 return os.path.join(cons.config.build_dir, leaf)
131
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 """
150 with open(fname, 'rb') as fd:
151 return fd.read()
152
153 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:
164 print(data, file=fd)
165 return fname
166
167 def make_compressed(filename):
168 util.run_and_log(cons, ['gzip', '-f', '-k', filename])
169 return filename + '.gz'
170
171 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:
185 text: Text to check (list of strings, one for each command issued)
186 match: String to search for
187 Return:
188 String containing unmatched portion of line
189 Exceptions:
190 ValueError: If match is not found
191
192 >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
193 '10'
194 >>> find_matching(['first line:10', 'second_line:20'], 'second line')
195 Traceback (most recent call last):
196 ...
197 ValueError: Test aborted
198 >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
199 '20'
200 >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
201 'third_line:')
202 '30'
203 """
204 __tracebackhide__ = True
205 for line in '\n'.join(text).splitlines():
206 pos = line.find(match)
207 if pos != -1:
208 return line[:pos] + line[pos + len(match):]
209
210 pytest.fail("Expected '%s' but not found in output")
211
212 def check_equal(expected_fname, actual_fname, failure_msg):
213 """Check that a file matches its expected contents
214
215 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
220 Args:
221 expected_fname: Filename containing expected contents
222 actual_fname: Filename containing actual contents
223 failure_msg: Message to print on failure
224 """
225 expected_data = read_file(expected_fname)
226 actual_data = read_file(actual_fname)
227 if len(expected_data) < len(actual_data):
228 actual_data = actual_data[:len(expected_data)]
229 assert expected_data == actual_data, failure_msg
230
231 def check_not_equal(expected_fname, actual_fname, failure_msg):
232 """Check that a file does not match its expected contents
233
234 Args:
235 expected_fname: Filename containing expected contents
236 actual_fname: Filename containing actual contents
237 failure_msg: Message to print on failure
238 """
239 expected_data = read_file(expected_fname)
240 actual_data = read_file(actual_fname)
241 assert expected_data != actual_data, failure_msg
242
243 def run_fit_test(mkimage):
244 """Basic sanity check of FIT loading in U-Boot
245
246 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 """
262 # Set up invariant files
263 control_dtb = fit_util.make_dtb(cons, base_fdt, 'u-boot')
264 kernel = fit_util.make_kernel(cons, 'test-kernel.bin', 'kernel')
265 ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
266 loadables1 = fit_util.make_kernel(cons, 'test-loadables1.bin', 'lenrek')
267 loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
268 kernel_out = make_fname('kernel-out.bin')
269 fdt = make_fname('u-boot.dtb')
270 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')
274
275 # Set up basic parameters with default values
276 params = {
277 'fit_addr' : 0x1000,
278
279 'kernel' : kernel,
280 'kernel_out' : kernel_out,
281 'kernel_addr' : 0x40000,
282 'kernel_size' : filesize(kernel),
283
284 'fdt' : fdt,
285 'fdt_out' : fdt_out,
286 'fdt_addr' : 0x80000,
287 'fdt_size' : filesize(control_dtb),
288 'fdt_load' : '',
289
290 'ramdisk' : ramdisk,
291 'ramdisk_out' : ramdisk_out,
292 'ramdisk_addr' : 0xc0000,
293 'ramdisk_size' : filesize(ramdisk),
294 'ramdisk_load' : '',
295 'ramdisk_config' : '',
296
297 'loadables1' : loadables1,
298 'loadables1_out' : loadables1_out,
299 'loadables1_addr' : 0x100000,
300 'loadables1_size' : filesize(loadables1),
301 'loadables1_load' : '',
302
303 'loadables2' : loadables2,
304 'loadables2_out' : loadables2_out,
305 'loadables2_addr' : 0x140000,
306 'loadables2_size' : filesize(loadables2),
307 'loadables2_load' : '',
308
309 'loadables_config' : '',
310 'compression' : 'none',
311 }
312
313 # Make a basic FIT and a script to load it
314 fit = fit_util.make_fit(cons, mkimage, base_its, params)
315 params['fit'] = fit
316 cmd = base_script % params
317
318 # First check that we can load a kernel
319 # We could perhaps reduce duplication with some loss of readability
320 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')
329
330 # Find out the offset in the FIT where U-Boot has found the FDT
331 line = find_matching(output, 'Booting using the fdt blob at ')
332 fit_offset = int(line, 16) - params['fit_addr']
333 fdt_magic = struct.pack('>L', 0xd00dfeed)
334 data = read_file(fit)
335
336 # Now find where it actually is in the FIT (skip the first word)
337 real_fit_offset = data.find(fdt_magic, 4)
338 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))
341
342 # Check if bootargs strings substitution works
343 output = cons.run_command_list([
344 'env set bootargs \\\"\'my_boot_var=${foo}\'\\\"',
345 'env set foo bar',
346 'bootm prep',
347 'env print bootargs'])
348 assert 'bootargs="my_boot_var=bar"' in output, "Bootargs strings not substituted"
349
350 # Now a kernel and an FDT
351 with cons.log.section('Kernel + FDT load'):
352 params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
353 fit = fit_util.make_fit(cons, mkimage, base_its, params)
354 cons.restart_uboot()
355 output = cons.run_command_list(cmd.splitlines())
356 check_equal(kernel, kernel_out, 'Kernel not loaded')
357 check_equal(control_dtb, fdt_out, 'FDT not loaded')
358 check_not_equal(ramdisk, ramdisk_out,
359 'Ramdisk loaded but should not be')
360
361 # Try a ramdisk
362 with cons.log.section('Kernel + FDT + Ramdisk load'):
363 params['ramdisk_config'] = 'ramdisk = "ramdisk-1";'
364 params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
365 fit = fit_util.make_fit(cons, mkimage, base_its, params)
366 cons.restart_uboot()
367 output = cons.run_command_list(cmd.splitlines())
368 check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
369
370 # Configuration with some Loadables
371 with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
372 params['loadables_config'] = 'loadables = "kernel-2", "ramdisk-2";'
373 params['loadables1_load'] = ('load = <%#x>;' %
374 params['loadables1_addr'])
375 params['loadables2_load'] = ('load = <%#x>;' %
376 params['loadables2_addr'])
377 fit = fit_util.make_fit(cons, mkimage, base_its, params)
378 cons.restart_uboot()
379 output = cons.run_command_list(cmd.splitlines())
380 check_equal(loadables1, loadables1_out,
381 'Loadables1 (kernel) not loaded')
382 check_equal(loadables2, loadables2_out,
383 'Loadables2 (ramdisk) not loaded')
384
385 # Kernel, FDT and Ramdisk all compressed
386 with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
387 params['compression'] = 'gzip'
388 params['kernel'] = make_compressed(kernel)
389 params['fdt'] = make_compressed(fdt)
390 params['ramdisk'] = make_compressed(ramdisk)
391 fit = fit_util.make_fit(cons, mkimage, base_its, params)
392 cons.restart_uboot()
393 output = cons.run_command_list(cmd.splitlines())
394 check_equal(kernel, kernel_out, 'Kernel not loaded')
395 check_equal(control_dtb, fdt_out, 'FDT not loaded')
396 check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
397 check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
398
399
400 cons = u_boot_console
401 # We need to use our own device tree file. Remember to restore it
402 # afterwards.
403 old_dtb = cons.config.dtb
404 try:
405 mkimage = cons.config.build_dir + '/tools/mkimage'
406 run_fit_test(mkimage)
407 finally:
408 # Go back to the original U-Boot with the correct dtb.
409 cons.config.dtb = old_dtb
410 cons.restart_uboot()