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