c46a065382703d5546f06834b7a1b07755a9b9dd
[platform/kernel/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 import hashlib
10 from optparse import OptionParser
11 import os
12 import shutil
13 import struct
14 import sys
15 import tempfile
16 import unittest
17
18 import binman
19 import cmdline
20 import command
21 import control
22 import elf
23 import fdt
24 import fdt_util
25 import fmap_util
26 import test_util
27 import state
28 import tools
29 import tout
30
31 # Contents of test files, corresponding to different entry types
32 U_BOOT_DATA           = '1234'
33 U_BOOT_IMG_DATA       = 'img'
34 U_BOOT_SPL_DATA       = '56780123456789abcde'
35 U_BOOT_TPL_DATA       = 'tpl'
36 BLOB_DATA             = '89'
37 ME_DATA               = '0abcd'
38 VGA_DATA              = 'vga'
39 U_BOOT_DTB_DATA       = 'udtb'
40 U_BOOT_SPL_DTB_DATA   = 'spldtb'
41 U_BOOT_TPL_DTB_DATA   = 'tpldtb'
42 X86_START16_DATA      = 'start16'
43 X86_START16_SPL_DATA  = 'start16spl'
44 X86_START16_TPL_DATA  = 'start16tpl'
45 U_BOOT_NODTB_DATA     = 'nodtb with microcode pointer somewhere in here'
46 U_BOOT_SPL_NODTB_DATA = 'splnodtb with microcode pointer somewhere in here'
47 FSP_DATA              = 'fsp'
48 CMC_DATA              = 'cmc'
49 VBT_DATA              = 'vbt'
50 MRC_DATA              = 'mrc'
51 TEXT_DATA             = 'text'
52 TEXT_DATA2            = 'text2'
53 TEXT_DATA3            = 'text3'
54 CROS_EC_RW_DATA       = 'ecrw'
55 GBB_DATA              = 'gbbd'
56 BMPBLK_DATA           = 'bmp'
57 VBLOCK_DATA           = 'vblk'
58 FILES_DATA            = ("sorry I'm late\nOh, don't bother apologising, I'm " +
59                          "sorry you're alive\n")
60 COMPRESS_DATA         = 'data to compress'
61
62
63 class TestFunctional(unittest.TestCase):
64     """Functional tests for binman
65
66     Most of these use a sample .dts file to build an image and then check
67     that it looks correct. The sample files are in the test/ subdirectory
68     and are numbered.
69
70     For each entry type a very small test file is created using fixed
71     string contents. This makes it easy to test that things look right, and
72     debug problems.
73
74     In some cases a 'real' file must be used - these are also supplied in
75     the test/ diurectory.
76     """
77     @classmethod
78     def setUpClass(self):
79         global entry
80         import entry
81
82         # Handle the case where argv[0] is 'python'
83         self._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
84         self._binman_pathname = os.path.join(self._binman_dir, 'binman')
85
86         # Create a temporary directory for input files
87         self._indir = tempfile.mkdtemp(prefix='binmant.')
88
89         # Create some test files
90         TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
91         TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
92         TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
93         TestFunctional._MakeInputFile('tpl/u-boot-tpl.bin', U_BOOT_TPL_DATA)
94         TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
95         TestFunctional._MakeInputFile('me.bin', ME_DATA)
96         TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
97         self._ResetDtbs()
98         TestFunctional._MakeInputFile('u-boot-x86-16bit.bin', X86_START16_DATA)
99         TestFunctional._MakeInputFile('spl/u-boot-x86-16bit-spl.bin',
100                                       X86_START16_SPL_DATA)
101         TestFunctional._MakeInputFile('tpl/u-boot-x86-16bit-tpl.bin',
102                                       X86_START16_TPL_DATA)
103         TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
104         TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
105                                       U_BOOT_SPL_NODTB_DATA)
106         TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
107         TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
108         TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
109         TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
110         TestFunctional._MakeInputFile('ecrw.bin', CROS_EC_RW_DATA)
111         TestFunctional._MakeInputDir('devkeys')
112         TestFunctional._MakeInputFile('bmpblk.bin', BMPBLK_DATA)
113         self._output_setup = False
114
115         # ELF file with a '_dt_ucode_base_size' symbol
116         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
117             TestFunctional._MakeInputFile('u-boot', fd.read())
118
119         # Intel flash descriptor file
120         with open(self.TestFile('descriptor.bin')) as fd:
121             TestFunctional._MakeInputFile('descriptor.bin', fd.read())
122
123         shutil.copytree(self.TestFile('files'),
124                         os.path.join(self._indir, 'files'))
125
126         TestFunctional._MakeInputFile('compress', COMPRESS_DATA)
127
128     @classmethod
129     def tearDownClass(self):
130         """Remove the temporary input directory and its contents"""
131         if self._indir:
132             shutil.rmtree(self._indir)
133         self._indir = None
134
135     def setUp(self):
136         # Enable this to turn on debugging output
137         # tout.Init(tout.DEBUG)
138         command.test_result = None
139
140     def tearDown(self):
141         """Remove the temporary output directory"""
142         tools._FinaliseForTest()
143
144     @classmethod
145     def _ResetDtbs(self):
146         TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
147         TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
148         TestFunctional._MakeInputFile('tpl/u-boot-tpl.dtb', U_BOOT_TPL_DTB_DATA)
149
150     def _RunBinman(self, *args, **kwargs):
151         """Run binman using the command line
152
153         Args:
154             Arguments to pass, as a list of strings
155             kwargs: Arguments to pass to Command.RunPipe()
156         """
157         result = command.RunPipe([[self._binman_pathname] + list(args)],
158                 capture=True, capture_stderr=True, raise_on_error=False)
159         if result.return_code and kwargs.get('raise_on_error', True):
160             raise Exception("Error running '%s': %s" % (' '.join(args),
161                             result.stdout + result.stderr))
162         return result
163
164     def _DoBinman(self, *args):
165         """Run binman using directly (in the same process)
166
167         Args:
168             Arguments to pass, as a list of strings
169         Returns:
170             Return value (0 for success)
171         """
172         args = list(args)
173         if '-D' in sys.argv:
174             args = args + ['-D']
175         (options, args) = cmdline.ParseArgs(args)
176         options.pager = 'binman-invalid-pager'
177         options.build_dir = self._indir
178
179         # For testing, you can force an increase in verbosity here
180         # options.verbosity = tout.DEBUG
181         return control.Binman(options, args)
182
183     def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
184                     entry_args=None, images=None, use_real_dtb=False):
185         """Run binman with a given test file
186
187         Args:
188             fname: Device-tree source filename to use (e.g. 05_simple.dts)
189             debug: True to enable debugging output
190             map: True to output map files for the images
191             update_dtb: Update the offset and size of each entry in the device
192                 tree before packing it into the image
193             entry_args: Dict of entry args to supply to binman
194                 key: arg name
195                 value: value of that arg
196             images: List of image names to build
197         """
198         args = ['-p', '-I', self._indir, '-d', self.TestFile(fname)]
199         if debug:
200             args.append('-D')
201         if map:
202             args.append('-m')
203         if update_dtb:
204             args.append('-up')
205         if not use_real_dtb:
206             args.append('--fake-dtb')
207         if entry_args:
208             for arg, value in entry_args.iteritems():
209                 args.append('-a%s=%s' % (arg, value))
210         if images:
211             for image in images:
212                 args += ['-i', image]
213         return self._DoBinman(*args)
214
215     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
216         """Set up a new test device-tree file
217
218         The given file is compiled and set up as the device tree to be used
219         for ths test.
220
221         Args:
222             fname: Filename of .dts file to read
223             outfile: Output filename for compiled device-tree binary
224
225         Returns:
226             Contents of device-tree binary
227         """
228         if not self._output_setup:
229             tools.PrepareOutputDir(self._indir, True)
230             self._output_setup = True
231         dtb = fdt_util.EnsureCompiled(self.TestFile(fname))
232         with open(dtb) as fd:
233             data = fd.read()
234             TestFunctional._MakeInputFile(outfile, data)
235             return data
236
237     def _GetDtbContentsForSplTpl(self, dtb_data, name):
238         """Create a version of the main DTB for SPL or SPL
239
240         For testing we don't actually have different versions of the DTB. With
241         U-Boot we normally run fdtgrep to remove unwanted nodes, but for tests
242         we don't normally have any unwanted nodes.
243
244         We still want the DTBs for SPL and TPL to be different though, since
245         otherwise it is confusing to know which one we are looking at. So add
246         an 'spl' or 'tpl' property to the top-level node.
247         """
248         dtb = fdt.Fdt.FromData(dtb_data)
249         dtb.Scan()
250         dtb.GetNode('/binman').AddZeroProp(name)
251         dtb.Sync(auto_resize=True)
252         dtb.Pack()
253         return dtb.GetContents()
254
255     def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
256                        update_dtb=False, entry_args=None, reset_dtbs=True):
257         """Run binman and return the resulting image
258
259         This runs binman with a given test file and then reads the resulting
260         output file. It is a shortcut function since most tests need to do
261         these steps.
262
263         Raises an assertion failure if binman returns a non-zero exit code.
264
265         Args:
266             fname: Device-tree source filename to use (e.g. 05_simple.dts)
267             use_real_dtb: True to use the test file as the contents of
268                 the u-boot-dtb entry. Normally this is not needed and the
269                 test contents (the U_BOOT_DTB_DATA string) can be used.
270                 But in some test we need the real contents.
271             map: True to output map files for the images
272             update_dtb: Update the offset and size of each entry in the device
273                 tree before packing it into the image
274
275         Returns:
276             Tuple:
277                 Resulting image contents
278                 Device tree contents
279                 Map data showing contents of image (or None if none)
280                 Output device tree binary filename ('u-boot.dtb' path)
281         """
282         dtb_data = None
283         # Use the compiled test file as the u-boot-dtb input
284         if use_real_dtb:
285             dtb_data = self._SetupDtb(fname)
286             infile = os.path.join(self._indir, 'u-boot.dtb')
287
288             # For testing purposes, make a copy of the DT for SPL and TPL. Add
289             # a node indicating which it is, so aid verification.
290             for name in ['spl', 'tpl']:
291                 dtb_fname = '%s/u-boot-%s.dtb' % (name, name)
292                 outfile = os.path.join(self._indir, dtb_fname)
293                 TestFunctional._MakeInputFile(dtb_fname,
294                         self._GetDtbContentsForSplTpl(dtb_data, name))
295
296         try:
297             retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
298                     entry_args=entry_args, use_real_dtb=use_real_dtb)
299             self.assertEqual(0, retcode)
300             out_dtb_fname = tools.GetOutputFilename('u-boot.dtb.out')
301
302             # Find the (only) image, read it and return its contents
303             image = control.images['image']
304             image_fname = tools.GetOutputFilename('image.bin')
305             self.assertTrue(os.path.exists(image_fname))
306             if map:
307                 map_fname = tools.GetOutputFilename('image.map')
308                 with open(map_fname) as fd:
309                     map_data = fd.read()
310             else:
311                 map_data = None
312             with open(image_fname) as fd:
313                 return fd.read(), dtb_data, map_data, out_dtb_fname
314         finally:
315             # Put the test file back
316             if reset_dtbs and use_real_dtb:
317                 self._ResetDtbs()
318
319     def _DoReadFile(self, fname, use_real_dtb=False):
320         """Helper function which discards the device-tree binary
321
322         Args:
323             fname: Device-tree source filename to use (e.g. 05_simple.dts)
324             use_real_dtb: True to use the test file as the contents of
325                 the u-boot-dtb entry. Normally this is not needed and the
326                 test contents (the U_BOOT_DTB_DATA string) can be used.
327                 But in some test we need the real contents.
328
329         Returns:
330             Resulting image contents
331         """
332         return self._DoReadFileDtb(fname, use_real_dtb)[0]
333
334     @classmethod
335     def _MakeInputFile(self, fname, contents):
336         """Create a new test input file, creating directories as needed
337
338         Args:
339             fname: Filename to create
340             contents: File contents to write in to the file
341         Returns:
342             Full pathname of file created
343         """
344         pathname = os.path.join(self._indir, fname)
345         dirname = os.path.dirname(pathname)
346         if dirname and not os.path.exists(dirname):
347             os.makedirs(dirname)
348         with open(pathname, 'wb') as fd:
349             fd.write(contents)
350         return pathname
351
352     @classmethod
353     def _MakeInputDir(self, dirname):
354         """Create a new test input directory, creating directories as needed
355
356         Args:
357             dirname: Directory name to create
358
359         Returns:
360             Full pathname of directory created
361         """
362         pathname = os.path.join(self._indir, dirname)
363         if not os.path.exists(pathname):
364             os.makedirs(pathname)
365         return pathname
366
367     @classmethod
368     def TestFile(self, fname):
369         return os.path.join(self._binman_dir, 'test', fname)
370
371     def AssertInList(self, grep_list, target):
372         """Assert that at least one of a list of things is in a target
373
374         Args:
375             grep_list: List of strings to check
376             target: Target string
377         """
378         for grep in grep_list:
379             if grep in target:
380                 return
381         self.fail("Error: '%' not found in '%s'" % (grep_list, target))
382
383     def CheckNoGaps(self, entries):
384         """Check that all entries fit together without gaps
385
386         Args:
387             entries: List of entries to check
388         """
389         offset = 0
390         for entry in entries.values():
391             self.assertEqual(offset, entry.offset)
392             offset += entry.size
393
394     def GetFdtLen(self, dtb):
395         """Get the totalsize field from a device-tree binary
396
397         Args:
398             dtb: Device-tree binary contents
399
400         Returns:
401             Total size of device-tree binary, from the header
402         """
403         return struct.unpack('>L', dtb[4:8])[0]
404
405     def _GetPropTree(self, dtb, prop_names):
406         def AddNode(node, path):
407             if node.name != '/':
408                 path += '/' + node.name
409             for subnode in node.subnodes:
410                 for prop in subnode.props.values():
411                     if prop.name in prop_names:
412                         prop_path = path + '/' + subnode.name + ':' + prop.name
413                         tree[prop_path[len('/binman/'):]] = fdt_util.fdt32_to_cpu(
414                             prop.value)
415                 AddNode(subnode, path)
416
417         tree = {}
418         AddNode(dtb.GetRoot(), '')
419         return tree
420
421     def testRun(self):
422         """Test a basic run with valid args"""
423         result = self._RunBinman('-h')
424
425     def testFullHelp(self):
426         """Test that the full help is displayed with -H"""
427         result = self._RunBinman('-H')
428         help_file = os.path.join(self._binman_dir, 'README')
429         # Remove possible extraneous strings
430         extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
431         gothelp = result.stdout.replace(extra, '')
432         self.assertEqual(len(gothelp), os.path.getsize(help_file))
433         self.assertEqual(0, len(result.stderr))
434         self.assertEqual(0, result.return_code)
435
436     def testFullHelpInternal(self):
437         """Test that the full help is displayed with -H"""
438         try:
439             command.test_result = command.CommandResult()
440             result = self._DoBinman('-H')
441             help_file = os.path.join(self._binman_dir, 'README')
442         finally:
443             command.test_result = None
444
445     def testHelp(self):
446         """Test that the basic help is displayed with -h"""
447         result = self._RunBinman('-h')
448         self.assertTrue(len(result.stdout) > 200)
449         self.assertEqual(0, len(result.stderr))
450         self.assertEqual(0, result.return_code)
451
452     def testBoard(self):
453         """Test that we can run it with a specific board"""
454         self._SetupDtb('05_simple.dts', 'sandbox/u-boot.dtb')
455         TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
456         result = self._DoBinman('-b', 'sandbox')
457         self.assertEqual(0, result)
458
459     def testNeedBoard(self):
460         """Test that we get an error when no board ius supplied"""
461         with self.assertRaises(ValueError) as e:
462             result = self._DoBinman()
463         self.assertIn("Must provide a board to process (use -b <board>)",
464                 str(e.exception))
465
466     def testMissingDt(self):
467         """Test that an invalid device-tree file generates an error"""
468         with self.assertRaises(Exception) as e:
469             self._RunBinman('-d', 'missing_file')
470         # We get one error from libfdt, and a different one from fdtget.
471         self.AssertInList(["Couldn't open blob from 'missing_file'",
472                            'No such file or directory'], str(e.exception))
473
474     def testBrokenDt(self):
475         """Test that an invalid device-tree source file generates an error
476
477         Since this is a source file it should be compiled and the error
478         will come from the device-tree compiler (dtc).
479         """
480         with self.assertRaises(Exception) as e:
481             self._RunBinman('-d', self.TestFile('01_invalid.dts'))
482         self.assertIn("FATAL ERROR: Unable to parse input tree",
483                 str(e.exception))
484
485     def testMissingNode(self):
486         """Test that a device tree without a 'binman' node generates an error"""
487         with self.assertRaises(Exception) as e:
488             self._DoBinman('-d', self.TestFile('02_missing_node.dts'))
489         self.assertIn("does not have a 'binman' node", str(e.exception))
490
491     def testEmpty(self):
492         """Test that an empty binman node works OK (i.e. does nothing)"""
493         result = self._RunBinman('-d', self.TestFile('03_empty.dts'))
494         self.assertEqual(0, len(result.stderr))
495         self.assertEqual(0, result.return_code)
496
497     def testInvalidEntry(self):
498         """Test that an invalid entry is flagged"""
499         with self.assertRaises(Exception) as e:
500             result = self._RunBinman('-d',
501                                      self.TestFile('04_invalid_entry.dts'))
502         self.assertIn("Unknown entry type 'not-a-valid-type' in node "
503                 "'/binman/not-a-valid-type'", str(e.exception))
504
505     def testSimple(self):
506         """Test a simple binman with a single file"""
507         data = self._DoReadFile('05_simple.dts')
508         self.assertEqual(U_BOOT_DATA, data)
509
510     def testSimpleDebug(self):
511         """Test a simple binman run with debugging enabled"""
512         data = self._DoTestFile('05_simple.dts', debug=True)
513
514     def testDual(self):
515         """Test that we can handle creating two images
516
517         This also tests image padding.
518         """
519         retcode = self._DoTestFile('06_dual_image.dts')
520         self.assertEqual(0, retcode)
521
522         image = control.images['image1']
523         self.assertEqual(len(U_BOOT_DATA), image._size)
524         fname = tools.GetOutputFilename('image1.bin')
525         self.assertTrue(os.path.exists(fname))
526         with open(fname) as fd:
527             data = fd.read()
528             self.assertEqual(U_BOOT_DATA, data)
529
530         image = control.images['image2']
531         self.assertEqual(3 + len(U_BOOT_DATA) + 5, image._size)
532         fname = tools.GetOutputFilename('image2.bin')
533         self.assertTrue(os.path.exists(fname))
534         with open(fname) as fd:
535             data = fd.read()
536             self.assertEqual(U_BOOT_DATA, data[3:7])
537             self.assertEqual(chr(0) * 3, data[:3])
538             self.assertEqual(chr(0) * 5, data[7:])
539
540     def testBadAlign(self):
541         """Test that an invalid alignment value is detected"""
542         with self.assertRaises(ValueError) as e:
543             self._DoTestFile('07_bad_align.dts')
544         self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
545                       "of two", str(e.exception))
546
547     def testPackSimple(self):
548         """Test that packing works as expected"""
549         retcode = self._DoTestFile('08_pack.dts')
550         self.assertEqual(0, retcode)
551         self.assertIn('image', control.images)
552         image = control.images['image']
553         entries = image.GetEntries()
554         self.assertEqual(5, len(entries))
555
556         # First u-boot
557         self.assertIn('u-boot', entries)
558         entry = entries['u-boot']
559         self.assertEqual(0, entry.offset)
560         self.assertEqual(len(U_BOOT_DATA), entry.size)
561
562         # Second u-boot, aligned to 16-byte boundary
563         self.assertIn('u-boot-align', entries)
564         entry = entries['u-boot-align']
565         self.assertEqual(16, entry.offset)
566         self.assertEqual(len(U_BOOT_DATA), entry.size)
567
568         # Third u-boot, size 23 bytes
569         self.assertIn('u-boot-size', entries)
570         entry = entries['u-boot-size']
571         self.assertEqual(20, entry.offset)
572         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
573         self.assertEqual(23, entry.size)
574
575         # Fourth u-boot, placed immediate after the above
576         self.assertIn('u-boot-next', entries)
577         entry = entries['u-boot-next']
578         self.assertEqual(43, entry.offset)
579         self.assertEqual(len(U_BOOT_DATA), entry.size)
580
581         # Fifth u-boot, placed at a fixed offset
582         self.assertIn('u-boot-fixed', entries)
583         entry = entries['u-boot-fixed']
584         self.assertEqual(61, entry.offset)
585         self.assertEqual(len(U_BOOT_DATA), entry.size)
586
587         self.assertEqual(65, image._size)
588
589     def testPackExtra(self):
590         """Test that extra packing feature works as expected"""
591         retcode = self._DoTestFile('09_pack_extra.dts')
592
593         self.assertEqual(0, retcode)
594         self.assertIn('image', control.images)
595         image = control.images['image']
596         entries = image.GetEntries()
597         self.assertEqual(5, len(entries))
598
599         # First u-boot with padding before and after
600         self.assertIn('u-boot', entries)
601         entry = entries['u-boot']
602         self.assertEqual(0, entry.offset)
603         self.assertEqual(3, entry.pad_before)
604         self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
605
606         # Second u-boot has an aligned size, but it has no effect
607         self.assertIn('u-boot-align-size-nop', entries)
608         entry = entries['u-boot-align-size-nop']
609         self.assertEqual(12, entry.offset)
610         self.assertEqual(4, entry.size)
611
612         # Third u-boot has an aligned size too
613         self.assertIn('u-boot-align-size', entries)
614         entry = entries['u-boot-align-size']
615         self.assertEqual(16, entry.offset)
616         self.assertEqual(32, entry.size)
617
618         # Fourth u-boot has an aligned end
619         self.assertIn('u-boot-align-end', entries)
620         entry = entries['u-boot-align-end']
621         self.assertEqual(48, entry.offset)
622         self.assertEqual(16, entry.size)
623
624         # Fifth u-boot immediately afterwards
625         self.assertIn('u-boot-align-both', entries)
626         entry = entries['u-boot-align-both']
627         self.assertEqual(64, entry.offset)
628         self.assertEqual(64, entry.size)
629
630         self.CheckNoGaps(entries)
631         self.assertEqual(128, image._size)
632
633     def testPackAlignPowerOf2(self):
634         """Test that invalid entry alignment is detected"""
635         with self.assertRaises(ValueError) as e:
636             self._DoTestFile('10_pack_align_power2.dts')
637         self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
638                       "of two", str(e.exception))
639
640     def testPackAlignSizePowerOf2(self):
641         """Test that invalid entry size alignment is detected"""
642         with self.assertRaises(ValueError) as e:
643             self._DoTestFile('11_pack_align_size_power2.dts')
644         self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
645                       "power of two", str(e.exception))
646
647     def testPackInvalidAlign(self):
648         """Test detection of an offset that does not match its alignment"""
649         with self.assertRaises(ValueError) as e:
650             self._DoTestFile('12_pack_inv_align.dts')
651         self.assertIn("Node '/binman/u-boot': Offset 0x5 (5) does not match "
652                       "align 0x4 (4)", str(e.exception))
653
654     def testPackInvalidSizeAlign(self):
655         """Test that invalid entry size alignment is detected"""
656         with self.assertRaises(ValueError) as e:
657             self._DoTestFile('13_pack_inv_size_align.dts')
658         self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
659                       "align-size 0x4 (4)", str(e.exception))
660
661     def testPackOverlap(self):
662         """Test that overlapping regions are detected"""
663         with self.assertRaises(ValueError) as e:
664             self._DoTestFile('14_pack_overlap.dts')
665         self.assertIn("Node '/binman/u-boot-align': Offset 0x3 (3) overlaps "
666                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
667                       str(e.exception))
668
669     def testPackEntryOverflow(self):
670         """Test that entries that overflow their size are detected"""
671         with self.assertRaises(ValueError) as e:
672             self._DoTestFile('15_pack_overflow.dts')
673         self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
674                       "but entry size is 0x3 (3)", str(e.exception))
675
676     def testPackImageOverflow(self):
677         """Test that entries which overflow the image size are detected"""
678         with self.assertRaises(ValueError) as e:
679             self._DoTestFile('16_pack_image_overflow.dts')
680         self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
681                       "size 0x3 (3)", str(e.exception))
682
683     def testPackImageSize(self):
684         """Test that the image size can be set"""
685         retcode = self._DoTestFile('17_pack_image_size.dts')
686         self.assertEqual(0, retcode)
687         self.assertIn('image', control.images)
688         image = control.images['image']
689         self.assertEqual(7, image._size)
690
691     def testPackImageSizeAlign(self):
692         """Test that image size alignemnt works as expected"""
693         retcode = self._DoTestFile('18_pack_image_align.dts')
694         self.assertEqual(0, retcode)
695         self.assertIn('image', control.images)
696         image = control.images['image']
697         self.assertEqual(16, image._size)
698
699     def testPackInvalidImageAlign(self):
700         """Test that invalid image alignment is detected"""
701         with self.assertRaises(ValueError) as e:
702             self._DoTestFile('19_pack_inv_image_align.dts')
703         self.assertIn("Section '/binman': Size 0x7 (7) does not match "
704                       "align-size 0x8 (8)", str(e.exception))
705
706     def testPackAlignPowerOf2(self):
707         """Test that invalid image alignment is detected"""
708         with self.assertRaises(ValueError) as e:
709             self._DoTestFile('20_pack_inv_image_align_power2.dts')
710         self.assertIn("Section '/binman': Alignment size 131 must be a power of "
711                       "two", str(e.exception))
712
713     def testImagePadByte(self):
714         """Test that the image pad byte can be specified"""
715         with open(self.TestFile('bss_data')) as fd:
716             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
717         data = self._DoReadFile('21_image_pad.dts')
718         self.assertEqual(U_BOOT_SPL_DATA + (chr(0xff) * 1) + U_BOOT_DATA, data)
719
720     def testImageName(self):
721         """Test that image files can be named"""
722         retcode = self._DoTestFile('22_image_name.dts')
723         self.assertEqual(0, retcode)
724         image = control.images['image1']
725         fname = tools.GetOutputFilename('test-name')
726         self.assertTrue(os.path.exists(fname))
727
728         image = control.images['image2']
729         fname = tools.GetOutputFilename('test-name.xx')
730         self.assertTrue(os.path.exists(fname))
731
732     def testBlobFilename(self):
733         """Test that generic blobs can be provided by filename"""
734         data = self._DoReadFile('23_blob.dts')
735         self.assertEqual(BLOB_DATA, data)
736
737     def testPackSorted(self):
738         """Test that entries can be sorted"""
739         data = self._DoReadFile('24_sorted.dts')
740         self.assertEqual(chr(0) * 1 + U_BOOT_SPL_DATA + chr(0) * 2 +
741                          U_BOOT_DATA, data)
742
743     def testPackZeroOffset(self):
744         """Test that an entry at offset 0 is not given a new offset"""
745         with self.assertRaises(ValueError) as e:
746             self._DoTestFile('25_pack_zero_size.dts')
747         self.assertIn("Node '/binman/u-boot-spl': Offset 0x0 (0) overlaps "
748                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
749                       str(e.exception))
750
751     def testPackUbootDtb(self):
752         """Test that a device tree can be added to U-Boot"""
753         data = self._DoReadFile('26_pack_u_boot_dtb.dts')
754         self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
755
756     def testPackX86RomNoSize(self):
757         """Test that the end-at-4gb property requires a size property"""
758         with self.assertRaises(ValueError) as e:
759             self._DoTestFile('27_pack_4gb_no_size.dts')
760         self.assertIn("Section '/binman': Section size must be provided when "
761                       "using end-at-4gb", str(e.exception))
762
763     def testPackX86RomOutside(self):
764         """Test that the end-at-4gb property checks for offset boundaries"""
765         with self.assertRaises(ValueError) as e:
766             self._DoTestFile('28_pack_4gb_outside.dts')
767         self.assertIn("Node '/binman/u-boot': Offset 0x0 (0) is outside "
768                       "the section starting at 0xffffffe0 (4294967264)",
769                       str(e.exception))
770
771     def testPackX86Rom(self):
772         """Test that a basic x86 ROM can be created"""
773         data = self._DoReadFile('29_x86-rom.dts')
774         self.assertEqual(U_BOOT_DATA + chr(0) * 7 + U_BOOT_SPL_DATA +
775                          chr(0) * 2, data)
776
777     def testPackX86RomMeNoDesc(self):
778         """Test that an invalid Intel descriptor entry is detected"""
779         TestFunctional._MakeInputFile('descriptor.bin', '')
780         with self.assertRaises(ValueError) as e:
781             self._DoTestFile('31_x86-rom-me.dts')
782         self.assertIn("Node '/binman/intel-descriptor': Cannot find FD "
783                       "signature", str(e.exception))
784
785     def testPackX86RomBadDesc(self):
786         """Test that the Intel requires a descriptor entry"""
787         with self.assertRaises(ValueError) as e:
788             self._DoTestFile('30_x86-rom-me-no-desc.dts')
789         self.assertIn("Node '/binman/intel-me': No offset set with "
790                       "offset-unset: should another entry provide this correct "
791                       "offset?", str(e.exception))
792
793     def testPackX86RomMe(self):
794         """Test that an x86 ROM with an ME region can be created"""
795         data = self._DoReadFile('31_x86-rom-me.dts')
796         self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
797
798     def testPackVga(self):
799         """Test that an image with a VGA binary can be created"""
800         data = self._DoReadFile('32_intel-vga.dts')
801         self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
802
803     def testPackStart16(self):
804         """Test that an image with an x86 start16 region can be created"""
805         data = self._DoReadFile('33_x86-start16.dts')
806         self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
807
808     def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False):
809         """Handle running a test for insertion of microcode
810
811         Args:
812             dts_fname: Name of test .dts file
813             nodtb_data: Data that we expect in the first section
814             ucode_second: True if the microsecond entry is second instead of
815                 third
816
817         Returns:
818             Tuple:
819                 Contents of first region (U-Boot or SPL)
820                 Offset and size components of microcode pointer, as inserted
821                     in the above (two 4-byte words)
822         """
823         data = self._DoReadFile(dts_fname, True)
824
825         # Now check the device tree has no microcode
826         if ucode_second:
827             ucode_content = data[len(nodtb_data):]
828             ucode_pos = len(nodtb_data)
829             dtb_with_ucode = ucode_content[16:]
830             fdt_len = self.GetFdtLen(dtb_with_ucode)
831         else:
832             dtb_with_ucode = data[len(nodtb_data):]
833             fdt_len = self.GetFdtLen(dtb_with_ucode)
834             ucode_content = dtb_with_ucode[fdt_len:]
835             ucode_pos = len(nodtb_data) + fdt_len
836         fname = tools.GetOutputFilename('test.dtb')
837         with open(fname, 'wb') as fd:
838             fd.write(dtb_with_ucode)
839         dtb = fdt.FdtScan(fname)
840         ucode = dtb.GetNode('/microcode')
841         self.assertTrue(ucode)
842         for node in ucode.subnodes:
843             self.assertFalse(node.props.get('data'))
844
845         # Check that the microcode appears immediately after the Fdt
846         # This matches the concatenation of the data properties in
847         # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
848         ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
849                                  0x78235609)
850         self.assertEqual(ucode_data, ucode_content[:len(ucode_data)])
851
852         # Check that the microcode pointer was inserted. It should match the
853         # expected offset and size
854         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
855                                    len(ucode_data))
856         u_boot = data[:len(nodtb_data)]
857         return u_boot, pos_and_size
858
859     def testPackUbootMicrocode(self):
860         """Test that x86 microcode can be handled correctly
861
862         We expect to see the following in the image, in order:
863             u-boot-nodtb.bin with a microcode pointer inserted at the correct
864                 place
865             u-boot.dtb with the microcode removed
866             the microcode
867         """
868         first, pos_and_size = self._RunMicrocodeTest('34_x86_ucode.dts',
869                                                      U_BOOT_NODTB_DATA)
870         self.assertEqual('nodtb with microcode' + pos_and_size +
871                          ' somewhere in here', first)
872
873     def _RunPackUbootSingleMicrocode(self):
874         """Test that x86 microcode can be handled correctly
875
876         We expect to see the following in the image, in order:
877             u-boot-nodtb.bin with a microcode pointer inserted at the correct
878                 place
879             u-boot.dtb with the microcode
880             an empty microcode region
881         """
882         # We need the libfdt library to run this test since only that allows
883         # finding the offset of a property. This is required by
884         # Entry_u_boot_dtb_with_ucode.ObtainContents().
885         data = self._DoReadFile('35_x86_single_ucode.dts', True)
886
887         second = data[len(U_BOOT_NODTB_DATA):]
888
889         fdt_len = self.GetFdtLen(second)
890         third = second[fdt_len:]
891         second = second[:fdt_len]
892
893         ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
894         self.assertIn(ucode_data, second)
895         ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
896
897         # Check that the microcode pointer was inserted. It should match the
898         # expected offset and size
899         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
900                                    len(ucode_data))
901         first = data[:len(U_BOOT_NODTB_DATA)]
902         self.assertEqual('nodtb with microcode' + pos_and_size +
903                          ' somewhere in here', first)
904
905     def testPackUbootSingleMicrocode(self):
906         """Test that x86 microcode can be handled correctly with fdt_normal.
907         """
908         self._RunPackUbootSingleMicrocode()
909
910     def testUBootImg(self):
911         """Test that u-boot.img can be put in a file"""
912         data = self._DoReadFile('36_u_boot_img.dts')
913         self.assertEqual(U_BOOT_IMG_DATA, data)
914
915     def testNoMicrocode(self):
916         """Test that a missing microcode region is detected"""
917         with self.assertRaises(ValueError) as e:
918             self._DoReadFile('37_x86_no_ucode.dts', True)
919         self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
920                       "node found in ", str(e.exception))
921
922     def testMicrocodeWithoutNode(self):
923         """Test that a missing u-boot-dtb-with-ucode node is detected"""
924         with self.assertRaises(ValueError) as e:
925             self._DoReadFile('38_x86_ucode_missing_node.dts', True)
926         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
927                 "microcode region u-boot-dtb-with-ucode", str(e.exception))
928
929     def testMicrocodeWithoutNode2(self):
930         """Test that a missing u-boot-ucode node is detected"""
931         with self.assertRaises(ValueError) as e:
932             self._DoReadFile('39_x86_ucode_missing_node2.dts', True)
933         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
934             "microcode region u-boot-ucode", str(e.exception))
935
936     def testMicrocodeWithoutPtrInElf(self):
937         """Test that a U-Boot binary without the microcode symbol is detected"""
938         # ELF file without a '_dt_ucode_base_size' symbol
939         try:
940             with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
941                 TestFunctional._MakeInputFile('u-boot', fd.read())
942
943             with self.assertRaises(ValueError) as e:
944                 self._RunPackUbootSingleMicrocode()
945             self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
946                     "_dt_ucode_base_size symbol in u-boot", str(e.exception))
947
948         finally:
949             # Put the original file back
950             with open(self.TestFile('u_boot_ucode_ptr')) as fd:
951                 TestFunctional._MakeInputFile('u-boot', fd.read())
952
953     def testMicrocodeNotInImage(self):
954         """Test that microcode must be placed within the image"""
955         with self.assertRaises(ValueError) as e:
956             self._DoReadFile('40_x86_ucode_not_in_image.dts', True)
957         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
958                 "pointer _dt_ucode_base_size at fffffe14 is outside the "
959                 "section ranging from 00000000 to 0000002e", str(e.exception))
960
961     def testWithoutMicrocode(self):
962         """Test that we can cope with an image without microcode (e.g. qemu)"""
963         with open(self.TestFile('u_boot_no_ucode_ptr')) as fd:
964             TestFunctional._MakeInputFile('u-boot', fd.read())
965         data, dtb, _, _ = self._DoReadFileDtb('44_x86_optional_ucode.dts', True)
966
967         # Now check the device tree has no microcode
968         self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
969         second = data[len(U_BOOT_NODTB_DATA):]
970
971         fdt_len = self.GetFdtLen(second)
972         self.assertEqual(dtb, second[:fdt_len])
973
974         used_len = len(U_BOOT_NODTB_DATA) + fdt_len
975         third = data[used_len:]
976         self.assertEqual(chr(0) * (0x200 - used_len), third)
977
978     def testUnknownPosSize(self):
979         """Test that microcode must be placed within the image"""
980         with self.assertRaises(ValueError) as e:
981             self._DoReadFile('41_unknown_pos_size.dts', True)
982         self.assertIn("Section '/binman': Unable to set offset/size for unknown "
983                 "entry 'invalid-entry'", str(e.exception))
984
985     def testPackFsp(self):
986         """Test that an image with a FSP binary can be created"""
987         data = self._DoReadFile('42_intel-fsp.dts')
988         self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
989
990     def testPackCmc(self):
991         """Test that an image with a CMC binary can be created"""
992         data = self._DoReadFile('43_intel-cmc.dts')
993         self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
994
995     def testPackVbt(self):
996         """Test that an image with a VBT binary can be created"""
997         data = self._DoReadFile('46_intel-vbt.dts')
998         self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
999
1000     def testSplBssPad(self):
1001         """Test that we can pad SPL's BSS with zeros"""
1002         # ELF file with a '__bss_size' symbol
1003         with open(self.TestFile('bss_data')) as fd:
1004             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1005         data = self._DoReadFile('47_spl_bss_pad.dts')
1006         self.assertEqual(U_BOOT_SPL_DATA + (chr(0) * 10) + U_BOOT_DATA, data)
1007
1008         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
1009             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1010         with self.assertRaises(ValueError) as e:
1011             data = self._DoReadFile('47_spl_bss_pad.dts')
1012         self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
1013                       str(e.exception))
1014
1015     def testPackStart16Spl(self):
1016         """Test that an image with an x86 start16 SPL region can be created"""
1017         data = self._DoReadFile('48_x86-start16-spl.dts')
1018         self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
1019
1020     def _PackUbootSplMicrocode(self, dts, ucode_second=False):
1021         """Helper function for microcode tests
1022
1023         We expect to see the following in the image, in order:
1024             u-boot-spl-nodtb.bin with a microcode pointer inserted at the
1025                 correct place
1026             u-boot.dtb with the microcode removed
1027             the microcode
1028
1029         Args:
1030             dts: Device tree file to use for test
1031             ucode_second: True if the microsecond entry is second instead of
1032                 third
1033         """
1034         # ELF file with a '_dt_ucode_base_size' symbol
1035         with open(self.TestFile('u_boot_ucode_ptr')) as fd:
1036             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1037         first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA,
1038                                                      ucode_second=ucode_second)
1039         self.assertEqual('splnodtb with microc' + pos_and_size +
1040                          'ter somewhere in here', first)
1041
1042     def testPackUbootSplMicrocode(self):
1043         """Test that x86 microcode can be handled correctly in SPL"""
1044         self._PackUbootSplMicrocode('49_x86_ucode_spl.dts')
1045
1046     def testPackUbootSplMicrocodeReorder(self):
1047         """Test that order doesn't matter for microcode entries
1048
1049         This is the same as testPackUbootSplMicrocode but when we process the
1050         u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode
1051         entry, so we reply on binman to try later.
1052         """
1053         self._PackUbootSplMicrocode('58_x86_ucode_spl_needs_retry.dts',
1054                                     ucode_second=True)
1055
1056     def testPackMrc(self):
1057         """Test that an image with an MRC binary can be created"""
1058         data = self._DoReadFile('50_intel_mrc.dts')
1059         self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
1060
1061     def testSplDtb(self):
1062         """Test that an image with spl/u-boot-spl.dtb can be created"""
1063         data = self._DoReadFile('51_u_boot_spl_dtb.dts')
1064         self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
1065
1066     def testSplNoDtb(self):
1067         """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
1068         data = self._DoReadFile('52_u_boot_spl_nodtb.dts')
1069         self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
1070
1071     def testSymbols(self):
1072         """Test binman can assign symbols embedded in U-Boot"""
1073         elf_fname = self.TestFile('u_boot_binman_syms')
1074         syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
1075         addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
1076         self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
1077
1078         with open(self.TestFile('u_boot_binman_syms')) as fd:
1079             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
1080         data = self._DoReadFile('53_symbols.dts')
1081         sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
1082         expected = (sym_values + U_BOOT_SPL_DATA[16:] + chr(0xff) +
1083                     U_BOOT_DATA +
1084                     sym_values + U_BOOT_SPL_DATA[16:])
1085         self.assertEqual(expected, data)
1086
1087     def testPackUnitAddress(self):
1088         """Test that we support multiple binaries with the same name"""
1089         data = self._DoReadFile('54_unit_address.dts')
1090         self.assertEqual(U_BOOT_DATA + U_BOOT_DATA, data)
1091
1092     def testSections(self):
1093         """Basic test of sections"""
1094         data = self._DoReadFile('55_sections.dts')
1095         expected = (U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12 +
1096                     U_BOOT_DATA + '&' * 4)
1097         self.assertEqual(expected, data)
1098
1099     def testMap(self):
1100         """Tests outputting a map of the images"""
1101         _, _, map_data, _ = self._DoReadFileDtb('55_sections.dts', map=True)
1102         self.assertEqual('''ImagePos    Offset      Size  Name
1103 00000000  00000000  00000028  main-section
1104 00000000   00000000  00000010  section@0
1105 00000000    00000000  00000004  u-boot
1106 00000010   00000010  00000010  section@1
1107 00000010    00000000  00000004  u-boot
1108 00000020   00000020  00000004  section@2
1109 00000020    00000000  00000004  u-boot
1110 ''', map_data)
1111
1112     def testNamePrefix(self):
1113         """Tests that name prefixes are used"""
1114         _, _, map_data, _ = self._DoReadFileDtb('56_name_prefix.dts', map=True)
1115         self.assertEqual('''ImagePos    Offset      Size  Name
1116 00000000  00000000  00000028  main-section
1117 00000000   00000000  00000010  section@0
1118 00000000    00000000  00000004  ro-u-boot
1119 00000010   00000010  00000010  section@1
1120 00000010    00000000  00000004  rw-u-boot
1121 ''', map_data)
1122
1123     def testUnknownContents(self):
1124         """Test that obtaining the contents works as expected"""
1125         with self.assertRaises(ValueError) as e:
1126             self._DoReadFile('57_unknown_contents.dts', True)
1127         self.assertIn("Section '/binman': Internal error: Could not complete "
1128                 "processing of contents: remaining [<_testing.Entry__testing ",
1129                 str(e.exception))
1130
1131     def testBadChangeSize(self):
1132         """Test that trying to change the size of an entry fails"""
1133         with self.assertRaises(ValueError) as e:
1134             self._DoReadFile('59_change_size.dts', True)
1135         self.assertIn("Node '/binman/_testing': Cannot update entry size from "
1136                       '2 to 1', str(e.exception))
1137
1138     def testUpdateFdt(self):
1139         """Test that we can update the device tree with offset/size info"""
1140         _, _, _, out_dtb_fname = self._DoReadFileDtb('60_fdt_update.dts',
1141                                                      update_dtb=True)
1142         dtb = fdt.Fdt(out_dtb_fname)
1143         dtb.Scan()
1144         props = self._GetPropTree(dtb, ['offset', 'size', 'image-pos'])
1145         self.assertEqual({
1146             'image-pos': 0,
1147             'offset': 0,
1148             '_testing:offset': 32,
1149             '_testing:size': 1,
1150             '_testing:image-pos': 32,
1151             'section@0/u-boot:offset': 0,
1152             'section@0/u-boot:size': len(U_BOOT_DATA),
1153             'section@0/u-boot:image-pos': 0,
1154             'section@0:offset': 0,
1155             'section@0:size': 16,
1156             'section@0:image-pos': 0,
1157
1158             'section@1/u-boot:offset': 0,
1159             'section@1/u-boot:size': len(U_BOOT_DATA),
1160             'section@1/u-boot:image-pos': 16,
1161             'section@1:offset': 16,
1162             'section@1:size': 16,
1163             'section@1:image-pos': 16,
1164             'size': 40
1165         }, props)
1166
1167     def testUpdateFdtBad(self):
1168         """Test that we detect when ProcessFdt never completes"""
1169         with self.assertRaises(ValueError) as e:
1170             self._DoReadFileDtb('61_fdt_update_bad.dts', update_dtb=True)
1171         self.assertIn('Could not complete processing of Fdt: remaining '
1172                       '[<_testing.Entry__testing', str(e.exception))
1173
1174     def testEntryArgs(self):
1175         """Test passing arguments to entries from the command line"""
1176         entry_args = {
1177             'test-str-arg': 'test1',
1178             'test-int-arg': '456',
1179         }
1180         self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
1181         self.assertIn('image', control.images)
1182         entry = control.images['image'].GetEntries()['_testing']
1183         self.assertEqual('test0', entry.test_str_fdt)
1184         self.assertEqual('test1', entry.test_str_arg)
1185         self.assertEqual(123, entry.test_int_fdt)
1186         self.assertEqual(456, entry.test_int_arg)
1187
1188     def testEntryArgsMissing(self):
1189         """Test missing arguments and properties"""
1190         entry_args = {
1191             'test-int-arg': '456',
1192         }
1193         self._DoReadFileDtb('63_entry_args_missing.dts', entry_args=entry_args)
1194         entry = control.images['image'].GetEntries()['_testing']
1195         self.assertEqual('test0', entry.test_str_fdt)
1196         self.assertEqual(None, entry.test_str_arg)
1197         self.assertEqual(None, entry.test_int_fdt)
1198         self.assertEqual(456, entry.test_int_arg)
1199
1200     def testEntryArgsRequired(self):
1201         """Test missing arguments and properties"""
1202         entry_args = {
1203             'test-int-arg': '456',
1204         }
1205         with self.assertRaises(ValueError) as e:
1206             self._DoReadFileDtb('64_entry_args_required.dts')
1207         self.assertIn("Node '/binman/_testing': Missing required "
1208             'properties/entry args: test-str-arg, test-int-fdt, test-int-arg',
1209             str(e.exception))
1210
1211     def testEntryArgsInvalidFormat(self):
1212         """Test that an invalid entry-argument format is detected"""
1213         args = ['-d', self.TestFile('64_entry_args_required.dts'), '-ano-value']
1214         with self.assertRaises(ValueError) as e:
1215             self._DoBinman(*args)
1216         self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
1217
1218     def testEntryArgsInvalidInteger(self):
1219         """Test that an invalid entry-argument integer is detected"""
1220         entry_args = {
1221             'test-int-arg': 'abc',
1222         }
1223         with self.assertRaises(ValueError) as e:
1224             self._DoReadFileDtb('62_entry_args.dts', entry_args=entry_args)
1225         self.assertIn("Node '/binman/_testing': Cannot convert entry arg "
1226                       "'test-int-arg' (value 'abc') to integer",
1227             str(e.exception))
1228
1229     def testEntryArgsInvalidDatatype(self):
1230         """Test that an invalid entry-argument datatype is detected
1231
1232         This test could be written in entry_test.py except that it needs
1233         access to control.entry_args, which seems more than that module should
1234         be able to see.
1235         """
1236         entry_args = {
1237             'test-bad-datatype-arg': '12',
1238         }
1239         with self.assertRaises(ValueError) as e:
1240             self._DoReadFileDtb('65_entry_args_unknown_datatype.dts',
1241                                 entry_args=entry_args)
1242         self.assertIn('GetArg() internal error: Unknown data type ',
1243                       str(e.exception))
1244
1245     def testText(self):
1246         """Test for a text entry type"""
1247         entry_args = {
1248             'test-id': TEXT_DATA,
1249             'test-id2': TEXT_DATA2,
1250             'test-id3': TEXT_DATA3,
1251         }
1252         data, _, _, _ = self._DoReadFileDtb('66_text.dts',
1253                                             entry_args=entry_args)
1254         expected = (TEXT_DATA + chr(0) * (8 - len(TEXT_DATA)) + TEXT_DATA2 +
1255                     TEXT_DATA3 + 'some text')
1256         self.assertEqual(expected, data)
1257
1258     def testEntryDocs(self):
1259         """Test for creation of entry documentation"""
1260         with test_util.capture_sys_output() as (stdout, stderr):
1261             control.WriteEntryDocs(binman.GetEntryModules())
1262         self.assertTrue(len(stdout.getvalue()) > 0)
1263
1264     def testEntryDocsMissing(self):
1265         """Test handling of missing entry documentation"""
1266         with self.assertRaises(ValueError) as e:
1267             with test_util.capture_sys_output() as (stdout, stderr):
1268                 control.WriteEntryDocs(binman.GetEntryModules(), 'u_boot')
1269         self.assertIn('Documentation is missing for modules: u_boot',
1270                       str(e.exception))
1271
1272     def testFmap(self):
1273         """Basic test of generation of a flashrom fmap"""
1274         data = self._DoReadFile('67_fmap.dts')
1275         fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1276         expected = U_BOOT_DATA + '!' * 12 + U_BOOT_DATA + 'a' * 12
1277         self.assertEqual(expected, data[:32])
1278         self.assertEqual('__FMAP__', fhdr.signature)
1279         self.assertEqual(1, fhdr.ver_major)
1280         self.assertEqual(0, fhdr.ver_minor)
1281         self.assertEqual(0, fhdr.base)
1282         self.assertEqual(16 + 16 +
1283                          fmap_util.FMAP_HEADER_LEN +
1284                          fmap_util.FMAP_AREA_LEN * 3, fhdr.image_size)
1285         self.assertEqual('FMAP', fhdr.name)
1286         self.assertEqual(3, fhdr.nareas)
1287         for fentry in fentries:
1288             self.assertEqual(0, fentry.flags)
1289
1290         self.assertEqual(0, fentries[0].offset)
1291         self.assertEqual(4, fentries[0].size)
1292         self.assertEqual('RO_U_BOOT', fentries[0].name)
1293
1294         self.assertEqual(16, fentries[1].offset)
1295         self.assertEqual(4, fentries[1].size)
1296         self.assertEqual('RW_U_BOOT', fentries[1].name)
1297
1298         self.assertEqual(32, fentries[2].offset)
1299         self.assertEqual(fmap_util.FMAP_HEADER_LEN +
1300                          fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
1301         self.assertEqual('FMAP', fentries[2].name)
1302
1303     def testBlobNamedByArg(self):
1304         """Test we can add a blob with the filename coming from an entry arg"""
1305         entry_args = {
1306             'cros-ec-rw-path': 'ecrw.bin',
1307         }
1308         data, _, _, _ = self._DoReadFileDtb('68_blob_named_by_arg.dts',
1309                                             entry_args=entry_args)
1310
1311     def testFill(self):
1312         """Test for an fill entry type"""
1313         data = self._DoReadFile('69_fill.dts')
1314         expected = 8 * chr(0xff) + 8 * chr(0)
1315         self.assertEqual(expected, data)
1316
1317     def testFillNoSize(self):
1318         """Test for an fill entry type with no size"""
1319         with self.assertRaises(ValueError) as e:
1320             self._DoReadFile('70_fill_no_size.dts')
1321         self.assertIn("'fill' entry must have a size property",
1322                       str(e.exception))
1323
1324     def _HandleGbbCommand(self, pipe_list):
1325         """Fake calls to the futility utility"""
1326         if pipe_list[0][0] == 'futility':
1327             fname = pipe_list[0][-1]
1328             # Append our GBB data to the file, which will happen every time the
1329             # futility command is called.
1330             with open(fname, 'a') as fd:
1331                 fd.write(GBB_DATA)
1332             return command.CommandResult()
1333
1334     def testGbb(self):
1335         """Test for the Chromium OS Google Binary Block"""
1336         command.test_result = self._HandleGbbCommand
1337         entry_args = {
1338             'keydir': 'devkeys',
1339             'bmpblk': 'bmpblk.bin',
1340         }
1341         data, _, _, _ = self._DoReadFileDtb('71_gbb.dts', entry_args=entry_args)
1342
1343         # Since futility
1344         expected = GBB_DATA + GBB_DATA + 8 * chr(0) + (0x2180 - 16) * chr(0)
1345         self.assertEqual(expected, data)
1346
1347     def testGbbTooSmall(self):
1348         """Test for the Chromium OS Google Binary Block being large enough"""
1349         with self.assertRaises(ValueError) as e:
1350             self._DoReadFileDtb('72_gbb_too_small.dts')
1351         self.assertIn("Node '/binman/gbb': GBB is too small",
1352                       str(e.exception))
1353
1354     def testGbbNoSize(self):
1355         """Test for the Chromium OS Google Binary Block having a size"""
1356         with self.assertRaises(ValueError) as e:
1357             self._DoReadFileDtb('73_gbb_no_size.dts')
1358         self.assertIn("Node '/binman/gbb': GBB must have a fixed size",
1359                       str(e.exception))
1360
1361     def _HandleVblockCommand(self, pipe_list):
1362         """Fake calls to the futility utility"""
1363         if pipe_list[0][0] == 'futility':
1364             fname = pipe_list[0][3]
1365             with open(fname, 'wb') as fd:
1366                 fd.write(VBLOCK_DATA)
1367             return command.CommandResult()
1368
1369     def testVblock(self):
1370         """Test for the Chromium OS Verified Boot Block"""
1371         command.test_result = self._HandleVblockCommand
1372         entry_args = {
1373             'keydir': 'devkeys',
1374         }
1375         data, _, _, _ = self._DoReadFileDtb('74_vblock.dts',
1376                                             entry_args=entry_args)
1377         expected = U_BOOT_DATA + VBLOCK_DATA + U_BOOT_DTB_DATA
1378         self.assertEqual(expected, data)
1379
1380     def testVblockNoContent(self):
1381         """Test we detect a vblock which has no content to sign"""
1382         with self.assertRaises(ValueError) as e:
1383             self._DoReadFile('75_vblock_no_content.dts')
1384         self.assertIn("Node '/binman/vblock': Vblock must have a 'content' "
1385                       'property', str(e.exception))
1386
1387     def testVblockBadPhandle(self):
1388         """Test that we detect a vblock with an invalid phandle in contents"""
1389         with self.assertRaises(ValueError) as e:
1390             self._DoReadFile('76_vblock_bad_phandle.dts')
1391         self.assertIn("Node '/binman/vblock': Cannot find node for phandle "
1392                       '1000', str(e.exception))
1393
1394     def testVblockBadEntry(self):
1395         """Test that we detect an entry that points to a non-entry"""
1396         with self.assertRaises(ValueError) as e:
1397             self._DoReadFile('77_vblock_bad_entry.dts')
1398         self.assertIn("Node '/binman/vblock': Cannot find entry for node "
1399                       "'other'", str(e.exception))
1400
1401     def testTpl(self):
1402         """Test that an image with TPL and ots device tree can be created"""
1403         # ELF file with a '__bss_size' symbol
1404         with open(self.TestFile('bss_data')) as fd:
1405             TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
1406         data = self._DoReadFile('78_u_boot_tpl.dts')
1407         self.assertEqual(U_BOOT_TPL_DATA + U_BOOT_TPL_DTB_DATA, data)
1408
1409     def testUsesPos(self):
1410         """Test that the 'pos' property cannot be used anymore"""
1411         with self.assertRaises(ValueError) as e:
1412            data = self._DoReadFile('79_uses_pos.dts')
1413         self.assertIn("Node '/binman/u-boot': Please use 'offset' instead of "
1414                       "'pos'", str(e.exception))
1415
1416     def testFillZero(self):
1417         """Test for an fill entry type with a size of 0"""
1418         data = self._DoReadFile('80_fill_empty.dts')
1419         self.assertEqual(chr(0) * 16, data)
1420
1421     def testTextMissing(self):
1422         """Test for a text entry type where there is no text"""
1423         with self.assertRaises(ValueError) as e:
1424             self._DoReadFileDtb('66_text.dts',)
1425         self.assertIn("Node '/binman/text': No value provided for text label "
1426                       "'test-id'", str(e.exception))
1427
1428     def testPackStart16Tpl(self):
1429         """Test that an image with an x86 start16 TPL region can be created"""
1430         data = self._DoReadFile('81_x86-start16-tpl.dts')
1431         self.assertEqual(X86_START16_TPL_DATA, data[:len(X86_START16_TPL_DATA)])
1432
1433     def testSelectImage(self):
1434         """Test that we can select which images to build"""
1435         with test_util.capture_sys_output() as (stdout, stderr):
1436             retcode = self._DoTestFile('06_dual_image.dts', images=['image2'])
1437         self.assertEqual(0, retcode)
1438         self.assertIn('Skipping images: image1', stdout.getvalue())
1439
1440         self.assertFalse(os.path.exists(tools.GetOutputFilename('image1.bin')))
1441         self.assertTrue(os.path.exists(tools.GetOutputFilename('image2.bin')))
1442
1443     def testUpdateFdtAll(self):
1444         """Test that all device trees are updated with offset/size info"""
1445         data, _, _, _ = self._DoReadFileDtb('82_fdt_update_all.dts',
1446                                             use_real_dtb=True, update_dtb=True)
1447
1448         base_expected = {
1449             'section:image-pos': 0,
1450             'u-boot-tpl-dtb:size': 513,
1451             'u-boot-spl-dtb:size': 513,
1452             'u-boot-spl-dtb:offset': 493,
1453             'image-pos': 0,
1454             'section/u-boot-dtb:image-pos': 0,
1455             'u-boot-spl-dtb:image-pos': 493,
1456             'section/u-boot-dtb:size': 493,
1457             'u-boot-tpl-dtb:image-pos': 1006,
1458             'section/u-boot-dtb:offset': 0,
1459             'section:size': 493,
1460             'offset': 0,
1461             'section:offset': 0,
1462             'u-boot-tpl-dtb:offset': 1006,
1463             'size': 1519
1464         }
1465
1466         # We expect three device-tree files in the output, one after the other.
1467         # Read them in sequence. We look for an 'spl' property in the SPL tree,
1468         # and 'tpl' in the TPL tree, to make sure they are distinct from the
1469         # main U-Boot tree. All three should have the same postions and offset.
1470         start = 0
1471         for item in ['', 'spl', 'tpl']:
1472             dtb = fdt.Fdt.FromData(data[start:])
1473             dtb.Scan()
1474             props = self._GetPropTree(dtb, ['offset', 'size', 'image-pos',
1475                                             'spl', 'tpl'])
1476             expected = dict(base_expected)
1477             if item:
1478                 expected[item] = 0
1479             self.assertEqual(expected, props)
1480             start += dtb._fdt_obj.totalsize()
1481
1482     def testUpdateFdtOutput(self):
1483         """Test that output DTB files are updated"""
1484         try:
1485             data, dtb_data, _, _ = self._DoReadFileDtb('82_fdt_update_all.dts',
1486                     use_real_dtb=True, update_dtb=True, reset_dtbs=False)
1487
1488             # Unfortunately, compiling a source file always results in a file
1489             # called source.dtb (see fdt_util.EnsureCompiled()). The test
1490             # source file (e.g. test/75_fdt_update_all.dts) thus does not enter
1491             # binman as a file called u-boot.dtb. To fix this, copy the file
1492             # over to the expected place.
1493             #tools.WriteFile(os.path.join(self._indir, 'u-boot.dtb'),
1494                     #tools.ReadFile(tools.GetOutputFilename('source.dtb')))
1495             start = 0
1496             for fname in ['u-boot.dtb.out', 'spl/u-boot-spl.dtb.out',
1497                           'tpl/u-boot-tpl.dtb.out']:
1498                 dtb = fdt.Fdt.FromData(data[start:])
1499                 size = dtb._fdt_obj.totalsize()
1500                 pathname = tools.GetOutputFilename(os.path.split(fname)[1])
1501                 outdata = tools.ReadFile(pathname)
1502                 name = os.path.split(fname)[0]
1503
1504                 if name:
1505                     orig_indata = self._GetDtbContentsForSplTpl(dtb_data, name)
1506                 else:
1507                     orig_indata = dtb_data
1508                 self.assertNotEqual(outdata, orig_indata,
1509                         "Expected output file '%s' be updated" % pathname)
1510                 self.assertEqual(outdata, data[start:start + size],
1511                         "Expected output file '%s' to match output image" %
1512                         pathname)
1513                 start += size
1514         finally:
1515             self._ResetDtbs()
1516
1517     def _decompress(self, data):
1518         out = os.path.join(self._indir, 'lz4.tmp')
1519         with open(out, 'wb') as fd:
1520             fd.write(data)
1521         return tools.Run('lz4', '-dc', out)
1522         '''
1523         try:
1524             orig = lz4.frame.decompress(data)
1525         except AttributeError:
1526             orig = lz4.decompress(data)
1527         '''
1528
1529     def testCompress(self):
1530         """Test compression of blobs"""
1531         data, _, _, out_dtb_fname = self._DoReadFileDtb('83_compress.dts',
1532                                             use_real_dtb=True, update_dtb=True)
1533         dtb = fdt.Fdt(out_dtb_fname)
1534         dtb.Scan()
1535         props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
1536         orig = self._decompress(data)
1537         self.assertEquals(COMPRESS_DATA, orig)
1538         expected = {
1539             'blob:uncomp-size': len(COMPRESS_DATA),
1540             'blob:size': len(data),
1541             'size': len(data),
1542             }
1543         self.assertEqual(expected, props)
1544
1545     def testFiles(self):
1546         """Test bringing in multiple files"""
1547         data = self._DoReadFile('84_files.dts')
1548         self.assertEqual(FILES_DATA, data)
1549
1550     def testFilesCompress(self):
1551         """Test bringing in multiple files and compressing them"""
1552         data = self._DoReadFile('85_files_compress.dts')
1553
1554         image = control.images['image']
1555         entries = image.GetEntries()
1556         files = entries['files']
1557         entries = files._section._entries
1558
1559         orig = ''
1560         for i in range(1, 3):
1561             key = '%d.dat' % i
1562             start = entries[key].image_pos
1563             len = entries[key].size
1564             chunk = data[start:start + len]
1565             orig += self._decompress(chunk)
1566
1567         self.assertEqual(FILES_DATA, orig)
1568
1569     def testFilesMissing(self):
1570         """Test missing files"""
1571         with self.assertRaises(ValueError) as e:
1572             data = self._DoReadFile('86_files_none.dts')
1573         self.assertIn("Node '/binman/files': Pattern \'files/*.none\' matched "
1574                       'no files', str(e.exception))
1575
1576     def testFilesNoPattern(self):
1577         """Test missing files"""
1578         with self.assertRaises(ValueError) as e:
1579             data = self._DoReadFile('87_files_no_pattern.dts')
1580         self.assertIn("Node '/binman/files': Missing 'pattern' property",
1581                       str(e.exception))
1582
1583     def testExpandSize(self):
1584         """Test an expanding entry"""
1585         data, _, map_data, _ = self._DoReadFileDtb('88_expand_size.dts',
1586                                                    map=True)
1587         expect = ('a' * 8 + U_BOOT_DATA +
1588                   MRC_DATA + 'b' * 1 + U_BOOT_DATA +
1589                   'c' * 8 + U_BOOT_DATA +
1590                   'd' * 8)
1591         self.assertEqual(expect, data)
1592         self.assertEqual('''ImagePos    Offset      Size  Name
1593 00000000  00000000  00000028  main-section
1594 00000000   00000000  00000008  fill
1595 00000008   00000008  00000004  u-boot
1596 0000000c   0000000c  00000004  section
1597 0000000c    00000000  00000003  intel-mrc
1598 00000010   00000010  00000004  u-boot2
1599 00000014   00000014  0000000c  section2
1600 00000014    00000000  00000008  fill
1601 0000001c    00000008  00000004  u-boot
1602 00000020   00000020  00000008  fill2
1603 ''', map_data)
1604
1605     def testExpandSizeBad(self):
1606         """Test an expanding entry which fails to provide contents"""
1607         with self.assertRaises(ValueError) as e:
1608             self._DoReadFileDtb('89_expand_size_bad.dts', map=True)
1609         self.assertIn("Node '/binman/_testing': Cannot obtain contents when "
1610                       'expanding entry', str(e.exception))
1611
1612     def testHash(self):
1613         """Test hashing of the contents of an entry"""
1614         _, _, _, out_dtb_fname = self._DoReadFileDtb('90_hash.dts',
1615                 use_real_dtb=True, update_dtb=True)
1616         dtb = fdt.Fdt(out_dtb_fname)
1617         dtb.Scan()
1618         hash_node = dtb.GetNode('/binman/u-boot/hash').props['value']
1619         m = hashlib.sha256()
1620         m.update(U_BOOT_DATA)
1621         self.assertEqual(m.digest(), ''.join(hash_node.value))
1622
1623     def testHashNoAlgo(self):
1624         with self.assertRaises(ValueError) as e:
1625             self._DoReadFileDtb('91_hash_no_algo.dts', update_dtb=True)
1626         self.assertIn("Node \'/binman/u-boot\': Missing \'algo\' property for "
1627                       'hash node', str(e.exception))
1628
1629     def testHashBadAlgo(self):
1630         with self.assertRaises(ValueError) as e:
1631             self._DoReadFileDtb('92_hash_bad_algo.dts', update_dtb=True)
1632         self.assertIn("Node '/binman/u-boot': Unknown hash algorithm",
1633                       str(e.exception))
1634
1635     def testHashSection(self):
1636         """Test hashing of the contents of an entry"""
1637         _, _, _, out_dtb_fname = self._DoReadFileDtb('99_hash_section.dts',
1638                 use_real_dtb=True, update_dtb=True)
1639         dtb = fdt.Fdt(out_dtb_fname)
1640         dtb.Scan()
1641         hash_node = dtb.GetNode('/binman/section/hash').props['value']
1642         m = hashlib.sha256()
1643         m.update(U_BOOT_DATA)
1644         m.update(16 * 'a')
1645         self.assertEqual(m.digest(), ''.join(hash_node.value))
1646
1647
1648 if __name__ == "__main__":
1649     unittest.main()