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