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