binman: Add a new 'image-pos' property
[oweals/u-boot.git] / tools / binman / ftest.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # To run a single test, change to this directory, and:
6 #
7 #    python -m unittest func_test.TestFunctional.testHelp
8
9 from optparse import OptionParser
10 import os
11 import shutil
12 import struct
13 import sys
14 import tempfile
15 import unittest
16
17 import binman
18 import cmdline
19 import command
20 import control
21 import elf
22 import fdt
23 import fdt_util
24 import tools
25 import tout
26
27 # Contents of test files, corresponding to different entry types
28 U_BOOT_DATA           = '1234'
29 U_BOOT_IMG_DATA       = 'img'
30 U_BOOT_SPL_DATA       = '56780123456789abcde'
31 BLOB_DATA             = '89'
32 ME_DATA               = '0abcd'
33 VGA_DATA              = 'vga'
34 U_BOOT_DTB_DATA       = 'udtb'
35 U_BOOT_SPL_DTB_DATA   = 'spldtb'
36 X86_START16_DATA      = 'start16'
37 X86_START16_SPL_DATA  = 'start16spl'
38 U_BOOT_NODTB_DATA     = 'nodtb with microcode pointer somewhere in here'
39 U_BOOT_SPL_NODTB_DATA = 'splnodtb with microcode pointer somewhere in here'
40 FSP_DATA              = 'fsp'
41 CMC_DATA              = 'cmc'
42 VBT_DATA              = 'vbt'
43 MRC_DATA              = 'mrc'
44
45 class TestFunctional(unittest.TestCase):
46     """Functional tests for binman
47
48     Most of these use a sample .dts file to build an image and then check
49     that it looks correct. The sample files are in the test/ subdirectory
50     and are numbered.
51
52     For each entry type a very small test file is created using fixed
53     string contents. This makes it easy to test that things look right, and
54     debug problems.
55
56     In some cases a 'real' file must be used - these are also supplied in
57     the test/ diurectory.
58     """
59     @classmethod
60     def setUpClass(self):
61         global entry
62         import entry
63
64         # Handle the case where argv[0] is 'python'
65         self._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
66         self._binman_pathname = os.path.join(self._binman_dir, 'binman')
67
68         # Create a temporary directory for input files
69         self._indir = tempfile.mkdtemp(prefix='binmant.')
70
71         # Create some test files
72         TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
73         TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
74         TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
75         TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
76         TestFunctional._MakeInputFile('me.bin', ME_DATA)
77         TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
78         TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
79         TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
80         TestFunctional._MakeInputFile('u-boot-x86-16bit.bin', X86_START16_DATA)
81         TestFunctional._MakeInputFile('spl/u-boot-x86-16bit-spl.bin',
82                                       X86_START16_SPL_DATA)
83         TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
84         TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
85                                       U_BOOT_SPL_NODTB_DATA)
86         TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
87         TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
88         TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
89         TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
90         self._output_setup = False
91
92         # ELF file with a '_dt_ucode_base_size' symbol
93         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
94             TestFunctional._MakeInputFile('u-boot', fd.read())
95
96         # Intel flash descriptor file
97         with open(self.TestFile('descriptor.bin')) as fd:
98             TestFunctional._MakeInputFile('descriptor.bin', fd.read())
99
100     @classmethod
101     def tearDownClass(self):
102         """Remove the temporary input directory and its contents"""
103         if self._indir:
104             shutil.rmtree(self._indir)
105         self._indir = None
106
107     def setUp(self):
108         # Enable this to turn on debugging output
109         # tout.Init(tout.DEBUG)
110         command.test_result = None
111
112     def tearDown(self):
113         """Remove the temporary output directory"""
114         tools._FinaliseForTest()
115
116     def _RunBinman(self, *args, **kwargs):
117         """Run binman using the command line
118
119         Args:
120             Arguments to pass, as a list of strings
121             kwargs: Arguments to pass to Command.RunPipe()
122         """
123         result = command.RunPipe([[self._binman_pathname] + list(args)],
124                 capture=True, capture_stderr=True, raise_on_error=False)
125         if result.return_code and kwargs.get('raise_on_error', True):
126             raise Exception("Error running '%s': %s" % (' '.join(args),
127                             result.stdout + result.stderr))
128         return result
129
130     def _DoBinman(self, *args):
131         """Run binman using directly (in the same process)
132
133         Args:
134             Arguments to pass, as a list of strings
135         Returns:
136             Return value (0 for success)
137         """
138         args = list(args)
139         if '-D' in sys.argv:
140             args = args + ['-D']
141         (options, args) = cmdline.ParseArgs(args)
142         options.pager = 'binman-invalid-pager'
143         options.build_dir = self._indir
144
145         # For testing, you can force an increase in verbosity here
146         # options.verbosity = tout.DEBUG
147         return control.Binman(options, args)
148
149     def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False):
150         """Run binman with a given test file
151
152         Args:
153             fname: Device-tree source filename to use (e.g. 05_simple.dts)
154             debug: True to enable debugging output
155             map: True to output map files for the images
156             update_dtb: Update the offset and size of each entry in the device
157                 tree before packing it into the image
158         """
159         args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)]
160         if debug:
161             args.append('-D')
162         if map:
163             args.append('-m')
164         if update_dtb:
165             args.append('-up')
166         return self._DoBinman(*args)
167
168     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
169         """Set up a new test device-tree file
170
171         The given file is compiled and set up as the device tree to be used
172         for ths test.
173
174         Args:
175             fname: Filename of .dts file to read
176             outfile: Output filename for compiled device-tree binary
177
178         Returns:
179             Contents of device-tree binary
180         """
181         if not self._output_setup:
182             tools.PrepareOutputDir(self._indir, True)
183             self._output_setup = True
184         dtb = fdt_util.EnsureCompiled(self.TestFile(fname))
185         with open(dtb) as fd:
186             data = fd.read()
187             TestFunctional._MakeInputFile(outfile, data)
188             return data
189
190     def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
191                        update_dtb=False):
192         """Run binman and return the resulting image
193
194         This runs binman with a given test file and then reads the resulting
195         output file. It is a shortcut function since most tests need to do
196         these steps.
197
198         Raises an assertion failure if binman returns a non-zero exit code.
199
200         Args:
201             fname: Device-tree source filename to use (e.g. 05_simple.dts)
202             use_real_dtb: True to use the test file as the contents of
203                 the u-boot-dtb entry. Normally this is not needed and the
204                 test contents (the U_BOOT_DTB_DATA string) can be used.
205                 But in some test we need the real contents.
206             map: True to output map files for the images
207             update_dtb: Update the offset and size of each entry in the device
208                 tree before packing it into the image
209
210         Returns:
211             Tuple:
212                 Resulting image contents
213                 Device tree contents
214                 Map data showing contents of image (or None if none)
215                 Output device tree binary filename ('u-boot.dtb' path)
216         """
217         dtb_data = None
218         # Use the compiled test file as the u-boot-dtb input
219         if use_real_dtb:
220             dtb_data = self._SetupDtb(fname)
221
222         try:
223             retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb)
224             self.assertEqual(0, retcode)
225             out_dtb_fname = control.GetFdtPath('u-boot.dtb')
226
227             # Find the (only) image, read it and return its contents
228             image = control.images['image']
229             image_fname = tools.GetOutputFilename('image.bin')
230             self.assertTrue(os.path.exists(image_fname))
231             if map:
232                 map_fname = tools.GetOutputFilename('image.map')
233                 with open(map_fname) as fd:
234                     map_data = fd.read()
235             else:
236                 map_data = None
237             with open(image_fname) as fd:
238                 return fd.read(), dtb_data, map_data, out_dtb_fname
239         finally:
240             # Put the test file back
241             if use_real_dtb:
242                 TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
243
244     def _DoReadFile(self, fname, use_real_dtb=False):
245         """Helper function which discards the device-tree binary
246
247         Args:
248             fname: Device-tree source filename to use (e.g. 05_simple.dts)
249             use_real_dtb: True to use the test file as the contents of
250                 the u-boot-dtb entry. Normally this is not needed and the
251                 test contents (the U_BOOT_DTB_DATA string) can be used.
252                 But in some test we need the real contents.
253
254         Returns:
255             Resulting image contents
256         """
257         return self._DoReadFileDtb(fname, use_real_dtb)[0]
258
259     @classmethod
260     def _MakeInputFile(self, fname, contents):
261         """Create a new test input file, creating directories as needed
262
263         Args:
264             fname: Filename to create
265             contents: File contents to write in to the file
266         Returns:
267             Full pathname of file created
268         """
269         pathname = os.path.join(self._indir, fname)
270         dirname = os.path.dirname(pathname)
271         if dirname and not os.path.exists(dirname):
272             os.makedirs(dirname)
273         with open(pathname, 'wb') as fd:
274             fd.write(contents)
275         return pathname
276
277     @classmethod
278     def TestFile(self, fname):
279         return os.path.join(self._binman_dir, 'test', fname)
280
281     def AssertInList(self, grep_list, target):
282         """Assert that at least one of a list of things is in a target
283
284         Args:
285             grep_list: List of strings to check
286             target: Target string
287         """
288         for grep in grep_list:
289             if grep in target:
290                 return
291         self.fail("Error: '%' not found in '%s'" % (grep_list, target))
292
293     def CheckNoGaps(self, entries):
294         """Check that all entries fit together without gaps
295
296         Args:
297             entries: List of entries to check
298         """
299         offset = 0
300         for entry in entries.values():
301             self.assertEqual(offset, entry.offset)
302             offset += entry.size
303
304     def GetFdtLen(self, dtb):
305         """Get the totalsize field from a device-tree binary
306
307         Args:
308             dtb: Device-tree binary contents
309
310         Returns:
311             Total size of device-tree binary, from the header
312         """
313         return struct.unpack('>L', dtb[4:8])[0]
314
315     def _GetPropTree(self, dtb_data, node_names):
316         def AddNode(node, path):
317             if node.name != '/':
318                 path += '/' + node.name
319             for subnode in node.subnodes:
320                 for prop in subnode.props.values():
321                     if prop.name in node_names:
322                         prop_path = path + '/' + subnode.name + ':' + prop.name
323                         tree[prop_path[len('/binman/'):]] = fdt_util.fdt32_to_cpu(
324                             prop.value)
325                 AddNode(subnode, path)
326
327         tree = {}
328         dtb = fdt.Fdt(dtb_data)
329         dtb.Scan()
330         AddNode(dtb.GetRoot(), '')
331         return tree
332
333     def testRun(self):
334         """Test a basic run with valid args"""
335         result = self._RunBinman('-h')
336
337     def testFullHelp(self):
338         """Test that the full help is displayed with -H"""
339         result = self._RunBinman('-H')
340         help_file = os.path.join(self._binman_dir, 'README')
341         # Remove possible extraneous strings
342         extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
343         gothelp = result.stdout.replace(extra, '')
344         self.assertEqual(len(gothelp), os.path.getsize(help_file))
345         self.assertEqual(0, len(result.stderr))
346         self.assertEqual(0, result.return_code)
347
348     def testFullHelpInternal(self):
349         """Test that the full help is displayed with -H"""
350         try:
351             command.test_result = command.CommandResult()
352             result = self._DoBinman('-H')
353             help_file = os.path.join(self._binman_dir, 'README')
354         finally:
355             command.test_result = None
356
357     def testHelp(self):
358         """Test that the basic help is displayed with -h"""
359         result = self._RunBinman('-h')
360         self.assertTrue(len(result.stdout) > 200)
361         self.assertEqual(0, len(result.stderr))
362         self.assertEqual(0, result.return_code)
363
364     def testBoard(self):
365         """Test that we can run it with a specific board"""
366         self._SetupDtb('05_simple.dts', 'sandbox/u-boot.dtb')
367         TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
368         result = self._DoBinman('-b', 'sandbox')
369         self.assertEqual(0, result)
370
371     def testNeedBoard(self):
372         """Test that we get an error when no board ius supplied"""
373         with self.assertRaises(ValueError) as e:
374             result = self._DoBinman()
375         self.assertIn("Must provide a board to process (use -b <board>)",
376                 str(e.exception))
377
378     def testMissingDt(self):
379         """Test that an invalid device-tree file generates an error"""
380         with self.assertRaises(Exception) as e:
381             self._RunBinman('-d', 'missing_file')
382         # We get one error from libfdt, and a different one from fdtget.
383         self.AssertInList(["Couldn't open blob from 'missing_file'",
384                            'No such file or directory'], str(e.exception))
385
386     def testBrokenDt(self):
387         """Test that an invalid device-tree source file generates an error
388
389         Since this is a source file it should be compiled and the error
390         will come from the device-tree compiler (dtc).
391         """
392         with self.assertRaises(Exception) as e:
393             self._RunBinman('-d', self.TestFile('01_invalid.dts'))
394         self.assertIn("FATAL ERROR: Unable to parse input tree",
395                 str(e.exception))
396
397     def testMissingNode(self):
398         """Test that a device tree without a 'binman' node generates an error"""
399         with self.assertRaises(Exception) as e:
400             self._DoBinman('-d', self.TestFile('02_missing_node.dts'))
401         self.assertIn("does not have a 'binman' node", str(e.exception))
402
403     def testEmpty(self):
404         """Test that an empty binman node works OK (i.e. does nothing)"""
405         result = self._RunBinman('-d', self.TestFile('03_empty.dts'))
406         self.assertEqual(0, len(result.stderr))
407         self.assertEqual(0, result.return_code)
408
409     def testInvalidEntry(self):
410         """Test that an invalid entry is flagged"""
411         with self.assertRaises(Exception) as e:
412             result = self._RunBinman('-d',
413                                      self.TestFile('04_invalid_entry.dts'))
414         self.assertIn("Unknown entry type 'not-a-valid-type' in node "
415                 "'/binman/not-a-valid-type'", str(e.exception))
416
417     def testSimple(self):
418         """Test a simple binman with a single file"""
419         data = self._DoReadFile('05_simple.dts')
420         self.assertEqual(U_BOOT_DATA, data)
421
422     def testSimpleDebug(self):
423         """Test a simple binman run with debugging enabled"""
424         data = self._DoTestFile('05_simple.dts', debug=True)
425
426     def testDual(self):
427         """Test that we can handle creating two images
428
429         This also tests image padding.
430         """
431         retcode = self._DoTestFile('06_dual_image.dts')
432         self.assertEqual(0, retcode)
433
434         image = control.images['image1']
435         self.assertEqual(len(U_BOOT_DATA), image._size)
436         fname = tools.GetOutputFilename('image1.bin')
437         self.assertTrue(os.path.exists(fname))
438         with open(fname) as fd:
439             data = fd.read()
440             self.assertEqual(U_BOOT_DATA, data)
441
442         image = control.images['image2']
443         self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
444         fname = tools.GetOutputFilename('image2.bin')
445         self.assertTrue(os.path.exists(fname))
446         with open(fname) as fd:
447             data = fd.read()
448             self.assertEqual(U_BOOT_DATA, data[3:7])
449             self.assertEqual(chr(0) * 3, data[:3])
450             self.assertEqual(chr(0) * 5, data[7:])
451
452     def testBadAlign(self):
453         """Test that an invalid alignment value is detected"""
454         with self.assertRaises(ValueError) as e:
455             self._DoTestFile('07_bad_align.dts')
456         self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
457                       "of two", str(e.exception))
458
459     def testPackSimple(self):
460         """Test that packing works as expected"""
461         retcode = self._DoTestFile('08_pack.dts')
462         self.assertEqual(0, retcode)
463         self.assertIn('image', control.images)
464         image = control.images['image']
465         entries = image.GetEntries()
466         self.assertEqual(5, len(entries))
467
468         # First u-boot
469         self.assertIn('u-boot', entries)
470         entry = entries['u-boot']
471         self.assertEqual(0, entry.offset)
472         self.assertEqual(len(U_BOOT_DATA), entry.size)
473
474         # Second u-boot, aligned to 16-byte boundary
475         self.assertIn('u-boot-align', entries)
476         entry = entries['u-boot-align']
477         self.assertEqual(16, entry.offset)
478         self.assertEqual(len(U_BOOT_DATA), entry.size)
479
480         # Third u-boot, size 23 bytes
481         self.assertIn('u-boot-size', entries)
482         entry = entries['u-boot-size']
483         self.assertEqual(20, entry.offset)
484         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
485         self.assertEqual(23, entry.size)
486
487         # Fourth u-boot, placed immediate after the above
488         self.assertIn('u-boot-next', entries)
489         entry = entries['u-boot-next']
490         self.assertEqual(43, entry.offset)
491         self.assertEqual(len(U_BOOT_DATA), entry.size)
492
493         # Fifth u-boot, placed at a fixed offset
494         self.assertIn('u-boot-fixed', entries)
495         entry = entries['u-boot-fixed']
496         self.assertEqual(61, entry.offset)
497         self.assertEqual(len(U_BOOT_DATA), entry.size)
498
499         self.assertEqual(65, image._size)
500
501     def testPackExtra(self):
502         """Test that extra packing feature works as expected"""
503         retcode = self._DoTestFile('09_pack_extra.dts')
504
505         self.assertEqual(0, retcode)
506         self.assertIn('image', control.images)
507         image = control.images['image']
508         entries = image.GetEntries()
509         self.assertEqual(5, len(entries))
510
511         # First u-boot with padding before and after
512         self.assertIn('u-boot', entries)
513         entry = entries['u-boot']
514         self.assertEqual(0, entry.offset)
515         self.assertEqual(3, entry.pad_before)
516         self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
517
518         # Second u-boot has an aligned size, but it has no effect
519         self.assertIn('u-boot-align-size-nop', entries)
520         entry = entries['u-boot-align-size-nop']
521         self.assertEqual(12, entry.offset)
522         self.assertEqual(4, entry.size)
523
524         # Third u-boot has an aligned size too
525         self.assertIn('u-boot-align-size', entries)
526         entry = entries['u-boot-align-size']
527         self.assertEqual(16, entry.offset)
528         self.assertEqual(32, entry.size)
529
530         # Fourth u-boot has an aligned end
531         self.assertIn('u-boot-align-end', entries)
532         entry = entries['u-boot-align-end']
533         self.assertEqual(48, entry.offset)
534         self.assertEqual(16, entry.size)
535
536         # Fifth u-boot immediately afterwards
537         self.assertIn('u-boot-align-both', entries)
538         entry = entries['u-boot-align-both']
539         self.assertEqual(64, entry.offset)
540         self.assertEqual(64, entry.size)
541
542         self.CheckNoGaps(entries)
543         self.assertEqual(128, image._size)
544
545     def testPackAlignPowerOf2(self):
546         """Test that invalid entry alignment is detected"""
547         with self.assertRaises(ValueError) as e:
548             self._DoTestFile('10_pack_align_power2.dts')
549         self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
550                       "of two", str(e.exception))
551
552     def testPackAlignSizePowerOf2(self):
553         """Test that invalid entry size alignment is detected"""
554         with self.assertRaises(ValueError) as e:
555             self._DoTestFile('11_pack_align_size_power2.dts')
556         self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
557                       "power of two", str(e.exception))
558
559     def testPackInvalidAlign(self):
560         """Test detection of an offset that does not match its alignment"""
561         with self.assertRaises(ValueError) as e:
562             self._DoTestFile('12_pack_inv_align.dts')
563         self.assertIn("Node '/binman/u-boot': Offset 0x5 (5) does not match "
564                       "align 0x4 (4)", str(e.exception))
565
566     def testPackInvalidSizeAlign(self):
567         """Test that invalid entry size alignment is detected"""
568         with self.assertRaises(ValueError) as e:
569             self._DoTestFile('13_pack_inv_size_align.dts')
570         self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
571                       "align-size 0x4 (4)", str(e.exception))
572
573     def testPackOverlap(self):
574         """Test that overlapping regions are detected"""
575         with self.assertRaises(ValueError) as e:
576             self._DoTestFile('14_pack_overlap.dts')
577         self.assertIn("Node '/binman/u-boot-align': Offset 0x3 (3) overlaps "
578                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
579                       str(e.exception))
580
581     def testPackEntryOverflow(self):
582         """Test that entries that overflow their size are detected"""
583         with self.assertRaises(ValueError) as e:
584             self._DoTestFile('15_pack_overflow.dts')
585         self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
586                       "but entry size is 0x3 (3)", str(e.exception))
587
588     def testPackImageOverflow(self):
589         """Test that entries which overflow the image size are detected"""
590         with self.assertRaises(ValueError) as e:
591             self._DoTestFile('16_pack_image_overflow.dts')
592         self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
593                       "size 0x3 (3)", str(e.exception))
594
595     def testPackImageSize(self):
596         """Test that the image size can be set"""
597         retcode = self._DoTestFile('17_pack_image_size.dts')
598         self.assertEqual(0, retcode)
599         self.assertIn('image', control.images)
600         image = control.images['image']
601         self.assertEqual(7, image._size)
602
603     def testPackImageSizeAlign(self):
604         """Test that image size alignemnt works as expected"""
605         retcode = self._DoTestFile('18_pack_image_align.dts')
606         self.assertEqual(0, retcode)
607         self.assertIn('image', control.images)
608         image = control.images['image']
609         self.assertEqual(16, image._size)
610
611     def testPackInvalidImageAlign(self):
612         """Test that invalid image alignment is detected"""
613         with self.assertRaises(ValueError) as e:
614             self._DoTestFile('19_pack_inv_image_align.dts')
615         self.assertIn("Section '/binman': Size 0x7 (7) does not match "
616                       "align-size 0x8 (8)", str(e.exception))
617
618     def testPackAlignPowerOf2(self):
619         """Test that invalid image alignment is detected"""
620         with self.assertRaises(ValueError) as e:
621             self._DoTestFile('20_pack_inv_image_align_power2.dts')
622         self.assertIn("Section '/binman': Alignment size 131 must be a power of "
623                       "two", str(e.exception))
624
625     def testImagePadByte(self):
626         """Test that the image pad byte can be specified"""
627         with open(self.TestFile('bss_data')) as fd:
628             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
629         data = self._DoReadFile('21_image_pad.dts')
630         self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
631
632     def testImageName(self):
633         """Test that image files can be named"""
634         retcode = self._DoTestFile('22_image_name.dts')
635         self.assertEqual(0, retcode)
636         image = control.images['image1']
637         fname = tools.GetOutputFilename('test-name')
638         self.assertTrue(os.path.exists(fname))
639
640         image = control.images['image2']
641         fname = tools.GetOutputFilename('test-name.xx')
642         self.assertTrue(os.path.exists(fname))
643
644     def testBlobFilename(self):
645         """Test that generic blobs can be provided by filename"""
646         data = self._DoReadFile('23_blob.dts')
647         self.assertEqual(BLOB_DATA, data)
648
649     def testPackSorted(self):
650         """Test that entries can be sorted"""
651         data = self._DoReadFile('24_sorted.dts')
652         self.assertEqual(chr(0) * 1 + U_BOOT_SPL_DATA + chr(0) * 2 +
653                          U_BOOT_DATA, data)
654
655     def testPackZeroOffset(self):
656         """Test that an entry at offset 0 is not given a new offset"""
657         with self.assertRaises(ValueError) as e:
658             self._DoTestFile('25_pack_zero_size.dts')
659         self.assertIn("Node '/binman/u-boot-spl': Offset 0x0 (0) overlaps "
660                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
661                       str(e.exception))
662
663     def testPackUbootDtb(self):
664         """Test that a device tree can be added to U-Boot"""
665         data = self._DoReadFile('26_pack_u_boot_dtb.dts')
666         self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
667
668     def testPackX86RomNoSize(self):
669         """Test that the end-at-4gb property requires a size property"""
670         with self.assertRaises(ValueError) as e:
671             self._DoTestFile('27_pack_4gb_no_size.dts')
672         self.assertIn("Section '/binman': Section size must be provided when "
673                       "using end-at-4gb", str(e.exception))
674
675     def testPackX86RomOutside(self):
676         """Test that the end-at-4gb property checks for offset boundaries"""
677         with self.assertRaises(ValueError) as e:
678             self._DoTestFile('28_pack_4gb_outside.dts')
679         self.assertIn("Node '/binman/u-boot': Offset 0x0 (0) is outside "
680                       "the section starting at 0xffffffe0 (4294967264)",
681                       str(e.exception))
682
683     def testPackX86Rom(self):
684         """Test that a basic x86 ROM can be created"""
685         data = self._DoReadFile('29_x86-rom.dts')
686         self.assertEqual(U_BOOT_DATA + chr(0) * 7 + U_BOOT_SPL_DATA +
687                          chr(0) * 2, data)
688
689     def testPackX86RomMeNoDesc(self):
690         """Test that an invalid Intel descriptor entry is detected"""
691         TestFunctional._MakeInputFile('descriptor.bin', '')
692         with self.assertRaises(ValueError) as e:
693             self._DoTestFile('31_x86-rom-me.dts')
694         self.assertIn("Node '/binman/intel-descriptor': Cannot find FD "
695                       "signature", str(e.exception))
696
697     def testPackX86RomBadDesc(self):
698         """Test that the Intel requires a descriptor entry"""
699         with self.assertRaises(ValueError) as e:
700             self._DoTestFile('30_x86-rom-me-no-desc.dts')
701         self.assertIn("Node '/binman/intel-me': No offset set with "
702                       "offset-unset: should another entry provide this correct "
703                       "offset?", str(e.exception))
704
705     def testPackX86RomMe(self):
706         """Test that an x86 ROM with an ME region can be created"""
707         data = self._DoReadFile('31_x86-rom-me.dts')
708         self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
709
710     def testPackVga(self):
711         """Test that an image with a VGA binary can be created"""
712         data = self._DoReadFile('32_intel-vga.dts')
713         self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
714
715     def testPackStart16(self):
716         """Test that an image with an x86 start16 region can be created"""
717         data = self._DoReadFile('33_x86-start16.dts')
718         self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
719
720     def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False):
721         """Handle running a test for insertion of microcode
722
723         Args:
724             dts_fname: Name of test .dts file
725             nodtb_data: Data that we expect in the first section
726             ucode_second: True if the microsecond entry is second instead of
727                 third
728
729         Returns:
730             Tuple:
731                 Contents of first region (U-Boot or SPL)
732                 Offset and size components of microcode pointer, as inserted
733                     in the above (two 4-byte words)
734         """
735         data = self._DoReadFile(dts_fname, True)
736
737         # Now check the device tree has no microcode
738         if ucode_second:
739             ucode_content = data[len(nodtb_data):]
740             ucode_pos = len(nodtb_data)
741             dtb_with_ucode = ucode_content[16:]
742             fdt_len = self.GetFdtLen(dtb_with_ucode)
743         else:
744             dtb_with_ucode = data[len(nodtb_data):]
745             fdt_len = self.GetFdtLen(dtb_with_ucode)
746             ucode_content = dtb_with_ucode[fdt_len:]
747             ucode_pos = len(nodtb_data) + fdt_len
748         fname = tools.GetOutputFilename('test.dtb')
749         with open(fname, 'wb') as fd:
750             fd.write(dtb_with_ucode)
751         dtb = fdt.FdtScan(fname)
752         ucode = dtb.GetNode('/microcode')
753         self.assertTrue(ucode)
754         for node in ucode.subnodes:
755             self.assertFalse(node.props.get('data'))
756
757         # Check that the microcode appears immediately after the Fdt
758         # This matches the concatenation of the data properties in
759         # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
760         ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
761                                  0x78235609)
762         self.assertEqual(ucode_data, ucode_content[:len(ucode_data)])
763
764         # Check that the microcode pointer was inserted. It should match the
765         # expected offset and size
766         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
767                                    len(ucode_data))
768         u_boot = data[:len(nodtb_data)]
769         return u_boot, pos_and_size
770
771     def testPackUbootMicrocode(self):
772         """Test that x86 microcode can be handled correctly
773
774         We expect to see the following in the image, in order:
775             u-boot-nodtb.bin with a microcode pointer inserted at the correct
776                 place
777             u-boot.dtb with the microcode removed
778             the microcode
779         """
780         first, pos_and_size = self._RunMicrocodeTest('34_x86_ucode.dts',
781                                                      U_BOOT_NODTB_DATA)
782         self.assertEqual('nodtb with microcode' + pos_and_size +
783                          ' somewhere in here', first)
784
785     def _RunPackUbootSingleMicrocode(self):
786         """Test that x86 microcode can be handled correctly
787
788         We expect to see the following in the image, in order:
789             u-boot-nodtb.bin with a microcode pointer inserted at the correct
790                 place
791             u-boot.dtb with the microcode
792             an empty microcode region
793         """
794         # We need the libfdt library to run this test since only that allows
795         # finding the offset of a property. This is required by
796         # Entry_u_boot_dtb_with_ucode.ObtainContents().
797         data = self._DoReadFile('35_x86_single_ucode.dts', True)
798
799         second = data[len(U_BOOT_NODTB_DATA):]
800
801         fdt_len = self.GetFdtLen(second)
802         third = second[fdt_len:]
803         second = second[:fdt_len]
804
805         ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
806         self.assertIn(ucode_data, second)
807         ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
808
809         # Check that the microcode pointer was inserted. It should match the
810         # expected offset and size
811         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
812                                    len(ucode_data))
813         first = data[:len(U_BOOT_NODTB_DATA)]
814         self.assertEqual('nodtb with microcode' + pos_and_size +
815                          ' somewhere in here', first)
816
817     def testPackUbootSingleMicrocode(self):
818         """Test that x86 microcode can be handled correctly with fdt_normal.
819         """
820         self._RunPackUbootSingleMicrocode()
821
822     def testUBootImg(self):
823         """Test that u-boot.img can be put in a file"""
824         data = self._DoReadFile('36_u_boot_img.dts')
825         self.assertEqual(U_BOOT_IMG_DATA, data)
826
827     def testNoMicrocode(self):
828         """Test that a missing microcode region is detected"""
829         with self.assertRaises(ValueError) as e:
830             self._DoReadFile('37_x86_no_ucode.dts', True)
831         self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
832                       "node found in ", str(e.exception))
833
834     def testMicrocodeWithoutNode(self):
835         """Test that a missing u-boot-dtb-with-ucode node is detected"""
836         with self.assertRaises(ValueError) as e:
837             self._DoReadFile('38_x86_ucode_missing_node.dts', True)
838         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
839                 "microcode region u-boot-dtb-with-ucode", str(e.exception))
840
841     def testMicrocodeWithoutNode2(self):
842         """Test that a missing u-boot-ucode node is detected"""
843         with self.assertRaises(ValueError) as e:
844             self._DoReadFile('39_x86_ucode_missing_node2.dts', True)
845         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
846             "microcode region u-boot-ucode", str(e.exception))
847
848     def testMicrocodeWithoutPtrInElf(self):
849         """Test that a U-Boot binary without the microcode symbol is detected"""
850         # ELF file without a '_dt_ucode_base_size' symbol
851         try:
852             with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
853                 TestFunctional._MakeInputFile('u-boot', fd.read())
854
855             with self.assertRaises(ValueError) as e:
856                 self._RunPackUbootSingleMicrocode()
857             self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
858                     "_dt_ucode_base_size symbol in u-boot", str(e.exception))
859
860         finally:
861             # Put the original file back
862             with open(self.TestFile('u_boot_ucode_ptr')) as fd:
863                 TestFunctional._MakeInputFile('u-boot', fd.read())
864
865     def testMicrocodeNotInImage(self):
866         """Test that microcode must be placed within the image"""
867         with self.assertRaises(ValueError) as e:
868             self._DoReadFile('40_x86_ucode_not_in_image.dts', True)
869         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
870                 "pointer _dt_ucode_base_size at fffffe14 is outside the "
871                 "section ranging from 00000000 to 0000002e", str(e.exception))
872
873     def testWithoutMicrocode(self):
874         """Test that we can cope with an image without microcode (e.g. qemu)"""
875         with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
876             TestFunctional._MakeInputFile('u-boot', fd.read())
877         data, dtb, _, _ = self._DoReadFileDtb('44_x86_optional_ucode.dts', True)
878
879         # Now check the device tree has no microcode
880         self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
881         second = data[len(U_BOOT_NODTB_DATA):]
882
883         fdt_len = self.GetFdtLen(second)
884         self.assertEqual(dtb, second[:fdt_len])
885
886         used_len = len(U_BOOT_NODTB_DATA) + fdt_len
887         third = data[used_len:]
888         self.assertEqual(chr(0) * (0x200 - used_len), third)
889
890     def testUnknownPosSize(self):
891         """Test that microcode must be placed within the image"""
892         with self.assertRaises(ValueError) as e:
893             self._DoReadFile('41_unknown_pos_size.dts', True)
894         self.assertIn("Section '/binman': Unable to set offset/size for unknown "
895                 "entry 'invalid-entry'", str(e.exception))
896
897     def testPackFsp(self):
898         """Test that an image with a FSP binary can be created"""
899         data = self._DoReadFile('42_intel-fsp.dts')
900         self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
901
902     def testPackCmc(self):
903         """Test that an image with a CMC binary can be created"""
904         data = self._DoReadFile('43_intel-cmc.dts')
905         self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
906
907     def testPackVbt(self):
908         """Test that an image with a VBT binary can be created"""
909         data = self._DoReadFile('46_intel-vbt.dts')
910         self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
911
912     def testSplBssPad(self):
913         """Test that we can pad SPL's BSS with zeros"""
914         # ELF file with a '__bss_size' symbol
915         with open(self.TestFile('bss_data')) as fd:
916             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
917         data = self._DoReadFile('47_spl_bss_pad.dts')
918         self.assertEqual(U_BOOT_SPL_DATA + (chr(0) * 10) + U_BOOT_DATA, data)
919
920         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
921             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
922         with self.assertRaises(ValueError) as e:
923             data = self._DoReadFile('47_spl_bss_pad.dts')
924         self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
925                       str(e.exception))
926
927     def testPackStart16Spl(self):
928         """Test that an image with an x86 start16 region can be created"""
929         data = self._DoReadFile('48_x86-start16-spl.dts')
930         self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
931
932     def _PackUbootSplMicrocode(self, dts, ucode_second=False):
933         """Helper function for microcode tests
934
935         We expect to see the following in the image, in order:
936             u-boot-spl-nodtb.bin with a microcode pointer inserted at the
937                 correct place
938             u-boot.dtb with the microcode removed
939             the microcode
940
941         Args:
942             dts: Device tree file to use for test
943             ucode_second: True if the microsecond entry is second instead of
944                 third
945         """
946         # ELF file with a '_dt_ucode_base_size' symbol
947         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
948             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
949         first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA,
950                                                      ucode_second=ucode_second)
951         self.assertEqual('splnodtb with microc' + pos_and_size +
952                          'ter somewhere in here', first)
953
954     def testPackUbootSplMicrocode(self):
955         """Test that x86 microcode can be handled correctly in SPL"""
956         self._PackUbootSplMicrocode('49_x86_ucode_spl.dts')
957
958     def testPackUbootSplMicrocodeReorder(self):
959         """Test that order doesn't matter for microcode entries
960
961         This is the same as testPackUbootSplMicrocode but when we process the
962         u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode
963         entry, so we reply on binman to try later.
964         """
965         self._PackUbootSplMicrocode('58_x86_ucode_spl_needs_retry.dts',
966                                     ucode_second=True)
967
968     def testPackMrc(self):
969         """Test that an image with an MRC binary can be created"""
970         data = self._DoReadFile('50_intel_mrc.dts')
971         self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
972
973     def testSplDtb(self):
974         """Test that an image with spl/u-boot-spl.dtb can be created"""
975         data = self._DoReadFile('51_u_boot_spl_dtb.dts')
976         self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
977
978     def testSplNoDtb(self):
979         """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
980         data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
981         self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
982
983     def testSymbols(self):
984         """Test binman can assign symbols embedded in U-Boot"""
985         elf_fname = self.TestFile('u_boot_binman_syms')
986         syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
987         addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
988         self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
989
990         with open(self.TestFile('u_boot_binman_syms')) as fd:
991             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
992         data = self._DoReadFile('53_symbols.dts')
993         sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
994         expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
995                     U_BOOT_DATA +
996                     sym_values + U_BOOT_SPL_DATA[16:])
997         self.assertEqual(expected, data)
998
999     def testPackUnitAddress(self):
1000         """Test that we support multiple binaries with the same name"""
1001         data = self._DoReadFile('54_unit_address.dts')
1002         self.assertEqual(U_BOOT_DATA + U_BOOT_DATA, data)
1003
1004     def testSections(self):
1005         """Basic test of sections"""
1006         data = self._DoReadFile('55_sections.dts')
1007         expected = (U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12 +
1008                     U_BOOT_DATA + '&' * 4)
1009         self.assertEqual(expected, data)
1010
1011     def testMap(self):
1012         """Tests outputting a map of the images"""
1013         _, _, map_data, _ = self._DoReadFileDtb('55_sections.dts', map=True)
1014         self.assertEqual('''  Offset      Size  Name
1015 00000000  00000028  main-section
1016  00000000  00000010  section@0
1017   00000000  00000004  u-boot
1018  00000010  00000010  section@1
1019   00000000  00000004  u-boot
1020  00000020  00000004  section@2
1021   00000000  00000004  u-boot
1022 ''', map_data)
1023
1024     def testNamePrefix(self):
1025         """Tests that name prefixes are used"""
1026         _, _, map_data, _ = self._DoReadFileDtb('56_name_prefix.dts', map=True)
1027         self.assertEqual('''  Offset      Size  Name
1028 00000000  00000028  main-section
1029  00000000  00000010  section@0
1030   00000000  00000004  ro-u-boot
1031  00000010  00000010  section@1
1032   00000000  00000004  rw-u-boot
1033 ''', map_data)
1034
1035     def testUnknownContents(self):
1036         """Test that obtaining the contents works as expected"""
1037         with self.assertRaises(ValueError) as e:
1038             self._DoReadFile('57_unknown_contents.dts', True)
1039         self.assertIn("Section '/binman': Internal error: Could not complete "
1040                 "processing of contents: remaining [<_testing.Entry__testing ",
1041                 str(e.exception))
1042
1043     def testBadChangeSize(self):
1044         """Test that trying to change the size of an entry fails"""
1045         with self.assertRaises(ValueError) as e:
1046             self._DoReadFile('59_change_size.dts', True)
1047         self.assertIn("Node '/binman/_testing': Cannot update entry size from "
1048                       '2 to 1', str(e.exception))
1049
1050     def testUpdateFdt(self):
1051         """Test that we can update the device tree with offset/size info"""
1052         _, _, _, out_dtb_fname = self._DoReadFileDtb('60_fdt_update.dts',
1053                                                      update_dtb=True)
1054         props = self._GetPropTree(out_dtb_fname, ['offset', 'size',
1055                                                   'image-pos'])
1056         with open('/tmp/x.dtb', 'wb') as outf:
1057             with open(out_dtb_fname) as inf:
1058                 outf.write(inf.read())
1059         self.assertEqual({
1060             'image-pos': 0,
1061             'offset': 0,
1062             '_testing:offset': 32,
1063             '_testing:size': 1,
1064             '_testing:image-pos': 32,
1065             'section@0/u-boot:offset': 0,
1066             'section@0/u-boot:size': len(U_BOOT_DATA),
1067             'section@0/u-boot:image-pos': 0,
1068             'section@0:offset': 0,
1069             'section@0:size': 16,
1070             'section@0:image-pos': 0,
1071
1072             'section@1/u-boot:offset': 0,
1073             'section@1/u-boot:size': len(U_BOOT_DATA),
1074             'section@1/u-boot:image-pos': 16,
1075             'section@1:offset': 16,
1076             'section@1:size': 16,
1077             'section@1:image-pos': 16,
1078             'size': 40
1079         }, props)
1080
1081     def testUpdateFdtBad(self):
1082         """Test that we detect when ProcessFdt never completes"""
1083         with self.assertRaises(ValueError) as e:
1084             self._DoReadFileDtb('61_fdt_update_bad.dts', update_dtb=True)
1085         self.assertIn('Could not complete processing of Fdt: remaining '
1086                       '[<_testing.Entry__testing', str(e.exception))
1087
1088 if __name__ == "__main__":
1089     unittest.main()