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