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