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