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