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