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