fdt: Fix alignment issue when reading 64-bits properties from fdt
[oweals/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 from __future__ import print_function
7
8 import os
9 import pytest
10 import struct
11 import u_boot_utils as util
12
13 # Define a base ITS which we can adjust using % and a dictionary
14 base_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";
27                         compression = "%(compression)s";
28                         load = <0x40000>;
29                         entry = <0x8>;
30                 };
31                 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                 };
40                 fdt@1 {
41                         description = "snow";
42                         data = /incbin/("%(fdt)s");
43                         type = "flat_dt";
44                         arch = "sandbox";
45                         %(fdt_load)s
46                         compression = "%(compression)s";
47                         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
59                         compression = "%(compression)s";
60                 };
61                 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                 };
70         };
71         configurations {
72                 default = "conf@1";
73                 conf@1 {
74                         kernel = "kernel@1";
75                         fdt = "fdt@1";
76                         %(ramdisk_config)s
77                         %(loadables_config)s
78                 };
79         };
80 };
81 '''
82
83 # Define a base FDT - currently we don't use anything in this
84 base_fdt = '''
85 /dts-v1/;
86
87 / {
88         model = "Sandbox Verified Boot Test";
89         compatible = "sandbox";
90
91         reset@0 {
92                 compatible = "sandbox,reset";
93         };
94
95 };
96 '''
97
98 # 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
100 # we expect 'bootm' to write things. Then quit.
101 base_script = '''
102 host load hostfs 0 %(fit_addr)x %(fit)s
103 fdt addr %(fit_addr)x
104 bootm start %(fit_addr)x
105 bootm loados
106 host save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
107 host save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
108 host save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
109 host save hostfs 0 %(loadables1_addr)x %(loadables1_out)s %(loadables1_size)x
110 host save hostfs 0 %(loadables2_addr)x %(loadables2_out)s %(loadables2_size)x
111 '''
112
113 @pytest.mark.boardspec('sandbox')
114 @pytest.mark.buildconfigspec('fit_signature')
115 @pytest.mark.requiredtool('dtc')
116 def test_fit(u_boot_console):
117     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         """
125
126         return os.path.join(cons.config.build_dir, leaf)
127
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         """
146         with open(fname, 'rb') as fd:
147             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:
158             print(base_fdt, file=fd)
159         util.run_and_log(cons, ['dtc', src, '-O', 'dtb', '-o', dtb])
160         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:
172             print(base_its % params, file=fd)
173         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)
189         util.run_and_log(cons, [mkimage, '-f', its, fit])
190         with open(make_fname('u-boot.dts'), 'w') as fd:
191             print(base_fdt, file=fd)
192         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:
207             print(data, file=fd)
208         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:
221             print(data, file=fd)
222         return fname
223
224     def make_compressed(filename):
225         util.run_and_log(cons, ['gzip', '-f', '-k', filename])
226         return filename + '.gz'
227
228     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:
242             text: Text to check (list of strings, one for each command issued)
243             match: String to search for
244         Return:
245             String containing unmatched portion of line
246         Exceptions:
247             ValueError: If match is not found
248
249         >>> find_matching(['first line:10', 'second_line:20'], 'first line:')
250         '10'
251         >>> find_matching(['first line:10', 'second_line:20'], 'second line')
252         Traceback (most recent call last):
253           ...
254         ValueError: Test aborted
255         >>> find_matching('first line:10\', 'second_line:20'], 'second_line:')
256         '20'
257         >>> find_matching('first line:10\', 'second_line:20\nthird_line:30'],
258                           'third_line:')
259         '30'
260         """
261         __tracebackhide__ = True
262         for line in '\n'.join(text).splitlines():
263             pos = line.find(match)
264             if pos != -1:
265                 return line[:pos] + line[pos + len(match):]
266
267         pytest.fail("Expected '%s' but not found in output")
268
269     def check_equal(expected_fname, actual_fname, failure_msg):
270         """Check that a file matches its expected contents
271
272         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
277         Args:
278             expected_fname: Filename containing expected contents
279             actual_fname: Filename containing actual contents
280             failure_msg: Message to print on failure
281         """
282         expected_data = read_file(expected_fname)
283         actual_data = read_file(actual_fname)
284         if len(expected_data) < len(actual_data):
285             actual_data = actual_data[:len(expected_data)]
286         assert expected_data == actual_data, failure_msg
287
288     def check_not_equal(expected_fname, actual_fname, failure_msg):
289         """Check that a file does not match its expected contents
290
291         Args:
292             expected_fname: Filename containing expected contents
293             actual_fname: Filename containing actual contents
294             failure_msg: Message to print on failure
295         """
296         expected_data = read_file(expected_fname)
297         actual_data = read_file(actual_fname)
298         assert expected_data != actual_data, failure_msg
299
300     def run_fit_test(mkimage):
301         """Basic sanity check of FIT loading in U-Boot
302
303         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         """
319         # 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')
326         fdt = make_fname('u-boot.dtb')
327         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')
331
332         # Set up basic parameters with default values
333         params = {
334             'fit_addr' : 0x1000,
335
336             'kernel' : kernel,
337             'kernel_out' : kernel_out,
338             'kernel_addr' : 0x40000,
339             'kernel_size' : filesize(kernel),
340
341             'fdt' : fdt,
342             'fdt_out' : fdt_out,
343             'fdt_addr' : 0x80000,
344             'fdt_size' : filesize(control_dtb),
345             'fdt_load' : '',
346
347             'ramdisk' : ramdisk,
348             'ramdisk_out' : ramdisk_out,
349             'ramdisk_addr' : 0xc0000,
350             'ramdisk_size' : filesize(ramdisk),
351             'ramdisk_load' : '',
352             'ramdisk_config' : '',
353
354             'loadables1' : loadables1,
355             'loadables1_out' : loadables1_out,
356             'loadables1_addr' : 0x100000,
357             'loadables1_size' : filesize(loadables1),
358             'loadables1_load' : '',
359
360             'loadables2' : loadables2,
361             'loadables2_out' : loadables2_out,
362             'loadables2_addr' : 0x140000,
363             'loadables2_size' : filesize(loadables2),
364             'loadables2_load' : '',
365
366             'loadables_config' : '',
367             'compression' : 'none',
368         }
369
370         # 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
374
375         # First check that we can load a kernel
376         # We could perhaps reduce duplication with some loss of readability
377         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')
386
387             # Find out the offset in the FIT where U-Boot has found the FDT
388             line = find_matching(output, 'Booting using the fdt blob at ')
389             fit_offset = int(line, 16) - params['fit_addr']
390             fdt_magic = struct.pack('>L', 0xd00dfeed)
391             data = read_file(fit)
392
393             # Now find where it actually is in the FIT (skip the first word)
394             real_fit_offset = data.find(fdt_magic, 4)
395             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))
398
399         # Now a kernel and an FDT
400         with cons.log.section('Kernel + FDT load'):
401             params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
402             fit = make_fit(mkimage, params)
403             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')
409
410         # Try a ramdisk
411         with cons.log.section('Kernel + FDT + Ramdisk load'):
412             params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
413             params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
414             fit = make_fit(mkimage, params)
415             cons.restart_uboot()
416             output = cons.run_command_list(cmd.splitlines())
417             check_equal(ramdisk, ramdisk_out, 'Ramdisk not loaded')
418
419         # Configuration with some Loadables
420         with cons.log.section('Kernel + FDT + Ramdisk load + Loadables'):
421             params['loadables_config'] = 'loadables = "kernel@2", "ramdisk@2";'
422             params['loadables1_load'] = ('load = <%#x>;' %
423                                          params['loadables1_addr'])
424             params['loadables2_load'] = ('load = <%#x>;' %
425                                          params['loadables2_addr'])
426             fit = make_fit(mkimage, params)
427             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')
433
434         # 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')
445             check_not_equal(ramdisk, ramdisk_out, 'Ramdisk got decompressed?')
446             check_equal(ramdisk + '.gz', ramdisk_out, 'Ramdist not loaded')
447
448
449     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()