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