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