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