test/py: use valid device tree in test_fit.py
[platform/kernel/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
11 # Define a base ITS which we can adjust using % and a dictionary
12 base_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";
25                         compression = "%(compression)s";
26                         load = <0x40000>;
27                         entry = <0x8>;
28                 };
29                 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                 };
38                 fdt@1 {
39                         description = "snow";
40                         data = /incbin/("%(fdt)s");
41                         type = "flat_dt";
42                         arch = "sandbox";
43                         %(fdt_load)s
44                         compression = "%(compression)s";
45                         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
57                         compression = "%(compression)s";
58                 };
59                 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                 };
68         };
69         configurations {
70                 default = "conf@1";
71                 conf@1 {
72                         kernel = "kernel@1";
73                         fdt = "fdt@1";
74                         %(ramdisk_config)s
75                         %(loadables_config)s
76                 };
77         };
78 };
79 '''
80
81 # Define a base FDT - currently we don't use anything in this
82 base_fdt = '''
83 /dts-v1/;
84
85 / {
86         #address-cells = <1>;
87         #size-cells = <0>;
88
89         model = "Sandbox Verified Boot Test";
90         compatible = "sandbox";
91
92         reset@0 {
93                 compatible = "sandbox,reset";
94                 reg = <0>;
95         };
96 };
97 '''
98
99 # This is the U-Boot script that is run for each test. First load the FIT,
100 # then run the 'bootm' command, then save out memory from the places where
101 # we expect 'bootm' to write things. Then quit.
102 base_script = '''
103 host load hostfs 0 %(fit_addr)x %(fit)s
104 fdt addr %(fit_addr)x
105 bootm start %(fit_addr)x
106 bootm loados
107 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
108 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
109 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
110 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
111 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
112 '''
113
114 @pytest.mark.boardspec('sandbox')
115 @pytest.mark.buildconfigspec('fit_signature')
116 @pytest.mark.requiredtool('dtc')
117 def test_fit(u_boot_console):
118     def make_fname(leaf):
119         """Make a temporary filename
120
121         Args:
122             leaf: Leaf name of file to create (within temporary directory)
123         Return:
124             Temporary filename
125         """
126
127         return os.path.join(cons.config.build_dir, leaf)
128
129     def filesize(fname):
130         """Get the size of a file
131
132         Args:
133             fname: Filename to check
134         Return:
135             Size of file in bytes
136         """
137         return os.stat(fname).st_size
138
139     def read_file(fname):
140         """Read the contents of a file
141
142         Args:
143             fname: Filename to read
144         Returns:
145             Contents of file as a string
146         """
147         with open(fname, 'rb') as fd:
148             return fd.read()
149
150     def make_dtb():
151         """Make a sample .dts file and compile it to a .dtb
152
153         Returns:
154             Filename of .dtb file created
155         """
156         src = make_fname('u-boot.dts')
157         dtb = make_fname('u-boot.dtb')
158         with open(src, 'w') as fd:
159             fd.write(base_fdt)
160         util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
161         return dtb
162
163     def make_its(params):
164         """Make a sample .its file with parameters embedded
165
166         Args:
167             params: Dictionary containing parameters to embed in the %() strings
168         Returns:
169             Filename of .its file created
170         """
171         its = make_fname('test.its')
172         with open(its, 'w') as fd:
173             print(base_its % params, file=fd)
174         return its
175
176     def make_fit(mkimage, params):
177         """Make a sample .fit file ready for loading
178
179         This creates a .its script with the selected parameters and uses mkimage to
180         turn this into a .fit image.
181
182         Args:
183             mkimage: Filename of 'mkimage' utility
184             params: Dictionary containing parameters to embed in the %() strings
185         Return:
186             Filename of .fit file created
187         """
188         fit = make_fname('test.fit')
189         its = make_its(params)
190         util.run_and_log(cons, [mkimage, '-f', its, fit])
191         with open(make_fname('u-boot.dts'), 'w') as fd:
192             fd.write(base_fdt)
193         return fit
194
195     def make_kernel(filename, text):
196         """Make a sample kernel with test data
197
198         Args:
199             filename: the name of the file you want to create
200         Returns:
201             Full path and filename of the kernel it created
202         """
203         fname = make_fname(filename)
204         data = ''
205         for i in range(100):
206             data += 'this %s %d is unlikely to boot\n' % (text, i)
207         with open(fname, 'w') as fd:
208             print(data, file=fd)
209         return fname
210
211     def make_ramdisk(filename, text):
212         """Make a sample ramdisk with test data
213
214         Returns:
215             Filename of ramdisk created
216         """
217         fname = make_fname(filename)
218         data = ''
219         for i in range(100):
220             data += '%s %d was seldom used in the middle ages\n' % (text, i)
221         with open(fname, 'w') as fd:
222             print(data, file=fd)
223         return fname
224
225     def make_compressed(filename):
226         util.run_and_log(cons, ['gzip', '-f', '-k', filename])
227         return filename + '.gz'
228
229     def find_matching(text, match):
230         """Find a match in a line of text, and return the unmatched line portion
231
232         This is used to extract a part of a line from some text. The match string
233         is used to locate the line - we use the first line that contains that
234         match text.
235
236         Once we find a match, we discard the match string itself from the line,
237         and return what remains.
238
239         TODO: If this function becomes more generally useful, we could change it
240         to use regex and return groups.
241
242         Args:
243             text: Text to check (list of strings, one for each command issued)
244             match: String to search for
245         Return:
246             String containing unmatched portion of line
247         Exceptions:
248             ValueError: If match is not found
249
250         >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
251         '10'
252         >>> find_matching(['first line:10', 'second_line:20'], 'second line')
253         Traceback (most recent call last):
254           ...
255         ValueError: Test aborted
256         >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
257         '20'
258         >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
259                           'third_line:')
260         '30'
261         """
262         __tracebackhide__ = True
263         for line in '\n'.join(text).splitlines():
264             pos = line.find(match)
265             if pos != -1:
266                 return line[:pos] + line[pos + len(match):]
267
268         pytest.fail("Expected '%s' but not found in output")
269
270     def check_equal(expected_fname, actual_fname, failure_msg):
271         """Check that a file matches its expected contents
272
273         This is always used on out-buffers whose size is decided by the test
274         script anyway, which in some cases may be larger than what we're
275         actually looking for. So it's safe to truncate it to the size of the
276         expected data.
277
278         Args:
279             expected_fname: Filename containing expected contents
280             actual_fname: Filename containing actual contents
281             failure_msg: Message to print on failure
282         """
283         expected_data = read_file(expected_fname)
284         actual_data = read_file(actual_fname)
285         if len(expected_data) < len(actual_data):
286             actual_data = actual_data[:len(expected_data)]
287         assert expected_data == actual_data, failure_msg
288
289     def check_not_equal(expected_fname, actual_fname, failure_msg):
290         """Check that a file does not match its expected contents
291
292         Args:
293             expected_fname: Filename containing expected contents
294             actual_fname: Filename containing actual contents
295             failure_msg: Message to print on failure
296         """
297         expected_data = read_file(expected_fname)
298         actual_data = read_file(actual_fname)
299         assert expected_data != actual_data, failure_msg
300
301     def run_fit_test(mkimage):
302         """Basic sanity check of FIT loading in U-Boot
303
304         TODO: Almost everything:
305           - hash algorithms - invalid hash/contents should be detected
306           - signature algorithms - invalid sig/contents should be detected
307           - compression
308           - checking that errors are detected like:
309                 - image overwriting
310                 - missing images
311                 - invalid configurations
312                 - incorrect os/arch/type fields
313                 - empty data
314                 - images too large/small
315                 - invalid FDT (e.g. putting a random binary in instead)
316           - default configuration selection
317           - bootm command line parameters should have desired effect
318           - run code coverage to make sure we are testing all the code
319         """
320         # Set up invariant files
321         control_dtb = make_dtb()
322         kernel = make_kernel('test-kernel.bin', 'kernel')
323         ramdisk = make_ramdisk('test-ramdisk.bin', 'ramdisk')
324         loadables1 = make_kernel('test-loadables1.bin', 'lenrek')
325         loadables2 = make_ramdisk('test-loadables2.bin', 'ksidmar')
326         kernel_out = make_fname('kernel-out.bin')
327         fdt = make_fname('u-boot.dtb')
328         fdt_out = make_fname('fdt-out.dtb')
329         ramdisk_out = make_fname('ramdisk-out.bin')
330         loadables1_out = make_fname('loadables1-out.bin')
331         loadables2_out = make_fname('loadables2-out.bin')
332
333         # Set up basic parameters with default values
334         params = {
335             'fit_addr' : 0x1000,
336
337             'kernel' : kernel,
338             'kernel_out' : kernel_out,
339             'kernel_addr' : 0x40000,
340             'kernel_size' : filesize(kernel),
341
342             'fdt' : fdt,
343             'fdt_out' : fdt_out,
344             'fdt_addr' : 0x80000,
345             'fdt_size' : filesize(control_dtb),
346             'fdt_load' : '',
347
348             'ramdisk' : ramdisk,
349             'ramdisk_out' : ramdisk_out,
350             'ramdisk_addr' : 0xc0000,
351             'ramdisk_size' : filesize(ramdisk),
352             'ramdisk_load' : '',
353             'ramdisk_config' : '',
354
355             'loadables1' : loadables1,
356             'loadables1_out' : loadables1_out,
357             'loadables1_addr' : 0x100000,
358             'loadables1_size' : filesize(loadables1),
359             'loadables1_load' : '',
360
361             'loadables2' : loadables2,
362             'loadables2_out' : loadables2_out,
363             'loadables2_addr' : 0x140000,
364             'loadables2_size' : filesize(loadables2),
365             'loadables2_load' : '',
366
367             'loadables_config' : '',
368             'compression' : 'none',
369         }
370
371         # Make a basic FIT and a script to load it
372         fit = make_fit(mkimage, params)
373         params['fit'] = fit
374         cmd = base_script % params
375
376         # First check that we can load a kernel
377         # We could perhaps reduce duplication with some loss of readability
378         cons.config.dtb = control_dtb
379         cons.restart_uboot()
380         with cons.log.section('Kernel load'):
381             output = cons.run_command_list(cmd.splitlines())
382             check_equal(kernel, kernel_out, 'Kernel not loaded')
383             check_not_equal(control_dtb, fdt_out,
384                             'FDT loaded but should be ignored')
385             check_not_equal(ramdisk, ramdisk_out,
386                             'Ramdisk loaded but should not be')
387
388             # Find out the offset in the FIT where U-Boot has found the FDT
389             line = find_matching(output, 'Booting using the fdt blob at ')
390             fit_offset = int(line, 16) - params['fit_addr']
391             fdt_magic = struct.pack('>L', 0xd00dfeed)
392             data = read_file(fit)
393
394             # Now find where it actually is in the FIT (skip the first word)
395             real_fit_offset = data.find(fdt_magic, 4)
396             assert fit_offset == real_fit_offset, (
397                   'U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
398                   (fit_offset, real_fit_offset))
399
400         # Now a kernel and an FDT
401         with cons.log.section('Kernel + FDT load'):
402             params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
403             fit = make_fit(mkimage, params)
404             cons.restart_uboot()
405             output = cons.run_command_list(cmd.splitlines())
406             check_equal(kernel, kernel_out, 'Kernel not loaded')
407             check_equal(control_dtb, fdt_out, 'FDT not loaded')
408             check_not_equal(ramdisk, ramdisk_out,
409                             'Ramdisk loaded but should not be')
410
411         # Try a ramdisk
412         with cons.log.section('Kernel + FDT + Ramdisk load'):
413             params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
414             params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
415             fit = make_fit(mkimage, params)
416             cons.restart_uboot()
417             output = cons.run_command_list(cmd.splitlines())
418             check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
419
420         # Configuration with some Loadables
421         with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
422             params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
423             params['loadables1_load'] = ('load = <%#x>;' %
424                                          params['loadables1_addr'])
425             params['loadables2_load'] = ('load = <%#x>;' %
426                                          params['loadables2_addr'])
427             fit = make_fit(mkimage, params)
428             cons.restart_uboot()
429             output = cons.run_command_list(cmd.splitlines())
430             check_equal(loadables1, loadables1_out,
431                         'Loadables1 (kernel) not loaded')
432             check_equal(loadables2, loadables2_out,
433                         'Loadables2 (ramdisk) not loaded')
434
435         # Kernel, FDT and Ramdisk all compressed
436         with cons.log.section('(Kernel + FDT + Ramdisk) compressed'):
437             params['compression'] = 'gzip'
438             params['kernel'] = make_compressed(kernel)
439             params['fdt'] = make_compressed(fdt)
440             params['ramdisk'] = make_compressed(ramdisk)
441             fit = make_fit(mkimage, params)
442             cons.restart_uboot()
443             output = cons.run_command_list(cmd.splitlines())
444             check_equal(kernel, kernel_out, 'Kernel not loaded')
445             check_equal(control_dtb, fdt_out, 'FDT not loaded')
446             check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
447             check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
448
449
450     cons = u_boot_console
451     try:
452         # We need to use our own device tree file. Remember to restore it
453         # afterwards.
454         old_dtb = cons.config.dtb
455         mkimage = cons.config.build_dir + '/tools/mkimage'
456         run_fit_test(mkimage)
457     finally:
458         # Go back to the original U-Boot with the correct dtb.
459         cons.config.dtb = old_dtb
460         cons.restart_uboot()