binman: Add support for Intel FIT
[oweals/u-boot.git] / tools / binman / ftest.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # To run a single test, change to this directory, and:
6 #
7 #    python -m unittest func_test.TestFunctional.testHelp
8
9 from __future__ import print_function
10
11 import hashlib
12 from optparse import OptionParser
13 import os
14 import shutil
15 import struct
16 import sys
17 import tempfile
18 import unittest
19
20 import binman
21 import cbfs_util
22 import cmdline
23 import command
24 import control
25 import elf
26 import fdt
27 from etype import fdtmap
28 from etype import image_header
29 import fdt_util
30 import fmap_util
31 import test_util
32 import gzip
33 from image import Image
34 import state
35 import tools
36 import tout
37
38 # Contents of test files, corresponding to different entry types
39 U_BOOT_DATA           = b'1234'
40 U_BOOT_IMG_DATA       = b'img'
41 U_BOOT_SPL_DATA       = b'56780123456789abcde'
42 U_BOOT_TPL_DATA       = b'tpl'
43 BLOB_DATA             = b'89'
44 ME_DATA               = b'0abcd'
45 VGA_DATA              = b'vga'
46 U_BOOT_DTB_DATA       = b'udtb'
47 U_BOOT_SPL_DTB_DATA   = b'spldtb'
48 U_BOOT_TPL_DTB_DATA   = b'tpldtb'
49 X86_START16_DATA      = b'start16'
50 X86_START16_SPL_DATA  = b'start16spl'
51 X86_START16_TPL_DATA  = b'start16tpl'
52 X86_RESET16_DATA      = b'reset16'
53 X86_RESET16_SPL_DATA  = b'reset16spl'
54 X86_RESET16_TPL_DATA  = b'reset16tpl'
55 PPC_MPC85XX_BR_DATA   = b'ppcmpc85xxbr'
56 U_BOOT_NODTB_DATA     = b'nodtb with microcode pointer somewhere in here'
57 U_BOOT_SPL_NODTB_DATA = b'splnodtb with microcode pointer somewhere in here'
58 U_BOOT_TPL_NODTB_DATA = b'tplnodtb with microcode pointer somewhere in here'
59 FSP_DATA              = b'fsp'
60 CMC_DATA              = b'cmc'
61 VBT_DATA              = b'vbt'
62 MRC_DATA              = b'mrc'
63 TEXT_DATA             = 'text'
64 TEXT_DATA2            = 'text2'
65 TEXT_DATA3            = 'text3'
66 CROS_EC_RW_DATA       = b'ecrw'
67 GBB_DATA              = b'gbbd'
68 BMPBLK_DATA           = b'bmp'
69 VBLOCK_DATA           = b'vblk'
70 FILES_DATA            = (b"sorry I'm late\nOh, don't bother apologising, I'm " +
71                          b"sorry you're alive\n")
72 COMPRESS_DATA         = b'compress xxxxxxxxxxxxxxxxxxxxxx data'
73 REFCODE_DATA          = b'refcode'
74
75 # The expected size for the device tree in some tests
76 EXTRACT_DTB_SIZE = 0x3c9
77
78 # Properties expected to be in the device tree when update_dtb is used
79 BASE_DTB_PROPS = ['offset', 'size', 'image-pos']
80
81 # Extra properties expected to be in the device tree when allow-repack is used
82 REPACK_DTB_PROPS = ['orig-offset', 'orig-size']
83
84
85 class TestFunctional(unittest.TestCase):
86     """Functional tests for binman
87
88     Most of these use a sample .dts file to build an image and then check
89     that it looks correct. The sample files are in the test/ subdirectory
90     and are numbered.
91
92     For each entry type a very small test file is created using fixed
93     string contents. This makes it easy to test that things look right, and
94     debug problems.
95
96     In some cases a 'real' file must be used - these are also supplied in
97     the test/ diurectory.
98     """
99     @classmethod
100     def setUpClass(cls):
101         global entry
102         import entry
103
104         # Handle the case where argv[0] is 'python'
105         cls._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
106         cls._binman_pathname = os.path.join(cls._binman_dir, 'binman')
107
108         # Create a temporary directory for input files
109         cls._indir = tempfile.mkdtemp(prefix='binmant.')
110
111         # Create some test files
112         TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
113         TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
114         TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
115         TestFunctional._MakeInputFile('tpl/u-boot-tpl.bin', U_BOOT_TPL_DATA)
116         TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
117         TestFunctional._MakeInputFile('me.bin', ME_DATA)
118         TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
119         cls._ResetDtbs()
120
121         TestFunctional._MakeInputFile('u-boot-br.bin', PPC_MPC85XX_BR_DATA)
122
123         TestFunctional._MakeInputFile('u-boot-x86-start16.bin', X86_START16_DATA)
124         TestFunctional._MakeInputFile('spl/u-boot-x86-start16-spl.bin',
125                                       X86_START16_SPL_DATA)
126         TestFunctional._MakeInputFile('tpl/u-boot-x86-start16-tpl.bin',
127                                       X86_START16_TPL_DATA)
128
129         TestFunctional._MakeInputFile('u-boot-x86-reset16.bin',
130                                       X86_RESET16_DATA)
131         TestFunctional._MakeInputFile('spl/u-boot-x86-reset16-spl.bin',
132                                       X86_RESET16_SPL_DATA)
133         TestFunctional._MakeInputFile('tpl/u-boot-x86-reset16-tpl.bin',
134                                       X86_RESET16_TPL_DATA)
135
136         TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
137         TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
138                                       U_BOOT_SPL_NODTB_DATA)
139         TestFunctional._MakeInputFile('tpl/u-boot-tpl-nodtb.bin',
140                                       U_BOOT_TPL_NODTB_DATA)
141         TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
142         TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
143         TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
144         TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
145         TestFunctional._MakeInputFile('ecrw.bin', CROS_EC_RW_DATA)
146         TestFunctional._MakeInputDir('devkeys')
147         TestFunctional._MakeInputFile('bmpblk.bin', BMPBLK_DATA)
148         TestFunctional._MakeInputFile('refcode.bin', REFCODE_DATA)
149
150         # ELF file with a '_dt_ucode_base_size' symbol
151         with open(cls.TestFile('u_boot_ucode_ptr'), 'rb') as fd:
152             TestFunctional._MakeInputFile('u-boot', fd.read())
153
154         # Intel flash descriptor file
155         with open(cls.TestFile('descriptor.bin'), 'rb') as fd:
156             TestFunctional._MakeInputFile('descriptor.bin', fd.read())
157
158         shutil.copytree(cls.TestFile('files'),
159                         os.path.join(cls._indir, 'files'))
160
161         TestFunctional._MakeInputFile('compress', COMPRESS_DATA)
162
163         # Travis-CI may have an old lz4
164         cls.have_lz4 = True
165         try:
166             tools.Run('lz4', '--no-frame-crc', '-c',
167                       os.path.join(cls._indir, 'u-boot.bin'))
168         except:
169             cls.have_lz4 = False
170
171     @classmethod
172     def tearDownClass(cls):
173         """Remove the temporary input directory and its contents"""
174         if cls.preserve_indir:
175             print('Preserving input dir: %s' % cls._indir)
176         else:
177             if cls._indir:
178                 shutil.rmtree(cls._indir)
179         cls._indir = None
180
181     @classmethod
182     def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False,
183                         toolpath=None, verbosity=None):
184         """Accept arguments controlling test execution
185
186         Args:
187             preserve_indir: Preserve the shared input directory used by all
188                 tests in this class.
189             preserve_outdir: Preserve the output directories used by tests. Each
190                 test has its own, so this is normally only useful when running a
191                 single test.
192             toolpath: ist of paths to use for tools
193         """
194         cls.preserve_indir = preserve_indir
195         cls.preserve_outdirs = preserve_outdirs
196         cls.toolpath = toolpath
197         cls.verbosity = verbosity
198
199     def _CheckLz4(self):
200         if not self.have_lz4:
201             self.skipTest('lz4 --no-frame-crc not available')
202
203     def _CleanupOutputDir(self):
204         """Remove the temporary output directory"""
205         if self.preserve_outdirs:
206             print('Preserving output dir: %s' % tools.outdir)
207         else:
208             tools._FinaliseForTest()
209
210     def setUp(self):
211         # Enable this to turn on debugging output
212         # tout.Init(tout.DEBUG)
213         command.test_result = None
214
215     def tearDown(self):
216         """Remove the temporary output directory"""
217         self._CleanupOutputDir()
218
219     def _SetupImageInTmpdir(self):
220         """Set up the output image in a new temporary directory
221
222         This is used when an image has been generated in the output directory,
223         but we want to run binman again. This will create a new output
224         directory and fail to delete the original one.
225
226         This creates a new temporary directory, copies the image to it (with a
227         new name) and removes the old output directory.
228
229         Returns:
230             Tuple:
231                 Temporary directory to use
232                 New image filename
233         """
234         image_fname = tools.GetOutputFilename('image.bin')
235         tmpdir = tempfile.mkdtemp(prefix='binman.')
236         updated_fname = os.path.join(tmpdir, 'image-updated.bin')
237         tools.WriteFile(updated_fname, tools.ReadFile(image_fname))
238         self._CleanupOutputDir()
239         return tmpdir, updated_fname
240
241     @classmethod
242     def _ResetDtbs(cls):
243         TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
244         TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
245         TestFunctional._MakeInputFile('tpl/u-boot-tpl.dtb', U_BOOT_TPL_DTB_DATA)
246
247     def _RunBinman(self, *args, **kwargs):
248         """Run binman using the command line
249
250         Args:
251             Arguments to pass, as a list of strings
252             kwargs: Arguments to pass to Command.RunPipe()
253         """
254         result = command.RunPipe([[self._binman_pathname] + list(args)],
255                 capture=True, capture_stderr=True, raise_on_error=False)
256         if result.return_code and kwargs.get('raise_on_error', True):
257             raise Exception("Error running '%s': %s" % (' '.join(args),
258                             result.stdout + result.stderr))
259         return result
260
261     def _DoBinman(self, *argv):
262         """Run binman using directly (in the same process)
263
264         Args:
265             Arguments to pass, as a list of strings
266         Returns:
267             Return value (0 for success)
268         """
269         argv = list(argv)
270         args = cmdline.ParseArgs(argv)
271         args.pager = 'binman-invalid-pager'
272         args.build_dir = self._indir
273
274         # For testing, you can force an increase in verbosity here
275         # args.verbosity = tout.DEBUG
276         return control.Binman(args)
277
278     def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
279                     entry_args=None, images=None, use_real_dtb=False,
280                     verbosity=None):
281         """Run binman with a given test file
282
283         Args:
284             fname: Device-tree source filename to use (e.g. 005_simple.dts)
285             debug: True to enable debugging output
286             map: True to output map files for the images
287             update_dtb: Update the offset and size of each entry in the device
288                 tree before packing it into the image
289             entry_args: Dict of entry args to supply to binman
290                 key: arg name
291                 value: value of that arg
292             images: List of image names to build
293         """
294         args = []
295         if debug:
296             args.append('-D')
297         if verbosity is not None:
298             args.append('-v%d' % verbosity)
299         elif self.verbosity:
300             args.append('-v%d' % self.verbosity)
301         if self.toolpath:
302             for path in self.toolpath:
303                 args += ['--toolpath', path]
304         args += ['build', '-p', '-I', self._indir, '-d', self.TestFile(fname)]
305         if map:
306             args.append('-m')
307         if update_dtb:
308             args.append('-u')
309         if not use_real_dtb:
310             args.append('--fake-dtb')
311         if entry_args:
312             for arg, value in entry_args.items():
313                 args.append('-a%s=%s' % (arg, value))
314         if images:
315             for image in images:
316                 args += ['-i', image]
317         return self._DoBinman(*args)
318
319     def _SetupDtb(self, fname, outfile='u-boot.dtb'):
320         """Set up a new test device-tree file
321
322         The given file is compiled and set up as the device tree to be used
323         for ths test.
324
325         Args:
326             fname: Filename of .dts file to read
327             outfile: Output filename for compiled device-tree binary
328
329         Returns:
330             Contents of device-tree binary
331         """
332         tmpdir = tempfile.mkdtemp(prefix='binmant.')
333         dtb = fdt_util.EnsureCompiled(self.TestFile(fname), tmpdir)
334         with open(dtb, 'rb') as fd:
335             data = fd.read()
336             TestFunctional._MakeInputFile(outfile, data)
337         shutil.rmtree(tmpdir)
338         return data
339
340     def _GetDtbContentsForSplTpl(self, dtb_data, name):
341         """Create a version of the main DTB for SPL or SPL
342
343         For testing we don't actually have different versions of the DTB. With
344         U-Boot we normally run fdtgrep to remove unwanted nodes, but for tests
345         we don't normally have any unwanted nodes.
346
347         We still want the DTBs for SPL and TPL to be different though, since
348         otherwise it is confusing to know which one we are looking at. So add
349         an 'spl' or 'tpl' property to the top-level node.
350         """
351         dtb = fdt.Fdt.FromData(dtb_data)
352         dtb.Scan()
353         dtb.GetNode('/binman').AddZeroProp(name)
354         dtb.Sync(auto_resize=True)
355         dtb.Pack()
356         return dtb.GetContents()
357
358     def _DoReadFileDtb(self, fname, use_real_dtb=False, map=False,
359                        update_dtb=False, entry_args=None, reset_dtbs=True):
360         """Run binman and return the resulting image
361
362         This runs binman with a given test file and then reads the resulting
363         output file. It is a shortcut function since most tests need to do
364         these steps.
365
366         Raises an assertion failure if binman returns a non-zero exit code.
367
368         Args:
369             fname: Device-tree source filename to use (e.g. 005_simple.dts)
370             use_real_dtb: True to use the test file as the contents of
371                 the u-boot-dtb entry. Normally this is not needed and the
372                 test contents (the U_BOOT_DTB_DATA string) can be used.
373                 But in some test we need the real contents.
374             map: True to output map files for the images
375             update_dtb: Update the offset and size of each entry in the device
376                 tree before packing it into the image
377
378         Returns:
379             Tuple:
380                 Resulting image contents
381                 Device tree contents
382                 Map data showing contents of image (or None if none)
383                 Output device tree binary filename ('u-boot.dtb' path)
384         """
385         dtb_data = None
386         # Use the compiled test file as the u-boot-dtb input
387         if use_real_dtb:
388             dtb_data = self._SetupDtb(fname)
389
390             # For testing purposes, make a copy of the DT for SPL and TPL. Add
391             # a node indicating which it is, so aid verification.
392             for name in ['spl', 'tpl']:
393                 dtb_fname = '%s/u-boot-%s.dtb' % (name, name)
394                 outfile = os.path.join(self._indir, dtb_fname)
395                 TestFunctional._MakeInputFile(dtb_fname,
396                         self._GetDtbContentsForSplTpl(dtb_data, name))
397
398         try:
399             retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
400                     entry_args=entry_args, use_real_dtb=use_real_dtb)
401             self.assertEqual(0, retcode)
402             out_dtb_fname = tools.GetOutputFilename('u-boot.dtb.out')
403
404             # Find the (only) image, read it and return its contents
405             image = control.images['image']
406             image_fname = tools.GetOutputFilename('image.bin')
407             self.assertTrue(os.path.exists(image_fname))
408             if map:
409                 map_fname = tools.GetOutputFilename('image.map')
410                 with open(map_fname) as fd:
411                     map_data = fd.read()
412             else:
413                 map_data = None
414             with open(image_fname, 'rb') as fd:
415                 return fd.read(), dtb_data, map_data, out_dtb_fname
416         finally:
417             # Put the test file back
418             if reset_dtbs and use_real_dtb:
419                 self._ResetDtbs()
420
421     def _DoReadFileRealDtb(self, fname):
422         """Run binman with a real .dtb file and return the resulting data
423
424         Args:
425             fname: DT source filename to use (e.g. 082_fdt_update_all.dts)
426
427         Returns:
428             Resulting image contents
429         """
430         return self._DoReadFileDtb(fname, use_real_dtb=True, update_dtb=True)[0]
431
432     def _DoReadFile(self, fname, use_real_dtb=False):
433         """Helper function which discards the device-tree binary
434
435         Args:
436             fname: Device-tree source filename to use (e.g. 005_simple.dts)
437             use_real_dtb: True to use the test file as the contents of
438                 the u-boot-dtb entry. Normally this is not needed and the
439                 test contents (the U_BOOT_DTB_DATA string) can be used.
440                 But in some test we need the real contents.
441
442         Returns:
443             Resulting image contents
444         """
445         return self._DoReadFileDtb(fname, use_real_dtb)[0]
446
447     @classmethod
448     def _MakeInputFile(cls, fname, contents):
449         """Create a new test input file, creating directories as needed
450
451         Args:
452             fname: Filename to create
453             contents: File contents to write in to the file
454         Returns:
455             Full pathname of file created
456         """
457         pathname = os.path.join(cls._indir, fname)
458         dirname = os.path.dirname(pathname)
459         if dirname and not os.path.exists(dirname):
460             os.makedirs(dirname)
461         with open(pathname, 'wb') as fd:
462             fd.write(contents)
463         return pathname
464
465     @classmethod
466     def _MakeInputDir(cls, dirname):
467         """Create a new test input directory, creating directories as needed
468
469         Args:
470             dirname: Directory name to create
471
472         Returns:
473             Full pathname of directory created
474         """
475         pathname = os.path.join(cls._indir, dirname)
476         if not os.path.exists(pathname):
477             os.makedirs(pathname)
478         return pathname
479
480     @classmethod
481     def _SetupSplElf(cls, src_fname='bss_data'):
482         """Set up an ELF file with a '_dt_ucode_base_size' symbol
483
484         Args:
485             Filename of ELF file to use as SPL
486         """
487         with open(cls.TestFile(src_fname), 'rb') as fd:
488             TestFunctional._MakeInputFile('spl/u-boot-spl', fd.read())
489
490     @classmethod
491     def TestFile(cls, fname):
492         return os.path.join(cls._binman_dir, 'test', fname)
493
494     def AssertInList(self, grep_list, target):
495         """Assert that at least one of a list of things is in a target
496
497         Args:
498             grep_list: List of strings to check
499             target: Target string
500         """
501         for grep in grep_list:
502             if grep in target:
503                 return
504         self.fail("Error: '%s' not found in '%s'" % (grep_list, target))
505
506     def CheckNoGaps(self, entries):
507         """Check that all entries fit together without gaps
508
509         Args:
510             entries: List of entries to check
511         """
512         offset = 0
513         for entry in entries.values():
514             self.assertEqual(offset, entry.offset)
515             offset += entry.size
516
517     def GetFdtLen(self, dtb):
518         """Get the totalsize field from a device-tree binary
519
520         Args:
521             dtb: Device-tree binary contents
522
523         Returns:
524             Total size of device-tree binary, from the header
525         """
526         return struct.unpack('>L', dtb[4:8])[0]
527
528     def _GetPropTree(self, dtb, prop_names, prefix='/binman/'):
529         def AddNode(node, path):
530             if node.name != '/':
531                 path += '/' + node.name
532             for prop in node.props.values():
533                 if prop.name in prop_names:
534                     prop_path = path + ':' + prop.name
535                     tree[prop_path[len(prefix):]] = fdt_util.fdt32_to_cpu(
536                         prop.value)
537             for subnode in node.subnodes:
538                 AddNode(subnode, path)
539
540         tree = {}
541         AddNode(dtb.GetRoot(), '')
542         return tree
543
544     def testRun(self):
545         """Test a basic run with valid args"""
546         result = self._RunBinman('-h')
547
548     def testFullHelp(self):
549         """Test that the full help is displayed with -H"""
550         result = self._RunBinman('-H')
551         help_file = os.path.join(self._binman_dir, 'README')
552         # Remove possible extraneous strings
553         extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
554         gothelp = result.stdout.replace(extra, '')
555         self.assertEqual(len(gothelp), os.path.getsize(help_file))
556         self.assertEqual(0, len(result.stderr))
557         self.assertEqual(0, result.return_code)
558
559     def testFullHelpInternal(self):
560         """Test that the full help is displayed with -H"""
561         try:
562             command.test_result = command.CommandResult()
563             result = self._DoBinman('-H')
564             help_file = os.path.join(self._binman_dir, 'README')
565         finally:
566             command.test_result = None
567
568     def testHelp(self):
569         """Test that the basic help is displayed with -h"""
570         result = self._RunBinman('-h')
571         self.assertTrue(len(result.stdout) > 200)
572         self.assertEqual(0, len(result.stderr))
573         self.assertEqual(0, result.return_code)
574
575     def testBoard(self):
576         """Test that we can run it with a specific board"""
577         self._SetupDtb('005_simple.dts', 'sandbox/u-boot.dtb')
578         TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
579         result = self._DoBinman('build', '-b', 'sandbox')
580         self.assertEqual(0, result)
581
582     def testNeedBoard(self):
583         """Test that we get an error when no board ius supplied"""
584         with self.assertRaises(ValueError) as e:
585             result = self._DoBinman('build')
586         self.assertIn("Must provide a board to process (use -b <board>)",
587                 str(e.exception))
588
589     def testMissingDt(self):
590         """Test that an invalid device-tree file generates an error"""
591         with self.assertRaises(Exception) as e:
592             self._RunBinman('build', '-d', 'missing_file')
593         # We get one error from libfdt, and a different one from fdtget.
594         self.AssertInList(["Couldn't open blob from 'missing_file'",
595                            'No such file or directory'], str(e.exception))
596
597     def testBrokenDt(self):
598         """Test that an invalid device-tree source file generates an error
599
600         Since this is a source file it should be compiled and the error
601         will come from the device-tree compiler (dtc).
602         """
603         with self.assertRaises(Exception) as e:
604             self._RunBinman('build', '-d', self.TestFile('001_invalid.dts'))
605         self.assertIn("FATAL ERROR: Unable to parse input tree",
606                 str(e.exception))
607
608     def testMissingNode(self):
609         """Test that a device tree without a 'binman' node generates an error"""
610         with self.assertRaises(Exception) as e:
611             self._DoBinman('build', '-d', self.TestFile('002_missing_node.dts'))
612         self.assertIn("does not have a 'binman' node", str(e.exception))
613
614     def testEmpty(self):
615         """Test that an empty binman node works OK (i.e. does nothing)"""
616         result = self._RunBinman('build', '-d', self.TestFile('003_empty.dts'))
617         self.assertEqual(0, len(result.stderr))
618         self.assertEqual(0, result.return_code)
619
620     def testInvalidEntry(self):
621         """Test that an invalid entry is flagged"""
622         with self.assertRaises(Exception) as e:
623             result = self._RunBinman('build', '-d',
624                                      self.TestFile('004_invalid_entry.dts'))
625         self.assertIn("Unknown entry type 'not-a-valid-type' in node "
626                 "'/binman/not-a-valid-type'", str(e.exception))
627
628     def testSimple(self):
629         """Test a simple binman with a single file"""
630         data = self._DoReadFile('005_simple.dts')
631         self.assertEqual(U_BOOT_DATA, data)
632
633     def testSimpleDebug(self):
634         """Test a simple binman run with debugging enabled"""
635         self._DoTestFile('005_simple.dts', debug=True)
636
637     def testDual(self):
638         """Test that we can handle creating two images
639
640         This also tests image padding.
641         """
642         retcode = self._DoTestFile('006_dual_image.dts')
643         self.assertEqual(0, retcode)
644
645         image = control.images['image1']
646         self.assertEqual(len(U_BOOT_DATA), image.size)
647         fname = tools.GetOutputFilename('image1.bin')
648         self.assertTrue(os.path.exists(fname))
649         with open(fname, 'rb') as fd:
650             data = fd.read()
651             self.assertEqual(U_BOOT_DATA, data)
652
653         image = control.images['image2']
654         self.assertEqual(3 + len(U_BOOT_DATA) + 5, image.size)
655         fname = tools.GetOutputFilename('image2.bin')
656         self.assertTrue(os.path.exists(fname))
657         with open(fname, 'rb') as fd:
658             data = fd.read()
659             self.assertEqual(U_BOOT_DATA, data[3:7])
660             self.assertEqual(tools.GetBytes(0, 3), data[:3])
661             self.assertEqual(tools.GetBytes(0, 5), data[7:])
662
663     def testBadAlign(self):
664         """Test that an invalid alignment value is detected"""
665         with self.assertRaises(ValueError) as e:
666             self._DoTestFile('007_bad_align.dts')
667         self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
668                       "of two", str(e.exception))
669
670     def testPackSimple(self):
671         """Test that packing works as expected"""
672         retcode = self._DoTestFile('008_pack.dts')
673         self.assertEqual(0, retcode)
674         self.assertIn('image', control.images)
675         image = control.images['image']
676         entries = image.GetEntries()
677         self.assertEqual(5, len(entries))
678
679         # First u-boot
680         self.assertIn('u-boot', entries)
681         entry = entries['u-boot']
682         self.assertEqual(0, entry.offset)
683         self.assertEqual(len(U_BOOT_DATA), entry.size)
684
685         # Second u-boot, aligned to 16-byte boundary
686         self.assertIn('u-boot-align', entries)
687         entry = entries['u-boot-align']
688         self.assertEqual(16, entry.offset)
689         self.assertEqual(len(U_BOOT_DATA), entry.size)
690
691         # Third u-boot, size 23 bytes
692         self.assertIn('u-boot-size', entries)
693         entry = entries['u-boot-size']
694         self.assertEqual(20, entry.offset)
695         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
696         self.assertEqual(23, entry.size)
697
698         # Fourth u-boot, placed immediate after the above
699         self.assertIn('u-boot-next', entries)
700         entry = entries['u-boot-next']
701         self.assertEqual(43, entry.offset)
702         self.assertEqual(len(U_BOOT_DATA), entry.size)
703
704         # Fifth u-boot, placed at a fixed offset
705         self.assertIn('u-boot-fixed', entries)
706         entry = entries['u-boot-fixed']
707         self.assertEqual(61, entry.offset)
708         self.assertEqual(len(U_BOOT_DATA), entry.size)
709
710         self.assertEqual(65, image.size)
711
712     def testPackExtra(self):
713         """Test that extra packing feature works as expected"""
714         retcode = self._DoTestFile('009_pack_extra.dts')
715
716         self.assertEqual(0, retcode)
717         self.assertIn('image', control.images)
718         image = control.images['image']
719         entries = image.GetEntries()
720         self.assertEqual(5, len(entries))
721
722         # First u-boot with padding before and after
723         self.assertIn('u-boot', entries)
724         entry = entries['u-boot']
725         self.assertEqual(0, entry.offset)
726         self.assertEqual(3, entry.pad_before)
727         self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
728
729         # Second u-boot has an aligned size, but it has no effect
730         self.assertIn('u-boot-align-size-nop', entries)
731         entry = entries['u-boot-align-size-nop']
732         self.assertEqual(12, entry.offset)
733         self.assertEqual(4, entry.size)
734
735         # Third u-boot has an aligned size too
736         self.assertIn('u-boot-align-size', entries)
737         entry = entries['u-boot-align-size']
738         self.assertEqual(16, entry.offset)
739         self.assertEqual(32, entry.size)
740
741         # Fourth u-boot has an aligned end
742         self.assertIn('u-boot-align-end', entries)
743         entry = entries['u-boot-align-end']
744         self.assertEqual(48, entry.offset)
745         self.assertEqual(16, entry.size)
746
747         # Fifth u-boot immediately afterwards
748         self.assertIn('u-boot-align-both', entries)
749         entry = entries['u-boot-align-both']
750         self.assertEqual(64, entry.offset)
751         self.assertEqual(64, entry.size)
752
753         self.CheckNoGaps(entries)
754         self.assertEqual(128, image.size)
755
756     def testPackAlignPowerOf2(self):
757         """Test that invalid entry alignment is detected"""
758         with self.assertRaises(ValueError) as e:
759             self._DoTestFile('010_pack_align_power2.dts')
760         self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
761                       "of two", str(e.exception))
762
763     def testPackAlignSizePowerOf2(self):
764         """Test that invalid entry size alignment is detected"""
765         with self.assertRaises(ValueError) as e:
766             self._DoTestFile('011_pack_align_size_power2.dts')
767         self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
768                       "power of two", str(e.exception))
769
770     def testPackInvalidAlign(self):
771         """Test detection of an offset that does not match its alignment"""
772         with self.assertRaises(ValueError) as e:
773             self._DoTestFile('012_pack_inv_align.dts')
774         self.assertIn("Node '/binman/u-boot': Offset 0x5 (5) does not match "
775                       "align 0x4 (4)", str(e.exception))
776
777     def testPackInvalidSizeAlign(self):
778         """Test that invalid entry size alignment is detected"""
779         with self.assertRaises(ValueError) as e:
780             self._DoTestFile('013_pack_inv_size_align.dts')
781         self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
782                       "align-size 0x4 (4)", str(e.exception))
783
784     def testPackOverlap(self):
785         """Test that overlapping regions are detected"""
786         with self.assertRaises(ValueError) as e:
787             self._DoTestFile('014_pack_overlap.dts')
788         self.assertIn("Node '/binman/u-boot-align': Offset 0x3 (3) overlaps "
789                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
790                       str(e.exception))
791
792     def testPackEntryOverflow(self):
793         """Test that entries that overflow their size are detected"""
794         with self.assertRaises(ValueError) as e:
795             self._DoTestFile('015_pack_overflow.dts')
796         self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
797                       "but entry size is 0x3 (3)", str(e.exception))
798
799     def testPackImageOverflow(self):
800         """Test that entries which overflow the image size are detected"""
801         with self.assertRaises(ValueError) as e:
802             self._DoTestFile('016_pack_image_overflow.dts')
803         self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
804                       "size 0x3 (3)", str(e.exception))
805
806     def testPackImageSize(self):
807         """Test that the image size can be set"""
808         retcode = self._DoTestFile('017_pack_image_size.dts')
809         self.assertEqual(0, retcode)
810         self.assertIn('image', control.images)
811         image = control.images['image']
812         self.assertEqual(7, image.size)
813
814     def testPackImageSizeAlign(self):
815         """Test that image size alignemnt works as expected"""
816         retcode = self._DoTestFile('018_pack_image_align.dts')
817         self.assertEqual(0, retcode)
818         self.assertIn('image', control.images)
819         image = control.images['image']
820         self.assertEqual(16, image.size)
821
822     def testPackInvalidImageAlign(self):
823         """Test that invalid image alignment is detected"""
824         with self.assertRaises(ValueError) as e:
825             self._DoTestFile('019_pack_inv_image_align.dts')
826         self.assertIn("Section '/binman': Size 0x7 (7) does not match "
827                       "align-size 0x8 (8)", str(e.exception))
828
829     def testPackAlignPowerOf2(self):
830         """Test that invalid image alignment is detected"""
831         with self.assertRaises(ValueError) as e:
832             self._DoTestFile('020_pack_inv_image_align_power2.dts')
833         self.assertIn("Image '/binman': Alignment size 131 must be a power of "
834                       "two", str(e.exception))
835
836     def testImagePadByte(self):
837         """Test that the image pad byte can be specified"""
838         self._SetupSplElf()
839         data = self._DoReadFile('021_image_pad.dts')
840         self.assertEqual(U_BOOT_SPL_DATA + tools.GetBytes(0xff, 1) +
841                          U_BOOT_DATA, data)
842
843     def testImageName(self):
844         """Test that image files can be named"""
845         retcode = self._DoTestFile('022_image_name.dts')
846         self.assertEqual(0, retcode)
847         image = control.images['image1']
848         fname = tools.GetOutputFilename('test-name')
849         self.assertTrue(os.path.exists(fname))
850
851         image = control.images['image2']
852         fname = tools.GetOutputFilename('test-name.xx')
853         self.assertTrue(os.path.exists(fname))
854
855     def testBlobFilename(self):
856         """Test that generic blobs can be provided by filename"""
857         data = self._DoReadFile('023_blob.dts')
858         self.assertEqual(BLOB_DATA, data)
859
860     def testPackSorted(self):
861         """Test that entries can be sorted"""
862         self._SetupSplElf()
863         data = self._DoReadFile('024_sorted.dts')
864         self.assertEqual(tools.GetBytes(0, 1) + U_BOOT_SPL_DATA +
865                          tools.GetBytes(0, 2) + U_BOOT_DATA, data)
866
867     def testPackZeroOffset(self):
868         """Test that an entry at offset 0 is not given a new offset"""
869         with self.assertRaises(ValueError) as e:
870             self._DoTestFile('025_pack_zero_size.dts')
871         self.assertIn("Node '/binman/u-boot-spl': Offset 0x0 (0) overlaps "
872                       "with previous entry '/binman/u-boot' ending at 0x4 (4)",
873                       str(e.exception))
874
875     def testPackUbootDtb(self):
876         """Test that a device tree can be added to U-Boot"""
877         data = self._DoReadFile('026_pack_u_boot_dtb.dts')
878         self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
879
880     def testPackX86RomNoSize(self):
881         """Test that the end-at-4gb property requires a size property"""
882         with self.assertRaises(ValueError) as e:
883             self._DoTestFile('027_pack_4gb_no_size.dts')
884         self.assertIn("Image '/binman': Section size must be provided when "
885                       "using end-at-4gb", str(e.exception))
886
887     def test4gbAndSkipAtStartTogether(self):
888         """Test that the end-at-4gb and skip-at-size property can't be used
889         together"""
890         with self.assertRaises(ValueError) as e:
891             self._DoTestFile('80_4gb_and_skip_at_start_together.dts')
892         self.assertIn("Image '/binman': Provide either 'end-at-4gb' or "
893                       "'skip-at-start'", str(e.exception))
894
895     def testPackX86RomOutside(self):
896         """Test that the end-at-4gb property checks for offset boundaries"""
897         with self.assertRaises(ValueError) as e:
898             self._DoTestFile('028_pack_4gb_outside.dts')
899         self.assertIn("Node '/binman/u-boot': Offset 0x0 (0) is outside "
900                       "the section starting at 0xffffffe0 (4294967264)",
901                       str(e.exception))
902
903     def testPackX86Rom(self):
904         """Test that a basic x86 ROM can be created"""
905         self._SetupSplElf()
906         data = self._DoReadFile('029_x86-rom.dts')
907         self.assertEqual(U_BOOT_DATA + tools.GetBytes(0, 7) + U_BOOT_SPL_DATA +
908                          tools.GetBytes(0, 2), data)
909
910     def testPackX86RomMeNoDesc(self):
911         """Test that an invalid Intel descriptor entry is detected"""
912         TestFunctional._MakeInputFile('descriptor.bin', b'')
913         with self.assertRaises(ValueError) as e:
914             self._DoTestFile('031_x86-rom-me.dts')
915         self.assertIn("Node '/binman/intel-descriptor': Cannot find Intel Flash Descriptor (FD) signature",
916                       str(e.exception))
917
918     def testPackX86RomBadDesc(self):
919         """Test that the Intel requires a descriptor entry"""
920         with self.assertRaises(ValueError) as e:
921             self._DoTestFile('030_x86-rom-me-no-desc.dts')
922         self.assertIn("Node '/binman/intel-me': No offset set with "
923                       "offset-unset: should another entry provide this correct "
924                       "offset?", str(e.exception))
925
926     def testPackX86RomMe(self):
927         """Test that an x86 ROM with an ME region can be created"""
928         data = self._DoReadFile('031_x86-rom-me.dts')
929         expected_desc = tools.ReadFile(self.TestFile('descriptor.bin'))
930         if data[:0x1000] != expected_desc:
931             self.fail('Expected descriptor binary at start of image')
932         self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
933
934     def testPackVga(self):
935         """Test that an image with a VGA binary can be created"""
936         data = self._DoReadFile('032_intel-vga.dts')
937         self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
938
939     def testPackStart16(self):
940         """Test that an image with an x86 start16 region can be created"""
941         data = self._DoReadFile('033_x86-start16.dts')
942         self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
943
944     def testPackPowerpcMpc85xxBootpgResetvec(self):
945         """Test that an image with powerpc-mpc85xx-bootpg-resetvec can be
946         created"""
947         data = self._DoReadFile('81_powerpc_mpc85xx_bootpg_resetvec.dts')
948         self.assertEqual(PPC_MPC85XX_BR_DATA, data[:len(PPC_MPC85XX_BR_DATA)])
949
950     def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False):
951         """Handle running a test for insertion of microcode
952
953         Args:
954             dts_fname: Name of test .dts file
955             nodtb_data: Data that we expect in the first section
956             ucode_second: True if the microsecond entry is second instead of
957                 third
958
959         Returns:
960             Tuple:
961                 Contents of first region (U-Boot or SPL)
962                 Offset and size components of microcode pointer, as inserted
963                     in the above (two 4-byte words)
964         """
965         data = self._DoReadFile(dts_fname, True)
966
967         # Now check the device tree has no microcode
968         if ucode_second:
969             ucode_content = data[len(nodtb_data):]
970             ucode_pos = len(nodtb_data)
971             dtb_with_ucode = ucode_content[16:]
972             fdt_len = self.GetFdtLen(dtb_with_ucode)
973         else:
974             dtb_with_ucode = data[len(nodtb_data):]
975             fdt_len = self.GetFdtLen(dtb_with_ucode)
976             ucode_content = dtb_with_ucode[fdt_len:]
977             ucode_pos = len(nodtb_data) + fdt_len
978         fname = tools.GetOutputFilename('test.dtb')
979         with open(fname, 'wb') as fd:
980             fd.write(dtb_with_ucode)
981         dtb = fdt.FdtScan(fname)
982         ucode = dtb.GetNode('/microcode')
983         self.assertTrue(ucode)
984         for node in ucode.subnodes:
985             self.assertFalse(node.props.get('data'))
986
987         # Check that the microcode appears immediately after the Fdt
988         # This matches the concatenation of the data properties in
989         # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
990         ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
991                                  0x78235609)
992         self.assertEqual(ucode_data, ucode_content[:len(ucode_data)])
993
994         # Check that the microcode pointer was inserted. It should match the
995         # expected offset and size
996         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
997                                    len(ucode_data))
998         u_boot = data[:len(nodtb_data)]
999         return u_boot, pos_and_size
1000
1001     def testPackUbootMicrocode(self):
1002         """Test that x86 microcode can be handled correctly
1003
1004         We expect to see the following in the image, in order:
1005             u-boot-nodtb.bin with a microcode pointer inserted at the correct
1006                 place
1007             u-boot.dtb with the microcode removed
1008             the microcode
1009         """
1010         first, pos_and_size = self._RunMicrocodeTest('034_x86_ucode.dts',
1011                                                      U_BOOT_NODTB_DATA)
1012         self.assertEqual(b'nodtb with microcode' + pos_and_size +
1013                          b' somewhere in here', first)
1014
1015     def _RunPackUbootSingleMicrocode(self):
1016         """Test that x86 microcode can be handled correctly
1017
1018         We expect to see the following in the image, in order:
1019             u-boot-nodtb.bin with a microcode pointer inserted at the correct
1020                 place
1021             u-boot.dtb with the microcode
1022             an empty microcode region
1023         """
1024         # We need the libfdt library to run this test since only that allows
1025         # finding the offset of a property. This is required by
1026         # Entry_u_boot_dtb_with_ucode.ObtainContents().
1027         data = self._DoReadFile('035_x86_single_ucode.dts', True)
1028
1029         second = data[len(U_BOOT_NODTB_DATA):]
1030
1031         fdt_len = self.GetFdtLen(second)
1032         third = second[fdt_len:]
1033         second = second[:fdt_len]
1034
1035         ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
1036         self.assertIn(ucode_data, second)
1037         ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
1038
1039         # Check that the microcode pointer was inserted. It should match the
1040         # expected offset and size
1041         pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
1042                                    len(ucode_data))
1043         first = data[:len(U_BOOT_NODTB_DATA)]
1044         self.assertEqual(b'nodtb with microcode' + pos_and_size +
1045                          b' somewhere in here', first)
1046
1047     def testPackUbootSingleMicrocode(self):
1048         """Test that x86 microcode can be handled correctly with fdt_normal.
1049         """
1050         self._RunPackUbootSingleMicrocode()
1051
1052     def testUBootImg(self):
1053         """Test that u-boot.img can be put in a file"""
1054         data = self._DoReadFile('036_u_boot_img.dts')
1055         self.assertEqual(U_BOOT_IMG_DATA, data)
1056
1057     def testNoMicrocode(self):
1058         """Test that a missing microcode region is detected"""
1059         with self.assertRaises(ValueError) as e:
1060             self._DoReadFile('037_x86_no_ucode.dts', True)
1061         self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
1062                       "node found in ", str(e.exception))
1063
1064     def testMicrocodeWithoutNode(self):
1065         """Test that a missing u-boot-dtb-with-ucode node is detected"""
1066         with self.assertRaises(ValueError) as e:
1067             self._DoReadFile('038_x86_ucode_missing_node.dts', True)
1068         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
1069                 "microcode region u-boot-dtb-with-ucode", str(e.exception))
1070
1071     def testMicrocodeWithoutNode2(self):
1072         """Test that a missing u-boot-ucode node is detected"""
1073         with self.assertRaises(ValueError) as e:
1074             self._DoReadFile('039_x86_ucode_missing_node2.dts', True)
1075         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
1076             "microcode region u-boot-ucode", str(e.exception))
1077
1078     def testMicrocodeWithoutPtrInElf(self):
1079         """Test that a U-Boot binary without the microcode symbol is detected"""
1080         # ELF file without a '_dt_ucode_base_size' symbol
1081         try:
1082             with open(self.TestFile('u_boot_no_ucode_ptr'), 'rb') as fd:
1083                 TestFunctional._MakeInputFile('u-boot', fd.read())
1084
1085             with self.assertRaises(ValueError) as e:
1086                 self._RunPackUbootSingleMicrocode()
1087             self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
1088                     "_dt_ucode_base_size symbol in u-boot", str(e.exception))
1089
1090         finally:
1091             # Put the original file back
1092             with open(self.TestFile('u_boot_ucode_ptr'), 'rb') as fd:
1093                 TestFunctional._MakeInputFile('u-boot', fd.read())
1094
1095     def testMicrocodeNotInImage(self):
1096         """Test that microcode must be placed within the image"""
1097         with self.assertRaises(ValueError) as e:
1098             self._DoReadFile('040_x86_ucode_not_in_image.dts', True)
1099         self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
1100                 "pointer _dt_ucode_base_size at fffffe14 is outside the "
1101                 "section ranging from 00000000 to 0000002e", str(e.exception))
1102
1103     def testWithoutMicrocode(self):
1104         """Test that we can cope with an image without microcode (e.g. qemu)"""
1105         with open(self.TestFile('u_boot_no_ucode_ptr'), 'rb') as fd:
1106             TestFunctional._MakeInputFile('u-boot', fd.read())
1107         data, dtb, _, _ = self._DoReadFileDtb('044_x86_optional_ucode.dts', True)
1108
1109         # Now check the device tree has no microcode
1110         self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
1111         second = data[len(U_BOOT_NODTB_DATA):]
1112
1113         fdt_len = self.GetFdtLen(second)
1114         self.assertEqual(dtb, second[:fdt_len])
1115
1116         used_len = len(U_BOOT_NODTB_DATA) + fdt_len
1117         third = data[used_len:]
1118         self.assertEqual(tools.GetBytes(0, 0x200 - used_len), third)
1119
1120     def testUnknownPosSize(self):
1121         """Test that microcode must be placed within the image"""
1122         with self.assertRaises(ValueError) as e:
1123             self._DoReadFile('041_unknown_pos_size.dts', True)
1124         self.assertIn("Section '/binman': Unable to set offset/size for unknown "
1125                 "entry 'invalid-entry'", str(e.exception))
1126
1127     def testPackFsp(self):
1128         """Test that an image with a FSP binary can be created"""
1129         data = self._DoReadFile('042_intel-fsp.dts')
1130         self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
1131
1132     def testPackCmc(self):
1133         """Test that an image with a CMC binary can be created"""
1134         data = self._DoReadFile('043_intel-cmc.dts')
1135         self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
1136
1137     def testPackVbt(self):
1138         """Test that an image with a VBT binary can be created"""
1139         data = self._DoReadFile('046_intel-vbt.dts')
1140         self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
1141
1142     def testSplBssPad(self):
1143         """Test that we can pad SPL's BSS with zeros"""
1144         # ELF file with a '__bss_size' symbol
1145         self._SetupSplElf()
1146         data = self._DoReadFile('047_spl_bss_pad.dts')
1147         self.assertEqual(U_BOOT_SPL_DATA + tools.GetBytes(0, 10) + U_BOOT_DATA,
1148                          data)
1149
1150     def testSplBssPadMissing(self):
1151         """Test that a missing symbol is detected"""
1152         self._SetupSplElf('u_boot_ucode_ptr')
1153         with self.assertRaises(ValueError) as e:
1154             self._DoReadFile('047_spl_bss_pad.dts')
1155         self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
1156                       str(e.exception))
1157
1158     def testPackStart16Spl(self):
1159         """Test that an image with an x86 start16 SPL region can be created"""
1160         data = self._DoReadFile('048_x86-start16-spl.dts')
1161         self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
1162
1163     def _PackUbootSplMicrocode(self, dts, ucode_second=False):
1164         """Helper function for microcode tests
1165
1166         We expect to see the following in the image, in order:
1167             u-boot-spl-nodtb.bin with a microcode pointer inserted at the
1168                 correct place
1169             u-boot.dtb with the microcode removed
1170             the microcode
1171
1172         Args:
1173             dts: Device tree file to use for test
1174             ucode_second: True if the microsecond entry is second instead of
1175                 third
1176         """
1177         self._SetupSplElf('u_boot_ucode_ptr')
1178         first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA,
1179                                                      ucode_second=ucode_second)
1180         self.assertEqual(b'splnodtb with microc' + pos_and_size +
1181                          b'ter somewhere in here', first)
1182
1183     def testPackUbootSplMicrocode(self):
1184         """Test that x86 microcode can be handled correctly in SPL"""
1185         self._PackUbootSplMicrocode('049_x86_ucode_spl.dts')
1186
1187     def testPackUbootSplMicrocodeReorder(self):
1188         """Test that order doesn't matter for microcode entries
1189
1190         This is the same as testPackUbootSplMicrocode but when we process the
1191         u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode
1192         entry, so we reply on binman to try later.
1193         """
1194         self._PackUbootSplMicrocode('058_x86_ucode_spl_needs_retry.dts',
1195                                     ucode_second=True)
1196
1197     def testPackMrc(self):
1198         """Test that an image with an MRC binary can be created"""
1199         data = self._DoReadFile('050_intel_mrc.dts')
1200         self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
1201
1202     def testSplDtb(self):
1203         """Test that an image with spl/u-boot-spl.dtb can be created"""
1204         data = self._DoReadFile('051_u_boot_spl_dtb.dts')
1205         self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
1206
1207     def testSplNoDtb(self):
1208         """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
1209         data = self._DoReadFile('052_u_boot_spl_nodtb.dts')
1210         self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
1211
1212     def testSymbols(self):
1213         """Test binman can assign symbols embedded in U-Boot"""
1214         elf_fname = self.TestFile('u_boot_binman_syms')
1215         syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
1216         addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
1217         self.assertEqual(syms['_binman_u_boot_spl_prop_offset'].address, addr)
1218
1219         self._SetupSplElf('u_boot_binman_syms')
1220         data = self._DoReadFile('053_symbols.dts')
1221         sym_values = struct.pack('<LQL', 0x24 + 0, 0x24 + 24, 0x24 + 20)
1222         expected = (sym_values + U_BOOT_SPL_DATA[16:] +
1223                     tools.GetBytes(0xff, 1) + U_BOOT_DATA + sym_values +
1224                     U_BOOT_SPL_DATA[16:])
1225         self.assertEqual(expected, data)
1226
1227     def testPackUnitAddress(self):
1228         """Test that we support multiple binaries with the same name"""
1229         data = self._DoReadFile('054_unit_address.dts')
1230         self.assertEqual(U_BOOT_DATA + U_BOOT_DATA, data)
1231
1232     def testSections(self):
1233         """Basic test of sections"""
1234         data = self._DoReadFile('055_sections.dts')
1235         expected = (U_BOOT_DATA + tools.GetBytes(ord('!'), 12) +
1236                     U_BOOT_DATA + tools.GetBytes(ord('a'), 12) +
1237                     U_BOOT_DATA + tools.GetBytes(ord('&'), 4))
1238         self.assertEqual(expected, data)
1239
1240     def testMap(self):
1241         """Tests outputting a map of the images"""
1242         _, _, map_data, _ = self._DoReadFileDtb('055_sections.dts', map=True)
1243         self.assertEqual('''ImagePos    Offset      Size  Name
1244 00000000  00000000  00000028  main-section
1245 00000000   00000000  00000010  section@0
1246 00000000    00000000  00000004  u-boot
1247 00000010   00000010  00000010  section@1
1248 00000010    00000000  00000004  u-boot
1249 00000020   00000020  00000004  section@2
1250 00000020    00000000  00000004  u-boot
1251 ''', map_data)
1252
1253     def testNamePrefix(self):
1254         """Tests that name prefixes are used"""
1255         _, _, map_data, _ = self._DoReadFileDtb('056_name_prefix.dts', map=True)
1256         self.assertEqual('''ImagePos    Offset      Size  Name
1257 00000000  00000000  00000028  main-section
1258 00000000   00000000  00000010  section@0
1259 00000000    00000000  00000004  ro-u-boot
1260 00000010   00000010  00000010  section@1
1261 00000010    00000000  00000004  rw-u-boot
1262 ''', map_data)
1263
1264     def testUnknownContents(self):
1265         """Test that obtaining the contents works as expected"""
1266         with self.assertRaises(ValueError) as e:
1267             self._DoReadFile('057_unknown_contents.dts', True)
1268         self.assertIn("Image '/binman': Internal error: Could not complete "
1269                 "processing of contents: remaining [<_testing.Entry__testing ",
1270                 str(e.exception))
1271
1272     def testBadChangeSize(self):
1273         """Test that trying to change the size of an entry fails"""
1274         try:
1275             state.SetAllowEntryExpansion(False)
1276             with self.assertRaises(ValueError) as e:
1277                 self._DoReadFile('059_change_size.dts', True)
1278             self.assertIn("Node '/binman/_testing': Cannot update entry size from 2 to 3",
1279                           str(e.exception))
1280         finally:
1281             state.SetAllowEntryExpansion(True)
1282
1283     def testUpdateFdt(self):
1284         """Test that we can update the device tree with offset/size info"""
1285         _, _, _, out_dtb_fname = self._DoReadFileDtb('060_fdt_update.dts',
1286                                                      update_dtb=True)
1287         dtb = fdt.Fdt(out_dtb_fname)
1288         dtb.Scan()
1289         props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS)
1290         self.assertEqual({
1291             'image-pos': 0,
1292             'offset': 0,
1293             '_testing:offset': 32,
1294             '_testing:size': 2,
1295             '_testing:image-pos': 32,
1296             'section@0/u-boot:offset': 0,
1297             'section@0/u-boot:size': len(U_BOOT_DATA),
1298             'section@0/u-boot:image-pos': 0,
1299             'section@0:offset': 0,
1300             'section@0:size': 16,
1301             'section@0:image-pos': 0,
1302
1303             'section@1/u-boot:offset': 0,
1304             'section@1/u-boot:size': len(U_BOOT_DATA),
1305             'section@1/u-boot:image-pos': 16,
1306             'section@1:offset': 16,
1307             'section@1:size': 16,
1308             'section@1:image-pos': 16,
1309             'size': 40
1310         }, props)
1311
1312     def testUpdateFdtBad(self):
1313         """Test that we detect when ProcessFdt never completes"""
1314         with self.assertRaises(ValueError) as e:
1315             self._DoReadFileDtb('061_fdt_update_bad.dts', update_dtb=True)
1316         self.assertIn('Could not complete processing of Fdt: remaining '
1317                       '[<_testing.Entry__testing', str(e.exception))
1318
1319     def testEntryArgs(self):
1320         """Test passing arguments to entries from the command line"""
1321         entry_args = {
1322             'test-str-arg': 'test1',
1323             'test-int-arg': '456',
1324         }
1325         self._DoReadFileDtb('062_entry_args.dts', entry_args=entry_args)
1326         self.assertIn('image', control.images)
1327         entry = control.images['image'].GetEntries()['_testing']
1328         self.assertEqual('test0', entry.test_str_fdt)
1329         self.assertEqual('test1', entry.test_str_arg)
1330         self.assertEqual(123, entry.test_int_fdt)
1331         self.assertEqual(456, entry.test_int_arg)
1332
1333     def testEntryArgsMissing(self):
1334         """Test missing arguments and properties"""
1335         entry_args = {
1336             'test-int-arg': '456',
1337         }
1338         self._DoReadFileDtb('063_entry_args_missing.dts', entry_args=entry_args)
1339         entry = control.images['image'].GetEntries()['_testing']
1340         self.assertEqual('test0', entry.test_str_fdt)
1341         self.assertEqual(None, entry.test_str_arg)
1342         self.assertEqual(None, entry.test_int_fdt)
1343         self.assertEqual(456, entry.test_int_arg)
1344
1345     def testEntryArgsRequired(self):
1346         """Test missing arguments and properties"""
1347         entry_args = {
1348             'test-int-arg': '456',
1349         }
1350         with self.assertRaises(ValueError) as e:
1351             self._DoReadFileDtb('064_entry_args_required.dts')
1352         self.assertIn("Node '/binman/_testing': Missing required "
1353             'properties/entry args: test-str-arg, test-int-fdt, test-int-arg',
1354             str(e.exception))
1355
1356     def testEntryArgsInvalidFormat(self):
1357         """Test that an invalid entry-argument format is detected"""
1358         args = ['build', '-d', self.TestFile('064_entry_args_required.dts'),
1359                 '-ano-value']
1360         with self.assertRaises(ValueError) as e:
1361             self._DoBinman(*args)
1362         self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
1363
1364     def testEntryArgsInvalidInteger(self):
1365         """Test that an invalid entry-argument integer is detected"""
1366         entry_args = {
1367             'test-int-arg': 'abc',
1368         }
1369         with self.assertRaises(ValueError) as e:
1370             self._DoReadFileDtb('062_entry_args.dts', entry_args=entry_args)
1371         self.assertIn("Node '/binman/_testing': Cannot convert entry arg "
1372                       "'test-int-arg' (value 'abc') to integer",
1373             str(e.exception))
1374
1375     def testEntryArgsInvalidDatatype(self):
1376         """Test that an invalid entry-argument datatype is detected
1377
1378         This test could be written in entry_test.py except that it needs
1379         access to control.entry_args, which seems more than that module should
1380         be able to see.
1381         """
1382         entry_args = {
1383             'test-bad-datatype-arg': '12',
1384         }
1385         with self.assertRaises(ValueError) as e:
1386             self._DoReadFileDtb('065_entry_args_unknown_datatype.dts',
1387                                 entry_args=entry_args)
1388         self.assertIn('GetArg() internal error: Unknown data type ',
1389                       str(e.exception))
1390
1391     def testText(self):
1392         """Test for a text entry type"""
1393         entry_args = {
1394             'test-id': TEXT_DATA,
1395             'test-id2': TEXT_DATA2,
1396             'test-id3': TEXT_DATA3,
1397         }
1398         data, _, _, _ = self._DoReadFileDtb('066_text.dts',
1399                                             entry_args=entry_args)
1400         expected = (tools.ToBytes(TEXT_DATA) +
1401                     tools.GetBytes(0, 8 - len(TEXT_DATA)) +
1402                     tools.ToBytes(TEXT_DATA2) + tools.ToBytes(TEXT_DATA3) +
1403                     b'some text' + b'more text')
1404         self.assertEqual(expected, data)
1405
1406     def testEntryDocs(self):
1407         """Test for creation of entry documentation"""
1408         with test_util.capture_sys_output() as (stdout, stderr):
1409             control.WriteEntryDocs(binman.GetEntryModules())
1410         self.assertTrue(len(stdout.getvalue()) > 0)
1411
1412     def testEntryDocsMissing(self):
1413         """Test handling of missing entry documentation"""
1414         with self.assertRaises(ValueError) as e:
1415             with test_util.capture_sys_output() as (stdout, stderr):
1416                 control.WriteEntryDocs(binman.GetEntryModules(), 'u_boot')
1417         self.assertIn('Documentation is missing for modules: u_boot',
1418                       str(e.exception))
1419
1420     def testFmap(self):
1421         """Basic test of generation of a flashrom fmap"""
1422         data = self._DoReadFile('067_fmap.dts')
1423         fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1424         expected = (U_BOOT_DATA + tools.GetBytes(ord('!'), 12) +
1425                     U_BOOT_DATA + tools.GetBytes(ord('a'), 12))
1426         self.assertEqual(expected, data[:32])
1427         self.assertEqual(b'__FMAP__', fhdr.signature)
1428         self.assertEqual(1, fhdr.ver_major)
1429         self.assertEqual(0, fhdr.ver_minor)
1430         self.assertEqual(0, fhdr.base)
1431         self.assertEqual(16 + 16 +
1432                          fmap_util.FMAP_HEADER_LEN +
1433                          fmap_util.FMAP_AREA_LEN * 3, fhdr.image_size)
1434         self.assertEqual(b'FMAP', fhdr.name)
1435         self.assertEqual(3, fhdr.nareas)
1436         for fentry in fentries:
1437             self.assertEqual(0, fentry.flags)
1438
1439         self.assertEqual(0, fentries[0].offset)
1440         self.assertEqual(4, fentries[0].size)
1441         self.assertEqual(b'RO_U_BOOT', fentries[0].name)
1442
1443         self.assertEqual(16, fentries[1].offset)
1444         self.assertEqual(4, fentries[1].size)
1445         self.assertEqual(b'RW_U_BOOT', fentries[1].name)
1446
1447         self.assertEqual(32, fentries[2].offset)
1448         self.assertEqual(fmap_util.FMAP_HEADER_LEN +
1449                          fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
1450         self.assertEqual(b'FMAP', fentries[2].name)
1451
1452     def testBlobNamedByArg(self):
1453         """Test we can add a blob with the filename coming from an entry arg"""
1454         entry_args = {
1455             'cros-ec-rw-path': 'ecrw.bin',
1456         }
1457         data, _, _, _ = self._DoReadFileDtb('068_blob_named_by_arg.dts',
1458                                             entry_args=entry_args)
1459
1460     def testFill(self):
1461         """Test for an fill entry type"""
1462         data = self._DoReadFile('069_fill.dts')
1463         expected = tools.GetBytes(0xff, 8) + tools.GetBytes(0, 8)
1464         self.assertEqual(expected, data)
1465
1466     def testFillNoSize(self):
1467         """Test for an fill entry type with no size"""
1468         with self.assertRaises(ValueError) as e:
1469             self._DoReadFile('070_fill_no_size.dts')
1470         self.assertIn("'fill' entry must have a size property",
1471                       str(e.exception))
1472
1473     def _HandleGbbCommand(self, pipe_list):
1474         """Fake calls to the futility utility"""
1475         if pipe_list[0][0] == 'futility':
1476             fname = pipe_list[0][-1]
1477             # Append our GBB data to the file, which will happen every time the
1478             # futility command is called.
1479             with open(fname, 'ab') as fd:
1480                 fd.write(GBB_DATA)
1481             return command.CommandResult()
1482
1483     def testGbb(self):
1484         """Test for the Chromium OS Google Binary Block"""
1485         command.test_result = self._HandleGbbCommand
1486         entry_args = {
1487             'keydir': 'devkeys',
1488             'bmpblk': 'bmpblk.bin',
1489         }
1490         data, _, _, _ = self._DoReadFileDtb('071_gbb.dts', entry_args=entry_args)
1491
1492         # Since futility
1493         expected = (GBB_DATA + GBB_DATA + tools.GetBytes(0, 8) +
1494                     tools.GetBytes(0, 0x2180 - 16))
1495         self.assertEqual(expected, data)
1496
1497     def testGbbTooSmall(self):
1498         """Test for the Chromium OS Google Binary Block being large enough"""
1499         with self.assertRaises(ValueError) as e:
1500             self._DoReadFileDtb('072_gbb_too_small.dts')
1501         self.assertIn("Node '/binman/gbb': GBB is too small",
1502                       str(e.exception))
1503
1504     def testGbbNoSize(self):
1505         """Test for the Chromium OS Google Binary Block having a size"""
1506         with self.assertRaises(ValueError) as e:
1507             self._DoReadFileDtb('073_gbb_no_size.dts')
1508         self.assertIn("Node '/binman/gbb': GBB must have a fixed size",
1509                       str(e.exception))
1510
1511     def _HandleVblockCommand(self, pipe_list):
1512         """Fake calls to the futility utility"""
1513         if pipe_list[0][0] == 'futility':
1514             fname = pipe_list[0][3]
1515             with open(fname, 'wb') as fd:
1516                 fd.write(VBLOCK_DATA)
1517             return command.CommandResult()
1518
1519     def testVblock(self):
1520         """Test for the Chromium OS Verified Boot Block"""
1521         command.test_result = self._HandleVblockCommand
1522         entry_args = {
1523             'keydir': 'devkeys',
1524         }
1525         data, _, _, _ = self._DoReadFileDtb('074_vblock.dts',
1526                                             entry_args=entry_args)
1527         expected = U_BOOT_DATA + VBLOCK_DATA + U_BOOT_DTB_DATA
1528         self.assertEqual(expected, data)
1529
1530     def testVblockNoContent(self):
1531         """Test we detect a vblock which has no content to sign"""
1532         with self.assertRaises(ValueError) as e:
1533             self._DoReadFile('075_vblock_no_content.dts')
1534         self.assertIn("Node '/binman/vblock': Vblock must have a 'content' "
1535                       'property', str(e.exception))
1536
1537     def testVblockBadPhandle(self):
1538         """Test that we detect a vblock with an invalid phandle in contents"""
1539         with self.assertRaises(ValueError) as e:
1540             self._DoReadFile('076_vblock_bad_phandle.dts')
1541         self.assertIn("Node '/binman/vblock': Cannot find node for phandle "
1542                       '1000', str(e.exception))
1543
1544     def testVblockBadEntry(self):
1545         """Test that we detect an entry that points to a non-entry"""
1546         with self.assertRaises(ValueError) as e:
1547             self._DoReadFile('077_vblock_bad_entry.dts')
1548         self.assertIn("Node '/binman/vblock': Cannot find entry for node "
1549                       "'other'", str(e.exception))
1550
1551     def testTpl(self):
1552         """Test that an image with TPL and ots device tree can be created"""
1553         # ELF file with a '__bss_size' symbol
1554         with open(self.TestFile('bss_data'), 'rb') as fd:
1555             TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
1556         data = self._DoReadFile('078_u_boot_tpl.dts')
1557         self.assertEqual(U_BOOT_TPL_DATA + U_BOOT_TPL_DTB_DATA, data)
1558
1559     def testUsesPos(self):
1560         """Test that the 'pos' property cannot be used anymore"""
1561         with self.assertRaises(ValueError) as e:
1562            data = self._DoReadFile('079_uses_pos.dts')
1563         self.assertIn("Node '/binman/u-boot': Please use 'offset' instead of "
1564                       "'pos'", str(e.exception))
1565
1566     def testFillZero(self):
1567         """Test for an fill entry type with a size of 0"""
1568         data = self._DoReadFile('080_fill_empty.dts')
1569         self.assertEqual(tools.GetBytes(0, 16), data)
1570
1571     def testTextMissing(self):
1572         """Test for a text entry type where there is no text"""
1573         with self.assertRaises(ValueError) as e:
1574             self._DoReadFileDtb('066_text.dts',)
1575         self.assertIn("Node '/binman/text': No value provided for text label "
1576                       "'test-id'", str(e.exception))
1577
1578     def testPackStart16Tpl(self):
1579         """Test that an image with an x86 start16 TPL region can be created"""
1580         data = self._DoReadFile('081_x86-start16-tpl.dts')
1581         self.assertEqual(X86_START16_TPL_DATA, data[:len(X86_START16_TPL_DATA)])
1582
1583     def testSelectImage(self):
1584         """Test that we can select which images to build"""
1585         expected = 'Skipping images: image1'
1586
1587         # We should only get the expected message in verbose mode
1588         for verbosity in (0, 2):
1589             with test_util.capture_sys_output() as (stdout, stderr):
1590                 retcode = self._DoTestFile('006_dual_image.dts',
1591                                            verbosity=verbosity,
1592                                            images=['image2'])
1593             self.assertEqual(0, retcode)
1594             if verbosity:
1595                 self.assertIn(expected, stdout.getvalue())
1596             else:
1597                 self.assertNotIn(expected, stdout.getvalue())
1598
1599             self.assertFalse(os.path.exists(tools.GetOutputFilename('image1.bin')))
1600             self.assertTrue(os.path.exists(tools.GetOutputFilename('image2.bin')))
1601             self._CleanupOutputDir()
1602
1603     def testUpdateFdtAll(self):
1604         """Test that all device trees are updated with offset/size info"""
1605         data = self._DoReadFileRealDtb('082_fdt_update_all.dts')
1606
1607         base_expected = {
1608             'section:image-pos': 0,
1609             'u-boot-tpl-dtb:size': 513,
1610             'u-boot-spl-dtb:size': 513,
1611             'u-boot-spl-dtb:offset': 493,
1612             'image-pos': 0,
1613             'section/u-boot-dtb:image-pos': 0,
1614             'u-boot-spl-dtb:image-pos': 493,
1615             'section/u-boot-dtb:size': 493,
1616             'u-boot-tpl-dtb:image-pos': 1006,
1617             'section/u-boot-dtb:offset': 0,
1618             'section:size': 493,
1619             'offset': 0,
1620             'section:offset': 0,
1621             'u-boot-tpl-dtb:offset': 1006,
1622             'size': 1519
1623         }
1624
1625         # We expect three device-tree files in the output, one after the other.
1626         # Read them in sequence. We look for an 'spl' property in the SPL tree,
1627         # and 'tpl' in the TPL tree, to make sure they are distinct from the
1628         # main U-Boot tree. All three should have the same postions and offset.
1629         start = 0
1630         for item in ['', 'spl', 'tpl']:
1631             dtb = fdt.Fdt.FromData(data[start:])
1632             dtb.Scan()
1633             props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS +
1634                                       ['spl', 'tpl'])
1635             expected = dict(base_expected)
1636             if item:
1637                 expected[item] = 0
1638             self.assertEqual(expected, props)
1639             start += dtb._fdt_obj.totalsize()
1640
1641     def testUpdateFdtOutput(self):
1642         """Test that output DTB files are updated"""
1643         try:
1644             data, dtb_data, _, _ = self._DoReadFileDtb('082_fdt_update_all.dts',
1645                     use_real_dtb=True, update_dtb=True, reset_dtbs=False)
1646
1647             # Unfortunately, compiling a source file always results in a file
1648             # called source.dtb (see fdt_util.EnsureCompiled()). The test
1649             # source file (e.g. test/075_fdt_update_all.dts) thus does not enter
1650             # binman as a file called u-boot.dtb. To fix this, copy the file
1651             # over to the expected place.
1652             #tools.WriteFile(os.path.join(self._indir, 'u-boot.dtb'),
1653                     #tools.ReadFile(tools.GetOutputFilename('source.dtb')))
1654             start = 0
1655             for fname in ['u-boot.dtb.out', 'spl/u-boot-spl.dtb.out',
1656                           'tpl/u-boot-tpl.dtb.out']:
1657                 dtb = fdt.Fdt.FromData(data[start:])
1658                 size = dtb._fdt_obj.totalsize()
1659                 pathname = tools.GetOutputFilename(os.path.split(fname)[1])
1660                 outdata = tools.ReadFile(pathname)
1661                 name = os.path.split(fname)[0]
1662
1663                 if name:
1664                     orig_indata = self._GetDtbContentsForSplTpl(dtb_data, name)
1665                 else:
1666                     orig_indata = dtb_data
1667                 self.assertNotEqual(outdata, orig_indata,
1668                         "Expected output file '%s' be updated" % pathname)
1669                 self.assertEqual(outdata, data[start:start + size],
1670                         "Expected output file '%s' to match output image" %
1671                         pathname)
1672                 start += size
1673         finally:
1674             self._ResetDtbs()
1675
1676     def _decompress(self, data):
1677         return tools.Decompress(data, 'lz4')
1678
1679     def testCompress(self):
1680         """Test compression of blobs"""
1681         self._CheckLz4()
1682         data, _, _, out_dtb_fname = self._DoReadFileDtb('083_compress.dts',
1683                                             use_real_dtb=True, update_dtb=True)
1684         dtb = fdt.Fdt(out_dtb_fname)
1685         dtb.Scan()
1686         props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
1687         orig = self._decompress(data)
1688         self.assertEquals(COMPRESS_DATA, orig)
1689         expected = {
1690             'blob:uncomp-size': len(COMPRESS_DATA),
1691             'blob:size': len(data),
1692             'size': len(data),
1693             }
1694         self.assertEqual(expected, props)
1695
1696     def testFiles(self):
1697         """Test bringing in multiple files"""
1698         data = self._DoReadFile('084_files.dts')
1699         self.assertEqual(FILES_DATA, data)
1700
1701     def testFilesCompress(self):
1702         """Test bringing in multiple files and compressing them"""
1703         self._CheckLz4()
1704         data = self._DoReadFile('085_files_compress.dts')
1705
1706         image = control.images['image']
1707         entries = image.GetEntries()
1708         files = entries['files']
1709         entries = files._entries
1710
1711         orig = b''
1712         for i in range(1, 3):
1713             key = '%d.dat' % i
1714             start = entries[key].image_pos
1715             len = entries[key].size
1716             chunk = data[start:start + len]
1717             orig += self._decompress(chunk)
1718
1719         self.assertEqual(FILES_DATA, orig)
1720
1721     def testFilesMissing(self):
1722         """Test missing files"""
1723         with self.assertRaises(ValueError) as e:
1724             data = self._DoReadFile('086_files_none.dts')
1725         self.assertIn("Node '/binman/files': Pattern \'files/*.none\' matched "
1726                       'no files', str(e.exception))
1727
1728     def testFilesNoPattern(self):
1729         """Test missing files"""
1730         with self.assertRaises(ValueError) as e:
1731             data = self._DoReadFile('087_files_no_pattern.dts')
1732         self.assertIn("Node '/binman/files': Missing 'pattern' property",
1733                       str(e.exception))
1734
1735     def testExpandSize(self):
1736         """Test an expanding entry"""
1737         data, _, map_data, _ = self._DoReadFileDtb('088_expand_size.dts',
1738                                                    map=True)
1739         expect = (tools.GetBytes(ord('a'), 8) + U_BOOT_DATA +
1740                   MRC_DATA + tools.GetBytes(ord('b'), 1) + U_BOOT_DATA +
1741                   tools.GetBytes(ord('c'), 8) + U_BOOT_DATA +
1742                   tools.GetBytes(ord('d'), 8))
1743         self.assertEqual(expect, data)
1744         self.assertEqual('''ImagePos    Offset      Size  Name
1745 00000000  00000000  00000028  main-section
1746 00000000   00000000  00000008  fill
1747 00000008   00000008  00000004  u-boot
1748 0000000c   0000000c  00000004  section
1749 0000000c    00000000  00000003  intel-mrc
1750 00000010   00000010  00000004  u-boot2
1751 00000014   00000014  0000000c  section2
1752 00000014    00000000  00000008  fill
1753 0000001c    00000008  00000004  u-boot
1754 00000020   00000020  00000008  fill2
1755 ''', map_data)
1756
1757     def testExpandSizeBad(self):
1758         """Test an expanding entry which fails to provide contents"""
1759         with test_util.capture_sys_output() as (stdout, stderr):
1760             with self.assertRaises(ValueError) as e:
1761                 self._DoReadFileDtb('089_expand_size_bad.dts', map=True)
1762         self.assertIn("Node '/binman/_testing': Cannot obtain contents when "
1763                       'expanding entry', str(e.exception))
1764
1765     def testHash(self):
1766         """Test hashing of the contents of an entry"""
1767         _, _, _, out_dtb_fname = self._DoReadFileDtb('090_hash.dts',
1768                 use_real_dtb=True, update_dtb=True)
1769         dtb = fdt.Fdt(out_dtb_fname)
1770         dtb.Scan()
1771         hash_node = dtb.GetNode('/binman/u-boot/hash').props['value']
1772         m = hashlib.sha256()
1773         m.update(U_BOOT_DATA)
1774         self.assertEqual(m.digest(), b''.join(hash_node.value))
1775
1776     def testHashNoAlgo(self):
1777         with self.assertRaises(ValueError) as e:
1778             self._DoReadFileDtb('091_hash_no_algo.dts', update_dtb=True)
1779         self.assertIn("Node \'/binman/u-boot\': Missing \'algo\' property for "
1780                       'hash node', str(e.exception))
1781
1782     def testHashBadAlgo(self):
1783         with self.assertRaises(ValueError) as e:
1784             self._DoReadFileDtb('092_hash_bad_algo.dts', update_dtb=True)
1785         self.assertIn("Node '/binman/u-boot': Unknown hash algorithm",
1786                       str(e.exception))
1787
1788     def testHashSection(self):
1789         """Test hashing of the contents of an entry"""
1790         _, _, _, out_dtb_fname = self._DoReadFileDtb('099_hash_section.dts',
1791                 use_real_dtb=True, update_dtb=True)
1792         dtb = fdt.Fdt(out_dtb_fname)
1793         dtb.Scan()
1794         hash_node = dtb.GetNode('/binman/section/hash').props['value']
1795         m = hashlib.sha256()
1796         m.update(U_BOOT_DATA)
1797         m.update(tools.GetBytes(ord('a'), 16))
1798         self.assertEqual(m.digest(), b''.join(hash_node.value))
1799
1800     def testPackUBootTplMicrocode(self):
1801         """Test that x86 microcode can be handled correctly in TPL
1802
1803         We expect to see the following in the image, in order:
1804             u-boot-tpl-nodtb.bin with a microcode pointer inserted at the correct
1805                 place
1806             u-boot-tpl.dtb with the microcode removed
1807             the microcode
1808         """
1809         with open(self.TestFile('u_boot_ucode_ptr'), 'rb') as fd:
1810             TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
1811         first, pos_and_size = self._RunMicrocodeTest('093_x86_tpl_ucode.dts',
1812                                                      U_BOOT_TPL_NODTB_DATA)
1813         self.assertEqual(b'tplnodtb with microc' + pos_and_size +
1814                          b'ter somewhere in here', first)
1815
1816     def testFmapX86(self):
1817         """Basic test of generation of a flashrom fmap"""
1818         data = self._DoReadFile('094_fmap_x86.dts')
1819         fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1820         expected = U_BOOT_DATA + MRC_DATA + tools.GetBytes(ord('a'), 32 - 7)
1821         self.assertEqual(expected, data[:32])
1822         fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1823
1824         self.assertEqual(0x100, fhdr.image_size)
1825
1826         self.assertEqual(0, fentries[0].offset)
1827         self.assertEqual(4, fentries[0].size)
1828         self.assertEqual(b'U_BOOT', fentries[0].name)
1829
1830         self.assertEqual(4, fentries[1].offset)
1831         self.assertEqual(3, fentries[1].size)
1832         self.assertEqual(b'INTEL_MRC', fentries[1].name)
1833
1834         self.assertEqual(32, fentries[2].offset)
1835         self.assertEqual(fmap_util.FMAP_HEADER_LEN +
1836                          fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
1837         self.assertEqual(b'FMAP', fentries[2].name)
1838
1839     def testFmapX86Section(self):
1840         """Basic test of generation of a flashrom fmap"""
1841         data = self._DoReadFile('095_fmap_x86_section.dts')
1842         expected = U_BOOT_DATA + MRC_DATA + tools.GetBytes(ord('b'), 32 - 7)
1843         self.assertEqual(expected, data[:32])
1844         fhdr, fentries = fmap_util.DecodeFmap(data[36:])
1845
1846         self.assertEqual(0x100, fhdr.image_size)
1847
1848         self.assertEqual(0, fentries[0].offset)
1849         self.assertEqual(4, fentries[0].size)
1850         self.assertEqual(b'U_BOOT', fentries[0].name)
1851
1852         self.assertEqual(4, fentries[1].offset)
1853         self.assertEqual(3, fentries[1].size)
1854         self.assertEqual(b'INTEL_MRC', fentries[1].name)
1855
1856         self.assertEqual(36, fentries[2].offset)
1857         self.assertEqual(fmap_util.FMAP_HEADER_LEN +
1858                          fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
1859         self.assertEqual(b'FMAP', fentries[2].name)
1860
1861     def testElf(self):
1862         """Basic test of ELF entries"""
1863         self._SetupSplElf()
1864         with open(self.TestFile('bss_data'), 'rb') as fd:
1865             TestFunctional._MakeInputFile('tpl/u-boot-tpl', fd.read())
1866         with open(self.TestFile('bss_data'), 'rb') as fd:
1867             TestFunctional._MakeInputFile('-boot', fd.read())
1868         data = self._DoReadFile('096_elf.dts')
1869
1870     def testElfStrip(self):
1871         """Basic test of ELF entries"""
1872         self._SetupSplElf()
1873         with open(self.TestFile('bss_data'), 'rb') as fd:
1874             TestFunctional._MakeInputFile('-boot', fd.read())
1875         data = self._DoReadFile('097_elf_strip.dts')
1876
1877     def testPackOverlapMap(self):
1878         """Test that overlapping regions are detected"""
1879         with test_util.capture_sys_output() as (stdout, stderr):
1880             with self.assertRaises(ValueError) as e:
1881                 self._DoTestFile('014_pack_overlap.dts', map=True)
1882         map_fname = tools.GetOutputFilename('image.map')
1883         self.assertEqual("Wrote map file '%s' to show errors\n" % map_fname,
1884                          stdout.getvalue())
1885
1886         # We should not get an inmage, but there should be a map file
1887         self.assertFalse(os.path.exists(tools.GetOutputFilename('image.bin')))
1888         self.assertTrue(os.path.exists(map_fname))
1889         map_data = tools.ReadFile(map_fname, binary=False)
1890         self.assertEqual('''ImagePos    Offset      Size  Name
1891 <none>    00000000  00000007  main-section
1892 <none>     00000000  00000004  u-boot
1893 <none>     00000003  00000004  u-boot-align
1894 ''', map_data)
1895
1896     def testPackRefCode(self):
1897         """Test that an image with an Intel Reference code binary works"""
1898         data = self._DoReadFile('100_intel_refcode.dts')
1899         self.assertEqual(REFCODE_DATA, data[:len(REFCODE_DATA)])
1900
1901     def testSectionOffset(self):
1902         """Tests use of a section with an offset"""
1903         data, _, map_data, _ = self._DoReadFileDtb('101_sections_offset.dts',
1904                                                    map=True)
1905         self.assertEqual('''ImagePos    Offset      Size  Name
1906 00000000  00000000  00000038  main-section
1907 00000004   00000004  00000010  section@0
1908 00000004    00000000  00000004  u-boot
1909 00000018   00000018  00000010  section@1
1910 00000018    00000000  00000004  u-boot
1911 0000002c   0000002c  00000004  section@2
1912 0000002c    00000000  00000004  u-boot
1913 ''', map_data)
1914         self.assertEqual(data,
1915                          tools.GetBytes(0x26, 4) + U_BOOT_DATA +
1916                              tools.GetBytes(0x21, 12) +
1917                          tools.GetBytes(0x26, 4) + U_BOOT_DATA +
1918                              tools.GetBytes(0x61, 12) +
1919                          tools.GetBytes(0x26, 4) + U_BOOT_DATA +
1920                              tools.GetBytes(0x26, 8))
1921
1922     def testCbfsRaw(self):
1923         """Test base handling of a Coreboot Filesystem (CBFS)
1924
1925         The exact contents of the CBFS is verified by similar tests in
1926         cbfs_util_test.py. The tests here merely check that the files added to
1927         the CBFS can be found in the final image.
1928         """
1929         data = self._DoReadFile('102_cbfs_raw.dts')
1930         size = 0xb0
1931
1932         cbfs = cbfs_util.CbfsReader(data)
1933         self.assertEqual(size, cbfs.rom_size)
1934
1935         self.assertIn('u-boot-dtb', cbfs.files)
1936         cfile = cbfs.files['u-boot-dtb']
1937         self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
1938
1939     def testCbfsArch(self):
1940         """Test on non-x86 architecture"""
1941         data = self._DoReadFile('103_cbfs_raw_ppc.dts')
1942         size = 0x100
1943
1944         cbfs = cbfs_util.CbfsReader(data)
1945         self.assertEqual(size, cbfs.rom_size)
1946
1947         self.assertIn('u-boot-dtb', cbfs.files)
1948         cfile = cbfs.files['u-boot-dtb']
1949         self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
1950
1951     def testCbfsStage(self):
1952         """Tests handling of a Coreboot Filesystem (CBFS)"""
1953         if not elf.ELF_TOOLS:
1954             self.skipTest('Python elftools not available')
1955         elf_fname = os.path.join(self._indir, 'cbfs-stage.elf')
1956         elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA)
1957         size = 0xb0
1958
1959         data = self._DoReadFile('104_cbfs_stage.dts')
1960         cbfs = cbfs_util.CbfsReader(data)
1961         self.assertEqual(size, cbfs.rom_size)
1962
1963         self.assertIn('u-boot', cbfs.files)
1964         cfile = cbfs.files['u-boot']
1965         self.assertEqual(U_BOOT_DATA + U_BOOT_DTB_DATA, cfile.data)
1966
1967     def testCbfsRawCompress(self):
1968         """Test handling of compressing raw files"""
1969         self._CheckLz4()
1970         data = self._DoReadFile('105_cbfs_raw_compress.dts')
1971         size = 0x140
1972
1973         cbfs = cbfs_util.CbfsReader(data)
1974         self.assertIn('u-boot', cbfs.files)
1975         cfile = cbfs.files['u-boot']
1976         self.assertEqual(COMPRESS_DATA, cfile.data)
1977
1978     def testCbfsBadArch(self):
1979         """Test handling of a bad architecture"""
1980         with self.assertRaises(ValueError) as e:
1981             self._DoReadFile('106_cbfs_bad_arch.dts')
1982         self.assertIn("Invalid architecture 'bad-arch'", str(e.exception))
1983
1984     def testCbfsNoSize(self):
1985         """Test handling of a missing size property"""
1986         with self.assertRaises(ValueError) as e:
1987             self._DoReadFile('107_cbfs_no_size.dts')
1988         self.assertIn('entry must have a size property', str(e.exception))
1989
1990     def testCbfsNoCOntents(self):
1991         """Test handling of a CBFS entry which does not provide contentsy"""
1992         with self.assertRaises(ValueError) as e:
1993             self._DoReadFile('108_cbfs_no_contents.dts')
1994         self.assertIn('Could not complete processing of contents',
1995                       str(e.exception))
1996
1997     def testCbfsBadCompress(self):
1998         """Test handling of a bad architecture"""
1999         with self.assertRaises(ValueError) as e:
2000             self._DoReadFile('109_cbfs_bad_compress.dts')
2001         self.assertIn("Invalid compression in 'u-boot': 'invalid-algo'",
2002                       str(e.exception))
2003
2004     def testCbfsNamedEntries(self):
2005         """Test handling of named entries"""
2006         data = self._DoReadFile('110_cbfs_name.dts')
2007
2008         cbfs = cbfs_util.CbfsReader(data)
2009         self.assertIn('FRED', cbfs.files)
2010         cfile1 = cbfs.files['FRED']
2011         self.assertEqual(U_BOOT_DATA, cfile1.data)
2012
2013         self.assertIn('hello', cbfs.files)
2014         cfile2 = cbfs.files['hello']
2015         self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
2016
2017     def _SetupIfwi(self, fname):
2018         """Set up to run an IFWI test
2019
2020         Args:
2021             fname: Filename of input file to provide (fitimage.bin or ifwi.bin)
2022         """
2023         self._SetupSplElf()
2024
2025         # Intel Integrated Firmware Image (IFWI) file
2026         with gzip.open(self.TestFile('%s.gz' % fname), 'rb') as fd:
2027             data = fd.read()
2028         TestFunctional._MakeInputFile(fname,data)
2029
2030     def _CheckIfwi(self, data):
2031         """Check that an image with an IFWI contains the correct output
2032
2033         Args:
2034             data: Conents of output file
2035         """
2036         expected_desc = tools.ReadFile(self.TestFile('descriptor.bin'))
2037         if data[:0x1000] != expected_desc:
2038             self.fail('Expected descriptor binary at start of image')
2039
2040         # We expect to find the TPL wil in subpart IBBP entry IBBL
2041         image_fname = tools.GetOutputFilename('image.bin')
2042         tpl_fname = tools.GetOutputFilename('tpl.out')
2043         tools.RunIfwiTool(image_fname, tools.CMD_EXTRACT, fname=tpl_fname,
2044                           subpart='IBBP', entry_name='IBBL')
2045
2046         tpl_data = tools.ReadFile(tpl_fname)
2047         self.assertEqual(tpl_data[:len(U_BOOT_TPL_DATA)], U_BOOT_TPL_DATA)
2048
2049     def testPackX86RomIfwi(self):
2050         """Test that an x86 ROM with Integrated Firmware Image can be created"""
2051         self._SetupIfwi('fitimage.bin')
2052         data = self._DoReadFile('111_x86-rom-ifwi.dts')
2053         self._CheckIfwi(data)
2054
2055     def testPackX86RomIfwiNoDesc(self):
2056         """Test that an x86 ROM with IFWI can be created from an ifwi.bin file"""
2057         self._SetupIfwi('ifwi.bin')
2058         data = self._DoReadFile('112_x86-rom-ifwi-nodesc.dts')
2059         self._CheckIfwi(data)
2060
2061     def testPackX86RomIfwiNoData(self):
2062         """Test that an x86 ROM with IFWI handles missing data"""
2063         self._SetupIfwi('ifwi.bin')
2064         with self.assertRaises(ValueError) as e:
2065             data = self._DoReadFile('113_x86-rom-ifwi-nodata.dts')
2066         self.assertIn('Could not complete processing of contents',
2067                       str(e.exception))
2068
2069     def testCbfsOffset(self):
2070         """Test a CBFS with files at particular offsets
2071
2072         Like all CFBS tests, this is just checking the logic that calls
2073         cbfs_util. See cbfs_util_test for fully tests (e.g. test_cbfs_offset()).
2074         """
2075         data = self._DoReadFile('114_cbfs_offset.dts')
2076         size = 0x200
2077
2078         cbfs = cbfs_util.CbfsReader(data)
2079         self.assertEqual(size, cbfs.rom_size)
2080
2081         self.assertIn('u-boot', cbfs.files)
2082         cfile = cbfs.files['u-boot']
2083         self.assertEqual(U_BOOT_DATA, cfile.data)
2084         self.assertEqual(0x40, cfile.cbfs_offset)
2085
2086         self.assertIn('u-boot-dtb', cbfs.files)
2087         cfile2 = cbfs.files['u-boot-dtb']
2088         self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
2089         self.assertEqual(0x140, cfile2.cbfs_offset)
2090
2091     def testFdtmap(self):
2092         """Test an FDT map can be inserted in the image"""
2093         data = self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
2094         fdtmap_data = data[len(U_BOOT_DATA):]
2095         magic = fdtmap_data[:8]
2096         self.assertEqual('_FDTMAP_', magic)
2097         self.assertEqual(tools.GetBytes(0, 8), fdtmap_data[8:16])
2098
2099         fdt_data = fdtmap_data[16:]
2100         dtb = fdt.Fdt.FromData(fdt_data)
2101         dtb.Scan()
2102         props = self._GetPropTree(dtb, BASE_DTB_PROPS, prefix='/')
2103         self.assertEqual({
2104             'image-pos': 0,
2105             'offset': 0,
2106             'u-boot:offset': 0,
2107             'u-boot:size': len(U_BOOT_DATA),
2108             'u-boot:image-pos': 0,
2109             'fdtmap:image-pos': 4,
2110             'fdtmap:offset': 4,
2111             'fdtmap:size': len(fdtmap_data),
2112             'size': len(data),
2113         }, props)
2114
2115     def testFdtmapNoMatch(self):
2116         """Check handling of an FDT map when the section cannot be found"""
2117         self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
2118
2119         # Mangle the section name, which should cause a mismatch between the
2120         # correct FDT path and the one expected by the section
2121         image = control.images['image']
2122         image._node.path += '-suffix'
2123         entries = image.GetEntries()
2124         fdtmap = entries['fdtmap']
2125         with self.assertRaises(ValueError) as e:
2126             fdtmap._GetFdtmap()
2127         self.assertIn("Cannot locate node for path '/binman-suffix'",
2128                       str(e.exception))
2129
2130     def testFdtmapHeader(self):
2131         """Test an FDT map and image header can be inserted in the image"""
2132         data = self.data = self._DoReadFileRealDtb('116_fdtmap_hdr.dts')
2133         fdtmap_pos = len(U_BOOT_DATA)
2134         fdtmap_data = data[fdtmap_pos:]
2135         fdt_data = fdtmap_data[16:]
2136         dtb = fdt.Fdt.FromData(fdt_data)
2137         fdt_size = dtb.GetFdtObj().totalsize()
2138         hdr_data = data[-8:]
2139         self.assertEqual('BinM', hdr_data[:4])
2140         offset = struct.unpack('<I', hdr_data[4:])[0] & 0xffffffff
2141         self.assertEqual(fdtmap_pos - 0x400, offset - (1 << 32))
2142
2143     def testFdtmapHeaderStart(self):
2144         """Test an image header can be inserted at the image start"""
2145         data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
2146         fdtmap_pos = 0x100 + len(U_BOOT_DATA)
2147         hdr_data = data[:8]
2148         self.assertEqual('BinM', hdr_data[:4])
2149         offset = struct.unpack('<I', hdr_data[4:])[0]
2150         self.assertEqual(fdtmap_pos, offset)
2151
2152     def testFdtmapHeaderPos(self):
2153         """Test an image header can be inserted at a chosen position"""
2154         data = self.data = self._DoReadFileRealDtb('118_fdtmap_hdr_pos.dts')
2155         fdtmap_pos = 0x100 + len(U_BOOT_DATA)
2156         hdr_data = data[0x80:0x88]
2157         self.assertEqual('BinM', hdr_data[:4])
2158         offset = struct.unpack('<I', hdr_data[4:])[0]
2159         self.assertEqual(fdtmap_pos, offset)
2160
2161     def testHeaderMissingFdtmap(self):
2162         """Test an image header requires an fdtmap"""
2163         with self.assertRaises(ValueError) as e:
2164             self.data = self._DoReadFileRealDtb('119_fdtmap_hdr_missing.dts')
2165         self.assertIn("'image_header' section must have an 'fdtmap' sibling",
2166                       str(e.exception))
2167
2168     def testHeaderNoLocation(self):
2169         """Test an image header with a no specified location is detected"""
2170         with self.assertRaises(ValueError) as e:
2171             self.data = self._DoReadFileRealDtb('120_hdr_no_location.dts')
2172         self.assertIn("Invalid location 'None', expected 'start' or 'end'",
2173                       str(e.exception))
2174
2175     def testEntryExpand(self):
2176         """Test expanding an entry after it is packed"""
2177         data = self._DoReadFile('121_entry_expand.dts')
2178         self.assertEqual(b'aaa', data[:3])
2179         self.assertEqual(U_BOOT_DATA, data[3:3 + len(U_BOOT_DATA)])
2180         self.assertEqual(b'aaa', data[-3:])
2181
2182     def testEntryExpandBad(self):
2183         """Test expanding an entry after it is packed, twice"""
2184         with self.assertRaises(ValueError) as e:
2185             self._DoReadFile('122_entry_expand_twice.dts')
2186         self.assertIn("Image '/binman': Entries changed size after packing",
2187                       str(e.exception))
2188
2189     def testEntryExpandSection(self):
2190         """Test expanding an entry within a section after it is packed"""
2191         data = self._DoReadFile('123_entry_expand_section.dts')
2192         self.assertEqual(b'aaa', data[:3])
2193         self.assertEqual(U_BOOT_DATA, data[3:3 + len(U_BOOT_DATA)])
2194         self.assertEqual(b'aaa', data[-3:])
2195
2196     def testCompressDtb(self):
2197         """Test that compress of device-tree files is supported"""
2198         self._CheckLz4()
2199         data = self.data = self._DoReadFileRealDtb('124_compress_dtb.dts')
2200         self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
2201         comp_data = data[len(U_BOOT_DATA):]
2202         orig = self._decompress(comp_data)
2203         dtb = fdt.Fdt.FromData(orig)
2204         dtb.Scan()
2205         props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
2206         expected = {
2207             'u-boot:size': len(U_BOOT_DATA),
2208             'u-boot-dtb:uncomp-size': len(orig),
2209             'u-boot-dtb:size': len(comp_data),
2210             'size': len(data),
2211             }
2212         self.assertEqual(expected, props)
2213
2214     def testCbfsUpdateFdt(self):
2215         """Test that we can update the device tree with CBFS offset/size info"""
2216         self._CheckLz4()
2217         data, _, _, out_dtb_fname = self._DoReadFileDtb('125_cbfs_update.dts',
2218                                                         update_dtb=True)
2219         dtb = fdt.Fdt(out_dtb_fname)
2220         dtb.Scan()
2221         props = self._GetPropTree(dtb, BASE_DTB_PROPS + ['uncomp-size'])
2222         del props['cbfs/u-boot:size']
2223         self.assertEqual({
2224             'offset': 0,
2225             'size': len(data),
2226             'image-pos': 0,
2227             'cbfs:offset': 0,
2228             'cbfs:size': len(data),
2229             'cbfs:image-pos': 0,
2230             'cbfs/u-boot:offset': 0x38,
2231             'cbfs/u-boot:uncomp-size': len(U_BOOT_DATA),
2232             'cbfs/u-boot:image-pos': 0x38,
2233             'cbfs/u-boot-dtb:offset': 0xb8,
2234             'cbfs/u-boot-dtb:size': len(U_BOOT_DATA),
2235             'cbfs/u-boot-dtb:image-pos': 0xb8,
2236             }, props)
2237
2238     def testCbfsBadType(self):
2239         """Test an image header with a no specified location is detected"""
2240         with self.assertRaises(ValueError) as e:
2241             self._DoReadFile('126_cbfs_bad_type.dts')
2242         self.assertIn("Unknown cbfs-type 'badtype'", str(e.exception))
2243
2244     def testList(self):
2245         """Test listing the files in an image"""
2246         self._CheckLz4()
2247         data = self._DoReadFile('127_list.dts')
2248         image = control.images['image']
2249         entries = image.BuildEntryList()
2250         self.assertEqual(7, len(entries))
2251
2252         ent = entries[0]
2253         self.assertEqual(0, ent.indent)
2254         self.assertEqual('main-section', ent.name)
2255         self.assertEqual('section', ent.etype)
2256         self.assertEqual(len(data), ent.size)
2257         self.assertEqual(0, ent.image_pos)
2258         self.assertEqual(None, ent.uncomp_size)
2259         self.assertEqual(0, ent.offset)
2260
2261         ent = entries[1]
2262         self.assertEqual(1, ent.indent)
2263         self.assertEqual('u-boot', ent.name)
2264         self.assertEqual('u-boot', ent.etype)
2265         self.assertEqual(len(U_BOOT_DATA), ent.size)
2266         self.assertEqual(0, ent.image_pos)
2267         self.assertEqual(None, ent.uncomp_size)
2268         self.assertEqual(0, ent.offset)
2269
2270         ent = entries[2]
2271         self.assertEqual(1, ent.indent)
2272         self.assertEqual('section', ent.name)
2273         self.assertEqual('section', ent.etype)
2274         section_size = ent.size
2275         self.assertEqual(0x100, ent.image_pos)
2276         self.assertEqual(None, ent.uncomp_size)
2277         self.assertEqual(0x100, ent.offset)
2278
2279         ent = entries[3]
2280         self.assertEqual(2, ent.indent)
2281         self.assertEqual('cbfs', ent.name)
2282         self.assertEqual('cbfs', ent.etype)
2283         self.assertEqual(0x400, ent.size)
2284         self.assertEqual(0x100, ent.image_pos)
2285         self.assertEqual(None, ent.uncomp_size)
2286         self.assertEqual(0, ent.offset)
2287
2288         ent = entries[4]
2289         self.assertEqual(3, ent.indent)
2290         self.assertEqual('u-boot', ent.name)
2291         self.assertEqual('u-boot', ent.etype)
2292         self.assertEqual(len(U_BOOT_DATA), ent.size)
2293         self.assertEqual(0x138, ent.image_pos)
2294         self.assertEqual(None, ent.uncomp_size)
2295         self.assertEqual(0x38, ent.offset)
2296
2297         ent = entries[5]
2298         self.assertEqual(3, ent.indent)
2299         self.assertEqual('u-boot-dtb', ent.name)
2300         self.assertEqual('text', ent.etype)
2301         self.assertGreater(len(COMPRESS_DATA), ent.size)
2302         self.assertEqual(0x178, ent.image_pos)
2303         self.assertEqual(len(COMPRESS_DATA), ent.uncomp_size)
2304         self.assertEqual(0x78, ent.offset)
2305
2306         ent = entries[6]
2307         self.assertEqual(2, ent.indent)
2308         self.assertEqual('u-boot-dtb', ent.name)
2309         self.assertEqual('u-boot-dtb', ent.etype)
2310         self.assertEqual(0x500, ent.image_pos)
2311         self.assertEqual(len(U_BOOT_DTB_DATA), ent.uncomp_size)
2312         dtb_size = ent.size
2313         # Compressing this data expands it since headers are added
2314         self.assertGreater(dtb_size, len(U_BOOT_DTB_DATA))
2315         self.assertEqual(0x400, ent.offset)
2316
2317         self.assertEqual(len(data), 0x100 + section_size)
2318         self.assertEqual(section_size, 0x400 + dtb_size)
2319
2320     def testFindFdtmap(self):
2321         """Test locating an FDT map in an image"""
2322         self._CheckLz4()
2323         data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2324         image = control.images['image']
2325         entries = image.GetEntries()
2326         entry = entries['fdtmap']
2327         self.assertEqual(entry.image_pos, fdtmap.LocateFdtmap(data))
2328
2329     def testFindFdtmapMissing(self):
2330         """Test failing to locate an FDP map"""
2331         data = self._DoReadFile('005_simple.dts')
2332         self.assertEqual(None, fdtmap.LocateFdtmap(data))
2333
2334     def testFindImageHeader(self):
2335         """Test locating a image header"""
2336         self._CheckLz4()
2337         data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2338         image = control.images['image']
2339         entries = image.GetEntries()
2340         entry = entries['fdtmap']
2341         # The header should point to the FDT map
2342         self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
2343
2344     def testFindImageHeaderStart(self):
2345         """Test locating a image header located at the start of an image"""
2346         data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
2347         image = control.images['image']
2348         entries = image.GetEntries()
2349         entry = entries['fdtmap']
2350         # The header should point to the FDT map
2351         self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
2352
2353     def testFindImageHeaderMissing(self):
2354         """Test failing to locate an image header"""
2355         data = self._DoReadFile('005_simple.dts')
2356         self.assertEqual(None, image_header.LocateHeaderOffset(data))
2357
2358     def testReadImage(self):
2359         """Test reading an image and accessing its FDT map"""
2360         self._CheckLz4()
2361         data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2362         image_fname = tools.GetOutputFilename('image.bin')
2363         orig_image = control.images['image']
2364         image = Image.FromFile(image_fname)
2365         self.assertEqual(orig_image.GetEntries().keys(),
2366                          image.GetEntries().keys())
2367
2368         orig_entry = orig_image.GetEntries()['fdtmap']
2369         entry = image.GetEntries()['fdtmap']
2370         self.assertEquals(orig_entry.offset, entry.offset)
2371         self.assertEquals(orig_entry.size, entry.size)
2372         self.assertEquals(orig_entry.image_pos, entry.image_pos)
2373
2374     def testReadImageNoHeader(self):
2375         """Test accessing an image's FDT map without an image header"""
2376         self._CheckLz4()
2377         data = self._DoReadFileRealDtb('129_decode_image_nohdr.dts')
2378         image_fname = tools.GetOutputFilename('image.bin')
2379         image = Image.FromFile(image_fname)
2380         self.assertTrue(isinstance(image, Image))
2381         self.assertEqual('image', image.image_name[-5:])
2382
2383     def testReadImageFail(self):
2384         """Test failing to read an image image's FDT map"""
2385         self._DoReadFile('005_simple.dts')
2386         image_fname = tools.GetOutputFilename('image.bin')
2387         with self.assertRaises(ValueError) as e:
2388             image = Image.FromFile(image_fname)
2389         self.assertIn("Cannot find FDT map in image", str(e.exception))
2390
2391     def testListCmd(self):
2392         """Test listing the files in an image using an Fdtmap"""
2393         self._CheckLz4()
2394         data = self._DoReadFileRealDtb('130_list_fdtmap.dts')
2395
2396         # lz4 compression size differs depending on the version
2397         image = control.images['image']
2398         entries = image.GetEntries()
2399         section_size = entries['section'].size
2400         fdt_size = entries['section'].GetEntries()['u-boot-dtb'].size
2401         fdtmap_offset = entries['fdtmap'].offset
2402
2403         try:
2404             tmpdir, updated_fname = self._SetupImageInTmpdir()
2405             with test_util.capture_sys_output() as (stdout, stderr):
2406                 self._DoBinman('ls', '-i', updated_fname)
2407         finally:
2408             shutil.rmtree(tmpdir)
2409         lines = stdout.getvalue().splitlines()
2410         expected = [
2411 'Name              Image-pos  Size  Entry-type    Offset  Uncomp-size',
2412 '----------------------------------------------------------------------',
2413 'main-section              0   c00  section            0',
2414 '  u-boot                  0     4  u-boot             0',
2415 '  section               100   %x  section          100' % section_size,
2416 '    cbfs                100   400  cbfs               0',
2417 '      u-boot            138     4  u-boot            38',
2418 '      u-boot-dtb        180   10f  u-boot-dtb        80          3c9',
2419 '    u-boot-dtb          500   %x  u-boot-dtb       400          3c9' % fdt_size,
2420 '  fdtmap                %x   3b4  fdtmap           %x' %
2421         (fdtmap_offset, fdtmap_offset),
2422 '  image-header          bf8     8  image-header     bf8',
2423             ]
2424         self.assertEqual(expected, lines)
2425
2426     def testListCmdFail(self):
2427         """Test failing to list an image"""
2428         self._DoReadFile('005_simple.dts')
2429         try:
2430             tmpdir, updated_fname = self._SetupImageInTmpdir()
2431             with self.assertRaises(ValueError) as e:
2432                 self._DoBinman('ls', '-i', updated_fname)
2433         finally:
2434             shutil.rmtree(tmpdir)
2435         self.assertIn("Cannot find FDT map in image", str(e.exception))
2436
2437     def _RunListCmd(self, paths, expected):
2438         """List out entries and check the result
2439
2440         Args:
2441             paths: List of paths to pass to the list command
2442             expected: Expected list of filenames to be returned, in order
2443         """
2444         self._CheckLz4()
2445         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2446         image_fname = tools.GetOutputFilename('image.bin')
2447         image = Image.FromFile(image_fname)
2448         lines = image.GetListEntries(paths)[1]
2449         files = [line[0].strip() for line in lines[1:]]
2450         self.assertEqual(expected, files)
2451
2452     def testListCmdSection(self):
2453         """Test listing the files in a section"""
2454         self._RunListCmd(['section'],
2455             ['section', 'cbfs', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
2456
2457     def testListCmdFile(self):
2458         """Test listing a particular file"""
2459         self._RunListCmd(['*u-boot-dtb'], ['u-boot-dtb', 'u-boot-dtb'])
2460
2461     def testListCmdWildcard(self):
2462         """Test listing a wildcarded file"""
2463         self._RunListCmd(['*boot*'],
2464             ['u-boot', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
2465
2466     def testListCmdWildcardMulti(self):
2467         """Test listing a wildcarded file"""
2468         self._RunListCmd(['*cb*', '*head*'],
2469             ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
2470
2471     def testListCmdEmpty(self):
2472         """Test listing a wildcarded file"""
2473         self._RunListCmd(['nothing'], [])
2474
2475     def testListCmdPath(self):
2476         """Test listing the files in a sub-entry of a section"""
2477         self._RunListCmd(['section/cbfs'], ['cbfs', 'u-boot', 'u-boot-dtb'])
2478
2479     def _RunExtractCmd(self, entry_name, decomp=True):
2480         """Extract an entry from an image
2481
2482         Args:
2483             entry_name: Entry name to extract
2484             decomp: True to decompress the data if compressed, False to leave
2485                 it in its raw uncompressed format
2486
2487         Returns:
2488             data from entry
2489         """
2490         self._CheckLz4()
2491         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2492         image_fname = tools.GetOutputFilename('image.bin')
2493         return control.ReadEntry(image_fname, entry_name, decomp)
2494
2495     def testExtractSimple(self):
2496         """Test extracting a single file"""
2497         data = self._RunExtractCmd('u-boot')
2498         self.assertEqual(U_BOOT_DATA, data)
2499
2500     def testExtractSection(self):
2501         """Test extracting the files in a section"""
2502         data = self._RunExtractCmd('section')
2503         cbfs_data = data[:0x400]
2504         cbfs = cbfs_util.CbfsReader(cbfs_data)
2505         self.assertEqual(['u-boot', 'u-boot-dtb', ''], cbfs.files.keys())
2506         dtb_data = data[0x400:]
2507         dtb = self._decompress(dtb_data)
2508         self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2509
2510     def testExtractCompressed(self):
2511         """Test extracting compressed data"""
2512         data = self._RunExtractCmd('section/u-boot-dtb')
2513         self.assertEqual(EXTRACT_DTB_SIZE, len(data))
2514
2515     def testExtractRaw(self):
2516         """Test extracting compressed data without decompressing it"""
2517         data = self._RunExtractCmd('section/u-boot-dtb', decomp=False)
2518         dtb = self._decompress(data)
2519         self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2520
2521     def testExtractCbfs(self):
2522         """Test extracting CBFS data"""
2523         data = self._RunExtractCmd('section/cbfs/u-boot')
2524         self.assertEqual(U_BOOT_DATA, data)
2525
2526     def testExtractCbfsCompressed(self):
2527         """Test extracting CBFS compressed data"""
2528         data = self._RunExtractCmd('section/cbfs/u-boot-dtb')
2529         self.assertEqual(EXTRACT_DTB_SIZE, len(data))
2530
2531     def testExtractCbfsRaw(self):
2532         """Test extracting CBFS compressed data without decompressing it"""
2533         data = self._RunExtractCmd('section/cbfs/u-boot-dtb', decomp=False)
2534         dtb = tools.Decompress(data, 'lzma', with_header=False)
2535         self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2536
2537     def testExtractBadEntry(self):
2538         """Test extracting a bad section path"""
2539         with self.assertRaises(ValueError) as e:
2540             self._RunExtractCmd('section/does-not-exist')
2541         self.assertIn("Entry 'does-not-exist' not found in '/section'",
2542                       str(e.exception))
2543
2544     def testExtractMissingFile(self):
2545         """Test extracting file that does not exist"""
2546         with self.assertRaises(IOError) as e:
2547             control.ReadEntry('missing-file', 'name')
2548
2549     def testExtractBadFile(self):
2550         """Test extracting an invalid file"""
2551         fname = os.path.join(self._indir, 'badfile')
2552         tools.WriteFile(fname, b'')
2553         with self.assertRaises(ValueError) as e:
2554             control.ReadEntry(fname, 'name')
2555
2556     def testExtractCmd(self):
2557         """Test extracting a file fron an image on the command line"""
2558         self._CheckLz4()
2559         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2560         fname = os.path.join(self._indir, 'output.extact')
2561         try:
2562             tmpdir, updated_fname = self._SetupImageInTmpdir()
2563             with test_util.capture_sys_output() as (stdout, stderr):
2564                 self._DoBinman('extract', '-i', updated_fname, 'u-boot',
2565                                '-f', fname)
2566         finally:
2567             shutil.rmtree(tmpdir)
2568         data = tools.ReadFile(fname)
2569         self.assertEqual(U_BOOT_DATA, data)
2570
2571     def testExtractOneEntry(self):
2572         """Test extracting a single entry fron an image """
2573         self._CheckLz4()
2574         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2575         image_fname = tools.GetOutputFilename('image.bin')
2576         fname = os.path.join(self._indir, 'output.extact')
2577         control.ExtractEntries(image_fname, fname, None, ['u-boot'])
2578         data = tools.ReadFile(fname)
2579         self.assertEqual(U_BOOT_DATA, data)
2580
2581     def _CheckExtractOutput(self, decomp):
2582         """Helper to test file output with and without decompression
2583
2584         Args:
2585             decomp: True to decompress entry data, False to output it raw
2586         """
2587         def _CheckPresent(entry_path, expect_data, expect_size=None):
2588             """Check and remove expected file
2589
2590             This checks the data/size of a file and removes the file both from
2591             the outfiles set and from the output directory. Once all files are
2592             processed, both the set and directory should be empty.
2593
2594             Args:
2595                 entry_path: Entry path
2596                 expect_data: Data to expect in file, or None to skip check
2597                 expect_size: Size of data to expect in file, or None to skip
2598             """
2599             path = os.path.join(outdir, entry_path)
2600             data = tools.ReadFile(path)
2601             os.remove(path)
2602             if expect_data:
2603                 self.assertEqual(expect_data, data)
2604             elif expect_size:
2605                 self.assertEqual(expect_size, len(data))
2606             outfiles.remove(path)
2607
2608         def _CheckDirPresent(name):
2609             """Remove expected directory
2610
2611             This gives an error if the directory does not exist as expected
2612
2613             Args:
2614                 name: Name of directory to remove
2615             """
2616             path = os.path.join(outdir, name)
2617             os.rmdir(path)
2618
2619         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2620         image_fname = tools.GetOutputFilename('image.bin')
2621         outdir = os.path.join(self._indir, 'extract')
2622         einfos = control.ExtractEntries(image_fname, None, outdir, [], decomp)
2623
2624         # Create a set of all file that were output (should be 9)
2625         outfiles = set()
2626         for root, dirs, files in os.walk(outdir):
2627             outfiles |= set([os.path.join(root, fname) for fname in files])
2628         self.assertEqual(9, len(outfiles))
2629         self.assertEqual(9, len(einfos))
2630
2631         image = control.images['image']
2632         entries = image.GetEntries()
2633
2634         # Check the 9 files in various ways
2635         section = entries['section']
2636         section_entries = section.GetEntries()
2637         cbfs_entries = section_entries['cbfs'].GetEntries()
2638         _CheckPresent('u-boot', U_BOOT_DATA)
2639         _CheckPresent('section/cbfs/u-boot', U_BOOT_DATA)
2640         dtb_len = EXTRACT_DTB_SIZE
2641         if not decomp:
2642             dtb_len = cbfs_entries['u-boot-dtb'].size
2643         _CheckPresent('section/cbfs/u-boot-dtb', None, dtb_len)
2644         if not decomp:
2645             dtb_len = section_entries['u-boot-dtb'].size
2646         _CheckPresent('section/u-boot-dtb', None, dtb_len)
2647
2648         fdtmap = entries['fdtmap']
2649         _CheckPresent('fdtmap', fdtmap.data)
2650         hdr = entries['image-header']
2651         _CheckPresent('image-header', hdr.data)
2652
2653         _CheckPresent('section/root', section.data)
2654         cbfs = section_entries['cbfs']
2655         _CheckPresent('section/cbfs/root', cbfs.data)
2656         data = tools.ReadFile(image_fname)
2657         _CheckPresent('root', data)
2658
2659         # There should be no files left. Remove all the directories to check.
2660         # If there are any files/dirs remaining, one of these checks will fail.
2661         self.assertEqual(0, len(outfiles))
2662         _CheckDirPresent('section/cbfs')
2663         _CheckDirPresent('section')
2664         _CheckDirPresent('')
2665         self.assertFalse(os.path.exists(outdir))
2666
2667     def testExtractAllEntries(self):
2668         """Test extracting all entries"""
2669         self._CheckLz4()
2670         self._CheckExtractOutput(decomp=True)
2671
2672     def testExtractAllEntriesRaw(self):
2673         """Test extracting all entries without decompressing them"""
2674         self._CheckLz4()
2675         self._CheckExtractOutput(decomp=False)
2676
2677     def testExtractSelectedEntries(self):
2678         """Test extracting some entries"""
2679         self._CheckLz4()
2680         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2681         image_fname = tools.GetOutputFilename('image.bin')
2682         outdir = os.path.join(self._indir, 'extract')
2683         einfos = control.ExtractEntries(image_fname, None, outdir,
2684                                         ['*cb*', '*head*'])
2685
2686         # File output is tested by testExtractAllEntries(), so just check that
2687         # the expected entries are selected
2688         names = [einfo.name for einfo in einfos]
2689         self.assertEqual(names,
2690                          ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
2691
2692     def testExtractNoEntryPaths(self):
2693         """Test extracting some entries"""
2694         self._CheckLz4()
2695         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2696         image_fname = tools.GetOutputFilename('image.bin')
2697         with self.assertRaises(ValueError) as e:
2698             control.ExtractEntries(image_fname, 'fname', None, [])
2699         self.assertIn('Must specify an entry path to write with -f',
2700                       str(e.exception))
2701
2702     def testExtractTooManyEntryPaths(self):
2703         """Test extracting some entries"""
2704         self._CheckLz4()
2705         self._DoReadFileRealDtb('130_list_fdtmap.dts')
2706         image_fname = tools.GetOutputFilename('image.bin')
2707         with self.assertRaises(ValueError) as e:
2708             control.ExtractEntries(image_fname, 'fname', None, ['a', 'b'])
2709         self.assertIn('Must specify exactly one entry path to write with -f',
2710                       str(e.exception))
2711
2712     def testPackAlignSection(self):
2713         """Test that sections can have alignment"""
2714         self._DoReadFile('131_pack_align_section.dts')
2715
2716         self.assertIn('image', control.images)
2717         image = control.images['image']
2718         entries = image.GetEntries()
2719         self.assertEqual(3, len(entries))
2720
2721         # First u-boot
2722         self.assertIn('u-boot', entries)
2723         entry = entries['u-boot']
2724         self.assertEqual(0, entry.offset)
2725         self.assertEqual(0, entry.image_pos)
2726         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
2727         self.assertEqual(len(U_BOOT_DATA), entry.size)
2728
2729         # Section0
2730         self.assertIn('section0', entries)
2731         section0 = entries['section0']
2732         self.assertEqual(0x10, section0.offset)
2733         self.assertEqual(0x10, section0.image_pos)
2734         self.assertEqual(len(U_BOOT_DATA), section0.size)
2735
2736         # Second u-boot
2737         section_entries = section0.GetEntries()
2738         self.assertIn('u-boot', section_entries)
2739         entry = section_entries['u-boot']
2740         self.assertEqual(0, entry.offset)
2741         self.assertEqual(0x10, entry.image_pos)
2742         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
2743         self.assertEqual(len(U_BOOT_DATA), entry.size)
2744
2745         # Section1
2746         self.assertIn('section1', entries)
2747         section1 = entries['section1']
2748         self.assertEqual(0x14, section1.offset)
2749         self.assertEqual(0x14, section1.image_pos)
2750         self.assertEqual(0x20, section1.size)
2751
2752         # Second u-boot
2753         section_entries = section1.GetEntries()
2754         self.assertIn('u-boot', section_entries)
2755         entry = section_entries['u-boot']
2756         self.assertEqual(0, entry.offset)
2757         self.assertEqual(0x14, entry.image_pos)
2758         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
2759         self.assertEqual(len(U_BOOT_DATA), entry.size)
2760
2761         # Section2
2762         self.assertIn('section2', section_entries)
2763         section2 = section_entries['section2']
2764         self.assertEqual(0x4, section2.offset)
2765         self.assertEqual(0x18, section2.image_pos)
2766         self.assertEqual(4, section2.size)
2767
2768         # Third u-boot
2769         section_entries = section2.GetEntries()
2770         self.assertIn('u-boot', section_entries)
2771         entry = section_entries['u-boot']
2772         self.assertEqual(0, entry.offset)
2773         self.assertEqual(0x18, entry.image_pos)
2774         self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
2775         self.assertEqual(len(U_BOOT_DATA), entry.size)
2776
2777     def _RunReplaceCmd(self, entry_name, data, decomp=True, allow_resize=True,
2778                        dts='132_replace.dts'):
2779         """Replace an entry in an image
2780
2781         This writes the entry data to update it, then opens the updated file and
2782         returns the value that it now finds there.
2783
2784         Args:
2785             entry_name: Entry name to replace
2786             data: Data to replace it with
2787             decomp: True to compress the data if needed, False if data is
2788                 already compressed so should be used as is
2789             allow_resize: True to allow entries to change size, False to raise
2790                 an exception
2791
2792         Returns:
2793             Tuple:
2794                 data from entry
2795                 data from fdtmap (excluding header)
2796                 Image object that was modified
2797         """
2798         dtb_data = self._DoReadFileDtb(dts, use_real_dtb=True,
2799                                        update_dtb=True)[1]
2800
2801         self.assertIn('image', control.images)
2802         image = control.images['image']
2803         entries = image.GetEntries()
2804         orig_dtb_data = entries['u-boot-dtb'].data
2805         orig_fdtmap_data = entries['fdtmap'].data
2806
2807         image_fname = tools.GetOutputFilename('image.bin')
2808         updated_fname = tools.GetOutputFilename('image-updated.bin')
2809         tools.WriteFile(updated_fname, tools.ReadFile(image_fname))
2810         image = control.WriteEntry(updated_fname, entry_name, data, decomp,
2811                                    allow_resize)
2812         data = control.ReadEntry(updated_fname, entry_name, decomp)
2813
2814         # The DT data should not change unless resized:
2815         if not allow_resize:
2816             new_dtb_data = entries['u-boot-dtb'].data
2817             self.assertEqual(new_dtb_data, orig_dtb_data)
2818             new_fdtmap_data = entries['fdtmap'].data
2819             self.assertEqual(new_fdtmap_data, orig_fdtmap_data)
2820
2821         return data, orig_fdtmap_data[fdtmap.FDTMAP_HDR_LEN:], image
2822
2823     def testReplaceSimple(self):
2824         """Test replacing a single file"""
2825         expected = b'x' * len(U_BOOT_DATA)
2826         data, expected_fdtmap, _ = self._RunReplaceCmd('u-boot', expected,
2827                                                     allow_resize=False)
2828         self.assertEqual(expected, data)
2829
2830         # Test that the state looks right. There should be an FDT for the fdtmap
2831         # that we jsut read back in, and it should match what we find in the
2832         # 'control' tables. Checking for an FDT that does not exist should
2833         # return None.
2834         path, fdtmap = state.GetFdtContents('fdtmap')
2835         self.assertIsNotNone(path)
2836         self.assertEqual(expected_fdtmap, fdtmap)
2837
2838         dtb = state.GetFdtForEtype('fdtmap')
2839         self.assertEqual(dtb.GetContents(), fdtmap)
2840
2841         missing_path, missing_fdtmap = state.GetFdtContents('missing')
2842         self.assertIsNone(missing_path)
2843         self.assertIsNone(missing_fdtmap)
2844
2845         missing_dtb = state.GetFdtForEtype('missing')
2846         self.assertIsNone(missing_dtb)
2847
2848         self.assertEqual('/binman', state.fdt_path_prefix)
2849
2850     def testReplaceResizeFail(self):
2851         """Test replacing a file by something larger"""
2852         expected = U_BOOT_DATA + b'x'
2853         with self.assertRaises(ValueError) as e:
2854             self._RunReplaceCmd('u-boot', expected, allow_resize=False,
2855                                 dts='139_replace_repack.dts')
2856         self.assertIn("Node '/u-boot': Entry data size does not match, but resize is disabled",
2857                       str(e.exception))
2858
2859     def testReplaceMulti(self):
2860         """Test replacing entry data where multiple images are generated"""
2861         data = self._DoReadFileDtb('133_replace_multi.dts', use_real_dtb=True,
2862                                    update_dtb=True)[0]
2863         expected = b'x' * len(U_BOOT_DATA)
2864         updated_fname = tools.GetOutputFilename('image-updated.bin')
2865         tools.WriteFile(updated_fname, data)
2866         entry_name = 'u-boot'
2867         control.WriteEntry(updated_fname, entry_name, expected,
2868                            allow_resize=False)
2869         data = control.ReadEntry(updated_fname, entry_name)
2870         self.assertEqual(expected, data)
2871
2872         # Check the state looks right.
2873         self.assertEqual('/binman/image', state.fdt_path_prefix)
2874
2875         # Now check we can write the first image
2876         image_fname = tools.GetOutputFilename('first-image.bin')
2877         updated_fname = tools.GetOutputFilename('first-updated.bin')
2878         tools.WriteFile(updated_fname, tools.ReadFile(image_fname))
2879         entry_name = 'u-boot'
2880         control.WriteEntry(updated_fname, entry_name, expected,
2881                            allow_resize=False)
2882         data = control.ReadEntry(updated_fname, entry_name)
2883         self.assertEqual(expected, data)
2884
2885         # Check the state looks right.
2886         self.assertEqual('/binman/first-image', state.fdt_path_prefix)
2887
2888     def testUpdateFdtAllRepack(self):
2889         """Test that all device trees are updated with offset/size info"""
2890         data = self._DoReadFileRealDtb('134_fdt_update_all_repack.dts')
2891         SECTION_SIZE = 0x300
2892         DTB_SIZE = 602
2893         FDTMAP_SIZE = 608
2894         base_expected = {
2895             'offset': 0,
2896             'size': SECTION_SIZE + DTB_SIZE * 2 + FDTMAP_SIZE,
2897             'image-pos': 0,
2898             'section:offset': 0,
2899             'section:size': SECTION_SIZE,
2900             'section:image-pos': 0,
2901             'section/u-boot-dtb:offset': 4,
2902             'section/u-boot-dtb:size': 636,
2903             'section/u-boot-dtb:image-pos': 4,
2904             'u-boot-spl-dtb:offset': SECTION_SIZE,
2905             'u-boot-spl-dtb:size': DTB_SIZE,
2906             'u-boot-spl-dtb:image-pos': SECTION_SIZE,
2907             'u-boot-tpl-dtb:offset': SECTION_SIZE + DTB_SIZE,
2908             'u-boot-tpl-dtb:image-pos': SECTION_SIZE + DTB_SIZE,
2909             'u-boot-tpl-dtb:size': DTB_SIZE,
2910             'fdtmap:offset': SECTION_SIZE + DTB_SIZE * 2,
2911             'fdtmap:size': FDTMAP_SIZE,
2912             'fdtmap:image-pos': SECTION_SIZE + DTB_SIZE * 2,
2913         }
2914         main_expected = {
2915             'section:orig-size': SECTION_SIZE,
2916             'section/u-boot-dtb:orig-offset': 4,
2917         }
2918
2919         # We expect three device-tree files in the output, with the first one
2920         # within a fixed-size section.
2921         # Read them in sequence. We look for an 'spl' property in the SPL tree,
2922         # and 'tpl' in the TPL tree, to make sure they are distinct from the
2923         # main U-Boot tree. All three should have the same positions and offset
2924         # except that the main tree should include the main_expected properties
2925         start = 4
2926         for item in ['', 'spl', 'tpl', None]:
2927             if item is None:
2928                 start += 16  # Move past fdtmap header
2929             dtb = fdt.Fdt.FromData(data[start:])
2930             dtb.Scan()
2931             props = self._GetPropTree(dtb,
2932                 BASE_DTB_PROPS + REPACK_DTB_PROPS + ['spl', 'tpl'],
2933                 prefix='/' if item is None else '/binman/')
2934             expected = dict(base_expected)
2935             if item:
2936                 expected[item] = 0
2937             else:
2938                 # Main DTB and fdtdec should include the 'orig-' properties
2939                 expected.update(main_expected)
2940             # Helpful for debugging:
2941             #for prop in sorted(props):
2942                 #print('prop %s %s %s' % (prop, props[prop], expected[prop]))
2943             self.assertEqual(expected, props)
2944             if item == '':
2945                 start = SECTION_SIZE
2946             else:
2947                 start += dtb._fdt_obj.totalsize()
2948
2949     def testFdtmapHeaderMiddle(self):
2950         """Test an FDT map in the middle of an image when it should be at end"""
2951         with self.assertRaises(ValueError) as e:
2952             self._DoReadFileRealDtb('135_fdtmap_hdr_middle.dts')
2953         self.assertIn("Invalid sibling order 'middle' for image-header: Must be at 'end' to match location",
2954                       str(e.exception))
2955
2956     def testFdtmapHeaderStartBad(self):
2957         """Test an FDT map in middle of an image when it should be at start"""
2958         with self.assertRaises(ValueError) as e:
2959             self._DoReadFileRealDtb('136_fdtmap_hdr_startbad.dts')
2960         self.assertIn("Invalid sibling order 'end' for image-header: Must be at 'start' to match location",
2961                       str(e.exception))
2962
2963     def testFdtmapHeaderEndBad(self):
2964         """Test an FDT map at the start of an image when it should be at end"""
2965         with self.assertRaises(ValueError) as e:
2966             self._DoReadFileRealDtb('137_fdtmap_hdr_endbad.dts')
2967         self.assertIn("Invalid sibling order 'start' for image-header: Must be at 'end' to match location",
2968                       str(e.exception))
2969
2970     def testFdtmapHeaderNoSize(self):
2971         """Test an image header at the end of an image with undefined size"""
2972         self._DoReadFileRealDtb('138_fdtmap_hdr_nosize.dts')
2973
2974     def testReplaceResize(self):
2975         """Test replacing a single file in an entry with a larger file"""
2976         expected = U_BOOT_DATA + b'x'
2977         data, _, image = self._RunReplaceCmd('u-boot', expected,
2978                                              dts='139_replace_repack.dts')
2979         self.assertEqual(expected, data)
2980
2981         entries = image.GetEntries()
2982         dtb_data = entries['u-boot-dtb'].data
2983         dtb = fdt.Fdt.FromData(dtb_data)
2984         dtb.Scan()
2985
2986         # The u-boot section should now be larger in the dtb
2987         node = dtb.GetNode('/binman/u-boot')
2988         self.assertEqual(len(expected), fdt_util.GetInt(node, 'size'))
2989
2990         # Same for the fdtmap
2991         fdata = entries['fdtmap'].data
2992         fdtb = fdt.Fdt.FromData(fdata[fdtmap.FDTMAP_HDR_LEN:])
2993         fdtb.Scan()
2994         fnode = fdtb.GetNode('/u-boot')
2995         self.assertEqual(len(expected), fdt_util.GetInt(fnode, 'size'))
2996
2997     def testReplaceResizeNoRepack(self):
2998         """Test replacing an entry with a larger file when not allowed"""
2999         expected = U_BOOT_DATA + b'x'
3000         with self.assertRaises(ValueError) as e:
3001             self._RunReplaceCmd('u-boot', expected)
3002         self.assertIn('Entry data size does not match, but allow-repack is not present for this image',
3003                       str(e.exception))
3004
3005     def testEntryShrink(self):
3006         """Test contracting an entry after it is packed"""
3007         try:
3008             state.SetAllowEntryContraction(True)
3009             data = self._DoReadFileDtb('140_entry_shrink.dts',
3010                                        update_dtb=True)[0]
3011         finally:
3012             state.SetAllowEntryContraction(False)
3013         self.assertEqual(b'a', data[:1])
3014         self.assertEqual(U_BOOT_DATA, data[1:1 + len(U_BOOT_DATA)])
3015         self.assertEqual(b'a', data[-1:])
3016
3017     def testEntryShrinkFail(self):
3018         """Test not being allowed to contract an entry after it is packed"""
3019         data = self._DoReadFileDtb('140_entry_shrink.dts', update_dtb=True)[0]
3020
3021         # In this case there is a spare byte at the end of the data. The size of
3022         # the contents is only 1 byte but we still have the size before it
3023         # shrunk.
3024         self.assertEqual(b'a\0', data[:2])
3025         self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
3026         self.assertEqual(b'a\0', data[-2:])
3027
3028     def testDescriptorOffset(self):
3029         """Test that the Intel descriptor is always placed at at the start"""
3030         data = self._DoReadFileDtb('141_descriptor_offset.dts')
3031         image = control.images['image']
3032         entries = image.GetEntries()
3033         desc = entries['intel-descriptor']
3034         self.assertEqual(0xff800000, desc.offset);
3035         self.assertEqual(0xff800000, desc.image_pos);
3036
3037     def testReplaceCbfs(self):
3038         """Test replacing a single file in CBFS without changing the size"""
3039         self._CheckLz4()
3040         expected = b'x' * len(U_BOOT_DATA)
3041         data = self._DoReadFileRealDtb('142_replace_cbfs.dts')
3042         updated_fname = tools.GetOutputFilename('image-updated.bin')
3043         tools.WriteFile(updated_fname, data)
3044         entry_name = 'section/cbfs/u-boot'
3045         control.WriteEntry(updated_fname, entry_name, expected,
3046                            allow_resize=True)
3047         data = control.ReadEntry(updated_fname, entry_name)
3048         self.assertEqual(expected, data)
3049
3050     def testReplaceResizeCbfs(self):
3051         """Test replacing a single file in CBFS with one of a different size"""
3052         self._CheckLz4()
3053         expected = U_BOOT_DATA + b'x'
3054         data = self._DoReadFileRealDtb('142_replace_cbfs.dts')
3055         updated_fname = tools.GetOutputFilename('image-updated.bin')
3056         tools.WriteFile(updated_fname, data)
3057         entry_name = 'section/cbfs/u-boot'
3058         control.WriteEntry(updated_fname, entry_name, expected,
3059                            allow_resize=True)
3060         data = control.ReadEntry(updated_fname, entry_name)
3061         self.assertEqual(expected, data)
3062
3063     def _SetupForReplace(self):
3064         """Set up some files to use to replace entries
3065
3066         This generates an image, copies it to a new file, extracts all the files
3067         in it and updates some of them
3068
3069         Returns:
3070             List
3071                 Image filename
3072                 Output directory
3073                 Expected values for updated entries, each a string
3074         """
3075         data = self._DoReadFileRealDtb('143_replace_all.dts')
3076
3077         updated_fname = tools.GetOutputFilename('image-updated.bin')
3078         tools.WriteFile(updated_fname, data)
3079
3080         outdir = os.path.join(self._indir, 'extract')
3081         einfos = control.ExtractEntries(updated_fname, None, outdir, [])
3082
3083         expected1 = b'x' + U_BOOT_DATA + b'y'
3084         u_boot_fname1 = os.path.join(outdir, 'u-boot')
3085         tools.WriteFile(u_boot_fname1, expected1)
3086
3087         expected2 = b'a' + U_BOOT_DATA + b'b'
3088         u_boot_fname2 = os.path.join(outdir, 'u-boot2')
3089         tools.WriteFile(u_boot_fname2, expected2)
3090
3091         expected_text = b'not the same text'
3092         text_fname = os.path.join(outdir, 'text')
3093         tools.WriteFile(text_fname, expected_text)
3094
3095         dtb_fname = os.path.join(outdir, 'u-boot-dtb')
3096         dtb = fdt.FdtScan(dtb_fname)
3097         node = dtb.GetNode('/binman/text')
3098         node.AddString('my-property', 'the value')
3099         dtb.Sync(auto_resize=True)
3100         dtb.Flush()
3101
3102         return updated_fname, outdir, expected1, expected2, expected_text
3103
3104     def _CheckReplaceMultiple(self, entry_paths):
3105         """Handle replacing the contents of multiple entries
3106
3107         Args:
3108             entry_paths: List of entry paths to replace
3109
3110         Returns:
3111             List
3112                 Dict of entries in the image:
3113                     key: Entry name
3114                     Value: Entry object
3115             Expected values for updated entries, each a string
3116         """
3117         updated_fname, outdir, expected1, expected2, expected_text = (
3118             self._SetupForReplace())
3119         control.ReplaceEntries(updated_fname, None, outdir, entry_paths)
3120
3121         image = Image.FromFile(updated_fname)
3122         image.LoadData()
3123         return image.GetEntries(), expected1, expected2, expected_text
3124
3125     def testReplaceAll(self):
3126         """Test replacing the contents of all entries"""
3127         entries, expected1, expected2, expected_text = (
3128             self._CheckReplaceMultiple([]))
3129         data = entries['u-boot'].data
3130         self.assertEqual(expected1, data)
3131
3132         data = entries['u-boot2'].data
3133         self.assertEqual(expected2, data)
3134
3135         data = entries['text'].data
3136         self.assertEqual(expected_text, data)
3137
3138         # Check that the device tree is updated
3139         data = entries['u-boot-dtb'].data
3140         dtb = fdt.Fdt.FromData(data)
3141         dtb.Scan()
3142         node = dtb.GetNode('/binman/text')
3143         self.assertEqual('the value', node.props['my-property'].value)
3144
3145     def testReplaceSome(self):
3146         """Test replacing the contents of a few entries"""
3147         entries, expected1, expected2, expected_text = (
3148             self._CheckReplaceMultiple(['u-boot2', 'text']))
3149
3150         # This one should not change
3151         data = entries['u-boot'].data
3152         self.assertEqual(U_BOOT_DATA, data)
3153
3154         data = entries['u-boot2'].data
3155         self.assertEqual(expected2, data)
3156
3157         data = entries['text'].data
3158         self.assertEqual(expected_text, data)
3159
3160     def testReplaceCmd(self):
3161         """Test replacing a file fron an image on the command line"""
3162         self._DoReadFileRealDtb('143_replace_all.dts')
3163
3164         try:
3165             tmpdir, updated_fname = self._SetupImageInTmpdir()
3166
3167             fname = os.path.join(tmpdir, 'update-u-boot.bin')
3168             expected = b'x' * len(U_BOOT_DATA)
3169             tools.WriteFile(fname, expected)
3170
3171             self._DoBinman('replace', '-i', updated_fname, 'u-boot', '-f', fname)
3172             data = tools.ReadFile(updated_fname)
3173             self.assertEqual(expected, data[:len(expected)])
3174             map_fname = os.path.join(tmpdir, 'image-updated.map')
3175             self.assertFalse(os.path.exists(map_fname))
3176         finally:
3177             shutil.rmtree(tmpdir)
3178
3179     def testReplaceCmdSome(self):
3180         """Test replacing some files fron an image on the command line"""
3181         updated_fname, outdir, expected1, expected2, expected_text = (
3182             self._SetupForReplace())
3183
3184         self._DoBinman('replace', '-i', updated_fname, '-I', outdir,
3185                        'u-boot2', 'text')
3186
3187         tools.PrepareOutputDir(None)
3188         image = Image.FromFile(updated_fname)
3189         image.LoadData()
3190         entries = image.GetEntries()
3191
3192         # This one should not change
3193         data = entries['u-boot'].data
3194         self.assertEqual(U_BOOT_DATA, data)
3195
3196         data = entries['u-boot2'].data
3197         self.assertEqual(expected2, data)
3198
3199         data = entries['text'].data
3200         self.assertEqual(expected_text, data)
3201
3202     def testReplaceMissing(self):
3203         """Test replacing entries where the file is missing"""
3204         updated_fname, outdir, expected1, expected2, expected_text = (
3205             self._SetupForReplace())
3206
3207         # Remove one of the files, to generate a warning
3208         u_boot_fname1 = os.path.join(outdir, 'u-boot')
3209         os.remove(u_boot_fname1)
3210
3211         with test_util.capture_sys_output() as (stdout, stderr):
3212             control.ReplaceEntries(updated_fname, None, outdir, [])
3213         self.assertIn("Skipping entry '/u-boot' from missing file",
3214                       stdout.getvalue())
3215
3216     def testReplaceCmdMap(self):
3217         """Test replacing a file fron an image on the command line"""
3218         self._DoReadFileRealDtb('143_replace_all.dts')
3219
3220         try:
3221             tmpdir, updated_fname = self._SetupImageInTmpdir()
3222
3223             fname = os.path.join(self._indir, 'update-u-boot.bin')
3224             expected = b'x' * len(U_BOOT_DATA)
3225             tools.WriteFile(fname, expected)
3226
3227             self._DoBinman('replace', '-i', updated_fname, 'u-boot',
3228                            '-f', fname, '-m')
3229             map_fname = os.path.join(tmpdir, 'image-updated.map')
3230             self.assertTrue(os.path.exists(map_fname))
3231         finally:
3232             shutil.rmtree(tmpdir)
3233
3234     def testReplaceNoEntryPaths(self):
3235         """Test replacing an entry without an entry path"""
3236         self._DoReadFileRealDtb('143_replace_all.dts')
3237         image_fname = tools.GetOutputFilename('image.bin')
3238         with self.assertRaises(ValueError) as e:
3239             control.ReplaceEntries(image_fname, 'fname', None, [])
3240         self.assertIn('Must specify an entry path to read with -f',
3241                       str(e.exception))
3242
3243     def testReplaceTooManyEntryPaths(self):
3244         """Test extracting some entries"""
3245         self._DoReadFileRealDtb('143_replace_all.dts')
3246         image_fname = tools.GetOutputFilename('image.bin')
3247         with self.assertRaises(ValueError) as e:
3248             control.ReplaceEntries(image_fname, 'fname', None, ['a', 'b'])
3249         self.assertIn('Must specify exactly one entry path to write with -f',
3250                       str(e.exception))
3251
3252     def testPackReset16(self):
3253         """Test that an image with an x86 reset16 region can be created"""
3254         data = self._DoReadFile('144_x86_reset16.dts')
3255         self.assertEqual(X86_RESET16_DATA, data[:len(X86_RESET16_DATA)])
3256
3257     def testPackReset16Spl(self):
3258         """Test that an image with an x86 reset16-spl region can be created"""
3259         data = self._DoReadFile('145_x86_reset16_spl.dts')
3260         self.assertEqual(X86_RESET16_SPL_DATA, data[:len(X86_RESET16_SPL_DATA)])
3261
3262     def testPackReset16Tpl(self):
3263         """Test that an image with an x86 reset16-tpl region can be created"""
3264         data = self._DoReadFile('146_x86_reset16_tpl.dts')
3265         self.assertEqual(X86_RESET16_TPL_DATA, data[:len(X86_RESET16_TPL_DATA)])
3266
3267     def testPackIntelFit(self):
3268         """Test that an image with an Intel FIT and pointer can be created"""
3269         data = self._DoReadFile('147_intel_fit.dts')
3270         self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
3271         fit = data[16:32];
3272         self.assertEqual(b'_FIT_   \x01\x00\x00\x00\x00\x01\x80}' , fit)
3273         ptr = struct.unpack('<i', data[0x40:0x44])[0]
3274
3275         image = control.images['image']
3276         entries = image.GetEntries()
3277         expected_ptr = entries['intel-fit'].image_pos - (1 << 32)
3278         self.assertEqual(expected_ptr, ptr)
3279
3280     def testPackIntelFitMissing(self):
3281         """Test detection of a FIT pointer with not FIT region"""
3282         with self.assertRaises(ValueError) as e:
3283             self._DoReadFile('148_intel_fit_missing.dts')
3284         self.assertIn("'intel-fit-ptr' section must have an 'intel-fit' sibling",
3285                       str(e.exception))
3286
3287
3288 if __name__ == "__main__":
3289     unittest.main()