]> git.kernelconcepts.de Git - karo-tx-uboot.git/blob - test/image/test-fit.py
tools/imagetool: remove linker script
[karo-tx-uboot.git] / test / image / test-fit.py
1 #!/usr/bin/python
2 #
3 # Copyright (c) 2013, Google Inc.
4 #
5 # Sanity check of the FIT handling in U-Boot
6 #
7 # SPDX-License-Identifier:      GPL-2.0+
8 #
9 # To run this:
10 #
11 # make O=sandbox sandbox_config
12 # make O=sandbox
13 # ./test/image/test-fit.py -u sandbox/u-boot
14
15 import doctest
16 from optparse import OptionParser
17 import os
18 import shutil
19 import struct
20 import sys
21 import tempfile
22
23 # Enable printing of all U-Boot output
24 DEBUG = True
25
26 # The 'command' library in patman is convenient for running commands
27 base_path = os.path.dirname(sys.argv[0])
28 patman = os.path.join(base_path, '../../tools/patman')
29 sys.path.append(patman)
30
31 import command
32
33 # Define a base ITS which we can adjust using % and a dictionary
34 base_its = '''
35 /dts-v1/;
36
37 / {
38         description = "Chrome OS kernel image with one or more FDT blobs";
39         #address-cells = <1>;
40
41         images {
42                 kernel@1 {
43                         data = /incbin/("%(kernel)s");
44                         type = "kernel";
45                         arch = "sandbox";
46                         os = "linux";
47                         compression = "none";
48                         load = <0x40000>;
49                         entry = <0x8>;
50                 };
51                 fdt@1 {
52                         description = "snow";
53                         data = /incbin/("u-boot.dtb");
54                         type = "flat_dt";
55                         arch = "sandbox";
56                         %(fdt_load)s
57                         compression = "none";
58                         signature@1 {
59                                 algo = "sha1,rsa2048";
60                                 key-name-hint = "dev";
61                         };
62                 };
63                 ramdisk@1 {
64                         description = "snow";
65                         data = /incbin/("%(ramdisk)s");
66                         type = "ramdisk";
67                         arch = "sandbox";
68                         os = "linux";
69                         %(ramdisk_load)s
70                         compression = "none";
71                 };
72         };
73         configurations {
74                 default = "conf@1";
75                 conf@1 {
76                         kernel = "kernel@1";
77                         fdt = "fdt@1";
78                         %(ramdisk_config)s
79                 };
80         };
81 };
82 '''
83
84 # Define a base FDT - currently we don't use anything in this
85 base_fdt = '''
86 /dts-v1/;
87
88 / {
89         model = "Sandbox Verified Boot Test";
90         compatible = "sandbox";
91
92 };
93 '''
94
95 # This is the U-Boot script that is run for each test. First load the fit,
96 # then do the 'bootm' command, then save out memory from the places where
97 # we expect 'bootm' to write things. Then quit.
98 base_script = '''
99 sb load hostfs 0 %(fit_addr)x %(fit)s
100 fdt addr %(fit_addr)x
101 bootm start %(fit_addr)x
102 bootm loados
103 sb save hostfs 0 %(kernel_addr)x %(kernel_out)s %(kernel_size)x
104 sb save hostfs 0 %(fdt_addr)x %(fdt_out)s %(fdt_size)x
105 sb save hostfs 0 %(ramdisk_addr)x %(ramdisk_out)s %(ramdisk_size)x
106 reset
107 '''
108
109 def debug_stdout(stdout):
110     if DEBUG:
111         print stdout
112
113 def make_fname(leaf):
114     """Make a temporary filename
115
116     Args:
117         leaf: Leaf name of file to create (within temporary directory)
118     Return:
119         Temporary filename
120     """
121     global base_dir
122
123     return os.path.join(base_dir, leaf)
124
125 def filesize(fname):
126     """Get the size of a file
127
128     Args:
129         fname: Filename to check
130     Return:
131         Size of file in bytes
132     """
133     return os.stat(fname).st_size
134
135 def read_file(fname):
136     """Read the contents of a file
137
138     Args:
139         fname: Filename to read
140     Returns:
141         Contents of file as a string
142     """
143     with open(fname, 'r') as fd:
144         return fd.read()
145
146 def make_dtb():
147     """Make a sample .dts file and compile it to a .dtb
148
149     Returns:
150         Filename of .dtb file created
151     """
152     src = make_fname('u-boot.dts')
153     dtb = make_fname('u-boot.dtb')
154     with open(src, 'w') as fd:
155         print >>fd, base_fdt
156     command.Output('dtc', src, '-O', 'dtb', '-o', dtb)
157     return dtb
158
159 def make_its(params):
160     """Make a sample .its file with parameters embedded
161
162     Args:
163         params: Dictionary containing parameters to embed in the %() strings
164     Returns:
165         Filename of .its file created
166     """
167     its = make_fname('test.its')
168     with open(its, 'w') as fd:
169         print >>fd, base_its % params
170     return its
171
172 def make_fit(mkimage, params):
173     """Make a sample .fit file ready for loading
174
175     This creates a .its script with the selected parameters and uses mkimage to
176     turn this into a .fit image.
177
178     Args:
179         mkimage: Filename of 'mkimage' utility
180         params: Dictionary containing parameters to embed in the %() strings
181     Return:
182         Filename of .fit file created
183     """
184     fit = make_fname('test.fit')
185     its = make_its(params)
186     command.Output(mkimage, '-f', its, fit)
187     with open(make_fname('u-boot.dts'), 'w') as fd:
188         print >>fd, base_fdt
189     return fit
190
191 def make_kernel():
192     """Make a sample kernel with test data
193
194     Returns:
195         Filename of kernel created
196     """
197     fname = make_fname('test-kernel.bin')
198     data = ''
199     for i in range(100):
200         data += 'this kernel %d is unlikely to boot\n' % i
201     with open(fname, 'w') as fd:
202         print >>fd, data
203     return fname
204
205 def make_ramdisk():
206     """Make a sample ramdisk with test data
207
208     Returns:
209         Filename of ramdisk created
210     """
211     fname = make_fname('test-ramdisk.bin')
212     data = ''
213     for i in range(100):
214         data += 'ramdisk %d was seldom used in the middle ages\n' % i
215     with open(fname, 'w') as fd:
216         print >>fd, data
217     return fname
218
219 def find_matching(text, match):
220     """Find a match in a line of text, and return the unmatched line portion
221
222     This is used to extract a part of a line from some text. The match string
223     is used to locate the line - we use the first line that contains that
224     match text.
225
226     Once we find a match, we discard the match string itself from the line,
227     and return what remains.
228
229     TODO: If this function becomes more generally useful, we could change it
230     to use regex and return groups.
231
232     Args:
233         text: Text to check (each line separated by \n)
234         match: String to search for
235     Return:
236         String containing unmatched portion of line
237     Exceptions:
238         ValueError: If match is not found
239
240     >>> find_matching('first line:10\\nsecond_line:20', 'first line:')
241     '10'
242     >>> find_matching('first line:10\\nsecond_line:20', 'second linex')
243     Traceback (most recent call last):
244       ...
245     ValueError: Test aborted
246     >>> find_matching('first line:10\\nsecond_line:20', 'second_line:')
247     '20'
248     """
249     for line in text.splitlines():
250         pos = line.find(match)
251         if pos != -1:
252             return line[:pos] + line[pos + len(match):]
253
254     print "Expected '%s' but not found in output:"
255     print text
256     raise ValueError('Test aborted')
257
258 def set_test(name):
259     """Set the name of the current test and print a message
260
261     Args:
262         name: Name of test
263     """
264     global test_name
265
266     test_name = name
267     print name
268
269 def fail(msg, stdout):
270     """Raise an error with a helpful failure message
271
272     Args:
273         msg: Message to display
274     """
275     print stdout
276     raise ValueError("Test '%s' failed: %s" % (test_name, msg))
277
278 def run_fit_test(mkimage, u_boot):
279     """Basic sanity check of FIT loading in U-Boot
280
281     TODO: Almost everything:
282        - hash algorithms - invalid hash/contents should be detected
283        - signature algorithms - invalid sig/contents should be detected
284        - compression
285        - checking that errors are detected like:
286             - image overwriting
287             - missing images
288             - invalid configurations
289             - incorrect os/arch/type fields
290             - empty data
291             - images too large/small
292             - invalid FDT (e.g. putting a random binary in instead)
293        - default configuration selection
294        - bootm command line parameters should have desired effect
295        - run code coverage to make sure we are testing all the code
296     """
297     global test_name
298
299     # Set up invariant files
300     control_dtb = make_dtb()
301     kernel = make_kernel()
302     ramdisk = make_ramdisk()
303     kernel_out = make_fname('kernel-out.bin')
304     fdt_out = make_fname('fdt-out.dtb')
305     ramdisk_out = make_fname('ramdisk-out.bin')
306
307     # Set up basic parameters with default values
308     params = {
309         'fit_addr' : 0x1000,
310
311         'kernel' : kernel,
312         'kernel_out' : kernel_out,
313         'kernel_addr' : 0x40000,
314         'kernel_size' : filesize(kernel),
315
316         'fdt_out' : fdt_out,
317         'fdt_addr' : 0x80000,
318         'fdt_size' : filesize(control_dtb),
319         'fdt_load' : '',
320
321         'ramdisk' : ramdisk,
322         'ramdisk_out' : ramdisk_out,
323         'ramdisk_addr' : 0xc0000,
324         'ramdisk_size' : filesize(ramdisk),
325         'ramdisk_load' : '',
326         'ramdisk_config' : '',
327     }
328
329     # Make a basic FIT and a script to load it
330     fit = make_fit(mkimage, params)
331     params['fit'] = fit
332     cmd = base_script % params
333
334     # First check that we can load a kernel
335     # We could perhaps reduce duplication with some loss of readability
336     set_test('Kernel load')
337     stdout = command.Output(u_boot, '-d', control_dtb, '-c', cmd)
338     debug_stdout(stdout)
339     if read_file(kernel) != read_file(kernel_out):
340         fail('Kernel not loaded', stdout)
341     if read_file(control_dtb) == read_file(fdt_out):
342         fail('FDT loaded but should be ignored', stdout)
343     if read_file(ramdisk) == read_file(ramdisk_out):
344         fail('Ramdisk loaded but should not be', stdout)
345
346     # Find out the offset in the FIT where U-Boot has found the FDT
347     line = find_matching(stdout, 'Booting using the fdt blob at ')
348     fit_offset = int(line, 16) - params['fit_addr']
349     fdt_magic = struct.pack('>L', 0xd00dfeed)
350     data = read_file(fit)
351
352     # Now find where it actually is in the FIT (skip the first word)
353     real_fit_offset = data.find(fdt_magic, 4)
354     if fit_offset != real_fit_offset:
355         fail('U-Boot loaded FDT from offset %#x, FDT is actually at %#x' %
356                 (fit_offset, real_fit_offset), stdout)
357
358     # Now a kernel and an FDT
359     set_test('Kernel + FDT load')
360     params['fdt_load'] = 'load = <%#x>;' % params['fdt_addr']
361     fit = make_fit(mkimage, params)
362     stdout = command.Output(u_boot, '-d', control_dtb, '-c', cmd)
363     debug_stdout(stdout)
364     if read_file(kernel) != read_file(kernel_out):
365         fail('Kernel not loaded', stdout)
366     if read_file(control_dtb) != read_file(fdt_out):
367         fail('FDT not loaded', stdout)
368     if read_file(ramdisk) == read_file(ramdisk_out):
369         fail('Ramdisk loaded but should not be', stdout)
370
371     # Try a ramdisk
372     set_test('Kernel + FDT + Ramdisk load')
373     params['ramdisk_config'] = 'ramdisk = "ramdisk@1";'
374     params['ramdisk_load'] = 'load = <%#x>;' % params['ramdisk_addr']
375     fit = make_fit(mkimage, params)
376     stdout = command.Output(u_boot, '-d', control_dtb, '-c', cmd)
377     debug_stdout(stdout)
378     if read_file(ramdisk) != read_file(ramdisk_out):
379         fail('Ramdisk not loaded', stdout)
380
381 def run_tests():
382     """Parse options, run the FIT tests and print the result"""
383     global base_path, base_dir
384
385     # Work in a temporary directory
386     base_dir = tempfile.mkdtemp()
387     parser = OptionParser()
388     parser.add_option('-u', '--u-boot',
389             default=os.path.join(base_path, 'u-boot'),
390             help='Select U-Boot sandbox binary')
391     parser.add_option('-k', '--keep', action='store_true',
392             help="Don't delete temporary directory even when tests pass")
393     parser.add_option('-t', '--selftest', action='store_true',
394             help='Run internal self tests')
395     (options, args) = parser.parse_args()
396
397     # Find the path to U-Boot, and assume mkimage is in its tools/mkimage dir
398     base_path = os.path.dirname(options.u_boot)
399     mkimage = os.path.join(base_path, 'tools/mkimage')
400
401     # There are a few doctests - handle these here
402     if options.selftest:
403         doctest.testmod()
404         return
405
406     title = 'FIT Tests'
407     print title, '\n', '=' * len(title)
408
409     run_fit_test(mkimage, options.u_boot)
410
411     print '\nTests passed'
412     print 'Caveat: this is only a sanity check - test coverage is poor'
413
414     # Remove the tempoerary directory unless we are asked to keep it
415     if options.keep:
416         print "Output files are in '%s'" % base_dir
417     else:
418         shutil.rmtree(base_dir)
419
420 run_tests()