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