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
9import collections
10import gzip
11import hashlib
12from optparse import OptionParser
13import os
14import re
15import shutil
16import struct
17import sys
18import tempfile
19import unittest
20import unittest.mock
21import urllib.error
22
23from binman import bintool
24from binman import cbfs_util
25from binman import cmdline
26from binman import control
27from binman import elf
28from binman import elf_test
29from binman import fip_util
30from binman import fmap_util
31from binman import state
32from dtoc import fdt
33from dtoc import fdt_util
34from binman.etype import fdtmap
35from binman.etype import image_header
36from binman.image import Image
37from u_boot_pylib import command
38from u_boot_pylib import test_util
39from u_boot_pylib import tools
40from u_boot_pylib import tout
41
42# Contents of test files, corresponding to different entry types
43U_BOOT_DATA           = b'1234'
44U_BOOT_IMG_DATA       = b'img'
45U_BOOT_SPL_DATA       = b'56780123456789abcdefghijklm'
46U_BOOT_TPL_DATA       = b'tpl9876543210fedcbazywvuts'
47U_BOOT_VPL_DATA       = b'vpl76543210fedcbazywxyz_'
48BLOB_DATA             = b'89'
49ME_DATA               = b'0abcd'
50VGA_DATA              = b'vga'
51EFI_CAPSULE_DATA      = b'efi'
52U_BOOT_DTB_DATA       = b'udtb'
53U_BOOT_SPL_DTB_DATA   = b'spldtb'
54U_BOOT_TPL_DTB_DATA   = b'tpldtb'
55U_BOOT_VPL_DTB_DATA   = b'vpldtb'
56X86_START16_DATA      = b'start16'
57X86_START16_SPL_DATA  = b'start16spl'
58X86_START16_TPL_DATA  = b'start16tpl'
59X86_RESET16_DATA      = b'reset16'
60X86_RESET16_SPL_DATA  = b'reset16spl'
61X86_RESET16_TPL_DATA  = b'reset16tpl'
62PPC_MPC85XX_BR_DATA   = b'ppcmpc85xxbr'
63U_BOOT_NODTB_DATA     = b'nodtb with microcode pointer somewhere in here'
64U_BOOT_SPL_NODTB_DATA = b'splnodtb with microcode pointer somewhere in here'
65U_BOOT_TPL_NODTB_DATA = b'tplnodtb with microcode pointer somewhere in here'
66U_BOOT_VPL_NODTB_DATA = b'vplnodtb'
67U_BOOT_EXP_DATA       = U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA
68U_BOOT_SPL_EXP_DATA   = U_BOOT_SPL_NODTB_DATA + U_BOOT_SPL_DTB_DATA
69U_BOOT_TPL_EXP_DATA   = U_BOOT_TPL_NODTB_DATA + U_BOOT_TPL_DTB_DATA
70FSP_DATA              = b'fsp'
71CMC_DATA              = b'cmc'
72VBT_DATA              = b'vbt'
73MRC_DATA              = b'mrc'
74TEXT_DATA             = 'text'
75TEXT_DATA2            = 'text2'
76TEXT_DATA3            = 'text3'
77CROS_EC_RW_DATA       = b'ecrw'
78GBB_DATA              = b'gbbd'
79BMPBLK_DATA           = b'bmp'
80VBLOCK_DATA           = b'vblk'
81FILES_DATA            = (b"sorry I'm late\nOh, don't bother apologising, I'm " +
82                         b"sorry you're alive\n")
83COMPRESS_DATA         = b'compress xxxxxxxxxxxxxxxxxxxxxx data'
84COMPRESS_DATA_BIG     = COMPRESS_DATA * 2
85REFCODE_DATA          = b'refcode'
86FSP_M_DATA            = b'fsp_m'
87FSP_S_DATA            = b'fsp_s'
88FSP_T_DATA            = b'fsp_t'
89ATF_BL31_DATA         = b'bl31'
90TEE_OS_DATA           = b'this is some tee OS data'
91TI_DM_DATA            = b'tidmtidm'
92ATF_BL2U_DATA         = b'bl2u'
93OPENSBI_DATA          = b'opensbi'
94SCP_DATA              = b'scp'
95ROCKCHIP_TPL_DATA     = b'rockchip-tpl'
96TEST_FDT1_DATA        = b'fdt1'
97TEST_FDT2_DATA        = b'test-fdt2'
98ENV_DATA              = b'var1=1\nvar2="2"'
99ENCRYPTED_IV_DATA     = b'123456'
100ENCRYPTED_KEY_DATA    = b'abcde'
101PRE_LOAD_MAGIC        = b'UBSH'
102PRE_LOAD_VERSION      = 0x11223344.to_bytes(4, 'big')
103PRE_LOAD_HDR_SIZE     = 0x00001000.to_bytes(4, 'big')
104TI_BOARD_CONFIG_DATA  = b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
105TI_UNSECURE_DATA      = b'unsecuredata'
106
107# Subdirectory of the input dir to use to put test FDTs
108TEST_FDT_SUBDIR       = 'fdts'
109
110# The expected size for the device tree in some tests
111EXTRACT_DTB_SIZE = 0x3c9
112
113# Properties expected to be in the device tree when update_dtb is used
114BASE_DTB_PROPS = ['offset', 'size', 'image-pos']
115
116# Extra properties expected to be in the device tree when allow-repack is used
117REPACK_DTB_PROPS = ['orig-offset', 'orig-size']
118
119# Supported compression bintools
120COMP_BINTOOLS = ['bzip2', 'gzip', 'lz4', 'lzma_alone', 'lzop', 'xz', 'zstd']
121
122TEE_ADDR = 0x5678
123
124# Firmware Management Protocol(FMP) GUID
125FW_MGMT_GUID = '6dcbd5ed-e82d-4c44-bda1-7194199ad92a'
126# Image GUID specified in the DTS
127CAPSULE_IMAGE_GUID = '09d7cf52-0720-4710-91d1-08469b7fe9c8'
128# Windows cert GUID
129WIN_CERT_TYPE_EFI_GUID = '4aafd29d-68df-49ee-8aa9-347d375665a7'
130# Empty capsule GUIDs
131EMPTY_CAPSULE_ACCEPT_GUID = '0c996046-bcc0-4d04-85ec-e1fcedf1c6f8'
132EMPTY_CAPSULE_REVERT_GUID = 'acd58b4b-c0e8-475f-99b5-6b3f7e07aaf0'
133
134class TestFunctional(unittest.TestCase):
135    """Functional tests for binman
136
137    Most of these use a sample .dts file to build an image and then check
138    that it looks correct. The sample files are in the test/ subdirectory
139    and are numbered.
140
141    For each entry type a very small test file is created using fixed
142    string contents. This makes it easy to test that things look right, and
143    debug problems.
144
145    In some cases a 'real' file must be used - these are also supplied in
146    the test/ diurectory.
147    """
148    @classmethod
149    def setUpClass(cls):
150        global entry
151        from binman import entry
152
153        # Handle the case where argv[0] is 'python'
154        cls._binman_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
155        cls._binman_pathname = os.path.join(cls._binman_dir, 'binman')
156
157        # Create a temporary directory for input files
158        cls._indir = tempfile.mkdtemp(prefix='binmant.')
159
160        # Create some test files
161        TestFunctional._MakeInputFile('u-boot.bin', U_BOOT_DATA)
162        TestFunctional._MakeInputFile('u-boot.img', U_BOOT_IMG_DATA)
163        TestFunctional._MakeInputFile('spl/u-boot-spl.bin', U_BOOT_SPL_DATA)
164        TestFunctional._MakeInputFile('tpl/u-boot-tpl.bin', U_BOOT_TPL_DATA)
165        TestFunctional._MakeInputFile('vpl/u-boot-vpl.bin', U_BOOT_VPL_DATA)
166        TestFunctional._MakeInputFile('blobfile', BLOB_DATA)
167        TestFunctional._MakeInputFile('me.bin', ME_DATA)
168        TestFunctional._MakeInputFile('vga.bin', VGA_DATA)
169        cls._ResetDtbs()
170
171        TestFunctional._MakeInputFile('u-boot-br.bin', PPC_MPC85XX_BR_DATA)
172
173        TestFunctional._MakeInputFile('u-boot-x86-start16.bin', X86_START16_DATA)
174        TestFunctional._MakeInputFile('spl/u-boot-x86-start16-spl.bin',
175                                      X86_START16_SPL_DATA)
176        TestFunctional._MakeInputFile('tpl/u-boot-x86-start16-tpl.bin',
177                                      X86_START16_TPL_DATA)
178
179        TestFunctional._MakeInputFile('u-boot-x86-reset16.bin',
180                                      X86_RESET16_DATA)
181        TestFunctional._MakeInputFile('spl/u-boot-x86-reset16-spl.bin',
182                                      X86_RESET16_SPL_DATA)
183        TestFunctional._MakeInputFile('tpl/u-boot-x86-reset16-tpl.bin',
184                                      X86_RESET16_TPL_DATA)
185
186        TestFunctional._MakeInputFile('u-boot-nodtb.bin', U_BOOT_NODTB_DATA)
187        TestFunctional._MakeInputFile('spl/u-boot-spl-nodtb.bin',
188                                      U_BOOT_SPL_NODTB_DATA)
189        TestFunctional._MakeInputFile('tpl/u-boot-tpl-nodtb.bin',
190                                      U_BOOT_TPL_NODTB_DATA)
191        TestFunctional._MakeInputFile('vpl/u-boot-vpl-nodtb.bin',
192                                      U_BOOT_VPL_NODTB_DATA)
193        TestFunctional._MakeInputFile('fsp.bin', FSP_DATA)
194        TestFunctional._MakeInputFile('cmc.bin', CMC_DATA)
195        TestFunctional._MakeInputFile('vbt.bin', VBT_DATA)
196        TestFunctional._MakeInputFile('mrc.bin', MRC_DATA)
197        TestFunctional._MakeInputFile('ecrw.bin', CROS_EC_RW_DATA)
198        TestFunctional._MakeInputDir('devkeys')
199        TestFunctional._MakeInputFile('bmpblk.bin', BMPBLK_DATA)
200        TestFunctional._MakeInputFile('refcode.bin', REFCODE_DATA)
201        TestFunctional._MakeInputFile('fsp_m.bin', FSP_M_DATA)
202        TestFunctional._MakeInputFile('fsp_s.bin', FSP_S_DATA)
203        TestFunctional._MakeInputFile('fsp_t.bin', FSP_T_DATA)
204
205        cls._elf_testdir = os.path.join(cls._indir, 'elftest')
206        elf_test.BuildElfTestFiles(cls._elf_testdir)
207
208        # ELF file with a '_dt_ucode_base_size' symbol
209        TestFunctional._MakeInputFile('u-boot',
210            tools.read_file(cls.ElfTestFile('u_boot_ucode_ptr')))
211
212        # Intel flash descriptor file
213        cls._SetupDescriptor()
214
215        shutil.copytree(cls.TestFile('files'),
216                        os.path.join(cls._indir, 'files'))
217
218        shutil.copytree(cls.TestFile('yaml'),
219                        os.path.join(cls._indir, 'yaml'))
220
221        TestFunctional._MakeInputFile('compress', COMPRESS_DATA)
222        TestFunctional._MakeInputFile('compress_big', COMPRESS_DATA_BIG)
223        TestFunctional._MakeInputFile('bl31.bin', ATF_BL31_DATA)
224        TestFunctional._MakeInputFile('tee-pager.bin', TEE_OS_DATA)
225        TestFunctional._MakeInputFile('dm.bin', TI_DM_DATA)
226        TestFunctional._MakeInputFile('bl2u.bin', ATF_BL2U_DATA)
227        TestFunctional._MakeInputFile('fw_dynamic.bin', OPENSBI_DATA)
228        TestFunctional._MakeInputFile('scp.bin', SCP_DATA)
229        TestFunctional._MakeInputFile('rockchip-tpl.bin', ROCKCHIP_TPL_DATA)
230        TestFunctional._MakeInputFile('ti_unsecure.bin', TI_UNSECURE_DATA)
231        TestFunctional._MakeInputFile('capsule_input.bin', EFI_CAPSULE_DATA)
232
233        # Add a few .dtb files for testing
234        TestFunctional._MakeInputFile('%s/test-fdt1.dtb' % TEST_FDT_SUBDIR,
235                                      TEST_FDT1_DATA)
236        TestFunctional._MakeInputFile('%s/test-fdt2.dtb' % TEST_FDT_SUBDIR,
237                                      TEST_FDT2_DATA)
238
239        TestFunctional._MakeInputFile('env.txt', ENV_DATA)
240
241        # ELF file with two sections in different parts of memory, used for both
242        # ATF and OP_TEE
243        TestFunctional._MakeInputFile('bl31.elf',
244            tools.read_file(cls.ElfTestFile('elf_sections')))
245        TestFunctional._MakeInputFile('tee.elf',
246            tools.read_file(cls.ElfTestFile('elf_sections')))
247
248        # Newer OP_TEE file in v1 binary format
249        cls.make_tee_bin('tee.bin')
250
251        # test files for encrypted tests
252        TestFunctional._MakeInputFile('encrypted-file.iv', ENCRYPTED_IV_DATA)
253        TestFunctional._MakeInputFile('encrypted-file.key', ENCRYPTED_KEY_DATA)
254
255        cls.comp_bintools = {}
256        for name in COMP_BINTOOLS:
257            cls.comp_bintools[name] = bintool.Bintool.create(name)
258
259    @classmethod
260    def tearDownClass(cls):
261        """Remove the temporary input directory and its contents"""
262        if cls.preserve_indir:
263            print('Preserving input dir: %s' % cls._indir)
264        else:
265            if cls._indir:
266                shutil.rmtree(cls._indir)
267        cls._indir = None
268
269    @classmethod
270    def setup_test_args(cls, preserve_indir=False, preserve_outdirs=False,
271                        toolpath=None, verbosity=None):
272        """Accept arguments controlling test execution
273
274        Args:
275            preserve_indir: Preserve the shared input directory used by all
276                tests in this class.
277            preserve_outdir: Preserve the output directories used by tests. Each
278                test has its own, so this is normally only useful when running a
279                single test.
280            toolpath: ist of paths to use for tools
281        """
282        cls.preserve_indir = preserve_indir
283        cls.preserve_outdirs = preserve_outdirs
284        cls.toolpath = toolpath
285        cls.verbosity = verbosity
286
287    def _CheckBintool(self, bintool):
288        if not bintool.is_present():
289            self.skipTest('%s not available' % bintool.name)
290
291    def _CheckLz4(self):
292        bintool = self.comp_bintools['lz4']
293        self._CheckBintool(bintool)
294
295    def _CleanupOutputDir(self):
296        """Remove the temporary output directory"""
297        if self.preserve_outdirs:
298            print('Preserving output dir: %s' % tools.outdir)
299        else:
300            tools._finalise_for_test()
301
302    def setUp(self):
303        # Enable this to turn on debugging output
304        # tout.init(tout.DEBUG)
305        command.test_result = None
306
307    def tearDown(self):
308        """Remove the temporary output directory"""
309        self._CleanupOutputDir()
310
311    def _SetupImageInTmpdir(self):
312        """Set up the output image in a new temporary directory
313
314        This is used when an image has been generated in the output directory,
315        but we want to run binman again. This will create a new output
316        directory and fail to delete the original one.
317
318        This creates a new temporary directory, copies the image to it (with a
319        new name) and removes the old output directory.
320
321        Returns:
322            Tuple:
323                Temporary directory to use
324                New image filename
325        """
326        image_fname = tools.get_output_filename('image.bin')
327        tmpdir = tempfile.mkdtemp(prefix='binman.')
328        updated_fname = os.path.join(tmpdir, 'image-updated.bin')
329        tools.write_file(updated_fname, tools.read_file(image_fname))
330        self._CleanupOutputDir()
331        return tmpdir, updated_fname
332
333    @classmethod
334    def _ResetDtbs(cls):
335        TestFunctional._MakeInputFile('u-boot.dtb', U_BOOT_DTB_DATA)
336        TestFunctional._MakeInputFile('spl/u-boot-spl.dtb', U_BOOT_SPL_DTB_DATA)
337        TestFunctional._MakeInputFile('tpl/u-boot-tpl.dtb', U_BOOT_TPL_DTB_DATA)
338        TestFunctional._MakeInputFile('vpl/u-boot-vpl.dtb', U_BOOT_VPL_DTB_DATA)
339
340    def _RunBinman(self, *args, **kwargs):
341        """Run binman using the command line
342
343        Args:
344            Arguments to pass, as a list of strings
345            kwargs: Arguments to pass to Command.RunPipe()
346        """
347        result = command.run_pipe([[self._binman_pathname] + list(args)],
348                capture=True, capture_stderr=True, raise_on_error=False)
349        if result.return_code and kwargs.get('raise_on_error', True):
350            raise Exception("Error running '%s': %s" % (' '.join(args),
351                            result.stdout + result.stderr))
352        return result
353
354    def _DoBinman(self, *argv):
355        """Run binman using directly (in the same process)
356
357        Args:
358            Arguments to pass, as a list of strings
359        Returns:
360            Return value (0 for success)
361        """
362        argv = list(argv)
363        args = cmdline.ParseArgs(argv)
364        args.pager = 'binman-invalid-pager'
365        args.build_dir = self._indir
366
367        # For testing, you can force an increase in verbosity here
368        # args.verbosity = tout.DEBUG
369        return control.Binman(args)
370
371    def _DoTestFile(self, fname, debug=False, map=False, update_dtb=False,
372                    entry_args=None, images=None, use_real_dtb=False,
373                    use_expanded=False, verbosity=None, allow_missing=False,
374                    allow_fake_blobs=False, extra_indirs=None, threads=None,
375                    test_section_timeout=False, update_fdt_in_elf=None,
376                    force_missing_bintools='', ignore_missing=False, output_dir=None):
377        """Run binman with a given test file
378
379        Args:
380            fname: Device-tree source filename to use (e.g. 005_simple.dts)
381            debug: True to enable debugging output
382            map: True to output map files for the images
383            update_dtb: Update the offset and size of each entry in the device
384                tree before packing it into the image
385            entry_args: Dict of entry args to supply to binman
386                key: arg name
387                value: value of that arg
388            images: List of image names to build
389            use_real_dtb: True to use the test file as the contents of
390                the u-boot-dtb entry. Normally this is not needed and the
391                test contents (the U_BOOT_DTB_DATA string) can be used.
392                But in some test we need the real contents.
393            use_expanded: True to use expanded entries where available, e.g.
394                'u-boot-expanded' instead of 'u-boot'
395            verbosity: Verbosity level to use (0-3, None=don't set it)
396            allow_missing: Set the '--allow-missing' flag so that missing
397                external binaries just produce a warning instead of an error
398            allow_fake_blobs: Set the '--fake-ext-blobs' flag
399            extra_indirs: Extra input directories to add using -I
400            threads: Number of threads to use (None for default, 0 for
401                single-threaded)
402            test_section_timeout: True to force the first time to timeout, as
403                used in testThreadTimeout()
404            update_fdt_in_elf: Value to pass with --update-fdt-in-elf=xxx
405            force_missing_tools (str): comma-separated list of bintools to
406                regard as missing
407            output_dir: Specific output directory to use for image using -O
408
409        Returns:
410            int return code, 0 on success
411        """
412        args = []
413        if debug:
414            args.append('-D')
415        if verbosity is not None:
416            args.append('-v%d' % verbosity)
417        elif self.verbosity:
418            args.append('-v%d' % self.verbosity)
419        if self.toolpath:
420            for path in self.toolpath:
421                args += ['--toolpath', path]
422        if threads is not None:
423            args.append('-T%d' % threads)
424        if test_section_timeout:
425            args.append('--test-section-timeout')
426        args += ['build', '-p', '-I', self._indir, '-d', self.TestFile(fname)]
427        if map:
428            args.append('-m')
429        if update_dtb:
430            args.append('-u')
431        if not use_real_dtb:
432            args.append('--fake-dtb')
433        if not use_expanded:
434            args.append('--no-expanded')
435        if entry_args:
436            for arg, value in entry_args.items():
437                args.append('-a%s=%s' % (arg, value))
438        if allow_missing:
439            args.append('-M')
440            if ignore_missing:
441                args.append('-W')
442        if allow_fake_blobs:
443            args.append('--fake-ext-blobs')
444        if force_missing_bintools:
445            args += ['--force-missing-bintools', force_missing_bintools]
446        if update_fdt_in_elf:
447            args += ['--update-fdt-in-elf', update_fdt_in_elf]
448        if images:
449            for image in images:
450                args += ['-i', image]
451        if extra_indirs:
452            for indir in extra_indirs:
453                args += ['-I', indir]
454        if output_dir:
455            args += ['-O', output_dir]
456        return self._DoBinman(*args)
457
458    def _SetupDtb(self, fname, outfile='u-boot.dtb'):
459        """Set up a new test device-tree file
460
461        The given file is compiled and set up as the device tree to be used
462        for ths test.
463
464        Args:
465            fname: Filename of .dts file to read
466            outfile: Output filename for compiled device-tree binary
467
468        Returns:
469            Contents of device-tree binary
470        """
471        tmpdir = tempfile.mkdtemp(prefix='binmant.')
472        dtb = fdt_util.EnsureCompiled(self.TestFile(fname), tmpdir)
473        with open(dtb, 'rb') as fd:
474            data = fd.read()
475            TestFunctional._MakeInputFile(outfile, data)
476        shutil.rmtree(tmpdir)
477        return data
478
479    def _GetDtbContentsForSpls(self, dtb_data, name):
480        """Create a version of the main DTB for SPL / TPL / VPL
481
482        For testing we don't actually have different versions of the DTB. With
483        U-Boot we normally run fdtgrep to remove unwanted nodes, but for tests
484        we don't normally have any unwanted nodes.
485
486        We still want the DTBs for SPL and TPL to be different though, since
487        otherwise it is confusing to know which one we are looking at. So add
488        an 'spl' or 'tpl' property to the top-level node.
489
490        Args:
491            dtb_data: dtb data to modify (this should be a value devicetree)
492            name: Name of a new property to add
493
494        Returns:
495            New dtb data with the property added
496        """
497        dtb = fdt.Fdt.FromData(dtb_data)
498        dtb.Scan()
499        dtb.GetNode('/binman').AddZeroProp(name)
500        dtb.Sync(auto_resize=True)
501        dtb.Pack()
502        return dtb.GetContents()
503
504    def _DoReadFileDtb(self, fname, use_real_dtb=False, use_expanded=False,
505                       map=False, update_dtb=False, entry_args=None,
506                       reset_dtbs=True, extra_indirs=None, threads=None):
507        """Run binman and return the resulting image
508
509        This runs binman with a given test file and then reads the resulting
510        output file. It is a shortcut function since most tests need to do
511        these steps.
512
513        Raises an assertion failure if binman returns a non-zero exit code.
514
515        Args:
516            fname: Device-tree source filename to use (e.g. 005_simple.dts)
517            use_real_dtb: True to use the test file as the contents of
518                the u-boot-dtb entry. Normally this is not needed and the
519                test contents (the U_BOOT_DTB_DATA string) can be used.
520                But in some test we need the real contents.
521            use_expanded: True to use expanded entries where available, e.g.
522                'u-boot-expanded' instead of 'u-boot'
523            map: True to output map files for the images
524            update_dtb: Update the offset and size of each entry in the device
525                tree before packing it into the image
526            entry_args: Dict of entry args to supply to binman
527                key: arg name
528                value: value of that arg
529            reset_dtbs: With use_real_dtb the test dtb is overwritten by this
530                function. If reset_dtbs is True, then the original test dtb
531                is written back before this function finishes
532            extra_indirs: Extra input directories to add using -I
533            threads: Number of threads to use (None for default, 0 for
534                single-threaded)
535
536        Returns:
537            Tuple:
538                Resulting image contents
539                Device tree contents
540                Map data showing contents of image (or None if none)
541                Output device tree binary filename ('u-boot.dtb' path)
542        """
543        dtb_data = None
544        # Use the compiled test file as the u-boot-dtb input
545        if use_real_dtb:
546            dtb_data = self._SetupDtb(fname)
547
548            # For testing purposes, make a copy of the DT for SPL and TPL. Add
549            # a node indicating which it is, so aid verification.
550            for name in ['spl', 'tpl', 'vpl']:
551                dtb_fname = '%s/u-boot-%s.dtb' % (name, name)
552                outfile = os.path.join(self._indir, dtb_fname)
553                TestFunctional._MakeInputFile(dtb_fname,
554                        self._GetDtbContentsForSpls(dtb_data, name))
555
556        try:
557            retcode = self._DoTestFile(fname, map=map, update_dtb=update_dtb,
558                    entry_args=entry_args, use_real_dtb=use_real_dtb,
559                    use_expanded=use_expanded, extra_indirs=extra_indirs,
560                    threads=threads)
561            self.assertEqual(0, retcode)
562            out_dtb_fname = tools.get_output_filename('u-boot.dtb.out')
563
564            # Find the (only) image, read it and return its contents
565            image = control.images['image']
566            image_fname = tools.get_output_filename('image.bin')
567            self.assertTrue(os.path.exists(image_fname))
568            if map:
569                map_fname = tools.get_output_filename('image.map')
570                with open(map_fname) as fd:
571                    map_data = fd.read()
572            else:
573                map_data = None
574            with open(image_fname, 'rb') as fd:
575                return fd.read(), dtb_data, map_data, out_dtb_fname
576        finally:
577            # Put the test file back
578            if reset_dtbs and use_real_dtb:
579                self._ResetDtbs()
580
581    def _DoReadFileRealDtb(self, fname):
582        """Run binman with a real .dtb file and return the resulting data
583
584        Args:
585            fname: DT source filename to use (e.g. 082_fdt_update_all.dts)
586
587        Returns:
588            Resulting image contents
589        """
590        return self._DoReadFileDtb(fname, use_real_dtb=True, update_dtb=True)[0]
591
592    def _DoReadFile(self, fname, use_real_dtb=False):
593        """Helper function which discards the device-tree binary
594
595        Args:
596            fname: Device-tree source filename to use (e.g. 005_simple.dts)
597            use_real_dtb: True to use the test file as the contents of
598                the u-boot-dtb entry. Normally this is not needed and the
599                test contents (the U_BOOT_DTB_DATA string) can be used.
600                But in some test we need the real contents.
601
602        Returns:
603            Resulting image contents
604        """
605        return self._DoReadFileDtb(fname, use_real_dtb)[0]
606
607    @classmethod
608    def _MakeInputFile(cls, fname, contents):
609        """Create a new test input file, creating directories as needed
610
611        Args:
612            fname: Filename to create
613            contents: File contents to write in to the file
614        Returns:
615            Full pathname of file created
616        """
617        pathname = os.path.join(cls._indir, fname)
618        dirname = os.path.dirname(pathname)
619        if dirname and not os.path.exists(dirname):
620            os.makedirs(dirname)
621        with open(pathname, 'wb') as fd:
622            fd.write(contents)
623        return pathname
624
625    @classmethod
626    def _MakeInputDir(cls, dirname):
627        """Create a new test input directory, creating directories as needed
628
629        Args:
630            dirname: Directory name to create
631
632        Returns:
633            Full pathname of directory created
634        """
635        pathname = os.path.join(cls._indir, dirname)
636        if not os.path.exists(pathname):
637            os.makedirs(pathname)
638        return pathname
639
640    @classmethod
641    def _SetupSplElf(cls, src_fname='bss_data'):
642        """Set up an ELF file with a '_dt_ucode_base_size' symbol
643
644        Args:
645            Filename of ELF file to use as SPL
646        """
647        TestFunctional._MakeInputFile('spl/u-boot-spl',
648            tools.read_file(cls.ElfTestFile(src_fname)))
649
650    @classmethod
651    def _SetupTplElf(cls, src_fname='bss_data'):
652        """Set up an ELF file with a '_dt_ucode_base_size' symbol
653
654        Args:
655            Filename of ELF file to use as TPL
656        """
657        TestFunctional._MakeInputFile('tpl/u-boot-tpl',
658            tools.read_file(cls.ElfTestFile(src_fname)))
659
660    @classmethod
661    def _SetupVplElf(cls, src_fname='bss_data'):
662        """Set up an ELF file with a '_dt_ucode_base_size' symbol
663
664        Args:
665            Filename of ELF file to use as VPL
666        """
667        TestFunctional._MakeInputFile('vpl/u-boot-vpl',
668            tools.read_file(cls.ElfTestFile(src_fname)))
669
670    @classmethod
671    def _SetupPmuFwlElf(cls, src_fname='bss_data'):
672        """Set up an ELF file with a '_dt_ucode_base_size' symbol
673
674        Args:
675            Filename of ELF file to use as VPL
676        """
677        TestFunctional._MakeInputFile('pmu-firmware.elf',
678            tools.read_file(cls.ElfTestFile(src_fname)))
679
680    @classmethod
681    def _SetupDescriptor(cls):
682        with open(cls.TestFile('descriptor.bin'), 'rb') as fd:
683            TestFunctional._MakeInputFile('descriptor.bin', fd.read())
684
685    @classmethod
686    def TestFile(cls, fname):
687        return os.path.join(cls._binman_dir, 'test', fname)
688
689    @classmethod
690    def ElfTestFile(cls, fname):
691        return os.path.join(cls._elf_testdir, fname)
692
693    @classmethod
694    def make_tee_bin(cls, fname, paged_sz=0, extra_data=b''):
695        init_sz, start_hi, start_lo, dummy = (len(U_BOOT_DATA), 0, TEE_ADDR, 0)
696        data = b'OPTE\x01xxx' + struct.pack('<5I', init_sz, start_hi, start_lo,
697                                            dummy, paged_sz) + U_BOOT_DATA
698        data += extra_data
699        TestFunctional._MakeInputFile(fname, data)
700
701    def AssertInList(self, grep_list, target):
702        """Assert that at least one of a list of things is in a target
703
704        Args:
705            grep_list: List of strings to check
706            target: Target string
707        """
708        for grep in grep_list:
709            if grep in target:
710                return
711        self.fail("Error: '%s' not found in '%s'" % (grep_list, target))
712
713    def CheckNoGaps(self, entries):
714        """Check that all entries fit together without gaps
715
716        Args:
717            entries: List of entries to check
718        """
719        offset = 0
720        for entry in entries.values():
721            self.assertEqual(offset, entry.offset)
722            offset += entry.size
723
724    def GetFdtLen(self, dtb):
725        """Get the totalsize field from a device-tree binary
726
727        Args:
728            dtb: Device-tree binary contents
729
730        Returns:
731            Total size of device-tree binary, from the header
732        """
733        return struct.unpack('>L', dtb[4:8])[0]
734
735    def _GetPropTree(self, dtb, prop_names, prefix='/binman/'):
736        def AddNode(node, path):
737            if node.name != '/':
738                path += '/' + node.name
739            for prop in node.props.values():
740                if prop.name in prop_names:
741                    prop_path = path + ':' + prop.name
742                    tree[prop_path[len(prefix):]] = fdt_util.fdt32_to_cpu(
743                        prop.value)
744            for subnode in node.subnodes:
745                AddNode(subnode, path)
746
747        tree = {}
748        AddNode(dtb.GetRoot(), '')
749        return tree
750
751    def _CheckSign(self, fit, key):
752        try:
753            tools.run('fit_check_sign', '-k', key, '-f', fit)
754        except:
755            self.fail('Expected signed FIT container')
756            return False
757        return True
758
759    def testRun(self):
760        """Test a basic run with valid args"""
761        result = self._RunBinman('-h')
762
763    def testFullHelp(self):
764        """Test that the full help is displayed with -H"""
765        result = self._RunBinman('-H')
766        help_file = os.path.join(self._binman_dir, 'README.rst')
767        # Remove possible extraneous strings
768        extra = '::::::::::::::\n' + help_file + '\n::::::::::::::\n'
769        gothelp = result.stdout.replace(extra, '')
770        self.assertEqual(len(gothelp), os.path.getsize(help_file))
771        self.assertEqual(0, len(result.stderr))
772        self.assertEqual(0, result.return_code)
773
774    def testFullHelpInternal(self):
775        """Test that the full help is displayed with -H"""
776        try:
777            command.test_result = command.CommandResult()
778            result = self._DoBinman('-H')
779            help_file = os.path.join(self._binman_dir, 'README.rst')
780        finally:
781            command.test_result = None
782
783    def testHelp(self):
784        """Test that the basic help is displayed with -h"""
785        result = self._RunBinman('-h')
786        self.assertTrue(len(result.stdout) > 200)
787        self.assertEqual(0, len(result.stderr))
788        self.assertEqual(0, result.return_code)
789
790    def testBoard(self):
791        """Test that we can run it with a specific board"""
792        self._SetupDtb('005_simple.dts', 'sandbox/u-boot.dtb')
793        TestFunctional._MakeInputFile('sandbox/u-boot.bin', U_BOOT_DATA)
794        result = self._DoBinman('build', '-n', '-b', 'sandbox')
795        self.assertEqual(0, result)
796
797    def testNeedBoard(self):
798        """Test that we get an error when no board ius supplied"""
799        with self.assertRaises(ValueError) as e:
800            result = self._DoBinman('build')
801        self.assertIn("Must provide a board to process (use -b <board>)",
802                str(e.exception))
803
804    def testMissingDt(self):
805        """Test that an invalid device-tree file generates an error"""
806        with self.assertRaises(Exception) as e:
807            self._RunBinman('build', '-d', 'missing_file')
808        # We get one error from libfdt, and a different one from fdtget.
809        self.AssertInList(["Couldn't open blob from 'missing_file'",
810                           'No such file or directory'], str(e.exception))
811
812    def testBrokenDt(self):
813        """Test that an invalid device-tree source file generates an error
814
815        Since this is a source file it should be compiled and the error
816        will come from the device-tree compiler (dtc).
817        """
818        with self.assertRaises(Exception) as e:
819            self._RunBinman('build', '-d', self.TestFile('001_invalid.dts'))
820        self.assertIn("FATAL ERROR: Unable to parse input tree",
821                str(e.exception))
822
823    def testMissingNode(self):
824        """Test that a device tree without a 'binman' node generates an error"""
825        with self.assertRaises(Exception) as e:
826            self._DoBinman('build', '-d', self.TestFile('002_missing_node.dts'))
827        self.assertIn("does not have a 'binman' node", str(e.exception))
828
829    def testEmpty(self):
830        """Test that an empty binman node works OK (i.e. does nothing)"""
831        result = self._RunBinman('build', '-d', self.TestFile('003_empty.dts'))
832        self.assertEqual(0, len(result.stderr))
833        self.assertEqual(0, result.return_code)
834
835    def testInvalidEntry(self):
836        """Test that an invalid entry is flagged"""
837        with self.assertRaises(Exception) as e:
838            result = self._RunBinman('build', '-d',
839                                     self.TestFile('004_invalid_entry.dts'))
840        self.assertIn("Unknown entry type 'not-a-valid-type' in node "
841                "'/binman/not-a-valid-type'", str(e.exception))
842
843    def testSimple(self):
844        """Test a simple binman with a single file"""
845        data = self._DoReadFile('005_simple.dts')
846        self.assertEqual(U_BOOT_DATA, data)
847
848    def testSimpleDebug(self):
849        """Test a simple binman run with debugging enabled"""
850        self._DoTestFile('005_simple.dts', debug=True)
851
852    def testDual(self):
853        """Test that we can handle creating two images
854
855        This also tests image padding.
856        """
857        retcode = self._DoTestFile('006_dual_image.dts')
858        self.assertEqual(0, retcode)
859
860        image = control.images['image1']
861        self.assertEqual(len(U_BOOT_DATA), image.size)
862        fname = tools.get_output_filename('image1.bin')
863        self.assertTrue(os.path.exists(fname))
864        with open(fname, 'rb') as fd:
865            data = fd.read()
866            self.assertEqual(U_BOOT_DATA, data)
867
868        image = control.images['image2']
869        self.assertEqual(3 + len(U_BOOT_DATA) + 5, image.size)
870        fname = tools.get_output_filename('image2.bin')
871        self.assertTrue(os.path.exists(fname))
872        with open(fname, 'rb') as fd:
873            data = fd.read()
874            self.assertEqual(U_BOOT_DATA, data[3:7])
875            self.assertEqual(tools.get_bytes(0, 3), data[:3])
876            self.assertEqual(tools.get_bytes(0, 5), data[7:])
877
878    def testBadAlign(self):
879        """Test that an invalid alignment value is detected"""
880        with self.assertRaises(ValueError) as e:
881            self._DoTestFile('007_bad_align.dts')
882        self.assertIn("Node '/binman/u-boot': Alignment 23 must be a power "
883                      "of two", str(e.exception))
884
885    def testPackSimple(self):
886        """Test that packing works as expected"""
887        retcode = self._DoTestFile('008_pack.dts')
888        self.assertEqual(0, retcode)
889        self.assertIn('image', control.images)
890        image = control.images['image']
891        entries = image.GetEntries()
892        self.assertEqual(5, len(entries))
893
894        # First u-boot
895        self.assertIn('u-boot', entries)
896        entry = entries['u-boot']
897        self.assertEqual(0, entry.offset)
898        self.assertEqual(len(U_BOOT_DATA), entry.size)
899
900        # Second u-boot, aligned to 16-byte boundary
901        self.assertIn('u-boot-align', entries)
902        entry = entries['u-boot-align']
903        self.assertEqual(16, entry.offset)
904        self.assertEqual(len(U_BOOT_DATA), entry.size)
905
906        # Third u-boot, size 23 bytes
907        self.assertIn('u-boot-size', entries)
908        entry = entries['u-boot-size']
909        self.assertEqual(20, entry.offset)
910        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
911        self.assertEqual(23, entry.size)
912
913        # Fourth u-boot, placed immediate after the above
914        self.assertIn('u-boot-next', entries)
915        entry = entries['u-boot-next']
916        self.assertEqual(43, entry.offset)
917        self.assertEqual(len(U_BOOT_DATA), entry.size)
918
919        # Fifth u-boot, placed at a fixed offset
920        self.assertIn('u-boot-fixed', entries)
921        entry = entries['u-boot-fixed']
922        self.assertEqual(61, entry.offset)
923        self.assertEqual(len(U_BOOT_DATA), entry.size)
924
925        self.assertEqual(65, image.size)
926
927    def testPackExtra(self):
928        """Test that extra packing feature works as expected"""
929        data, _, _, out_dtb_fname = self._DoReadFileDtb('009_pack_extra.dts',
930                                                        update_dtb=True)
931
932        self.assertIn('image', control.images)
933        image = control.images['image']
934        entries = image.GetEntries()
935        self.assertEqual(6, len(entries))
936
937        # First u-boot with padding before and after (included in minimum size)
938        self.assertIn('u-boot', entries)
939        entry = entries['u-boot']
940        self.assertEqual(0, entry.offset)
941        self.assertEqual(3, entry.pad_before)
942        self.assertEqual(3 + 5 + len(U_BOOT_DATA), entry.size)
943        self.assertEqual(U_BOOT_DATA, entry.data)
944        self.assertEqual(tools.get_bytes(0, 3) + U_BOOT_DATA +
945                         tools.get_bytes(0, 5), data[:entry.size])
946        pos = entry.size
947
948        # Second u-boot has an aligned size, but it has no effect
949        self.assertIn('u-boot-align-size-nop', entries)
950        entry = entries['u-boot-align-size-nop']
951        self.assertEqual(pos, entry.offset)
952        self.assertEqual(len(U_BOOT_DATA), entry.size)
953        self.assertEqual(U_BOOT_DATA, entry.data)
954        self.assertEqual(U_BOOT_DATA, data[pos:pos + entry.size])
955        pos += entry.size
956
957        # Third u-boot has an aligned size too
958        self.assertIn('u-boot-align-size', entries)
959        entry = entries['u-boot-align-size']
960        self.assertEqual(pos, entry.offset)
961        self.assertEqual(32, entry.size)
962        self.assertEqual(U_BOOT_DATA, entry.data)
963        self.assertEqual(U_BOOT_DATA + tools.get_bytes(0, 32 - len(U_BOOT_DATA)),
964                         data[pos:pos + entry.size])
965        pos += entry.size
966
967        # Fourth u-boot has an aligned end
968        self.assertIn('u-boot-align-end', entries)
969        entry = entries['u-boot-align-end']
970        self.assertEqual(48, entry.offset)
971        self.assertEqual(16, entry.size)
972        self.assertEqual(U_BOOT_DATA, entry.data[:len(U_BOOT_DATA)])
973        self.assertEqual(U_BOOT_DATA + tools.get_bytes(0, 16 - len(U_BOOT_DATA)),
974                         data[pos:pos + entry.size])
975        pos += entry.size
976
977        # Fifth u-boot immediately afterwards
978        self.assertIn('u-boot-align-both', entries)
979        entry = entries['u-boot-align-both']
980        self.assertEqual(64, entry.offset)
981        self.assertEqual(64, entry.size)
982        self.assertEqual(U_BOOT_DATA, entry.data[:len(U_BOOT_DATA)])
983        self.assertEqual(U_BOOT_DATA + tools.get_bytes(0, 64 - len(U_BOOT_DATA)),
984                         data[pos:pos + entry.size])
985
986        # Sixth u-boot with both minimum size and aligned size
987        self.assertIn('u-boot-min-size', entries)
988        entry = entries['u-boot-min-size']
989        self.assertEqual(128, entry.offset)
990        self.assertEqual(32, entry.size)
991        self.assertEqual(U_BOOT_DATA, entry.data[:len(U_BOOT_DATA)])
992        self.assertEqual(U_BOOT_DATA + tools.get_bytes(0, 32 - len(U_BOOT_DATA)),
993                         data[pos:pos + entry.size])
994
995        self.CheckNoGaps(entries)
996        self.assertEqual(160, image.size)
997
998        dtb = fdt.Fdt(out_dtb_fname)
999        dtb.Scan()
1000        props = self._GetPropTree(dtb, ['size', 'offset', 'image-pos'])
1001        expected = {
1002            'image-pos': 0,
1003            'offset': 0,
1004            'size': 160,
1005
1006            'u-boot:image-pos': 0,
1007            'u-boot:offset': 0,
1008            'u-boot:size': 3 + 5 + len(U_BOOT_DATA),
1009
1010            'u-boot-align-size-nop:image-pos': 12,
1011            'u-boot-align-size-nop:offset': 12,
1012            'u-boot-align-size-nop:size': 4,
1013
1014            'u-boot-align-size:image-pos': 16,
1015            'u-boot-align-size:offset': 16,
1016            'u-boot-align-size:size': 32,
1017
1018            'u-boot-align-end:image-pos': 48,
1019            'u-boot-align-end:offset': 48,
1020            'u-boot-align-end:size': 16,
1021
1022            'u-boot-align-both:image-pos': 64,
1023            'u-boot-align-both:offset': 64,
1024            'u-boot-align-both:size': 64,
1025
1026            'u-boot-min-size:image-pos': 128,
1027            'u-boot-min-size:offset': 128,
1028            'u-boot-min-size:size': 32,
1029            }
1030        self.assertEqual(expected, props)
1031
1032    def testPackAlignPowerOf2(self):
1033        """Test that invalid entry alignment is detected"""
1034        with self.assertRaises(ValueError) as e:
1035            self._DoTestFile('010_pack_align_power2.dts')
1036        self.assertIn("Node '/binman/u-boot': Alignment 5 must be a power "
1037                      "of two", str(e.exception))
1038
1039    def testPackAlignSizePowerOf2(self):
1040        """Test that invalid entry size alignment is detected"""
1041        with self.assertRaises(ValueError) as e:
1042            self._DoTestFile('011_pack_align_size_power2.dts')
1043        self.assertIn("Node '/binman/u-boot': Alignment size 55 must be a "
1044                      "power of two", str(e.exception))
1045
1046    def testPackInvalidAlign(self):
1047        """Test detection of an offset that does not match its alignment"""
1048        with self.assertRaises(ValueError) as e:
1049            self._DoTestFile('012_pack_inv_align.dts')
1050        self.assertIn("Node '/binman/u-boot': Offset 0x5 (5) does not match "
1051                      "align 0x4 (4)", str(e.exception))
1052
1053    def testPackInvalidSizeAlign(self):
1054        """Test that invalid entry size alignment is detected"""
1055        with self.assertRaises(ValueError) as e:
1056            self._DoTestFile('013_pack_inv_size_align.dts')
1057        self.assertIn("Node '/binman/u-boot': Size 0x5 (5) does not match "
1058                      "align-size 0x4 (4)", str(e.exception))
1059
1060    def testPackOverlap(self):
1061        """Test that overlapping regions are detected"""
1062        with self.assertRaises(ValueError) as e:
1063            self._DoTestFile('014_pack_overlap.dts')
1064        self.assertIn("Node '/binman/u-boot-align': Offset 0x3 (3) overlaps "
1065                      "with previous entry '/binman/u-boot' ending at 0x4 (4)",
1066                      str(e.exception))
1067
1068    def testPackEntryOverflow(self):
1069        """Test that entries that overflow their size are detected"""
1070        with self.assertRaises(ValueError) as e:
1071            self._DoTestFile('015_pack_overflow.dts')
1072        self.assertIn("Node '/binman/u-boot': Entry contents size is 0x4 (4) "
1073                      "but entry size is 0x3 (3)", str(e.exception))
1074
1075    def testPackImageOverflow(self):
1076        """Test that entries which overflow the image size are detected"""
1077        with self.assertRaises(ValueError) as e:
1078            self._DoTestFile('016_pack_image_overflow.dts')
1079        self.assertIn("Section '/binman': contents size 0x4 (4) exceeds section "
1080                      "size 0x3 (3)", str(e.exception))
1081
1082    def testPackImageSize(self):
1083        """Test that the image size can be set"""
1084        retcode = self._DoTestFile('017_pack_image_size.dts')
1085        self.assertEqual(0, retcode)
1086        self.assertIn('image', control.images)
1087        image = control.images['image']
1088        self.assertEqual(7, image.size)
1089
1090    def testPackImageSizeAlign(self):
1091        """Test that image size alignemnt works as expected"""
1092        retcode = self._DoTestFile('018_pack_image_align.dts')
1093        self.assertEqual(0, retcode)
1094        self.assertIn('image', control.images)
1095        image = control.images['image']
1096        self.assertEqual(16, image.size)
1097
1098    def testPackInvalidImageAlign(self):
1099        """Test that invalid image alignment is detected"""
1100        with self.assertRaises(ValueError) as e:
1101            self._DoTestFile('019_pack_inv_image_align.dts')
1102        self.assertIn("Section '/binman': Size 0x7 (7) does not match "
1103                      "align-size 0x8 (8)", str(e.exception))
1104
1105    def testPackAlignPowerOf2Inv(self):
1106        """Test that invalid image alignment is detected"""
1107        with self.assertRaises(ValueError) as e:
1108            self._DoTestFile('020_pack_inv_image_align_power2.dts')
1109        self.assertIn("Image '/binman': Alignment size 131 must be a power of "
1110                      "two", str(e.exception))
1111
1112    def testImagePadByte(self):
1113        """Test that the image pad byte can be specified"""
1114        self._SetupSplElf()
1115        data = self._DoReadFile('021_image_pad.dts')
1116        self.assertEqual(U_BOOT_SPL_DATA + tools.get_bytes(0xff, 1) +
1117                         U_BOOT_DATA, data)
1118
1119    def testImageName(self):
1120        """Test that image files can be named"""
1121        retcode = self._DoTestFile('022_image_name.dts')
1122        self.assertEqual(0, retcode)
1123        image = control.images['image1']
1124        fname = tools.get_output_filename('test-name')
1125        self.assertTrue(os.path.exists(fname))
1126
1127        image = control.images['image2']
1128        fname = tools.get_output_filename('test-name.xx')
1129        self.assertTrue(os.path.exists(fname))
1130
1131    def testBlobFilename(self):
1132        """Test that generic blobs can be provided by filename"""
1133        data = self._DoReadFile('023_blob.dts')
1134        self.assertEqual(BLOB_DATA, data)
1135
1136    def testPackSorted(self):
1137        """Test that entries can be sorted"""
1138        self._SetupSplElf()
1139        data = self._DoReadFile('024_sorted.dts')
1140        self.assertEqual(tools.get_bytes(0, 1) + U_BOOT_SPL_DATA +
1141                         tools.get_bytes(0, 2) + U_BOOT_DATA, data)
1142
1143    def testPackZeroOffset(self):
1144        """Test that an entry at offset 0 is not given a new offset"""
1145        self._SetupSplElf()
1146        with self.assertRaises(ValueError) as e:
1147            self._DoTestFile('025_pack_zero_size.dts')
1148        self.assertIn("Node '/binman/u-boot-spl': Offset 0x0 (0) overlaps "
1149                      "with previous entry '/binman/u-boot' ending at 0x4 (4)",
1150                      str(e.exception))
1151
1152    def testPackUbootDtb(self):
1153        """Test that a device tree can be added to U-Boot"""
1154        data = self._DoReadFile('026_pack_u_boot_dtb.dts')
1155        self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA, data)
1156
1157    def testPackX86RomNoSize(self):
1158        """Test that the end-at-4gb property requires a size property"""
1159        self._SetupSplElf()
1160        with self.assertRaises(ValueError) as e:
1161            self._DoTestFile('027_pack_4gb_no_size.dts')
1162        self.assertIn("Image '/binman': Section size must be provided when "
1163                      "using end-at-4gb", str(e.exception))
1164
1165    def test4gbAndSkipAtStartTogether(self):
1166        """Test that the end-at-4gb and skip-at-size property can't be used
1167        together"""
1168        self._SetupSplElf()
1169        with self.assertRaises(ValueError) as e:
1170            self._DoTestFile('098_4gb_and_skip_at_start_together.dts')
1171        self.assertIn("Image '/binman': Provide either 'end-at-4gb' or "
1172                      "'skip-at-start'", str(e.exception))
1173
1174    def testPackX86RomOutside(self):
1175        """Test that the end-at-4gb property checks for offset boundaries"""
1176        self._SetupSplElf()
1177        with self.assertRaises(ValueError) as e:
1178            self._DoTestFile('028_pack_4gb_outside.dts')
1179        self.assertIn("Node '/binman/u-boot': Offset 0x0 (0) size 0x4 (4) "
1180                      "is outside the section '/binman' starting at "
1181                      '0xffffffe0 (4294967264) of size 0x20 (32)',
1182                      str(e.exception))
1183
1184    def testPackX86Rom(self):
1185        """Test that a basic x86 ROM can be created"""
1186        self._SetupSplElf()
1187        data = self._DoReadFile('029_x86_rom.dts')
1188        self.assertEqual(U_BOOT_DATA + tools.get_bytes(0, 3) + U_BOOT_SPL_DATA +
1189                         tools.get_bytes(0, 2), data)
1190
1191    def testPackX86RomMeNoDesc(self):
1192        """Test that an invalid Intel descriptor entry is detected"""
1193        try:
1194            TestFunctional._MakeInputFile('descriptor-empty.bin', b'')
1195            with self.assertRaises(ValueError) as e:
1196                self._DoTestFile('163_x86_rom_me_empty.dts')
1197            self.assertIn("Node '/binman/intel-descriptor': Cannot find Intel Flash Descriptor (FD) signature",
1198                          str(e.exception))
1199        finally:
1200            self._SetupDescriptor()
1201
1202    def testPackX86RomBadDesc(self):
1203        """Test that the Intel requires a descriptor entry"""
1204        with self.assertRaises(ValueError) as e:
1205            self._DoTestFile('030_x86_rom_me_no_desc.dts')
1206        self.assertIn("Node '/binman/intel-me': No offset set with "
1207                      "offset-unset: should another entry provide this correct "
1208                      "offset?", str(e.exception))
1209
1210    def testPackX86RomMe(self):
1211        """Test that an x86 ROM with an ME region can be created"""
1212        data = self._DoReadFile('031_x86_rom_me.dts')
1213        expected_desc = tools.read_file(self.TestFile('descriptor.bin'))
1214        if data[:0x1000] != expected_desc:
1215            self.fail('Expected descriptor binary at start of image')
1216        self.assertEqual(ME_DATA, data[0x1000:0x1000 + len(ME_DATA)])
1217
1218    def testPackVga(self):
1219        """Test that an image with a VGA binary can be created"""
1220        data = self._DoReadFile('032_intel_vga.dts')
1221        self.assertEqual(VGA_DATA, data[:len(VGA_DATA)])
1222
1223    def testPackStart16(self):
1224        """Test that an image with an x86 start16 region can be created"""
1225        data = self._DoReadFile('033_x86_start16.dts')
1226        self.assertEqual(X86_START16_DATA, data[:len(X86_START16_DATA)])
1227
1228    def testPackPowerpcMpc85xxBootpgResetvec(self):
1229        """Test that an image with powerpc-mpc85xx-bootpg-resetvec can be
1230        created"""
1231        data = self._DoReadFile('150_powerpc_mpc85xx_bootpg_resetvec.dts')
1232        self.assertEqual(PPC_MPC85XX_BR_DATA, data[:len(PPC_MPC85XX_BR_DATA)])
1233
1234    def _RunMicrocodeTest(self, dts_fname, nodtb_data, ucode_second=False):
1235        """Handle running a test for insertion of microcode
1236
1237        Args:
1238            dts_fname: Name of test .dts file
1239            nodtb_data: Data that we expect in the first section
1240            ucode_second: True if the microsecond entry is second instead of
1241                third
1242
1243        Returns:
1244            Tuple:
1245                Contents of first region (U-Boot or SPL)
1246                Offset and size components of microcode pointer, as inserted
1247                    in the above (two 4-byte words)
1248        """
1249        data = self._DoReadFile(dts_fname, True)
1250
1251        # Now check the device tree has no microcode
1252        if ucode_second:
1253            ucode_content = data[len(nodtb_data):]
1254            ucode_pos = len(nodtb_data)
1255            dtb_with_ucode = ucode_content[16:]
1256            fdt_len = self.GetFdtLen(dtb_with_ucode)
1257        else:
1258            dtb_with_ucode = data[len(nodtb_data):]
1259            fdt_len = self.GetFdtLen(dtb_with_ucode)
1260            ucode_content = dtb_with_ucode[fdt_len:]
1261            ucode_pos = len(nodtb_data) + fdt_len
1262        fname = tools.get_output_filename('test.dtb')
1263        with open(fname, 'wb') as fd:
1264            fd.write(dtb_with_ucode)
1265        dtb = fdt.FdtScan(fname)
1266        ucode = dtb.GetNode('/microcode')
1267        self.assertTrue(ucode)
1268        for node in ucode.subnodes:
1269            self.assertFalse(node.props.get('data'))
1270
1271        # Check that the microcode appears immediately after the Fdt
1272        # This matches the concatenation of the data properties in
1273        # the /microcode/update@xxx nodes in 34_x86_ucode.dts.
1274        ucode_data = struct.pack('>4L', 0x12345678, 0x12345679, 0xabcd0000,
1275                                 0x78235609)
1276        self.assertEqual(ucode_data, ucode_content[:len(ucode_data)])
1277
1278        # Check that the microcode pointer was inserted. It should match the
1279        # expected offset and size
1280        pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
1281                                   len(ucode_data))
1282        u_boot = data[:len(nodtb_data)]
1283        return u_boot, pos_and_size
1284
1285    def testPackUbootMicrocode(self):
1286        """Test that x86 microcode can be handled correctly
1287
1288        We expect to see the following in the image, in order:
1289            u-boot-nodtb.bin with a microcode pointer inserted at the correct
1290                place
1291            u-boot.dtb with the microcode removed
1292            the microcode
1293        """
1294        first, pos_and_size = self._RunMicrocodeTest('034_x86_ucode.dts',
1295                                                     U_BOOT_NODTB_DATA)
1296        self.assertEqual(b'nodtb with microcode' + pos_and_size +
1297                         b' somewhere in here', first)
1298
1299    def _RunPackUbootSingleMicrocode(self):
1300        """Test that x86 microcode can be handled correctly
1301
1302        We expect to see the following in the image, in order:
1303            u-boot-nodtb.bin with a microcode pointer inserted at the correct
1304                place
1305            u-boot.dtb with the microcode
1306            an empty microcode region
1307        """
1308        # We need the libfdt library to run this test since only that allows
1309        # finding the offset of a property. This is required by
1310        # Entry_u_boot_dtb_with_ucode.ObtainContents().
1311        data = self._DoReadFile('035_x86_single_ucode.dts', True)
1312
1313        second = data[len(U_BOOT_NODTB_DATA):]
1314
1315        fdt_len = self.GetFdtLen(second)
1316        third = second[fdt_len:]
1317        second = second[:fdt_len]
1318
1319        ucode_data = struct.pack('>2L', 0x12345678, 0x12345679)
1320        self.assertIn(ucode_data, second)
1321        ucode_pos = second.find(ucode_data) + len(U_BOOT_NODTB_DATA)
1322
1323        # Check that the microcode pointer was inserted. It should match the
1324        # expected offset and size
1325        pos_and_size = struct.pack('<2L', 0xfffffe00 + ucode_pos,
1326                                   len(ucode_data))
1327        first = data[:len(U_BOOT_NODTB_DATA)]
1328        self.assertEqual(b'nodtb with microcode' + pos_and_size +
1329                         b' somewhere in here', first)
1330
1331    def testPackUbootSingleMicrocode(self):
1332        """Test that x86 microcode can be handled correctly with fdt_normal.
1333        """
1334        self._RunPackUbootSingleMicrocode()
1335
1336    def testUBootImg(self):
1337        """Test that u-boot.img can be put in a file"""
1338        data = self._DoReadFile('036_u_boot_img.dts')
1339        self.assertEqual(U_BOOT_IMG_DATA, data)
1340
1341    def testNoMicrocode(self):
1342        """Test that a missing microcode region is detected"""
1343        with self.assertRaises(ValueError) as e:
1344            self._DoReadFile('037_x86_no_ucode.dts', True)
1345        self.assertIn("Node '/binman/u-boot-dtb-with-ucode': No /microcode "
1346                      "node found in ", str(e.exception))
1347
1348    def testMicrocodeWithoutNode(self):
1349        """Test that a missing u-boot-dtb-with-ucode node is detected"""
1350        with self.assertRaises(ValueError) as e:
1351            self._DoReadFile('038_x86_ucode_missing_node.dts', True)
1352        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
1353                "microcode region u-boot-dtb-with-ucode", str(e.exception))
1354
1355    def testMicrocodeWithoutNode2(self):
1356        """Test that a missing u-boot-ucode node is detected"""
1357        with self.assertRaises(ValueError) as e:
1358            self._DoReadFile('039_x86_ucode_missing_node2.dts', True)
1359        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot find "
1360            "microcode region u-boot-ucode", str(e.exception))
1361
1362    def testMicrocodeWithoutPtrInElf(self):
1363        """Test that a U-Boot binary without the microcode symbol is detected"""
1364        # ELF file without a '_dt_ucode_base_size' symbol
1365        try:
1366            TestFunctional._MakeInputFile('u-boot',
1367                tools.read_file(self.ElfTestFile('u_boot_no_ucode_ptr')))
1368
1369            with self.assertRaises(ValueError) as e:
1370                self._RunPackUbootSingleMicrocode()
1371            self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Cannot locate "
1372                    "_dt_ucode_base_size symbol in u-boot", str(e.exception))
1373
1374        finally:
1375            # Put the original file back
1376            TestFunctional._MakeInputFile('u-boot',
1377                tools.read_file(self.ElfTestFile('u_boot_ucode_ptr')))
1378
1379    def testMicrocodeNotInImage(self):
1380        """Test that microcode must be placed within the image"""
1381        with self.assertRaises(ValueError) as e:
1382            self._DoReadFile('040_x86_ucode_not_in_image.dts', True)
1383        self.assertIn("Node '/binman/u-boot-with-ucode-ptr': Microcode "
1384                "pointer _dt_ucode_base_size at fffffe14 is outside the "
1385                "section ranging from 00000000 to 0000002e", str(e.exception))
1386
1387    def testWithoutMicrocode(self):
1388        """Test that we can cope with an image without microcode (e.g. qemu)"""
1389        TestFunctional._MakeInputFile('u-boot',
1390            tools.read_file(self.ElfTestFile('u_boot_no_ucode_ptr')))
1391        data, dtb, _, _ = self._DoReadFileDtb('044_x86_optional_ucode.dts', True)
1392
1393        # Now check the device tree has no microcode
1394        self.assertEqual(U_BOOT_NODTB_DATA, data[:len(U_BOOT_NODTB_DATA)])
1395        second = data[len(U_BOOT_NODTB_DATA):]
1396
1397        fdt_len = self.GetFdtLen(second)
1398        self.assertEqual(dtb, second[:fdt_len])
1399
1400        used_len = len(U_BOOT_NODTB_DATA) + fdt_len
1401        third = data[used_len:]
1402        self.assertEqual(tools.get_bytes(0, 0x200 - used_len), third)
1403
1404    def testUnknownPosSize(self):
1405        """Test that microcode must be placed within the image"""
1406        with self.assertRaises(ValueError) as e:
1407            self._DoReadFile('041_unknown_pos_size.dts', True)
1408        self.assertIn("Section '/binman': Unable to set offset/size for unknown "
1409                "entry 'invalid-entry'", str(e.exception))
1410
1411    def testPackFsp(self):
1412        """Test that an image with a FSP binary can be created"""
1413        data = self._DoReadFile('042_intel_fsp.dts')
1414        self.assertEqual(FSP_DATA, data[:len(FSP_DATA)])
1415
1416    def testPackCmc(self):
1417        """Test that an image with a CMC binary can be created"""
1418        data = self._DoReadFile('043_intel_cmc.dts')
1419        self.assertEqual(CMC_DATA, data[:len(CMC_DATA)])
1420
1421    def testPackVbt(self):
1422        """Test that an image with a VBT binary can be created"""
1423        data = self._DoReadFile('046_intel_vbt.dts')
1424        self.assertEqual(VBT_DATA, data[:len(VBT_DATA)])
1425
1426    def testSplBssPad(self):
1427        """Test that we can pad SPL's BSS with zeros"""
1428        # ELF file with a '__bss_size' symbol
1429        self._SetupSplElf()
1430        data = self._DoReadFile('047_spl_bss_pad.dts')
1431        self.assertEqual(U_BOOT_SPL_DATA + tools.get_bytes(0, 10) + U_BOOT_DATA,
1432                         data)
1433
1434    def testSplBssPadMissing(self):
1435        """Test that a missing symbol is detected"""
1436        self._SetupSplElf('u_boot_ucode_ptr')
1437        with self.assertRaises(ValueError) as e:
1438            self._DoReadFile('047_spl_bss_pad.dts')
1439        self.assertIn('Expected __bss_size symbol in spl/u-boot-spl',
1440                      str(e.exception))
1441
1442    def testPackStart16Spl(self):
1443        """Test that an image with an x86 start16 SPL region can be created"""
1444        data = self._DoReadFile('048_x86_start16_spl.dts')
1445        self.assertEqual(X86_START16_SPL_DATA, data[:len(X86_START16_SPL_DATA)])
1446
1447    def _PackUbootSplMicrocode(self, dts, ucode_second=False):
1448        """Helper function for microcode tests
1449
1450        We expect to see the following in the image, in order:
1451            u-boot-spl-nodtb.bin with a microcode pointer inserted at the
1452                correct place
1453            u-boot.dtb with the microcode removed
1454            the microcode
1455
1456        Args:
1457            dts: Device tree file to use for test
1458            ucode_second: True if the microsecond entry is second instead of
1459                third
1460        """
1461        self._SetupSplElf('u_boot_ucode_ptr')
1462        first, pos_and_size = self._RunMicrocodeTest(dts, U_BOOT_SPL_NODTB_DATA,
1463                                                     ucode_second=ucode_second)
1464        self.assertEqual(b'splnodtb with microc' + pos_and_size +
1465                         b'ter somewhere in here', first)
1466
1467    def testPackUbootSplMicrocode(self):
1468        """Test that x86 microcode can be handled correctly in SPL"""
1469        self._SetupSplElf()
1470        self._PackUbootSplMicrocode('049_x86_ucode_spl.dts')
1471
1472    def testPackUbootSplMicrocodeReorder(self):
1473        """Test that order doesn't matter for microcode entries
1474
1475        This is the same as testPackUbootSplMicrocode but when we process the
1476        u-boot-ucode entry we have not yet seen the u-boot-dtb-with-ucode
1477        entry, so we reply on binman to try later.
1478        """
1479        self._PackUbootSplMicrocode('058_x86_ucode_spl_needs_retry.dts',
1480                                    ucode_second=True)
1481
1482    def testPackMrc(self):
1483        """Test that an image with an MRC binary can be created"""
1484        data = self._DoReadFile('050_intel_mrc.dts')
1485        self.assertEqual(MRC_DATA, data[:len(MRC_DATA)])
1486
1487    def testSplDtb(self):
1488        """Test that an image with spl/u-boot-spl.dtb can be created"""
1489        self._SetupSplElf()
1490        data = self._DoReadFile('051_u_boot_spl_dtb.dts')
1491        self.assertEqual(U_BOOT_SPL_DTB_DATA, data[:len(U_BOOT_SPL_DTB_DATA)])
1492
1493    def testSplNoDtb(self):
1494        """Test that an image with spl/u-boot-spl-nodtb.bin can be created"""
1495        self._SetupSplElf()
1496        data = self._DoReadFile('052_u_boot_spl_nodtb.dts')
1497        self.assertEqual(U_BOOT_SPL_NODTB_DATA, data[:len(U_BOOT_SPL_NODTB_DATA)])
1498
1499    def checkSymbols(self, dts, base_data, u_boot_offset, entry_args=None,
1500                     use_expanded=False, no_write_symbols=False):
1501        """Check the image contains the expected symbol values
1502
1503        Args:
1504            dts: Device tree file to use for test
1505            base_data: Data before and after 'u-boot' section
1506            u_boot_offset: Offset of 'u-boot' section in image
1507            entry_args: Dict of entry args to supply to binman
1508                key: arg name
1509                value: value of that arg
1510            use_expanded: True to use expanded entries where available, e.g.
1511                'u-boot-expanded' instead of 'u-boot'
1512        """
1513        elf_fname = self.ElfTestFile('u_boot_binman_syms')
1514        syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
1515        addr = elf.GetSymbolAddress(elf_fname, '__image_copy_start')
1516        self.assertEqual(syms['_binman_sym_magic'].address, addr)
1517        self.assertEqual(syms['_binman_u_boot_spl_any_prop_offset'].address,
1518                         addr + 4)
1519
1520        self._SetupSplElf('u_boot_binman_syms')
1521        data = self._DoReadFileDtb(dts, entry_args=entry_args,
1522                                   use_expanded=use_expanded)[0]
1523        # The image should contain the symbols from u_boot_binman_syms.c
1524        # Note that image_pos is adjusted by the base address of the image,
1525        # which is 0x10 in our test image
1526        sym_values = struct.pack('<LLQLL', elf.BINMAN_SYM_MAGIC_VALUE,
1527                                 0x00, u_boot_offset + len(U_BOOT_DATA),
1528                                 0x10 + u_boot_offset, 0x04)
1529        if no_write_symbols:
1530            expected = (base_data +
1531                        tools.get_bytes(0xff, 0x38 - len(base_data)) +
1532                        U_BOOT_DATA + base_data)
1533        else:
1534            expected = (sym_values + base_data[24:] +
1535                        tools.get_bytes(0xff, 1) + U_BOOT_DATA + sym_values +
1536                        base_data[24:])
1537        self.assertEqual(expected, data)
1538
1539    def testSymbols(self):
1540        """Test binman can assign symbols embedded in U-Boot"""
1541        self.checkSymbols('053_symbols.dts', U_BOOT_SPL_DATA, 0x1c)
1542
1543    def testSymbolsNoDtb(self):
1544        """Test binman can assign symbols embedded in U-Boot SPL"""
1545        self.checkSymbols('196_symbols_nodtb.dts',
1546                          U_BOOT_SPL_NODTB_DATA + U_BOOT_SPL_DTB_DATA,
1547                          0x38)
1548
1549    def testPackUnitAddress(self):
1550        """Test that we support multiple binaries with the same name"""
1551        data = self._DoReadFile('054_unit_address.dts')
1552        self.assertEqual(U_BOOT_DATA + U_BOOT_DATA, data)
1553
1554    def testSections(self):
1555        """Basic test of sections"""
1556        data = self._DoReadFile('055_sections.dts')
1557        expected = (U_BOOT_DATA + tools.get_bytes(ord('!'), 12) +
1558                    U_BOOT_DATA + tools.get_bytes(ord('a'), 12) +
1559                    U_BOOT_DATA + tools.get_bytes(ord('&'), 4))
1560        self.assertEqual(expected, data)
1561
1562    def testMap(self):
1563        """Tests outputting a map of the images"""
1564        _, _, map_data, _ = self._DoReadFileDtb('055_sections.dts', map=True)
1565        self.assertEqual('''ImagePos    Offset      Size  Name
156600000000  00000000  00000028  image
156700000000   00000000  00000010  section@0
156800000000    00000000  00000004  u-boot
156900000010   00000010  00000010  section@1
157000000010    00000000  00000004  u-boot
157100000020   00000020  00000004  section@2
157200000020    00000000  00000004  u-boot
1573''', map_data)
1574
1575    def testNamePrefix(self):
1576        """Tests that name prefixes are used"""
1577        _, _, map_data, _ = self._DoReadFileDtb('056_name_prefix.dts', map=True)
1578        self.assertEqual('''ImagePos    Offset      Size  Name
157900000000  00000000  00000028  image
158000000000   00000000  00000010  section@0
158100000000    00000000  00000004  ro-u-boot
158200000010   00000010  00000010  section@1
158300000010    00000000  00000004  rw-u-boot
1584''', map_data)
1585
1586    def testUnknownContents(self):
1587        """Test that obtaining the contents works as expected"""
1588        with self.assertRaises(ValueError) as e:
1589            self._DoReadFile('057_unknown_contents.dts', True)
1590        self.assertIn("Image '/binman': Internal error: Could not complete "
1591                "processing of contents: remaining ["
1592                "<binman.etype._testing.Entry__testing ", str(e.exception))
1593
1594    def testBadChangeSize(self):
1595        """Test that trying to change the size of an entry fails"""
1596        try:
1597            state.SetAllowEntryExpansion(False)
1598            with self.assertRaises(ValueError) as e:
1599                self._DoReadFile('059_change_size.dts', True)
1600            self.assertIn("Node '/binman/_testing': Cannot update entry size from 2 to 3",
1601                          str(e.exception))
1602        finally:
1603            state.SetAllowEntryExpansion(True)
1604
1605    def testUpdateFdt(self):
1606        """Test that we can update the device tree with offset/size info"""
1607        _, _, _, out_dtb_fname = self._DoReadFileDtb('060_fdt_update.dts',
1608                                                     update_dtb=True)
1609        dtb = fdt.Fdt(out_dtb_fname)
1610        dtb.Scan()
1611        props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS)
1612        self.assertEqual({
1613            'image-pos': 0,
1614            'offset': 0,
1615            '_testing:offset': 32,
1616            '_testing:size': 2,
1617            '_testing:image-pos': 32,
1618            'section@0/u-boot:offset': 0,
1619            'section@0/u-boot:size': len(U_BOOT_DATA),
1620            'section@0/u-boot:image-pos': 0,
1621            'section@0:offset': 0,
1622            'section@0:size': 16,
1623            'section@0:image-pos': 0,
1624
1625            'section@1/u-boot:offset': 0,
1626            'section@1/u-boot:size': len(U_BOOT_DATA),
1627            'section@1/u-boot:image-pos': 16,
1628            'section@1:offset': 16,
1629            'section@1:size': 16,
1630            'section@1:image-pos': 16,
1631            'size': 40
1632        }, props)
1633
1634    def testUpdateFdtBad(self):
1635        """Test that we detect when ProcessFdt never completes"""
1636        with self.assertRaises(ValueError) as e:
1637            self._DoReadFileDtb('061_fdt_update_bad.dts', update_dtb=True)
1638        self.assertIn('Could not complete processing of Fdt: remaining '
1639                      '[<binman.etype._testing.Entry__testing',
1640                        str(e.exception))
1641
1642    def testEntryArgs(self):
1643        """Test passing arguments to entries from the command line"""
1644        entry_args = {
1645            'test-str-arg': 'test1',
1646            'test-int-arg': '456',
1647        }
1648        self._DoReadFileDtb('062_entry_args.dts', entry_args=entry_args)
1649        self.assertIn('image', control.images)
1650        entry = control.images['image'].GetEntries()['_testing']
1651        self.assertEqual('test0', entry.test_str_fdt)
1652        self.assertEqual('test1', entry.test_str_arg)
1653        self.assertEqual(123, entry.test_int_fdt)
1654        self.assertEqual(456, entry.test_int_arg)
1655
1656    def testEntryArgsMissing(self):
1657        """Test missing arguments and properties"""
1658        entry_args = {
1659            'test-int-arg': '456',
1660        }
1661        self._DoReadFileDtb('063_entry_args_missing.dts', entry_args=entry_args)
1662        entry = control.images['image'].GetEntries()['_testing']
1663        self.assertEqual('test0', entry.test_str_fdt)
1664        self.assertEqual(None, entry.test_str_arg)
1665        self.assertEqual(None, entry.test_int_fdt)
1666        self.assertEqual(456, entry.test_int_arg)
1667
1668    def testEntryArgsRequired(self):
1669        """Test missing arguments and properties"""
1670        entry_args = {
1671            'test-int-arg': '456',
1672        }
1673        with self.assertRaises(ValueError) as e:
1674            self._DoReadFileDtb('064_entry_args_required.dts')
1675        self.assertIn("Node '/binman/_testing': "
1676            'Missing required properties/entry args: test-str-arg, '
1677            'test-int-fdt, test-int-arg',
1678            str(e.exception))
1679
1680    def testEntryArgsInvalidFormat(self):
1681        """Test that an invalid entry-argument format is detected"""
1682        args = ['build', '-d', self.TestFile('064_entry_args_required.dts'),
1683                '-ano-value']
1684        with self.assertRaises(ValueError) as e:
1685            self._DoBinman(*args)
1686        self.assertIn("Invalid entry arguemnt 'no-value'", str(e.exception))
1687
1688    def testEntryArgsInvalidInteger(self):
1689        """Test that an invalid entry-argument integer is detected"""
1690        entry_args = {
1691            'test-int-arg': 'abc',
1692        }
1693        with self.assertRaises(ValueError) as e:
1694            self._DoReadFileDtb('062_entry_args.dts', entry_args=entry_args)
1695        self.assertIn("Node '/binman/_testing': Cannot convert entry arg "
1696                      "'test-int-arg' (value 'abc') to integer",
1697            str(e.exception))
1698
1699    def testEntryArgsInvalidDatatype(self):
1700        """Test that an invalid entry-argument datatype is detected
1701
1702        This test could be written in entry_test.py except that it needs
1703        access to control.entry_args, which seems more than that module should
1704        be able to see.
1705        """
1706        entry_args = {
1707            'test-bad-datatype-arg': '12',
1708        }
1709        with self.assertRaises(ValueError) as e:
1710            self._DoReadFileDtb('065_entry_args_unknown_datatype.dts',
1711                                entry_args=entry_args)
1712        self.assertIn('GetArg() internal error: Unknown data type ',
1713                      str(e.exception))
1714
1715    def testText(self):
1716        """Test for a text entry type"""
1717        entry_args = {
1718            'test-id': TEXT_DATA,
1719            'test-id2': TEXT_DATA2,
1720            'test-id3': TEXT_DATA3,
1721        }
1722        data, _, _, _ = self._DoReadFileDtb('066_text.dts',
1723                                            entry_args=entry_args)
1724        expected = (tools.to_bytes(TEXT_DATA) +
1725                    tools.get_bytes(0, 8 - len(TEXT_DATA)) +
1726                    tools.to_bytes(TEXT_DATA2) + tools.to_bytes(TEXT_DATA3) +
1727                    b'some text' + b'more text')
1728        self.assertEqual(expected, data)
1729
1730    def testEntryDocs(self):
1731        """Test for creation of entry documentation"""
1732        with test_util.capture_sys_output() as (stdout, stderr):
1733            control.WriteEntryDocs(control.GetEntryModules())
1734        self.assertTrue(len(stdout.getvalue()) > 0)
1735
1736    def testEntryDocsMissing(self):
1737        """Test handling of missing entry documentation"""
1738        with self.assertRaises(ValueError) as e:
1739            with test_util.capture_sys_output() as (stdout, stderr):
1740                control.WriteEntryDocs(control.GetEntryModules(), 'u_boot')
1741        self.assertIn('Documentation is missing for modules: u_boot',
1742                      str(e.exception))
1743
1744    def testFmap(self):
1745        """Basic test of generation of a flashrom fmap"""
1746        data = self._DoReadFile('067_fmap.dts')
1747        fhdr, fentries = fmap_util.DecodeFmap(data[32:])
1748        expected = (U_BOOT_DATA + tools.get_bytes(ord('!'), 12) +
1749                    U_BOOT_DATA + tools.get_bytes(ord('a'), 12))
1750        self.assertEqual(expected, data[:32])
1751        self.assertEqual(b'__FMAP__', fhdr.signature)
1752        self.assertEqual(1, fhdr.ver_major)
1753        self.assertEqual(0, fhdr.ver_minor)
1754        self.assertEqual(0, fhdr.base)
1755        expect_size = fmap_util.FMAP_HEADER_LEN + fmap_util.FMAP_AREA_LEN * 5
1756        self.assertEqual(16 + 16 + expect_size, fhdr.image_size)
1757        self.assertEqual(b'FMAP', fhdr.name)
1758        self.assertEqual(5, fhdr.nareas)
1759        fiter = iter(fentries)
1760
1761        fentry = next(fiter)
1762        self.assertEqual(b'SECTION0', fentry.name)
1763        self.assertEqual(0, fentry.offset)
1764        self.assertEqual(16, fentry.size)
1765        self.assertEqual(fmap_util.FMAP_AREA_PRESERVE, fentry.flags)
1766
1767        fentry = next(fiter)
1768        self.assertEqual(b'RO_U_BOOT', fentry.name)
1769        self.assertEqual(0, fentry.offset)
1770        self.assertEqual(4, fentry.size)
1771        self.assertEqual(0, fentry.flags)
1772
1773        fentry = next(fiter)
1774        self.assertEqual(b'SECTION1', fentry.name)
1775        self.assertEqual(16, fentry.offset)
1776        self.assertEqual(16, fentry.size)
1777        self.assertEqual(0, fentry.flags)
1778
1779        fentry = next(fiter)
1780        self.assertEqual(b'RW_U_BOOT', fentry.name)
1781        self.assertEqual(16, fentry.offset)
1782        self.assertEqual(4, fentry.size)
1783        self.assertEqual(0, fentry.flags)
1784
1785        fentry = next(fiter)
1786        self.assertEqual(b'FMAP', fentry.name)
1787        self.assertEqual(32, fentry.offset)
1788        self.assertEqual(expect_size, fentry.size)
1789        self.assertEqual(0, fentry.flags)
1790
1791    def testBlobNamedByArg(self):
1792        """Test we can add a blob with the filename coming from an entry arg"""
1793        entry_args = {
1794            'cros-ec-rw-path': 'ecrw.bin',
1795        }
1796        self._DoReadFileDtb('068_blob_named_by_arg.dts', entry_args=entry_args)
1797
1798    def testFill(self):
1799        """Test for an fill entry type"""
1800        data = self._DoReadFile('069_fill.dts')
1801        expected = tools.get_bytes(0xff, 8) + tools.get_bytes(0, 8)
1802        self.assertEqual(expected, data)
1803
1804    def testFillNoSize(self):
1805        """Test for an fill entry type with no size"""
1806        with self.assertRaises(ValueError) as e:
1807            self._DoReadFile('070_fill_no_size.dts')
1808        self.assertIn("'fill' entry is missing properties: size",
1809                      str(e.exception))
1810
1811    def _HandleGbbCommand(self, pipe_list):
1812        """Fake calls to the futility utility"""
1813        if 'futility' in pipe_list[0][0]:
1814            fname = pipe_list[0][-1]
1815            # Append our GBB data to the file, which will happen every time the
1816            # futility command is called.
1817            with open(fname, 'ab') as fd:
1818                fd.write(GBB_DATA)
1819            return command.CommandResult()
1820
1821    def testGbb(self):
1822        """Test for the Chromium OS Google Binary Block"""
1823        command.test_result = self._HandleGbbCommand
1824        entry_args = {
1825            'keydir': 'devkeys',
1826            'bmpblk': 'bmpblk.bin',
1827        }
1828        data, _, _, _ = self._DoReadFileDtb('071_gbb.dts', entry_args=entry_args)
1829
1830        # Since futility
1831        expected = (GBB_DATA + GBB_DATA + tools.get_bytes(0, 8) +
1832                    tools.get_bytes(0, 0x2180 - 16))
1833        self.assertEqual(expected, data)
1834
1835    def testGbbTooSmall(self):
1836        """Test for the Chromium OS Google Binary Block being large enough"""
1837        with self.assertRaises(ValueError) as e:
1838            self._DoReadFileDtb('072_gbb_too_small.dts')
1839        self.assertIn("Node '/binman/gbb': GBB is too small",
1840                      str(e.exception))
1841
1842    def testGbbNoSize(self):
1843        """Test for the Chromium OS Google Binary Block having a size"""
1844        with self.assertRaises(ValueError) as e:
1845            self._DoReadFileDtb('073_gbb_no_size.dts')
1846        self.assertIn("Node '/binman/gbb': GBB must have a fixed size",
1847                      str(e.exception))
1848
1849    def testGbbMissing(self):
1850        """Test that binman still produces an image if futility is missing"""
1851        entry_args = {
1852            'keydir': 'devkeys',
1853        }
1854        with test_util.capture_sys_output() as (_, stderr):
1855            self._DoTestFile('071_gbb.dts', force_missing_bintools='futility',
1856                             entry_args=entry_args)
1857        err = stderr.getvalue()
1858        self.assertRegex(err, "Image 'image'.*missing bintools.*: futility")
1859
1860    def _HandleVblockCommand(self, pipe_list):
1861        """Fake calls to the futility utility
1862
1863        The expected pipe is:
1864
1865           [('futility', 'vbutil_firmware', '--vblock',
1866             'vblock.vblock', '--keyblock', 'devkeys/firmware.keyblock',
1867             '--signprivate', 'devkeys/firmware_data_key.vbprivk',
1868             '--version', '1', '--fv', 'input.vblock', '--kernelkey',
1869             'devkeys/kernel_subkey.vbpubk', '--flags', '1')]
1870
1871        This writes to the output file (here, 'vblock.vblock'). If
1872        self._hash_data is False, it writes VBLOCK_DATA, else it writes a hash
1873        of the input data (here, 'input.vblock').
1874        """
1875        if 'futility' in pipe_list[0][0]:
1876            fname = pipe_list[0][3]
1877            with open(fname, 'wb') as fd:
1878                if self._hash_data:
1879                    infile = pipe_list[0][11]
1880                    m = hashlib.sha256()
1881                    data = tools.read_file(infile)
1882                    m.update(data)
1883                    fd.write(m.digest())
1884                else:
1885                    fd.write(VBLOCK_DATA)
1886
1887            return command.CommandResult()
1888
1889    def testVblock(self):
1890        """Test for the Chromium OS Verified Boot Block"""
1891        self._hash_data = False
1892        command.test_result = self._HandleVblockCommand
1893        entry_args = {
1894            'keydir': 'devkeys',
1895        }
1896        data, _, _, _ = self._DoReadFileDtb('074_vblock.dts',
1897                                            entry_args=entry_args)
1898        expected = U_BOOT_DATA + VBLOCK_DATA + U_BOOT_DTB_DATA
1899        self.assertEqual(expected, data)
1900
1901    def testVblockNoContent(self):
1902        """Test we detect a vblock which has no content to sign"""
1903        with self.assertRaises(ValueError) as e:
1904            self._DoReadFile('075_vblock_no_content.dts')
1905        self.assertIn("Node '/binman/vblock': Collection must have a 'content' "
1906                      'property', str(e.exception))
1907
1908    def testVblockBadPhandle(self):
1909        """Test that we detect a vblock with an invalid phandle in contents"""
1910        with self.assertRaises(ValueError) as e:
1911            self._DoReadFile('076_vblock_bad_phandle.dts')
1912        self.assertIn("Node '/binman/vblock': Cannot find node for phandle "
1913                      '1000', str(e.exception))
1914
1915    def testVblockBadEntry(self):
1916        """Test that we detect an entry that points to a non-entry"""
1917        with self.assertRaises(ValueError) as e:
1918            self._DoReadFile('077_vblock_bad_entry.dts')
1919        self.assertIn("Node '/binman/vblock': Cannot find entry for node "
1920                      "'other'", str(e.exception))
1921
1922    def testVblockContent(self):
1923        """Test that the vblock signs the right data"""
1924        self._hash_data = True
1925        command.test_result = self._HandleVblockCommand
1926        entry_args = {
1927            'keydir': 'devkeys',
1928        }
1929        data = self._DoReadFileDtb(
1930            '189_vblock_content.dts', use_real_dtb=True, update_dtb=True,
1931            entry_args=entry_args)[0]
1932        hashlen = 32  # SHA256 hash is 32 bytes
1933        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
1934        hashval = data[-hashlen:]
1935        dtb = data[len(U_BOOT_DATA):-hashlen]
1936
1937        expected_data = U_BOOT_DATA + dtb
1938
1939        # The hashval should be a hash of the dtb
1940        m = hashlib.sha256()
1941        m.update(expected_data)
1942        expected_hashval = m.digest()
1943        self.assertEqual(expected_hashval, hashval)
1944
1945    def testVblockMissing(self):
1946        """Test that binman still produces an image if futility is missing"""
1947        entry_args = {
1948            'keydir': 'devkeys',
1949        }
1950        with test_util.capture_sys_output() as (_, stderr):
1951            self._DoTestFile('074_vblock.dts',
1952                             force_missing_bintools='futility',
1953                             entry_args=entry_args)
1954        err = stderr.getvalue()
1955        self.assertRegex(err, "Image 'image'.*missing bintools.*: futility")
1956
1957    def testTpl(self):
1958        """Test that an image with TPL and its device tree can be created"""
1959        # ELF file with a '__bss_size' symbol
1960        self._SetupTplElf()
1961        data = self._DoReadFile('078_u_boot_tpl.dts')
1962        self.assertEqual(U_BOOT_TPL_DATA + U_BOOT_TPL_DTB_DATA, data)
1963
1964    def testUsesPos(self):
1965        """Test that the 'pos' property cannot be used anymore"""
1966        with self.assertRaises(ValueError) as e:
1967           data = self._DoReadFile('079_uses_pos.dts')
1968        self.assertIn("Node '/binman/u-boot': Please use 'offset' instead of "
1969                      "'pos'", str(e.exception))
1970
1971    def testFillZero(self):
1972        """Test for an fill entry type with a size of 0"""
1973        data = self._DoReadFile('080_fill_empty.dts')
1974        self.assertEqual(tools.get_bytes(0, 16), data)
1975
1976    def testTextMissing(self):
1977        """Test for a text entry type where there is no text"""
1978        with self.assertRaises(ValueError) as e:
1979            self._DoReadFileDtb('066_text.dts',)
1980        self.assertIn("Node '/binman/text': No value provided for text label "
1981                      "'test-id'", str(e.exception))
1982
1983    def testPackStart16Tpl(self):
1984        """Test that an image with an x86 start16 TPL region can be created"""
1985        data = self._DoReadFile('081_x86_start16_tpl.dts')
1986        self.assertEqual(X86_START16_TPL_DATA, data[:len(X86_START16_TPL_DATA)])
1987
1988    def testSelectImage(self):
1989        """Test that we can select which images to build"""
1990        expected = 'Skipping images: image1'
1991
1992        # We should only get the expected message in verbose mode
1993        for verbosity in (0, 2):
1994            with test_util.capture_sys_output() as (stdout, stderr):
1995                retcode = self._DoTestFile('006_dual_image.dts',
1996                                           verbosity=verbosity,
1997                                           images=['image2'])
1998            self.assertEqual(0, retcode)
1999            if verbosity:
2000                self.assertIn(expected, stdout.getvalue())
2001            else:
2002                self.assertNotIn(expected, stdout.getvalue())
2003
2004            self.assertFalse(os.path.exists(tools.get_output_filename('image1.bin')))
2005            self.assertTrue(os.path.exists(tools.get_output_filename('image2.bin')))
2006            self._CleanupOutputDir()
2007
2008    def testUpdateFdtAll(self):
2009        """Test that all device trees are updated with offset/size info"""
2010        self._SetupSplElf()
2011        self._SetupTplElf()
2012        data = self._DoReadFileRealDtb('082_fdt_update_all.dts')
2013
2014        base_expected = {
2015            'offset': 0,
2016            'image-pos': 0,
2017            'size': 2320,
2018            'section:offset': 0,
2019            'section:image-pos': 0,
2020            'section:size': 565,
2021            'section/u-boot-dtb:offset': 0,
2022            'section/u-boot-dtb:image-pos': 0,
2023            'section/u-boot-dtb:size': 565,
2024            'u-boot-spl-dtb:offset': 565,
2025            'u-boot-spl-dtb:image-pos': 565,
2026            'u-boot-spl-dtb:size': 585,
2027            'u-boot-tpl-dtb:offset': 1150,
2028            'u-boot-tpl-dtb:image-pos': 1150,
2029            'u-boot-tpl-dtb:size': 585,
2030            'u-boot-vpl-dtb:image-pos': 1735,
2031            'u-boot-vpl-dtb:offset': 1735,
2032            'u-boot-vpl-dtb:size': 585,
2033        }
2034
2035        # We expect three device-tree files in the output, one after the other.
2036        # Read them in sequence. We look for an 'spl' property in the SPL tree,
2037        # and 'tpl' in the TPL tree, to make sure they are distinct from the
2038        # main U-Boot tree. All three should have the same postions and offset.
2039        start = 0
2040        self.maxDiff = None
2041        for item in ['', 'spl', 'tpl', 'vpl']:
2042            dtb = fdt.Fdt.FromData(data[start:])
2043            dtb.Scan()
2044            props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS +
2045                                      ['spl', 'tpl', 'vpl'])
2046            expected = dict(base_expected)
2047            if item:
2048                expected[item] = 0
2049            self.assertEqual(expected, props)
2050            start += dtb._fdt_obj.totalsize()
2051
2052    def testUpdateFdtOutput(self):
2053        """Test that output DTB files are updated"""
2054        try:
2055            data, dtb_data, _, _ = self._DoReadFileDtb('082_fdt_update_all.dts',
2056                    use_real_dtb=True, update_dtb=True, reset_dtbs=False)
2057
2058            # Unfortunately, compiling a source file always results in a file
2059            # called source.dtb (see fdt_util.EnsureCompiled()). The test
2060            # source file (e.g. test/075_fdt_update_all.dts) thus does not enter
2061            # binman as a file called u-boot.dtb. To fix this, copy the file
2062            # over to the expected place.
2063            start = 0
2064            for fname in ['u-boot.dtb.out', 'spl/u-boot-spl.dtb.out',
2065                          'tpl/u-boot-tpl.dtb.out', 'vpl/u-boot-vpl.dtb.out']:
2066                dtb = fdt.Fdt.FromData(data[start:])
2067                size = dtb._fdt_obj.totalsize()
2068                pathname = tools.get_output_filename(os.path.split(fname)[1])
2069                outdata = tools.read_file(pathname)
2070                name = os.path.split(fname)[0]
2071
2072                if name:
2073                    orig_indata = self._GetDtbContentsForSpls(dtb_data, name)
2074                else:
2075                    orig_indata = dtb_data
2076                self.assertNotEqual(outdata, orig_indata,
2077                        "Expected output file '%s' be updated" % pathname)
2078                self.assertEqual(outdata, data[start:start + size],
2079                        "Expected output file '%s' to match output image" %
2080                        pathname)
2081                start += size
2082        finally:
2083            self._ResetDtbs()
2084
2085    def _decompress(self, data):
2086        bintool = self.comp_bintools['lz4']
2087        return bintool.decompress(data)
2088
2089    def testCompress(self):
2090        """Test compression of blobs"""
2091        self._CheckLz4()
2092        data, _, _, out_dtb_fname = self._DoReadFileDtb('083_compress.dts',
2093                                            use_real_dtb=True, update_dtb=True)
2094        dtb = fdt.Fdt(out_dtb_fname)
2095        dtb.Scan()
2096        props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
2097        orig = self._decompress(data)
2098        self.assertEquals(COMPRESS_DATA, orig)
2099
2100        # Do a sanity check on various fields
2101        image = control.images['image']
2102        entries = image.GetEntries()
2103        self.assertEqual(1, len(entries))
2104
2105        entry = entries['blob']
2106        self.assertEqual(COMPRESS_DATA, entry.uncomp_data)
2107        self.assertEqual(len(COMPRESS_DATA), entry.uncomp_size)
2108        orig = self._decompress(entry.data)
2109        self.assertEqual(orig, entry.uncomp_data)
2110
2111        self.assertEqual(image.data, entry.data)
2112
2113        expected = {
2114            'blob:uncomp-size': len(COMPRESS_DATA),
2115            'blob:size': len(data),
2116            'size': len(data),
2117            }
2118        self.assertEqual(expected, props)
2119
2120    def testFiles(self):
2121        """Test bringing in multiple files"""
2122        data = self._DoReadFile('084_files.dts')
2123        self.assertEqual(FILES_DATA, data)
2124
2125    def testFilesCompress(self):
2126        """Test bringing in multiple files and compressing them"""
2127        self._CheckLz4()
2128        data = self._DoReadFile('085_files_compress.dts')
2129
2130        image = control.images['image']
2131        entries = image.GetEntries()
2132        files = entries['files']
2133        entries = files._entries
2134
2135        orig = b''
2136        for i in range(1, 3):
2137            key = '%d.dat' % i
2138            start = entries[key].image_pos
2139            len = entries[key].size
2140            chunk = data[start:start + len]
2141            orig += self._decompress(chunk)
2142
2143        self.assertEqual(FILES_DATA, orig)
2144
2145    def testFilesMissing(self):
2146        """Test missing files"""
2147        with self.assertRaises(ValueError) as e:
2148            data = self._DoReadFile('086_files_none.dts')
2149        self.assertIn("Node '/binman/files': Pattern \'files/*.none\' matched "
2150                      'no files', str(e.exception))
2151
2152    def testFilesNoPattern(self):
2153        """Test missing files"""
2154        with self.assertRaises(ValueError) as e:
2155            data = self._DoReadFile('087_files_no_pattern.dts')
2156        self.assertIn("Node '/binman/files': Missing 'pattern' property",
2157                      str(e.exception))
2158
2159    def testExtendSize(self):
2160        """Test an extending entry"""
2161        data, _, map_data, _ = self._DoReadFileDtb('088_extend_size.dts',
2162                                                   map=True)
2163        expect = (tools.get_bytes(ord('a'), 8) + U_BOOT_DATA +
2164                  MRC_DATA + tools.get_bytes(ord('b'), 1) + U_BOOT_DATA +
2165                  tools.get_bytes(ord('c'), 8) + U_BOOT_DATA +
2166                  tools.get_bytes(ord('d'), 8))
2167        self.assertEqual(expect, data)
2168        self.assertEqual('''ImagePos    Offset      Size  Name
216900000000  00000000  00000028  image
217000000000   00000000  00000008  fill
217100000008   00000008  00000004  u-boot
21720000000c   0000000c  00000004  section
21730000000c    00000000  00000003  intel-mrc
217400000010   00000010  00000004  u-boot2
217500000014   00000014  0000000c  section2
217600000014    00000000  00000008  fill
21770000001c    00000008  00000004  u-boot
217800000020   00000020  00000008  fill2
2179''', map_data)
2180
2181    def testExtendSizeBad(self):
2182        """Test an extending entry which fails to provide contents"""
2183        with test_util.capture_sys_output() as (stdout, stderr):
2184            with self.assertRaises(ValueError) as e:
2185                self._DoReadFileDtb('089_extend_size_bad.dts', map=True)
2186        self.assertIn("Node '/binman/_testing': Cannot obtain contents when "
2187                      'expanding entry', str(e.exception))
2188
2189    def testHash(self):
2190        """Test hashing of the contents of an entry"""
2191        _, _, _, out_dtb_fname = self._DoReadFileDtb('090_hash.dts',
2192                use_real_dtb=True, update_dtb=True)
2193        dtb = fdt.Fdt(out_dtb_fname)
2194        dtb.Scan()
2195        hash_node = dtb.GetNode('/binman/u-boot/hash').props['value']
2196        m = hashlib.sha256()
2197        m.update(U_BOOT_DATA)
2198        self.assertEqual(m.digest(), b''.join(hash_node.value))
2199
2200    def testHashNoAlgo(self):
2201        with self.assertRaises(ValueError) as e:
2202            self._DoReadFileDtb('091_hash_no_algo.dts', update_dtb=True)
2203        self.assertIn("Node \'/binman/u-boot\': Missing \'algo\' property for "
2204                      'hash node', str(e.exception))
2205
2206    def testHashBadAlgo(self):
2207        with self.assertRaises(ValueError) as e:
2208            self._DoReadFileDtb('092_hash_bad_algo.dts', update_dtb=True)
2209        self.assertIn("Node '/binman/u-boot': Unknown hash algorithm 'invalid'",
2210                      str(e.exception))
2211
2212    def testHashSection(self):
2213        """Test hashing of the contents of an entry"""
2214        _, _, _, out_dtb_fname = self._DoReadFileDtb('099_hash_section.dts',
2215                use_real_dtb=True, update_dtb=True)
2216        dtb = fdt.Fdt(out_dtb_fname)
2217        dtb.Scan()
2218        hash_node = dtb.GetNode('/binman/section/hash').props['value']
2219        m = hashlib.sha256()
2220        m.update(U_BOOT_DATA)
2221        m.update(tools.get_bytes(ord('a'), 16))
2222        self.assertEqual(m.digest(), b''.join(hash_node.value))
2223
2224    def testPackUBootTplMicrocode(self):
2225        """Test that x86 microcode can be handled correctly in TPL
2226
2227        We expect to see the following in the image, in order:
2228            u-boot-tpl-nodtb.bin with a microcode pointer inserted at the correct
2229                place
2230            u-boot-tpl.dtb with the microcode removed
2231            the microcode
2232        """
2233        self._SetupTplElf('u_boot_ucode_ptr')
2234        first, pos_and_size = self._RunMicrocodeTest('093_x86_tpl_ucode.dts',
2235                                                     U_BOOT_TPL_NODTB_DATA)
2236        self.assertEqual(b'tplnodtb with microc' + pos_and_size +
2237                         b'ter somewhere in here', first)
2238
2239    def testFmapX86(self):
2240        """Basic test of generation of a flashrom fmap"""
2241        data = self._DoReadFile('094_fmap_x86.dts')
2242        fhdr, fentries = fmap_util.DecodeFmap(data[32:])
2243        expected = U_BOOT_DATA + MRC_DATA + tools.get_bytes(ord('a'), 32 - 7)
2244        self.assertEqual(expected, data[:32])
2245        fhdr, fentries = fmap_util.DecodeFmap(data[32:])
2246
2247        self.assertEqual(0x100, fhdr.image_size)
2248
2249        self.assertEqual(0, fentries[0].offset)
2250        self.assertEqual(4, fentries[0].size)
2251        self.assertEqual(b'U_BOOT', fentries[0].name)
2252
2253        self.assertEqual(4, fentries[1].offset)
2254        self.assertEqual(3, fentries[1].size)
2255        self.assertEqual(b'INTEL_MRC', fentries[1].name)
2256
2257        self.assertEqual(32, fentries[2].offset)
2258        self.assertEqual(fmap_util.FMAP_HEADER_LEN +
2259                         fmap_util.FMAP_AREA_LEN * 3, fentries[2].size)
2260        self.assertEqual(b'FMAP', fentries[2].name)
2261
2262    def testFmapX86Section(self):
2263        """Basic test of generation of a flashrom fmap"""
2264        data = self._DoReadFile('095_fmap_x86_section.dts')
2265        expected = U_BOOT_DATA + MRC_DATA + tools.get_bytes(ord('b'), 32 - 7)
2266        self.assertEqual(expected, data[:32])
2267        fhdr, fentries = fmap_util.DecodeFmap(data[36:])
2268
2269        self.assertEqual(0x180, fhdr.image_size)
2270        expect_size = fmap_util.FMAP_HEADER_LEN + fmap_util.FMAP_AREA_LEN * 4
2271        fiter = iter(fentries)
2272
2273        fentry = next(fiter)
2274        self.assertEqual(b'U_BOOT', fentry.name)
2275        self.assertEqual(0, fentry.offset)
2276        self.assertEqual(4, fentry.size)
2277
2278        fentry = next(fiter)
2279        self.assertEqual(b'SECTION', fentry.name)
2280        self.assertEqual(4, fentry.offset)
2281        self.assertEqual(0x20 + expect_size, fentry.size)
2282
2283        fentry = next(fiter)
2284        self.assertEqual(b'INTEL_MRC', fentry.name)
2285        self.assertEqual(4, fentry.offset)
2286        self.assertEqual(3, fentry.size)
2287
2288        fentry = next(fiter)
2289        self.assertEqual(b'FMAP', fentry.name)
2290        self.assertEqual(36, fentry.offset)
2291        self.assertEqual(expect_size, fentry.size)
2292
2293    def testElf(self):
2294        """Basic test of ELF entries"""
2295        self._SetupSplElf()
2296        self._SetupTplElf()
2297        with open(self.ElfTestFile('bss_data'), 'rb') as fd:
2298            TestFunctional._MakeInputFile('-boot', fd.read())
2299        data = self._DoReadFile('096_elf.dts')
2300
2301    def testElfStrip(self):
2302        """Basic test of ELF entries"""
2303        self._SetupSplElf()
2304        with open(self.ElfTestFile('bss_data'), 'rb') as fd:
2305            TestFunctional._MakeInputFile('-boot', fd.read())
2306        data = self._DoReadFile('097_elf_strip.dts')
2307
2308    def testPackOverlapMap(self):
2309        """Test that overlapping regions are detected"""
2310        with test_util.capture_sys_output() as (stdout, stderr):
2311            with self.assertRaises(ValueError) as e:
2312                self._DoTestFile('014_pack_overlap.dts', map=True)
2313        map_fname = tools.get_output_filename('image.map')
2314        self.assertEqual("Wrote map file '%s' to show errors\n" % map_fname,
2315                         stdout.getvalue())
2316
2317        # We should not get an inmage, but there should be a map file
2318        self.assertFalse(os.path.exists(tools.get_output_filename('image.bin')))
2319        self.assertTrue(os.path.exists(map_fname))
2320        map_data = tools.read_file(map_fname, binary=False)
2321        self.assertEqual('''ImagePos    Offset      Size  Name
2322<none>    00000000  00000008  image
2323<none>     00000000  00000004  u-boot
2324<none>     00000003  00000004  u-boot-align
2325''', map_data)
2326
2327    def testPackRefCode(self):
2328        """Test that an image with an Intel Reference code binary works"""
2329        data = self._DoReadFile('100_intel_refcode.dts')
2330        self.assertEqual(REFCODE_DATA, data[:len(REFCODE_DATA)])
2331
2332    def testSectionOffset(self):
2333        """Tests use of a section with an offset"""
2334        data, _, map_data, _ = self._DoReadFileDtb('101_sections_offset.dts',
2335                                                   map=True)
2336        self.assertEqual('''ImagePos    Offset      Size  Name
233700000000  00000000  00000038  image
233800000004   00000004  00000010  section@0
233900000004    00000000  00000004  u-boot
234000000018   00000018  00000010  section@1
234100000018    00000000  00000004  u-boot
23420000002c   0000002c  00000004  section@2
23430000002c    00000000  00000004  u-boot
2344''', map_data)
2345        self.assertEqual(data,
2346                         tools.get_bytes(0x26, 4) + U_BOOT_DATA +
2347                             tools.get_bytes(0x21, 12) +
2348                         tools.get_bytes(0x26, 4) + U_BOOT_DATA +
2349                             tools.get_bytes(0x61, 12) +
2350                         tools.get_bytes(0x26, 4) + U_BOOT_DATA +
2351                             tools.get_bytes(0x26, 8))
2352
2353    def testCbfsRaw(self):
2354        """Test base handling of a Coreboot Filesystem (CBFS)
2355
2356        The exact contents of the CBFS is verified by similar tests in
2357        cbfs_util_test.py. The tests here merely check that the files added to
2358        the CBFS can be found in the final image.
2359        """
2360        data = self._DoReadFile('102_cbfs_raw.dts')
2361        size = 0xb0
2362
2363        cbfs = cbfs_util.CbfsReader(data)
2364        self.assertEqual(size, cbfs.rom_size)
2365
2366        self.assertIn('u-boot-dtb', cbfs.files)
2367        cfile = cbfs.files['u-boot-dtb']
2368        self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
2369
2370    def testCbfsArch(self):
2371        """Test on non-x86 architecture"""
2372        data = self._DoReadFile('103_cbfs_raw_ppc.dts')
2373        size = 0x100
2374
2375        cbfs = cbfs_util.CbfsReader(data)
2376        self.assertEqual(size, cbfs.rom_size)
2377
2378        self.assertIn('u-boot-dtb', cbfs.files)
2379        cfile = cbfs.files['u-boot-dtb']
2380        self.assertEqual(U_BOOT_DTB_DATA, cfile.data)
2381
2382    def testCbfsStage(self):
2383        """Tests handling of a Coreboot Filesystem (CBFS)"""
2384        if not elf.ELF_TOOLS:
2385            self.skipTest('Python elftools not available')
2386        elf_fname = os.path.join(self._indir, 'cbfs-stage.elf')
2387        elf.MakeElf(elf_fname, U_BOOT_DATA, U_BOOT_DTB_DATA)
2388        size = 0xb0
2389
2390        data = self._DoReadFile('104_cbfs_stage.dts')
2391        cbfs = cbfs_util.CbfsReader(data)
2392        self.assertEqual(size, cbfs.rom_size)
2393
2394        self.assertIn('u-boot', cbfs.files)
2395        cfile = cbfs.files['u-boot']
2396        self.assertEqual(U_BOOT_DATA + U_BOOT_DTB_DATA, cfile.data)
2397
2398    def testCbfsRawCompress(self):
2399        """Test handling of compressing raw files"""
2400        self._CheckLz4()
2401        data = self._DoReadFile('105_cbfs_raw_compress.dts')
2402        size = 0x140
2403
2404        cbfs = cbfs_util.CbfsReader(data)
2405        self.assertIn('u-boot', cbfs.files)
2406        cfile = cbfs.files['u-boot']
2407        self.assertEqual(COMPRESS_DATA, cfile.data)
2408
2409    def testCbfsBadArch(self):
2410        """Test handling of a bad architecture"""
2411        with self.assertRaises(ValueError) as e:
2412            self._DoReadFile('106_cbfs_bad_arch.dts')
2413        self.assertIn("Invalid architecture 'bad-arch'", str(e.exception))
2414
2415    def testCbfsNoSize(self):
2416        """Test handling of a missing size property"""
2417        with self.assertRaises(ValueError) as e:
2418            self._DoReadFile('107_cbfs_no_size.dts')
2419        self.assertIn('entry must have a size property', str(e.exception))
2420
2421    def testCbfsNoContents(self):
2422        """Test handling of a CBFS entry which does not provide contentsy"""
2423        with self.assertRaises(ValueError) as e:
2424            self._DoReadFile('108_cbfs_no_contents.dts')
2425        self.assertIn('Could not complete processing of contents',
2426                      str(e.exception))
2427
2428    def testCbfsBadCompress(self):
2429        """Test handling of a bad architecture"""
2430        with self.assertRaises(ValueError) as e:
2431            self._DoReadFile('109_cbfs_bad_compress.dts')
2432        self.assertIn("Invalid compression in 'u-boot': 'invalid-algo'",
2433                      str(e.exception))
2434
2435    def testCbfsNamedEntries(self):
2436        """Test handling of named entries"""
2437        data = self._DoReadFile('110_cbfs_name.dts')
2438
2439        cbfs = cbfs_util.CbfsReader(data)
2440        self.assertIn('FRED', cbfs.files)
2441        cfile1 = cbfs.files['FRED']
2442        self.assertEqual(U_BOOT_DATA, cfile1.data)
2443
2444        self.assertIn('hello', cbfs.files)
2445        cfile2 = cbfs.files['hello']
2446        self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
2447
2448    def _SetupIfwi(self, fname):
2449        """Set up to run an IFWI test
2450
2451        Args:
2452            fname: Filename of input file to provide (fitimage.bin or ifwi.bin)
2453        """
2454        self._SetupSplElf()
2455        self._SetupTplElf()
2456
2457        # Intel Integrated Firmware Image (IFWI) file
2458        with gzip.open(self.TestFile('%s.gz' % fname), 'rb') as fd:
2459            data = fd.read()
2460        TestFunctional._MakeInputFile(fname,data)
2461
2462    def _CheckIfwi(self, data):
2463        """Check that an image with an IFWI contains the correct output
2464
2465        Args:
2466            data: Conents of output file
2467        """
2468        expected_desc = tools.read_file(self.TestFile('descriptor.bin'))
2469        if data[:0x1000] != expected_desc:
2470            self.fail('Expected descriptor binary at start of image')
2471
2472        # We expect to find the TPL wil in subpart IBBP entry IBBL
2473        image_fname = tools.get_output_filename('image.bin')
2474        tpl_fname = tools.get_output_filename('tpl.out')
2475        ifwitool = bintool.Bintool.create('ifwitool')
2476        ifwitool.extract(image_fname, 'IBBP', 'IBBL', tpl_fname)
2477
2478        tpl_data = tools.read_file(tpl_fname)
2479        self.assertEqual(U_BOOT_TPL_DATA, tpl_data[:len(U_BOOT_TPL_DATA)])
2480
2481    def testPackX86RomIfwi(self):
2482        """Test that an x86 ROM with Integrated Firmware Image can be created"""
2483        self._SetupIfwi('fitimage.bin')
2484        data = self._DoReadFile('111_x86_rom_ifwi.dts')
2485        self._CheckIfwi(data)
2486
2487    def testPackX86RomIfwiNoDesc(self):
2488        """Test that an x86 ROM with IFWI can be created from an ifwi.bin file"""
2489        self._SetupIfwi('ifwi.bin')
2490        data = self._DoReadFile('112_x86_rom_ifwi_nodesc.dts')
2491        self._CheckIfwi(data)
2492
2493    def testPackX86RomIfwiNoData(self):
2494        """Test that an x86 ROM with IFWI handles missing data"""
2495        self._SetupIfwi('ifwi.bin')
2496        with self.assertRaises(ValueError) as e:
2497            data = self._DoReadFile('113_x86_rom_ifwi_nodata.dts')
2498        self.assertIn('Could not complete processing of contents',
2499                      str(e.exception))
2500
2501    def testIfwiMissing(self):
2502        """Test that binman still produces an image if ifwitool is missing"""
2503        self._SetupIfwi('fitimage.bin')
2504        with test_util.capture_sys_output() as (_, stderr):
2505            self._DoTestFile('111_x86_rom_ifwi.dts',
2506                             force_missing_bintools='ifwitool')
2507        err = stderr.getvalue()
2508        self.assertRegex(err,
2509                         "Image 'image'.*missing bintools.*: ifwitool")
2510
2511    def testCbfsOffset(self):
2512        """Test a CBFS with files at particular offsets
2513
2514        Like all CFBS tests, this is just checking the logic that calls
2515        cbfs_util. See cbfs_util_test for fully tests (e.g. test_cbfs_offset()).
2516        """
2517        data = self._DoReadFile('114_cbfs_offset.dts')
2518        size = 0x200
2519
2520        cbfs = cbfs_util.CbfsReader(data)
2521        self.assertEqual(size, cbfs.rom_size)
2522
2523        self.assertIn('u-boot', cbfs.files)
2524        cfile = cbfs.files['u-boot']
2525        self.assertEqual(U_BOOT_DATA, cfile.data)
2526        self.assertEqual(0x40, cfile.cbfs_offset)
2527
2528        self.assertIn('u-boot-dtb', cbfs.files)
2529        cfile2 = cbfs.files['u-boot-dtb']
2530        self.assertEqual(U_BOOT_DTB_DATA, cfile2.data)
2531        self.assertEqual(0x140, cfile2.cbfs_offset)
2532
2533    def testFdtmap(self):
2534        """Test an FDT map can be inserted in the image"""
2535        data = self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
2536        fdtmap_data = data[len(U_BOOT_DATA):]
2537        magic = fdtmap_data[:8]
2538        self.assertEqual(b'_FDTMAP_', magic)
2539        self.assertEqual(tools.get_bytes(0, 8), fdtmap_data[8:16])
2540
2541        fdt_data = fdtmap_data[16:]
2542        dtb = fdt.Fdt.FromData(fdt_data)
2543        dtb.Scan()
2544        props = self._GetPropTree(dtb, BASE_DTB_PROPS, prefix='/')
2545        self.assertEqual({
2546            'image-pos': 0,
2547            'offset': 0,
2548            'u-boot:offset': 0,
2549            'u-boot:size': len(U_BOOT_DATA),
2550            'u-boot:image-pos': 0,
2551            'fdtmap:image-pos': 4,
2552            'fdtmap:offset': 4,
2553            'fdtmap:size': len(fdtmap_data),
2554            'size': len(data),
2555        }, props)
2556
2557    def testFdtmapNoMatch(self):
2558        """Check handling of an FDT map when the section cannot be found"""
2559        self.data = self._DoReadFileRealDtb('115_fdtmap.dts')
2560
2561        # Mangle the section name, which should cause a mismatch between the
2562        # correct FDT path and the one expected by the section
2563        image = control.images['image']
2564        image._node.path += '-suffix'
2565        entries = image.GetEntries()
2566        fdtmap = entries['fdtmap']
2567        with self.assertRaises(ValueError) as e:
2568            fdtmap._GetFdtmap()
2569        self.assertIn("Cannot locate node for path '/binman-suffix'",
2570                      str(e.exception))
2571
2572    def testFdtmapHeader(self):
2573        """Test an FDT map and image header can be inserted in the image"""
2574        data = self.data = self._DoReadFileRealDtb('116_fdtmap_hdr.dts')
2575        fdtmap_pos = len(U_BOOT_DATA)
2576        fdtmap_data = data[fdtmap_pos:]
2577        fdt_data = fdtmap_data[16:]
2578        dtb = fdt.Fdt.FromData(fdt_data)
2579        fdt_size = dtb.GetFdtObj().totalsize()
2580        hdr_data = data[-8:]
2581        self.assertEqual(b'BinM', hdr_data[:4])
2582        offset = struct.unpack('<I', hdr_data[4:])[0] & 0xffffffff
2583        self.assertEqual(fdtmap_pos - 0x400, offset - (1 << 32))
2584
2585    def testFdtmapHeaderStart(self):
2586        """Test an image header can be inserted at the image start"""
2587        data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
2588        fdtmap_pos = 0x100 + len(U_BOOT_DATA)
2589        hdr_data = data[:8]
2590        self.assertEqual(b'BinM', hdr_data[:4])
2591        offset = struct.unpack('<I', hdr_data[4:])[0]
2592        self.assertEqual(fdtmap_pos, offset)
2593
2594    def testFdtmapHeaderPos(self):
2595        """Test an image header can be inserted at a chosen position"""
2596        data = self.data = self._DoReadFileRealDtb('118_fdtmap_hdr_pos.dts')
2597        fdtmap_pos = 0x100 + len(U_BOOT_DATA)
2598        hdr_data = data[0x80:0x88]
2599        self.assertEqual(b'BinM', hdr_data[:4])
2600        offset = struct.unpack('<I', hdr_data[4:])[0]
2601        self.assertEqual(fdtmap_pos, offset)
2602
2603    def testHeaderMissingFdtmap(self):
2604        """Test an image header requires an fdtmap"""
2605        with self.assertRaises(ValueError) as e:
2606            self.data = self._DoReadFileRealDtb('119_fdtmap_hdr_missing.dts')
2607        self.assertIn("'image_header' section must have an 'fdtmap' sibling",
2608                      str(e.exception))
2609
2610    def testHeaderNoLocation(self):
2611        """Test an image header with a no specified location is detected"""
2612        with self.assertRaises(ValueError) as e:
2613            self.data = self._DoReadFileRealDtb('120_hdr_no_location.dts')
2614        self.assertIn("Invalid location 'None', expected 'start' or 'end'",
2615                      str(e.exception))
2616
2617    def testEntryExpand(self):
2618        """Test extending an entry after it is packed"""
2619        data = self._DoReadFile('121_entry_extend.dts')
2620        self.assertEqual(b'aaa', data[:3])
2621        self.assertEqual(U_BOOT_DATA, data[3:3 + len(U_BOOT_DATA)])
2622        self.assertEqual(b'aaa', data[-3:])
2623
2624    def testEntryExtendBad(self):
2625        """Test extending an entry after it is packed, twice"""
2626        with self.assertRaises(ValueError) as e:
2627            self._DoReadFile('122_entry_extend_twice.dts')
2628        self.assertIn("Image '/binman': Entries changed size after packing",
2629                      str(e.exception))
2630
2631    def testEntryExtendSection(self):
2632        """Test extending an entry within a section after it is packed"""
2633        data = self._DoReadFile('123_entry_extend_section.dts')
2634        self.assertEqual(b'aaa', data[:3])
2635        self.assertEqual(U_BOOT_DATA, data[3:3 + len(U_BOOT_DATA)])
2636        self.assertEqual(b'aaa', data[-3:])
2637
2638    def testCompressDtb(self):
2639        """Test that compress of device-tree files is supported"""
2640        self._CheckLz4()
2641        data = self.data = self._DoReadFileRealDtb('124_compress_dtb.dts')
2642        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
2643        comp_data = data[len(U_BOOT_DATA):]
2644        orig = self._decompress(comp_data)
2645        dtb = fdt.Fdt.FromData(orig)
2646        dtb.Scan()
2647        props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
2648        expected = {
2649            'u-boot:size': len(U_BOOT_DATA),
2650            'u-boot-dtb:uncomp-size': len(orig),
2651            'u-boot-dtb:size': len(comp_data),
2652            'size': len(data),
2653            }
2654        self.assertEqual(expected, props)
2655
2656    def testCbfsUpdateFdt(self):
2657        """Test that we can update the device tree with CBFS offset/size info"""
2658        self._CheckLz4()
2659        data, _, _, out_dtb_fname = self._DoReadFileDtb('125_cbfs_update.dts',
2660                                                        update_dtb=True)
2661        dtb = fdt.Fdt(out_dtb_fname)
2662        dtb.Scan()
2663        props = self._GetPropTree(dtb, BASE_DTB_PROPS + ['uncomp-size'])
2664        del props['cbfs/u-boot:size']
2665        self.assertEqual({
2666            'offset': 0,
2667            'size': len(data),
2668            'image-pos': 0,
2669            'cbfs:offset': 0,
2670            'cbfs:size': len(data),
2671            'cbfs:image-pos': 0,
2672            'cbfs/u-boot:offset': 0x30,
2673            'cbfs/u-boot:uncomp-size': len(U_BOOT_DATA),
2674            'cbfs/u-boot:image-pos': 0x30,
2675            'cbfs/u-boot-dtb:offset': 0xa4,
2676            'cbfs/u-boot-dtb:size': len(U_BOOT_DATA),
2677            'cbfs/u-boot-dtb:image-pos': 0xa4,
2678            }, props)
2679
2680    def testCbfsBadType(self):
2681        """Test an image header with a no specified location is detected"""
2682        with self.assertRaises(ValueError) as e:
2683            self._DoReadFile('126_cbfs_bad_type.dts')
2684        self.assertIn("Unknown cbfs-type 'badtype'", str(e.exception))
2685
2686    def testList(self):
2687        """Test listing the files in an image"""
2688        self._CheckLz4()
2689        data = self._DoReadFile('127_list.dts')
2690        image = control.images['image']
2691        entries = image.BuildEntryList()
2692        self.assertEqual(7, len(entries))
2693
2694        ent = entries[0]
2695        self.assertEqual(0, ent.indent)
2696        self.assertEqual('image', ent.name)
2697        self.assertEqual('section', ent.etype)
2698        self.assertEqual(len(data), ent.size)
2699        self.assertEqual(0, ent.image_pos)
2700        self.assertEqual(None, ent.uncomp_size)
2701        self.assertEqual(0, ent.offset)
2702
2703        ent = entries[1]
2704        self.assertEqual(1, ent.indent)
2705        self.assertEqual('u-boot', ent.name)
2706        self.assertEqual('u-boot', ent.etype)
2707        self.assertEqual(len(U_BOOT_DATA), ent.size)
2708        self.assertEqual(0, ent.image_pos)
2709        self.assertEqual(None, ent.uncomp_size)
2710        self.assertEqual(0, ent.offset)
2711
2712        ent = entries[2]
2713        self.assertEqual(1, ent.indent)
2714        self.assertEqual('section', ent.name)
2715        self.assertEqual('section', ent.etype)
2716        section_size = ent.size
2717        self.assertEqual(0x100, ent.image_pos)
2718        self.assertEqual(None, ent.uncomp_size)
2719        self.assertEqual(0x100, ent.offset)
2720
2721        ent = entries[3]
2722        self.assertEqual(2, ent.indent)
2723        self.assertEqual('cbfs', ent.name)
2724        self.assertEqual('cbfs', ent.etype)
2725        self.assertEqual(0x400, ent.size)
2726        self.assertEqual(0x100, ent.image_pos)
2727        self.assertEqual(None, ent.uncomp_size)
2728        self.assertEqual(0, ent.offset)
2729
2730        ent = entries[4]
2731        self.assertEqual(3, ent.indent)
2732        self.assertEqual('u-boot', ent.name)
2733        self.assertEqual('u-boot', ent.etype)
2734        self.assertEqual(len(U_BOOT_DATA), ent.size)
2735        self.assertEqual(0x138, ent.image_pos)
2736        self.assertEqual(None, ent.uncomp_size)
2737        self.assertEqual(0x38, ent.offset)
2738
2739        ent = entries[5]
2740        self.assertEqual(3, ent.indent)
2741        self.assertEqual('u-boot-dtb', ent.name)
2742        self.assertEqual('text', ent.etype)
2743        self.assertGreater(len(COMPRESS_DATA), ent.size)
2744        self.assertEqual(0x178, ent.image_pos)
2745        self.assertEqual(len(COMPRESS_DATA), ent.uncomp_size)
2746        self.assertEqual(0x78, ent.offset)
2747
2748        ent = entries[6]
2749        self.assertEqual(2, ent.indent)
2750        self.assertEqual('u-boot-dtb', ent.name)
2751        self.assertEqual('u-boot-dtb', ent.etype)
2752        self.assertEqual(0x500, ent.image_pos)
2753        self.assertEqual(len(U_BOOT_DTB_DATA), ent.uncomp_size)
2754        dtb_size = ent.size
2755        # Compressing this data expands it since headers are added
2756        self.assertGreater(dtb_size, len(U_BOOT_DTB_DATA))
2757        self.assertEqual(0x400, ent.offset)
2758
2759        self.assertEqual(len(data), 0x100 + section_size)
2760        self.assertEqual(section_size, 0x400 + dtb_size)
2761
2762    def testFindFdtmap(self):
2763        """Test locating an FDT map in an image"""
2764        self._CheckLz4()
2765        data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2766        image = control.images['image']
2767        entries = image.GetEntries()
2768        entry = entries['fdtmap']
2769        self.assertEqual(entry.image_pos, fdtmap.LocateFdtmap(data))
2770
2771    def testFindFdtmapMissing(self):
2772        """Test failing to locate an FDP map"""
2773        data = self._DoReadFile('005_simple.dts')
2774        self.assertEqual(None, fdtmap.LocateFdtmap(data))
2775
2776    def testFindImageHeader(self):
2777        """Test locating a image header"""
2778        self._CheckLz4()
2779        data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2780        image = control.images['image']
2781        entries = image.GetEntries()
2782        entry = entries['fdtmap']
2783        # The header should point to the FDT map
2784        self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
2785
2786    def testFindImageHeaderStart(self):
2787        """Test locating a image header located at the start of an image"""
2788        data = self.data = self._DoReadFileRealDtb('117_fdtmap_hdr_start.dts')
2789        image = control.images['image']
2790        entries = image.GetEntries()
2791        entry = entries['fdtmap']
2792        # The header should point to the FDT map
2793        self.assertEqual(entry.image_pos, image_header.LocateHeaderOffset(data))
2794
2795    def testFindImageHeaderMissing(self):
2796        """Test failing to locate an image header"""
2797        data = self._DoReadFile('005_simple.dts')
2798        self.assertEqual(None, image_header.LocateHeaderOffset(data))
2799
2800    def testReadImage(self):
2801        """Test reading an image and accessing its FDT map"""
2802        self._CheckLz4()
2803        data = self.data = self._DoReadFileRealDtb('128_decode_image.dts')
2804        image_fname = tools.get_output_filename('image.bin')
2805        orig_image = control.images['image']
2806        image = Image.FromFile(image_fname)
2807        self.assertEqual(orig_image.GetEntries().keys(),
2808                         image.GetEntries().keys())
2809
2810        orig_entry = orig_image.GetEntries()['fdtmap']
2811        entry = image.GetEntries()['fdtmap']
2812        self.assertEquals(orig_entry.offset, entry.offset)
2813        self.assertEquals(orig_entry.size, entry.size)
2814        self.assertEquals(orig_entry.image_pos, entry.image_pos)
2815
2816    def testReadImageNoHeader(self):
2817        """Test accessing an image's FDT map without an image header"""
2818        self._CheckLz4()
2819        data = self._DoReadFileRealDtb('129_decode_image_nohdr.dts')
2820        image_fname = tools.get_output_filename('image.bin')
2821        image = Image.FromFile(image_fname)
2822        self.assertTrue(isinstance(image, Image))
2823        self.assertEqual('image', image.image_name[-5:])
2824
2825    def testReadImageFail(self):
2826        """Test failing to read an image image's FDT map"""
2827        self._DoReadFile('005_simple.dts')
2828        image_fname = tools.get_output_filename('image.bin')
2829        with self.assertRaises(ValueError) as e:
2830            image = Image.FromFile(image_fname)
2831        self.assertIn("Cannot find FDT map in image", str(e.exception))
2832
2833    def testListCmd(self):
2834        """Test listing the files in an image using an Fdtmap"""
2835        self._CheckLz4()
2836        data = self._DoReadFileRealDtb('130_list_fdtmap.dts')
2837
2838        # lz4 compression size differs depending on the version
2839        image = control.images['image']
2840        entries = image.GetEntries()
2841        section_size = entries['section'].size
2842        fdt_size = entries['section'].GetEntries()['u-boot-dtb'].size
2843        fdtmap_offset = entries['fdtmap'].offset
2844
2845        tmpdir = None
2846        try:
2847            tmpdir, updated_fname = self._SetupImageInTmpdir()
2848            with test_util.capture_sys_output() as (stdout, stderr):
2849                self._DoBinman('ls', '-i', updated_fname)
2850        finally:
2851            if tmpdir:
2852                shutil.rmtree(tmpdir)
2853        lines = stdout.getvalue().splitlines()
2854        expected = [
2855'Name              Image-pos  Size  Entry-type    Offset  Uncomp-size',
2856'----------------------------------------------------------------------',
2857'image                     0   c00  section            0',
2858'  u-boot                  0     4  u-boot             0',
2859'  section               100   %x  section          100' % section_size,
2860'    cbfs                100   400  cbfs               0',
2861'      u-boot            120     4  u-boot            20',
2862'      u-boot-dtb        180   105  u-boot-dtb        80          3c9',
2863'    u-boot-dtb          500   %x  u-boot-dtb       400          3c9' % fdt_size,
2864'  fdtmap                %x   3bd  fdtmap           %x' %
2865        (fdtmap_offset, fdtmap_offset),
2866'  image-header          bf8     8  image-header     bf8',
2867            ]
2868        self.assertEqual(expected, lines)
2869
2870    def testListCmdFail(self):
2871        """Test failing to list an image"""
2872        self._DoReadFile('005_simple.dts')
2873        tmpdir = None
2874        try:
2875            tmpdir, updated_fname = self._SetupImageInTmpdir()
2876            with self.assertRaises(ValueError) as e:
2877                self._DoBinman('ls', '-i', updated_fname)
2878        finally:
2879            if tmpdir:
2880                shutil.rmtree(tmpdir)
2881        self.assertIn("Cannot find FDT map in image", str(e.exception))
2882
2883    def _RunListCmd(self, paths, expected):
2884        """List out entries and check the result
2885
2886        Args:
2887            paths: List of paths to pass to the list command
2888            expected: Expected list of filenames to be returned, in order
2889        """
2890        self._CheckLz4()
2891        self._DoReadFileRealDtb('130_list_fdtmap.dts')
2892        image_fname = tools.get_output_filename('image.bin')
2893        image = Image.FromFile(image_fname)
2894        lines = image.GetListEntries(paths)[1]
2895        files = [line[0].strip() for line in lines[1:]]
2896        self.assertEqual(expected, files)
2897
2898    def testListCmdSection(self):
2899        """Test listing the files in a section"""
2900        self._RunListCmd(['section'],
2901            ['section', 'cbfs', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
2902
2903    def testListCmdFile(self):
2904        """Test listing a particular file"""
2905        self._RunListCmd(['*u-boot-dtb'], ['u-boot-dtb', 'u-boot-dtb'])
2906
2907    def testListCmdWildcard(self):
2908        """Test listing a wildcarded file"""
2909        self._RunListCmd(['*boot*'],
2910            ['u-boot', 'u-boot', 'u-boot-dtb', 'u-boot-dtb'])
2911
2912    def testListCmdWildcardMulti(self):
2913        """Test listing a wildcarded file"""
2914        self._RunListCmd(['*cb*', '*head*'],
2915            ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
2916
2917    def testListCmdEmpty(self):
2918        """Test listing a wildcarded file"""
2919        self._RunListCmd(['nothing'], [])
2920
2921    def testListCmdPath(self):
2922        """Test listing the files in a sub-entry of a section"""
2923        self._RunListCmd(['section/cbfs'], ['cbfs', 'u-boot', 'u-boot-dtb'])
2924
2925    def _RunExtractCmd(self, entry_name, decomp=True):
2926        """Extract an entry from an image
2927
2928        Args:
2929            entry_name: Entry name to extract
2930            decomp: True to decompress the data if compressed, False to leave
2931                it in its raw uncompressed format
2932
2933        Returns:
2934            data from entry
2935        """
2936        self._CheckLz4()
2937        self._DoReadFileRealDtb('130_list_fdtmap.dts')
2938        image_fname = tools.get_output_filename('image.bin')
2939        return control.ReadEntry(image_fname, entry_name, decomp)
2940
2941    def testExtractSimple(self):
2942        """Test extracting a single file"""
2943        data = self._RunExtractCmd('u-boot')
2944        self.assertEqual(U_BOOT_DATA, data)
2945
2946    def testExtractSection(self):
2947        """Test extracting the files in a section"""
2948        data = self._RunExtractCmd('section')
2949        cbfs_data = data[:0x400]
2950        cbfs = cbfs_util.CbfsReader(cbfs_data)
2951        self.assertEqual(['u-boot', 'u-boot-dtb', ''], list(cbfs.files.keys()))
2952        dtb_data = data[0x400:]
2953        dtb = self._decompress(dtb_data)
2954        self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2955
2956    def testExtractCompressed(self):
2957        """Test extracting compressed data"""
2958        data = self._RunExtractCmd('section/u-boot-dtb')
2959        self.assertEqual(EXTRACT_DTB_SIZE, len(data))
2960
2961    def testExtractRaw(self):
2962        """Test extracting compressed data without decompressing it"""
2963        data = self._RunExtractCmd('section/u-boot-dtb', decomp=False)
2964        dtb = self._decompress(data)
2965        self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2966
2967    def testExtractCbfs(self):
2968        """Test extracting CBFS data"""
2969        data = self._RunExtractCmd('section/cbfs/u-boot')
2970        self.assertEqual(U_BOOT_DATA, data)
2971
2972    def testExtractCbfsCompressed(self):
2973        """Test extracting CBFS compressed data"""
2974        data = self._RunExtractCmd('section/cbfs/u-boot-dtb')
2975        self.assertEqual(EXTRACT_DTB_SIZE, len(data))
2976
2977    def testExtractCbfsRaw(self):
2978        """Test extracting CBFS compressed data without decompressing it"""
2979        bintool = self.comp_bintools['lzma_alone']
2980        self._CheckBintool(bintool)
2981        data = self._RunExtractCmd('section/cbfs/u-boot-dtb', decomp=False)
2982        dtb = bintool.decompress(data)
2983        self.assertEqual(EXTRACT_DTB_SIZE, len(dtb))
2984
2985    def testExtractBadEntry(self):
2986        """Test extracting a bad section path"""
2987        with self.assertRaises(ValueError) as e:
2988            self._RunExtractCmd('section/does-not-exist')
2989        self.assertIn("Entry 'does-not-exist' not found in '/section'",
2990                      str(e.exception))
2991
2992    def testExtractMissingFile(self):
2993        """Test extracting file that does not exist"""
2994        with self.assertRaises(IOError) as e:
2995            control.ReadEntry('missing-file', 'name')
2996
2997    def testExtractBadFile(self):
2998        """Test extracting an invalid file"""
2999        fname = os.path.join(self._indir, 'badfile')
3000        tools.write_file(fname, b'')
3001        with self.assertRaises(ValueError) as e:
3002            control.ReadEntry(fname, 'name')
3003
3004    def testExtractCmd(self):
3005        """Test extracting a file fron an image on the command line"""
3006        self._CheckLz4()
3007        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3008        fname = os.path.join(self._indir, 'output.extact')
3009        tmpdir = None
3010        try:
3011            tmpdir, updated_fname = self._SetupImageInTmpdir()
3012            with test_util.capture_sys_output() as (stdout, stderr):
3013                self._DoBinman('extract', '-i', updated_fname, 'u-boot',
3014                               '-f', fname)
3015        finally:
3016            if tmpdir:
3017                shutil.rmtree(tmpdir)
3018        data = tools.read_file(fname)
3019        self.assertEqual(U_BOOT_DATA, data)
3020
3021    def testExtractOneEntry(self):
3022        """Test extracting a single entry fron an image """
3023        self._CheckLz4()
3024        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3025        image_fname = tools.get_output_filename('image.bin')
3026        fname = os.path.join(self._indir, 'output.extact')
3027        control.ExtractEntries(image_fname, fname, None, ['u-boot'])
3028        data = tools.read_file(fname)
3029        self.assertEqual(U_BOOT_DATA, data)
3030
3031    def _CheckExtractOutput(self, decomp):
3032        """Helper to test file output with and without decompression
3033
3034        Args:
3035            decomp: True to decompress entry data, False to output it raw
3036        """
3037        def _CheckPresent(entry_path, expect_data, expect_size=None):
3038            """Check and remove expected file
3039
3040            This checks the data/size of a file and removes the file both from
3041            the outfiles set and from the output directory. Once all files are
3042            processed, both the set and directory should be empty.
3043
3044            Args:
3045                entry_path: Entry path
3046                expect_data: Data to expect in file, or None to skip check
3047                expect_size: Size of data to expect in file, or None to skip
3048            """
3049            path = os.path.join(outdir, entry_path)
3050            data = tools.read_file(path)
3051            os.remove(path)
3052            if expect_data:
3053                self.assertEqual(expect_data, data)
3054            elif expect_size:
3055                self.assertEqual(expect_size, len(data))
3056            outfiles.remove(path)
3057
3058        def _CheckDirPresent(name):
3059            """Remove expected directory
3060
3061            This gives an error if the directory does not exist as expected
3062
3063            Args:
3064                name: Name of directory to remove
3065            """
3066            path = os.path.join(outdir, name)
3067            os.rmdir(path)
3068
3069        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3070        image_fname = tools.get_output_filename('image.bin')
3071        outdir = os.path.join(self._indir, 'extract')
3072        einfos = control.ExtractEntries(image_fname, None, outdir, [], decomp)
3073
3074        # Create a set of all file that were output (should be 9)
3075        outfiles = set()
3076        for root, dirs, files in os.walk(outdir):
3077            outfiles |= set([os.path.join(root, fname) for fname in files])
3078        self.assertEqual(9, len(outfiles))
3079        self.assertEqual(9, len(einfos))
3080
3081        image = control.images['image']
3082        entries = image.GetEntries()
3083
3084        # Check the 9 files in various ways
3085        section = entries['section']
3086        section_entries = section.GetEntries()
3087        cbfs_entries = section_entries['cbfs'].GetEntries()
3088        _CheckPresent('u-boot', U_BOOT_DATA)
3089        _CheckPresent('section/cbfs/u-boot', U_BOOT_DATA)
3090        dtb_len = EXTRACT_DTB_SIZE
3091        if not decomp:
3092            dtb_len = cbfs_entries['u-boot-dtb'].size
3093        _CheckPresent('section/cbfs/u-boot-dtb', None, dtb_len)
3094        if not decomp:
3095            dtb_len = section_entries['u-boot-dtb'].size
3096        _CheckPresent('section/u-boot-dtb', None, dtb_len)
3097
3098        fdtmap = entries['fdtmap']
3099        _CheckPresent('fdtmap', fdtmap.data)
3100        hdr = entries['image-header']
3101        _CheckPresent('image-header', hdr.data)
3102
3103        _CheckPresent('section/root', section.data)
3104        cbfs = section_entries['cbfs']
3105        _CheckPresent('section/cbfs/root', cbfs.data)
3106        data = tools.read_file(image_fname)
3107        _CheckPresent('root', data)
3108
3109        # There should be no files left. Remove all the directories to check.
3110        # If there are any files/dirs remaining, one of these checks will fail.
3111        self.assertEqual(0, len(outfiles))
3112        _CheckDirPresent('section/cbfs')
3113        _CheckDirPresent('section')
3114        _CheckDirPresent('')
3115        self.assertFalse(os.path.exists(outdir))
3116
3117    def testExtractAllEntries(self):
3118        """Test extracting all entries"""
3119        self._CheckLz4()
3120        self._CheckExtractOutput(decomp=True)
3121
3122    def testExtractAllEntriesRaw(self):
3123        """Test extracting all entries without decompressing them"""
3124        self._CheckLz4()
3125        self._CheckExtractOutput(decomp=False)
3126
3127    def testExtractSelectedEntries(self):
3128        """Test extracting some entries"""
3129        self._CheckLz4()
3130        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3131        image_fname = tools.get_output_filename('image.bin')
3132        outdir = os.path.join(self._indir, 'extract')
3133        einfos = control.ExtractEntries(image_fname, None, outdir,
3134                                        ['*cb*', '*head*'])
3135
3136        # File output is tested by testExtractAllEntries(), so just check that
3137        # the expected entries are selected
3138        names = [einfo.name for einfo in einfos]
3139        self.assertEqual(names,
3140                         ['cbfs', 'u-boot', 'u-boot-dtb', 'image-header'])
3141
3142    def testExtractNoEntryPaths(self):
3143        """Test extracting some entries"""
3144        self._CheckLz4()
3145        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3146        image_fname = tools.get_output_filename('image.bin')
3147        with self.assertRaises(ValueError) as e:
3148            control.ExtractEntries(image_fname, 'fname', None, [])
3149        self.assertIn('Must specify an entry path to write with -f',
3150                      str(e.exception))
3151
3152    def testExtractTooManyEntryPaths(self):
3153        """Test extracting some entries"""
3154        self._CheckLz4()
3155        self._DoReadFileRealDtb('130_list_fdtmap.dts')
3156        image_fname = tools.get_output_filename('image.bin')
3157        with self.assertRaises(ValueError) as e:
3158            control.ExtractEntries(image_fname, 'fname', None, ['a', 'b'])
3159        self.assertIn('Must specify exactly one entry path to write with -f',
3160                      str(e.exception))
3161
3162    def testPackAlignSection(self):
3163        """Test that sections can have alignment"""
3164        self._DoReadFile('131_pack_align_section.dts')
3165
3166        self.assertIn('image', control.images)
3167        image = control.images['image']
3168        entries = image.GetEntries()
3169        self.assertEqual(3, len(entries))
3170
3171        # First u-boot
3172        self.assertIn('u-boot', entries)
3173        entry = entries['u-boot']
3174        self.assertEqual(0, entry.offset)
3175        self.assertEqual(0, entry.image_pos)
3176        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
3177        self.assertEqual(len(U_BOOT_DATA), entry.size)
3178
3179        # Section0
3180        self.assertIn('section0', entries)
3181        section0 = entries['section0']
3182        self.assertEqual(0x10, section0.offset)
3183        self.assertEqual(0x10, section0.image_pos)
3184        self.assertEqual(len(U_BOOT_DATA), section0.size)
3185
3186        # Second u-boot
3187        section_entries = section0.GetEntries()
3188        self.assertIn('u-boot', section_entries)
3189        entry = section_entries['u-boot']
3190        self.assertEqual(0, entry.offset)
3191        self.assertEqual(0x10, entry.image_pos)
3192        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
3193        self.assertEqual(len(U_BOOT_DATA), entry.size)
3194
3195        # Section1
3196        self.assertIn('section1', entries)
3197        section1 = entries['section1']
3198        self.assertEqual(0x14, section1.offset)
3199        self.assertEqual(0x14, section1.image_pos)
3200        self.assertEqual(0x20, section1.size)
3201
3202        # Second u-boot
3203        section_entries = section1.GetEntries()
3204        self.assertIn('u-boot', section_entries)
3205        entry = section_entries['u-boot']
3206        self.assertEqual(0, entry.offset)
3207        self.assertEqual(0x14, entry.image_pos)
3208        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
3209        self.assertEqual(len(U_BOOT_DATA), entry.size)
3210
3211        # Section2
3212        self.assertIn('section2', section_entries)
3213        section2 = section_entries['section2']
3214        self.assertEqual(0x4, section2.offset)
3215        self.assertEqual(0x18, section2.image_pos)
3216        self.assertEqual(4, section2.size)
3217
3218        # Third u-boot
3219        section_entries = section2.GetEntries()
3220        self.assertIn('u-boot', section_entries)
3221        entry = section_entries['u-boot']
3222        self.assertEqual(0, entry.offset)
3223        self.assertEqual(0x18, entry.image_pos)
3224        self.assertEqual(len(U_BOOT_DATA), entry.contents_size)
3225        self.assertEqual(len(U_BOOT_DATA), entry.size)
3226
3227    def _RunReplaceCmd(self, entry_name, data, decomp=True, allow_resize=True,
3228                       dts='132_replace.dts'):
3229        """Replace an entry in an image
3230
3231        This writes the entry data to update it, then opens the updated file and
3232        returns the value that it now finds there.
3233
3234        Args:
3235            entry_name: Entry name to replace
3236            data: Data to replace it with
3237            decomp: True to compress the data if needed, False if data is
3238                already compressed so should be used as is
3239            allow_resize: True to allow entries to change size, False to raise
3240                an exception
3241
3242        Returns:
3243            Tuple:
3244                data from entry
3245                data from fdtmap (excluding header)
3246                Image object that was modified
3247        """
3248        dtb_data = self._DoReadFileDtb(dts, use_real_dtb=True,
3249                                       update_dtb=True)[1]
3250
3251        self.assertIn('image', control.images)
3252        image = control.images['image']
3253        entries = image.GetEntries()
3254        orig_dtb_data = entries['u-boot-dtb'].data
3255        orig_fdtmap_data = entries['fdtmap'].data
3256
3257        image_fname = tools.get_output_filename('image.bin')
3258        updated_fname = tools.get_output_filename('image-updated.bin')
3259        tools.write_file(updated_fname, tools.read_file(image_fname))
3260        image = control.WriteEntry(updated_fname, entry_name, data, decomp,
3261                                   allow_resize)
3262        data = control.ReadEntry(updated_fname, entry_name, decomp)
3263
3264        # The DT data should not change unless resized:
3265        if not allow_resize:
3266            new_dtb_data = entries['u-boot-dtb'].data
3267            self.assertEqual(new_dtb_data, orig_dtb_data)
3268            new_fdtmap_data = entries['fdtmap'].data
3269            self.assertEqual(new_fdtmap_data, orig_fdtmap_data)
3270
3271        return data, orig_fdtmap_data[fdtmap.FDTMAP_HDR_LEN:], image
3272
3273    def testReplaceSimple(self):
3274        """Test replacing a single file"""
3275        expected = b'x' * len(U_BOOT_DATA)
3276        data, expected_fdtmap, _ = self._RunReplaceCmd('u-boot', expected,
3277                                                    allow_resize=False)
3278        self.assertEqual(expected, data)
3279
3280        # Test that the state looks right. There should be an FDT for the fdtmap
3281        # that we jsut read back in, and it should match what we find in the
3282        # 'control' tables. Checking for an FDT that does not exist should
3283        # return None.
3284        path, fdtmap = state.GetFdtContents('fdtmap')
3285        self.assertIsNotNone(path)
3286        self.assertEqual(expected_fdtmap, fdtmap)
3287
3288        dtb = state.GetFdtForEtype('fdtmap')
3289        self.assertEqual(dtb.GetContents(), fdtmap)
3290
3291        missing_path, missing_fdtmap = state.GetFdtContents('missing')
3292        self.assertIsNone(missing_path)
3293        self.assertIsNone(missing_fdtmap)
3294
3295        missing_dtb = state.GetFdtForEtype('missing')
3296        self.assertIsNone(missing_dtb)
3297
3298        self.assertEqual('/binman', state.fdt_path_prefix)
3299
3300    def testReplaceResizeFail(self):
3301        """Test replacing a file by something larger"""
3302        expected = U_BOOT_DATA + b'x'
3303        with self.assertRaises(ValueError) as e:
3304            self._RunReplaceCmd('u-boot', expected, allow_resize=False,
3305                                dts='139_replace_repack.dts')
3306        self.assertIn("Node '/u-boot': Entry data size does not match, but resize is disabled",
3307                      str(e.exception))
3308
3309    def testReplaceMulti(self):
3310        """Test replacing entry data where multiple images are generated"""
3311        data = self._DoReadFileDtb('133_replace_multi.dts', use_real_dtb=True,
3312                                   update_dtb=True)[0]
3313        expected = b'x' * len(U_BOOT_DATA)
3314        updated_fname = tools.get_output_filename('image-updated.bin')
3315        tools.write_file(updated_fname, data)
3316        entry_name = 'u-boot'
3317        control.WriteEntry(updated_fname, entry_name, expected,
3318                           allow_resize=False)
3319        data = control.ReadEntry(updated_fname, entry_name)
3320        self.assertEqual(expected, data)
3321
3322        # Check the state looks right.
3323        self.assertEqual('/binman/image', state.fdt_path_prefix)
3324
3325        # Now check we can write the first image
3326        image_fname = tools.get_output_filename('first-image.bin')
3327        updated_fname = tools.get_output_filename('first-updated.bin')
3328        tools.write_file(updated_fname, tools.read_file(image_fname))
3329        entry_name = 'u-boot'
3330        control.WriteEntry(updated_fname, entry_name, expected,
3331                           allow_resize=False)
3332        data = control.ReadEntry(updated_fname, entry_name)
3333        self.assertEqual(expected, data)
3334
3335        # Check the state looks right.
3336        self.assertEqual('/binman/first-image', state.fdt_path_prefix)
3337
3338    def testUpdateFdtAllRepack(self):
3339        """Test that all device trees are updated with offset/size info"""
3340        self._SetupSplElf()
3341        self._SetupTplElf()
3342        data = self._DoReadFileRealDtb('134_fdt_update_all_repack.dts')
3343        SECTION_SIZE = 0x300
3344        DTB_SIZE = 602
3345        FDTMAP_SIZE = 608
3346        base_expected = {
3347            'offset': 0,
3348            'size': SECTION_SIZE + DTB_SIZE * 2 + FDTMAP_SIZE,
3349            'image-pos': 0,
3350            'section:offset': 0,
3351            'section:size': SECTION_SIZE,
3352            'section:image-pos': 0,
3353            'section/u-boot-dtb:offset': 4,
3354            'section/u-boot-dtb:size': 636,
3355            'section/u-boot-dtb:image-pos': 4,
3356            'u-boot-spl-dtb:offset': SECTION_SIZE,
3357            'u-boot-spl-dtb:size': DTB_SIZE,
3358            'u-boot-spl-dtb:image-pos': SECTION_SIZE,
3359            'u-boot-tpl-dtb:offset': SECTION_SIZE + DTB_SIZE,
3360            'u-boot-tpl-dtb:image-pos': SECTION_SIZE + DTB_SIZE,
3361            'u-boot-tpl-dtb:size': DTB_SIZE,
3362            'fdtmap:offset': SECTION_SIZE + DTB_SIZE * 2,
3363            'fdtmap:size': FDTMAP_SIZE,
3364            'fdtmap:image-pos': SECTION_SIZE + DTB_SIZE * 2,
3365        }
3366        main_expected = {
3367            'section:orig-size': SECTION_SIZE,
3368            'section/u-boot-dtb:orig-offset': 4,
3369        }
3370
3371        # We expect three device-tree files in the output, with the first one
3372        # within a fixed-size section.
3373        # Read them in sequence. We look for an 'spl' property in the SPL tree,
3374        # and 'tpl' in the TPL tree, to make sure they are distinct from the
3375        # main U-Boot tree. All three should have the same positions and offset
3376        # except that the main tree should include the main_expected properties
3377        start = 4
3378        for item in ['', 'spl', 'tpl', None]:
3379            if item is None:
3380                start += 16  # Move past fdtmap header
3381            dtb = fdt.Fdt.FromData(data[start:])
3382            dtb.Scan()
3383            props = self._GetPropTree(dtb,
3384                BASE_DTB_PROPS + REPACK_DTB_PROPS + ['spl', 'tpl'],
3385                prefix='/' if item is None else '/binman/')
3386            expected = dict(base_expected)
3387            if item:
3388                expected[item] = 0
3389            else:
3390                # Main DTB and fdtdec should include the 'orig-' properties
3391                expected.update(main_expected)
3392            # Helpful for debugging:
3393            #for prop in sorted(props):
3394                #print('prop %s %s %s' % (prop, props[prop], expected[prop]))
3395            self.assertEqual(expected, props)
3396            if item == '':
3397                start = SECTION_SIZE
3398            else:
3399                start += dtb._fdt_obj.totalsize()
3400
3401    def testFdtmapHeaderMiddle(self):
3402        """Test an FDT map in the middle of an image when it should be at end"""
3403        with self.assertRaises(ValueError) as e:
3404            self._DoReadFileRealDtb('135_fdtmap_hdr_middle.dts')
3405        self.assertIn("Invalid sibling order 'middle' for image-header: Must be at 'end' to match location",
3406                      str(e.exception))
3407
3408    def testFdtmapHeaderStartBad(self):
3409        """Test an FDT map in middle of an image when it should be at start"""
3410        with self.assertRaises(ValueError) as e:
3411            self._DoReadFileRealDtb('136_fdtmap_hdr_startbad.dts')
3412        self.assertIn("Invalid sibling order 'end' for image-header: Must be at 'start' to match location",
3413                      str(e.exception))
3414
3415    def testFdtmapHeaderEndBad(self):
3416        """Test an FDT map at the start of an image when it should be at end"""
3417        with self.assertRaises(ValueError) as e:
3418            self._DoReadFileRealDtb('137_fdtmap_hdr_endbad.dts')
3419        self.assertIn("Invalid sibling order 'start' for image-header: Must be at 'end' to match location",
3420                      str(e.exception))
3421
3422    def testFdtmapHeaderNoSize(self):
3423        """Test an image header at the end of an image with undefined size"""
3424        self._DoReadFileRealDtb('138_fdtmap_hdr_nosize.dts')
3425
3426    def testReplaceResize(self):
3427        """Test replacing a single file in an entry with a larger file"""
3428        expected = U_BOOT_DATA + b'x'
3429        data, _, image = self._RunReplaceCmd('u-boot', expected,
3430                                             dts='139_replace_repack.dts')
3431        self.assertEqual(expected, data)
3432
3433        entries = image.GetEntries()
3434        dtb_data = entries['u-boot-dtb'].data
3435        dtb = fdt.Fdt.FromData(dtb_data)
3436        dtb.Scan()
3437
3438        # The u-boot section should now be larger in the dtb
3439        node = dtb.GetNode('/binman/u-boot')
3440        self.assertEqual(len(expected), fdt_util.GetInt(node, 'size'))
3441
3442        # Same for the fdtmap
3443        fdata = entries['fdtmap'].data
3444        fdtb = fdt.Fdt.FromData(fdata[fdtmap.FDTMAP_HDR_LEN:])
3445        fdtb.Scan()
3446        fnode = fdtb.GetNode('/u-boot')
3447        self.assertEqual(len(expected), fdt_util.GetInt(fnode, 'size'))
3448
3449    def testReplaceResizeNoRepack(self):
3450        """Test replacing an entry with a larger file when not allowed"""
3451        expected = U_BOOT_DATA + b'x'
3452        with self.assertRaises(ValueError) as e:
3453            self._RunReplaceCmd('u-boot', expected)
3454        self.assertIn('Entry data size does not match, but allow-repack is not present for this image',
3455                      str(e.exception))
3456
3457    def testEntryShrink(self):
3458        """Test contracting an entry after it is packed"""
3459        try:
3460            state.SetAllowEntryContraction(True)
3461            data = self._DoReadFileDtb('140_entry_shrink.dts',
3462                                       update_dtb=True)[0]
3463        finally:
3464            state.SetAllowEntryContraction(False)
3465        self.assertEqual(b'a', data[:1])
3466        self.assertEqual(U_BOOT_DATA, data[1:1 + len(U_BOOT_DATA)])
3467        self.assertEqual(b'a', data[-1:])
3468
3469    def testEntryShrinkFail(self):
3470        """Test not being allowed to contract an entry after it is packed"""
3471        data = self._DoReadFileDtb('140_entry_shrink.dts', update_dtb=True)[0]
3472
3473        # In this case there is a spare byte at the end of the data. The size of
3474        # the contents is only 1 byte but we still have the size before it
3475        # shrunk.
3476        self.assertEqual(b'a\0', data[:2])
3477        self.assertEqual(U_BOOT_DATA, data[2:2 + len(U_BOOT_DATA)])
3478        self.assertEqual(b'a\0', data[-2:])
3479
3480    def testDescriptorOffset(self):
3481        """Test that the Intel descriptor is always placed at at the start"""
3482        data = self._DoReadFileDtb('141_descriptor_offset.dts')
3483        image = control.images['image']
3484        entries = image.GetEntries()
3485        desc = entries['intel-descriptor']
3486        self.assertEqual(0xff800000, desc.offset);
3487        self.assertEqual(0xff800000, desc.image_pos);
3488
3489    def testReplaceCbfs(self):
3490        """Test replacing a single file in CBFS without changing the size"""
3491        self._CheckLz4()
3492        expected = b'x' * len(U_BOOT_DATA)
3493        data = self._DoReadFileRealDtb('142_replace_cbfs.dts')
3494        updated_fname = tools.get_output_filename('image-updated.bin')
3495        tools.write_file(updated_fname, data)
3496        entry_name = 'section/cbfs/u-boot'
3497        control.WriteEntry(updated_fname, entry_name, expected,
3498                           allow_resize=True)
3499        data = control.ReadEntry(updated_fname, entry_name)
3500        self.assertEqual(expected, data)
3501
3502    def testReplaceResizeCbfs(self):
3503        """Test replacing a single file in CBFS with one of a different size"""
3504        self._CheckLz4()
3505        expected = U_BOOT_DATA + b'x'
3506        data = self._DoReadFileRealDtb('142_replace_cbfs.dts')
3507        updated_fname = tools.get_output_filename('image-updated.bin')
3508        tools.write_file(updated_fname, data)
3509        entry_name = 'section/cbfs/u-boot'
3510        control.WriteEntry(updated_fname, entry_name, expected,
3511                           allow_resize=True)
3512        data = control.ReadEntry(updated_fname, entry_name)
3513        self.assertEqual(expected, data)
3514
3515    def _SetupForReplace(self):
3516        """Set up some files to use to replace entries
3517
3518        This generates an image, copies it to a new file, extracts all the files
3519        in it and updates some of them
3520
3521        Returns:
3522            List
3523                Image filename
3524                Output directory
3525                Expected values for updated entries, each a string
3526        """
3527        data = self._DoReadFileRealDtb('143_replace_all.dts')
3528
3529        updated_fname = tools.get_output_filename('image-updated.bin')
3530        tools.write_file(updated_fname, data)
3531
3532        outdir = os.path.join(self._indir, 'extract')
3533        einfos = control.ExtractEntries(updated_fname, None, outdir, [])
3534
3535        expected1 = b'x' + U_BOOT_DATA + b'y'
3536        u_boot_fname1 = os.path.join(outdir, 'u-boot')
3537        tools.write_file(u_boot_fname1, expected1)
3538
3539        expected2 = b'a' + U_BOOT_DATA + b'b'
3540        u_boot_fname2 = os.path.join(outdir, 'u-boot2')
3541        tools.write_file(u_boot_fname2, expected2)
3542
3543        expected_text = b'not the same text'
3544        text_fname = os.path.join(outdir, 'text')
3545        tools.write_file(text_fname, expected_text)
3546
3547        dtb_fname = os.path.join(outdir, 'u-boot-dtb')
3548        dtb = fdt.FdtScan(dtb_fname)
3549        node = dtb.GetNode('/binman/text')
3550        node.AddString('my-property', 'the value')
3551        dtb.Sync(auto_resize=True)
3552        dtb.Flush()
3553
3554        return updated_fname, outdir, expected1, expected2, expected_text
3555
3556    def _CheckReplaceMultiple(self, entry_paths):
3557        """Handle replacing the contents of multiple entries
3558
3559        Args:
3560            entry_paths: List of entry paths to replace
3561
3562        Returns:
3563            List
3564                Dict of entries in the image:
3565                    key: Entry name
3566                    Value: Entry object
3567            Expected values for updated entries, each a string
3568        """
3569        updated_fname, outdir, expected1, expected2, expected_text = (
3570            self._SetupForReplace())
3571        control.ReplaceEntries(updated_fname, None, outdir, entry_paths)
3572
3573        image = Image.FromFile(updated_fname)
3574        image.LoadData()
3575        return image.GetEntries(), expected1, expected2, expected_text
3576
3577    def testReplaceAll(self):
3578        """Test replacing the contents of all entries"""
3579        entries, expected1, expected2, expected_text = (
3580            self._CheckReplaceMultiple([]))
3581        data = entries['u-boot'].data
3582        self.assertEqual(expected1, data)
3583
3584        data = entries['u-boot2'].data
3585        self.assertEqual(expected2, data)
3586
3587        data = entries['text'].data
3588        self.assertEqual(expected_text, data)
3589
3590        # Check that the device tree is updated
3591        data = entries['u-boot-dtb'].data
3592        dtb = fdt.Fdt.FromData(data)
3593        dtb.Scan()
3594        node = dtb.GetNode('/binman/text')
3595        self.assertEqual('the value', node.props['my-property'].value)
3596
3597    def testReplaceSome(self):
3598        """Test replacing the contents of a few entries"""
3599        entries, expected1, expected2, expected_text = (
3600            self._CheckReplaceMultiple(['u-boot2', 'text']))
3601
3602        # This one should not change
3603        data = entries['u-boot'].data
3604        self.assertEqual(U_BOOT_DATA, data)
3605
3606        data = entries['u-boot2'].data
3607        self.assertEqual(expected2, data)
3608
3609        data = entries['text'].data
3610        self.assertEqual(expected_text, data)
3611
3612    def testReplaceCmd(self):
3613        """Test replacing a file fron an image on the command line"""
3614        self._DoReadFileRealDtb('143_replace_all.dts')
3615
3616        try:
3617            tmpdir, updated_fname = self._SetupImageInTmpdir()
3618
3619            fname = os.path.join(tmpdir, 'update-u-boot.bin')
3620            expected = b'x' * len(U_BOOT_DATA)
3621            tools.write_file(fname, expected)
3622
3623            self._DoBinman('replace', '-i', updated_fname, 'u-boot', '-f', fname)
3624            data = tools.read_file(updated_fname)
3625            self.assertEqual(expected, data[:len(expected)])
3626            map_fname = os.path.join(tmpdir, 'image-updated.map')
3627            self.assertFalse(os.path.exists(map_fname))
3628        finally:
3629            shutil.rmtree(tmpdir)
3630
3631    def testReplaceCmdSome(self):
3632        """Test replacing some files fron an image on the command line"""
3633        updated_fname, outdir, expected1, expected2, expected_text = (
3634            self._SetupForReplace())
3635
3636        self._DoBinman('replace', '-i', updated_fname, '-I', outdir,
3637                       'u-boot2', 'text')
3638
3639        tools.prepare_output_dir(None)
3640        image = Image.FromFile(updated_fname)
3641        image.LoadData()
3642        entries = image.GetEntries()
3643
3644        # This one should not change
3645        data = entries['u-boot'].data
3646        self.assertEqual(U_BOOT_DATA, data)
3647
3648        data = entries['u-boot2'].data
3649        self.assertEqual(expected2, data)
3650
3651        data = entries['text'].data
3652        self.assertEqual(expected_text, data)
3653
3654    def testReplaceMissing(self):
3655        """Test replacing entries where the file is missing"""
3656        updated_fname, outdir, expected1, expected2, expected_text = (
3657            self._SetupForReplace())
3658
3659        # Remove one of the files, to generate a warning
3660        u_boot_fname1 = os.path.join(outdir, 'u-boot')
3661        os.remove(u_boot_fname1)
3662
3663        with test_util.capture_sys_output() as (stdout, stderr):
3664            control.ReplaceEntries(updated_fname, None, outdir, [])
3665        self.assertIn("Skipping entry '/u-boot' from missing file",
3666                      stderr.getvalue())
3667
3668    def testReplaceCmdMap(self):
3669        """Test replacing a file fron an image on the command line"""
3670        self._DoReadFileRealDtb('143_replace_all.dts')
3671
3672        try:
3673            tmpdir, updated_fname = self._SetupImageInTmpdir()
3674
3675            fname = os.path.join(self._indir, 'update-u-boot.bin')
3676            expected = b'x' * len(U_BOOT_DATA)
3677            tools.write_file(fname, expected)
3678
3679            self._DoBinman('replace', '-i', updated_fname, 'u-boot',
3680                           '-f', fname, '-m')
3681            map_fname = os.path.join(tmpdir, 'image-updated.map')
3682            self.assertTrue(os.path.exists(map_fname))
3683        finally:
3684            shutil.rmtree(tmpdir)
3685
3686    def testReplaceNoEntryPaths(self):
3687        """Test replacing an entry without an entry path"""
3688        self._DoReadFileRealDtb('143_replace_all.dts')
3689        image_fname = tools.get_output_filename('image.bin')
3690        with self.assertRaises(ValueError) as e:
3691            control.ReplaceEntries(image_fname, 'fname', None, [])
3692        self.assertIn('Must specify an entry path to read with -f',
3693                      str(e.exception))
3694
3695    def testReplaceTooManyEntryPaths(self):
3696        """Test extracting some entries"""
3697        self._DoReadFileRealDtb('143_replace_all.dts')
3698        image_fname = tools.get_output_filename('image.bin')
3699        with self.assertRaises(ValueError) as e:
3700            control.ReplaceEntries(image_fname, 'fname', None, ['a', 'b'])
3701        self.assertIn('Must specify exactly one entry path to write with -f',
3702                      str(e.exception))
3703
3704    def testPackReset16(self):
3705        """Test that an image with an x86 reset16 region can be created"""
3706        data = self._DoReadFile('144_x86_reset16.dts')
3707        self.assertEqual(X86_RESET16_DATA, data[:len(X86_RESET16_DATA)])
3708
3709    def testPackReset16Spl(self):
3710        """Test that an image with an x86 reset16-spl region can be created"""
3711        data = self._DoReadFile('145_x86_reset16_spl.dts')
3712        self.assertEqual(X86_RESET16_SPL_DATA, data[:len(X86_RESET16_SPL_DATA)])
3713
3714    def testPackReset16Tpl(self):
3715        """Test that an image with an x86 reset16-tpl region can be created"""
3716        data = self._DoReadFile('146_x86_reset16_tpl.dts')
3717        self.assertEqual(X86_RESET16_TPL_DATA, data[:len(X86_RESET16_TPL_DATA)])
3718
3719    def testPackIntelFit(self):
3720        """Test that an image with an Intel FIT and pointer can be created"""
3721        data = self._DoReadFile('147_intel_fit.dts')
3722        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
3723        fit = data[16:32];
3724        self.assertEqual(b'_FIT_   \x01\x00\x00\x00\x00\x01\x80}' , fit)
3725        ptr = struct.unpack('<i', data[0x40:0x44])[0]
3726
3727        image = control.images['image']
3728        entries = image.GetEntries()
3729        expected_ptr = entries['intel-fit'].image_pos - (1 << 32)
3730        self.assertEqual(expected_ptr, ptr)
3731
3732    def testPackIntelFitMissing(self):
3733        """Test detection of a FIT pointer with not FIT region"""
3734        with self.assertRaises(ValueError) as e:
3735            self._DoReadFile('148_intel_fit_missing.dts')
3736        self.assertIn("'intel-fit-ptr' section must have an 'intel-fit' sibling",
3737                      str(e.exception))
3738
3739    def _CheckSymbolsTplSection(self, dts, expected_vals):
3740        data = self._DoReadFile(dts)
3741        sym_values = struct.pack('<LLQLL', elf.BINMAN_SYM_MAGIC_VALUE, *expected_vals)
3742        upto1 = 4 + len(U_BOOT_SPL_DATA)
3743        expected1 = tools.get_bytes(0xff, 4) + sym_values + U_BOOT_SPL_DATA[24:]
3744        self.assertEqual(expected1, data[:upto1])
3745
3746        upto2 = upto1 + 1 + len(U_BOOT_SPL_DATA)
3747        expected2 = tools.get_bytes(0xff, 1) + sym_values + U_BOOT_SPL_DATA[24:]
3748        self.assertEqual(expected2, data[upto1:upto2])
3749
3750        upto3 = 0x3c + len(U_BOOT_DATA)
3751        expected3 = tools.get_bytes(0xff, 1) + U_BOOT_DATA
3752        self.assertEqual(expected3, data[upto2:upto3])
3753
3754        expected4 = sym_values + U_BOOT_TPL_DATA[24:]
3755        self.assertEqual(expected4, data[upto3:upto3 + len(U_BOOT_TPL_DATA)])
3756
3757    def testSymbolsTplSection(self):
3758        """Test binman can assign symbols embedded in U-Boot TPL in a section"""
3759        self._SetupSplElf('u_boot_binman_syms')
3760        self._SetupTplElf('u_boot_binman_syms')
3761        self._CheckSymbolsTplSection('149_symbols_tpl.dts',
3762                                     [0x04, 0x20, 0x10 + 0x3c, 0x04])
3763
3764    def testSymbolsTplSectionX86(self):
3765        """Test binman can assign symbols in a section with end-at-4gb"""
3766        self._SetupSplElf('u_boot_binman_syms_x86')
3767        self._SetupTplElf('u_boot_binman_syms_x86')
3768        self._CheckSymbolsTplSection('155_symbols_tpl_x86.dts',
3769                                     [0xffffff04, 0xffffff20, 0xffffff3c,
3770                                      0x04])
3771
3772    def testPackX86RomIfwiSectiom(self):
3773        """Test that a section can be placed in an IFWI region"""
3774        self._SetupIfwi('fitimage.bin')
3775        data = self._DoReadFile('151_x86_rom_ifwi_section.dts')
3776        self._CheckIfwi(data)
3777
3778    def testPackFspM(self):
3779        """Test that an image with a FSP memory-init binary can be created"""
3780        data = self._DoReadFile('152_intel_fsp_m.dts')
3781        self.assertEqual(FSP_M_DATA, data[:len(FSP_M_DATA)])
3782
3783    def testPackFspS(self):
3784        """Test that an image with a FSP silicon-init binary can be created"""
3785        data = self._DoReadFile('153_intel_fsp_s.dts')
3786        self.assertEqual(FSP_S_DATA, data[:len(FSP_S_DATA)])
3787
3788    def testPackFspT(self):
3789        """Test that an image with a FSP temp-ram-init binary can be created"""
3790        data = self._DoReadFile('154_intel_fsp_t.dts')
3791        self.assertEqual(FSP_T_DATA, data[:len(FSP_T_DATA)])
3792
3793    def testMkimage(self):
3794        """Test using mkimage to build an image"""
3795        self._SetupSplElf()
3796        data = self._DoReadFile('156_mkimage.dts')
3797
3798        # Just check that the data appears in the file somewhere
3799        self.assertIn(U_BOOT_SPL_DATA, data)
3800
3801    def testMkimageMissing(self):
3802        """Test that binman still produces an image if mkimage is missing"""
3803        self._SetupSplElf()
3804        with test_util.capture_sys_output() as (_, stderr):
3805            self._DoTestFile('156_mkimage.dts',
3806                             force_missing_bintools='mkimage')
3807        err = stderr.getvalue()
3808        self.assertRegex(err, "Image 'image'.*missing bintools.*: mkimage")
3809
3810    def testExtblob(self):
3811        """Test an image with an external blob"""
3812        data = self._DoReadFile('157_blob_ext.dts')
3813        self.assertEqual(REFCODE_DATA, data)
3814
3815    def testExtblobMissing(self):
3816        """Test an image with a missing external blob"""
3817        with self.assertRaises(ValueError) as e:
3818            self._DoReadFile('158_blob_ext_missing.dts')
3819        self.assertIn("Filename 'missing-file' not found in input path",
3820                      str(e.exception))
3821
3822    def testExtblobMissingOk(self):
3823        """Test an image with an missing external blob that is allowed"""
3824        with test_util.capture_sys_output() as (stdout, stderr):
3825            ret = self._DoTestFile('158_blob_ext_missing.dts',
3826                                   allow_missing=True)
3827        self.assertEqual(103, ret)
3828        err = stderr.getvalue()
3829        self.assertIn('(missing-file)', err)
3830        self.assertRegex(err, "Image 'image'.*missing.*: blob-ext")
3831        self.assertIn('Some images are invalid', err)
3832
3833    def testExtblobMissingOkFlag(self):
3834        """Test an image with an missing external blob allowed with -W"""
3835        with test_util.capture_sys_output() as (stdout, stderr):
3836            ret = self._DoTestFile('158_blob_ext_missing.dts',
3837                                   allow_missing=True, ignore_missing=True)
3838        self.assertEqual(0, ret)
3839        err = stderr.getvalue()
3840        self.assertIn('(missing-file)', err)
3841        self.assertRegex(err, "Image 'image'.*missing.*: blob-ext")
3842        self.assertIn('Some images are invalid', err)
3843
3844    def testExtblobMissingOkSect(self):
3845        """Test an image with an missing external blob that is allowed"""
3846        with test_util.capture_sys_output() as (stdout, stderr):
3847            self._DoTestFile('159_blob_ext_missing_sect.dts',
3848                             allow_missing=True)
3849        err = stderr.getvalue()
3850        self.assertRegex(err, "Image 'image'.*missing.*: blob-ext blob-ext2")
3851
3852    def testPackX86RomMeMissingDesc(self):
3853        """Test that an missing Intel descriptor entry is allowed"""
3854        with test_util.capture_sys_output() as (stdout, stderr):
3855            self._DoTestFile('164_x86_rom_me_missing.dts', allow_missing=True)
3856        err = stderr.getvalue()
3857        self.assertRegex(err, "Image 'image'.*missing.*: intel-descriptor")
3858
3859    def testPackX86RomMissingIfwi(self):
3860        """Test that an x86 ROM with Integrated Firmware Image can be created"""
3861        self._SetupIfwi('fitimage.bin')
3862        pathname = os.path.join(self._indir, 'fitimage.bin')
3863        os.remove(pathname)
3864        with test_util.capture_sys_output() as (stdout, stderr):
3865            self._DoTestFile('111_x86_rom_ifwi.dts', allow_missing=True)
3866        err = stderr.getvalue()
3867        self.assertRegex(err, "Image 'image'.*missing.*: intel-ifwi")
3868
3869    def testPackOverlapZero(self):
3870        """Test that zero-size overlapping regions are ignored"""
3871        self._DoTestFile('160_pack_overlap_zero.dts')
3872
3873    def _CheckSimpleFitData(self, fit_data, kernel_data, fdt1_data):
3874        # The data should be inside the FIT
3875        dtb = fdt.Fdt.FromData(fit_data)
3876        dtb.Scan()
3877        fnode = dtb.GetNode('/images/kernel')
3878        self.assertIn('data', fnode.props)
3879
3880        fname = os.path.join(self._indir, 'fit_data.fit')
3881        tools.write_file(fname, fit_data)
3882        out = tools.run('dumpimage', '-l', fname)
3883
3884        # Check a few features to make sure the plumbing works. We don't need
3885        # to test the operation of mkimage or dumpimage here. First convert the
3886        # output into a dict where the keys are the fields printed by dumpimage
3887        # and the values are a list of values for each field
3888        lines = out.splitlines()
3889
3890        # Converts "Compression:  gzip compressed" into two groups:
3891        # 'Compression' and 'gzip compressed'
3892        re_line = re.compile(r'^ *([^:]*)(?:: *(.*))?$')
3893        vals = collections.defaultdict(list)
3894        for line in lines:
3895            mat = re_line.match(line)
3896            vals[mat.group(1)].append(mat.group(2))
3897
3898        self.assertEquals('FIT description: test-desc', lines[0])
3899        self.assertIn('Created:', lines[1])
3900        self.assertIn('Image 0 (kernel)', vals)
3901        self.assertIn('Hash value', vals)
3902        data_sizes = vals.get('Data Size')
3903        self.assertIsNotNone(data_sizes)
3904        self.assertEqual(2, len(data_sizes))
3905        # Format is "4 Bytes = 0.00 KiB = 0.00 MiB" so take the first word
3906        self.assertEqual(len(kernel_data), int(data_sizes[0].split()[0]))
3907        self.assertEqual(len(fdt1_data), int(data_sizes[1].split()[0]))
3908
3909        # Check if entry listing correctly omits /images/
3910        image = control.images['image']
3911        fit_entry = image.GetEntries()['fit']
3912        subentries = list(fit_entry.GetEntries().keys())
3913        expected = ['kernel', 'fdt-1']
3914        self.assertEqual(expected, subentries)
3915
3916    def testSimpleFit(self):
3917        """Test an image with a FIT inside"""
3918        self._SetupSplElf()
3919        data = self._DoReadFile('161_fit.dts')
3920        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
3921        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
3922        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
3923
3924        self._CheckSimpleFitData(fit_data, U_BOOT_DATA, U_BOOT_SPL_DTB_DATA)
3925
3926    def testSimpleFitExpandsSubentries(self):
3927        """Test that FIT images expand their subentries"""
3928        data = self._DoReadFileDtb('161_fit.dts', use_expanded=True)[0]
3929        self.assertEqual(U_BOOT_EXP_DATA, data[:len(U_BOOT_EXP_DATA)])
3930        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
3931        fit_data = data[len(U_BOOT_EXP_DATA):-len(U_BOOT_NODTB_DATA)]
3932
3933        self._CheckSimpleFitData(fit_data, U_BOOT_EXP_DATA, U_BOOT_SPL_DTB_DATA)
3934
3935    def testSimpleFitImagePos(self):
3936        """Test that we have correct image-pos for FIT subentries"""
3937        data, _, _, out_dtb_fname = self._DoReadFileDtb('161_fit.dts',
3938                                                        update_dtb=True)
3939        dtb = fdt.Fdt(out_dtb_fname)
3940        dtb.Scan()
3941        props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS)
3942
3943        self.maxDiff = None
3944        self.assertEqual({
3945            'image-pos': 0,
3946            'offset': 0,
3947            'size': 1890,
3948
3949            'u-boot:image-pos': 0,
3950            'u-boot:offset': 0,
3951            'u-boot:size': 4,
3952
3953            'fit:image-pos': 4,
3954            'fit:offset': 4,
3955            'fit:size': 1840,
3956
3957            'fit/images/kernel:image-pos': 304,
3958            'fit/images/kernel:offset': 300,
3959            'fit/images/kernel:size': 4,
3960
3961            'fit/images/kernel/u-boot:image-pos': 304,
3962            'fit/images/kernel/u-boot:offset': 0,
3963            'fit/images/kernel/u-boot:size': 4,
3964
3965            'fit/images/fdt-1:image-pos': 552,
3966            'fit/images/fdt-1:offset': 548,
3967            'fit/images/fdt-1:size': 6,
3968
3969            'fit/images/fdt-1/u-boot-spl-dtb:image-pos': 552,
3970            'fit/images/fdt-1/u-boot-spl-dtb:offset': 0,
3971            'fit/images/fdt-1/u-boot-spl-dtb:size': 6,
3972
3973            'u-boot-nodtb:image-pos': 1844,
3974            'u-boot-nodtb:offset': 1844,
3975            'u-boot-nodtb:size': 46,
3976        }, props)
3977
3978        # Actually check the data is where we think it is
3979        for node, expected in [
3980            ("u-boot", U_BOOT_DATA),
3981            ("fit/images/kernel", U_BOOT_DATA),
3982            ("fit/images/kernel/u-boot", U_BOOT_DATA),
3983            ("fit/images/fdt-1", U_BOOT_SPL_DTB_DATA),
3984            ("fit/images/fdt-1/u-boot-spl-dtb", U_BOOT_SPL_DTB_DATA),
3985            ("u-boot-nodtb", U_BOOT_NODTB_DATA),
3986        ]:
3987            image_pos = props[f"{node}:image-pos"]
3988            size = props[f"{node}:size"]
3989            self.assertEqual(len(expected), size)
3990            self.assertEqual(expected, data[image_pos:image_pos+size])
3991
3992    def testFitExternal(self):
3993        """Test an image with an FIT with external images"""
3994        data = self._DoReadFile('162_fit_external.dts')
3995        fit_data = data[len(U_BOOT_DATA):-2]  # _testing is 2 bytes
3996
3997        # Size of the external-data region as set up by mkimage
3998        external_data_size = len(U_BOOT_DATA) + 2
3999        expected_size = (len(U_BOOT_DATA) + 0x400 +
4000                         tools.align(external_data_size, 4) +
4001                         len(U_BOOT_NODTB_DATA))
4002
4003        # The data should be outside the FIT
4004        dtb = fdt.Fdt.FromData(fit_data)
4005        dtb.Scan()
4006        fnode = dtb.GetNode('/images/kernel')
4007        self.assertNotIn('data', fnode.props)
4008        self.assertEqual(len(U_BOOT_DATA),
4009                         fdt_util.fdt32_to_cpu(fnode.props['data-size'].value))
4010        fit_pos = 0x400;
4011        self.assertEqual(
4012            fit_pos,
4013            fdt_util.fdt32_to_cpu(fnode.props['data-position'].value))
4014
4015        self.assertEquals(expected_size, len(data))
4016        actual_pos = len(U_BOOT_DATA) + fit_pos
4017        self.assertEqual(U_BOOT_DATA + b'aa',
4018                         data[actual_pos:actual_pos + external_data_size])
4019
4020    def testFitExternalImagePos(self):
4021        """Test that we have correct image-pos for external FIT subentries"""
4022        data, _, _, out_dtb_fname = self._DoReadFileDtb('162_fit_external.dts',
4023                                                        update_dtb=True)
4024        dtb = fdt.Fdt(out_dtb_fname)
4025        dtb.Scan()
4026        props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS)
4027
4028        self.assertEqual({
4029            'image-pos': 0,
4030            'offset': 0,
4031            'size': 1082,
4032
4033            'u-boot:image-pos': 0,
4034            'u-boot:offset': 0,
4035            'u-boot:size': 4,
4036
4037            'fit:size': 1032,
4038            'fit:offset': 4,
4039            'fit:image-pos': 4,
4040
4041            'fit/images/kernel:size': 4,
4042            'fit/images/kernel:offset': 1024,
4043            'fit/images/kernel:image-pos': 1028,
4044
4045            'fit/images/kernel/u-boot:size': 4,
4046            'fit/images/kernel/u-boot:offset': 0,
4047            'fit/images/kernel/u-boot:image-pos': 1028,
4048
4049            'fit/images/fdt-1:size': 2,
4050            'fit/images/fdt-1:offset': 1028,
4051            'fit/images/fdt-1:image-pos': 1032,
4052
4053            'fit/images/fdt-1/_testing:size': 2,
4054            'fit/images/fdt-1/_testing:offset': 0,
4055            'fit/images/fdt-1/_testing:image-pos': 1032,
4056
4057            'u-boot-nodtb:image-pos': 1036,
4058            'u-boot-nodtb:offset': 1036,
4059            'u-boot-nodtb:size': 46,
4060         }, props)
4061
4062        # Actually check the data is where we think it is
4063        for node, expected in [
4064            ("u-boot", U_BOOT_DATA),
4065            ("fit/images/kernel", U_BOOT_DATA),
4066            ("fit/images/kernel/u-boot", U_BOOT_DATA),
4067            ("fit/images/fdt-1", b'aa'),
4068            ("fit/images/fdt-1/_testing", b'aa'),
4069            ("u-boot-nodtb", U_BOOT_NODTB_DATA),
4070        ]:
4071            image_pos = props[f"{node}:image-pos"]
4072            size = props[f"{node}:size"]
4073            self.assertEqual(len(expected), size)
4074            self.assertEqual(expected, data[image_pos:image_pos+size])
4075
4076    def testFitMissing(self):
4077        """Test that binman complains if mkimage is missing"""
4078        with self.assertRaises(ValueError) as e:
4079            self._DoTestFile('162_fit_external.dts',
4080                             force_missing_bintools='mkimage')
4081        self.assertIn("Node '/binman/fit': Missing tool: 'mkimage'",
4082                      str(e.exception))
4083
4084    def testFitMissingOK(self):
4085        """Test that binman still produces a FIT image if mkimage is missing"""
4086        with test_util.capture_sys_output() as (_, stderr):
4087            self._DoTestFile('162_fit_external.dts', allow_missing=True,
4088                             force_missing_bintools='mkimage')
4089        err = stderr.getvalue()
4090        self.assertRegex(err, "Image 'image'.*missing bintools.*: mkimage")
4091
4092    def testSectionIgnoreHashSignature(self):
4093        """Test that sections ignore hash, signature nodes for its data"""
4094        data = self._DoReadFile('165_section_ignore_hash_signature.dts')
4095        expected = (U_BOOT_DATA + U_BOOT_DATA)
4096        self.assertEqual(expected, data)
4097
4098    def testPadInSections(self):
4099        """Test pad-before, pad-after for entries in sections"""
4100        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4101            '166_pad_in_sections.dts', update_dtb=True)
4102        expected = (U_BOOT_DATA + tools.get_bytes(ord('!'), 12) +
4103                    U_BOOT_DATA + tools.get_bytes(ord('!'), 6) +
4104                    U_BOOT_DATA)
4105        self.assertEqual(expected, data)
4106
4107        dtb = fdt.Fdt(out_dtb_fname)
4108        dtb.Scan()
4109        props = self._GetPropTree(dtb, ['size', 'image-pos', 'offset'])
4110        expected = {
4111            'image-pos': 0,
4112            'offset': 0,
4113            'size': 12 + 6 + 3 * len(U_BOOT_DATA),
4114
4115            'section:image-pos': 0,
4116            'section:offset': 0,
4117            'section:size': 12 + 6 + 3 * len(U_BOOT_DATA),
4118
4119            'section/before:image-pos': 0,
4120            'section/before:offset': 0,
4121            'section/before:size': len(U_BOOT_DATA),
4122
4123            'section/u-boot:image-pos': 4,
4124            'section/u-boot:offset': 4,
4125            'section/u-boot:size': 12 + len(U_BOOT_DATA) + 6,
4126
4127            'section/after:image-pos': 26,
4128            'section/after:offset': 26,
4129            'section/after:size': len(U_BOOT_DATA),
4130            }
4131        self.assertEqual(expected, props)
4132
4133    def testFitImageSubentryAlignment(self):
4134        """Test relative alignability of FIT image subentries"""
4135        self._SetupSplElf()
4136        entry_args = {
4137            'test-id': TEXT_DATA,
4138        }
4139        data, _, _, _ = self._DoReadFileDtb('167_fit_image_subentry_alignment.dts',
4140                                            entry_args=entry_args)
4141        dtb = fdt.Fdt.FromData(data)
4142        dtb.Scan()
4143
4144        node = dtb.GetNode('/images/kernel')
4145        data = dtb.GetProps(node)["data"].bytes
4146        align_pad = 0x10 - (len(U_BOOT_SPL_DATA) % 0x10)
4147        expected = (tools.get_bytes(0, 0x20) + U_BOOT_SPL_DATA +
4148                    tools.get_bytes(0, align_pad) + U_BOOT_DATA)
4149        self.assertEqual(expected, data)
4150
4151        node = dtb.GetNode('/images/fdt-1')
4152        data = dtb.GetProps(node)["data"].bytes
4153        expected = (U_BOOT_SPL_DTB_DATA + tools.get_bytes(0, 20) +
4154                    tools.to_bytes(TEXT_DATA) + tools.get_bytes(0, 30) +
4155                    U_BOOT_DTB_DATA)
4156        self.assertEqual(expected, data)
4157
4158    def testFitExtblobMissingOk(self):
4159        """Test a FIT with a missing external blob that is allowed"""
4160        with test_util.capture_sys_output() as (stdout, stderr):
4161            self._DoTestFile('168_fit_missing_blob.dts',
4162                             allow_missing=True)
4163        err = stderr.getvalue()
4164        self.assertRegex(err, "Image 'image'.*missing.*: atf-bl31")
4165
4166    def testBlobNamedByArgMissing(self):
4167        """Test handling of a missing entry arg"""
4168        with self.assertRaises(ValueError) as e:
4169            self._DoReadFile('068_blob_named_by_arg.dts')
4170        self.assertIn("Missing required properties/entry args: cros-ec-rw-path",
4171                      str(e.exception))
4172
4173    def testPackBl31(self):
4174        """Test that an image with an ATF BL31 binary can be created"""
4175        data = self._DoReadFile('169_atf_bl31.dts')
4176        self.assertEqual(ATF_BL31_DATA, data[:len(ATF_BL31_DATA)])
4177
4178    def testPackScp(self):
4179        """Test that an image with an SCP binary can be created"""
4180        data = self._DoReadFile('172_scp.dts')
4181        self.assertEqual(SCP_DATA, data[:len(SCP_DATA)])
4182
4183    def testFitFdt(self):
4184        """Test an image with an FIT with multiple FDT images"""
4185        def _CheckFdt(seq, expected_data):
4186            """Check the FDT nodes
4187
4188            Args:
4189                seq: Sequence number to check (0 or 1)
4190                expected_data: Expected contents of 'data' property
4191            """
4192            name = 'fdt-%d' % seq
4193            fnode = dtb.GetNode('/images/%s' % name)
4194            self.assertIsNotNone(fnode)
4195            self.assertEqual({'description','type', 'compression', 'data'},
4196                             set(fnode.props.keys()))
4197            self.assertEqual(expected_data, fnode.props['data'].bytes)
4198            self.assertEqual('fdt-test-fdt%d.dtb' % seq,
4199                             fnode.props['description'].value)
4200            self.assertEqual(fnode.subnodes[0].name, 'hash')
4201
4202        def _CheckConfig(seq, expected_data):
4203            """Check the configuration nodes
4204
4205            Args:
4206                seq: Sequence number to check (0 or 1)
4207                expected_data: Expected contents of 'data' property
4208            """
4209            cnode = dtb.GetNode('/configurations')
4210            self.assertIn('default', cnode.props)
4211            self.assertEqual('config-2', cnode.props['default'].value)
4212
4213            name = 'config-%d' % seq
4214            fnode = dtb.GetNode('/configurations/%s' % name)
4215            self.assertIsNotNone(fnode)
4216            self.assertEqual({'description','firmware', 'loadables', 'fdt'},
4217                             set(fnode.props.keys()))
4218            self.assertEqual('conf-test-fdt%d.dtb' % seq,
4219                             fnode.props['description'].value)
4220            self.assertEqual('fdt-%d' % seq, fnode.props['fdt'].value)
4221
4222        entry_args = {
4223            'of-list': 'test-fdt1 test-fdt2',
4224            'default-dt': 'test-fdt2',
4225        }
4226        data = self._DoReadFileDtb(
4227            '170_fit_fdt.dts',
4228            entry_args=entry_args,
4229            extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
4230        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
4231        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
4232
4233        dtb = fdt.Fdt.FromData(fit_data)
4234        dtb.Scan()
4235        fnode = dtb.GetNode('/images/kernel')
4236        self.assertIn('data', fnode.props)
4237
4238        # Check all the properties in fdt-1 and fdt-2
4239        _CheckFdt(1, TEST_FDT1_DATA)
4240        _CheckFdt(2, TEST_FDT2_DATA)
4241
4242        # Check configurations
4243        _CheckConfig(1, TEST_FDT1_DATA)
4244        _CheckConfig(2, TEST_FDT2_DATA)
4245
4246    def testFitFdtMissingList(self):
4247        """Test handling of a missing 'of-list' entry arg"""
4248        with self.assertRaises(ValueError) as e:
4249            self._DoReadFile('170_fit_fdt.dts')
4250        self.assertIn("Generator node requires 'of-list' entry argument",
4251                      str(e.exception))
4252
4253    def testFitFdtEmptyList(self):
4254        """Test handling of an empty 'of-list' entry arg"""
4255        entry_args = {
4256            'of-list': '',
4257        }
4258        data = self._DoReadFileDtb('170_fit_fdt.dts', entry_args=entry_args)[0]
4259
4260    def testFitFdtMissingProp(self):
4261        """Test handling of a missing 'fit,fdt-list' property"""
4262        with self.assertRaises(ValueError) as e:
4263            self._DoReadFile('171_fit_fdt_missing_prop.dts')
4264        self.assertIn("Generator node requires 'fit,fdt-list' property",
4265                      str(e.exception))
4266
4267    def testFitFdtMissing(self):
4268        """Test handling of a missing 'default-dt' entry arg"""
4269        entry_args = {
4270            'of-list': 'test-fdt1 test-fdt2',
4271        }
4272        with self.assertRaises(ValueError) as e:
4273            self._DoReadFileDtb(
4274                '170_fit_fdt.dts',
4275                entry_args=entry_args,
4276                extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
4277        self.assertIn("Generated 'default' node requires default-dt entry argument",
4278                      str(e.exception))
4279
4280    def testFitFdtNotInList(self):
4281        """Test handling of a default-dt that is not in the of-list"""
4282        entry_args = {
4283            'of-list': 'test-fdt1 test-fdt2',
4284            'default-dt': 'test-fdt3',
4285        }
4286        with self.assertRaises(ValueError) as e:
4287            self._DoReadFileDtb(
4288                '170_fit_fdt.dts',
4289                entry_args=entry_args,
4290                extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
4291        self.assertIn("default-dt entry argument 'test-fdt3' not found in fdt list: test-fdt1, test-fdt2",
4292                      str(e.exception))
4293
4294    def testFitExtblobMissingHelp(self):
4295        """Test display of help messages when an external blob is missing"""
4296        control.missing_blob_help = control._ReadMissingBlobHelp()
4297        control.missing_blob_help['wibble'] = 'Wibble test'
4298        control.missing_blob_help['another'] = 'Another test'
4299        with test_util.capture_sys_output() as (stdout, stderr):
4300            self._DoTestFile('168_fit_missing_blob.dts',
4301                             allow_missing=True)
4302        err = stderr.getvalue()
4303
4304        # We can get the tag from the name, the type or the missing-msg
4305        # property. Check all three.
4306        self.assertIn('You may need to build ARM Trusted', err)
4307        self.assertIn('Wibble test', err)
4308        self.assertIn('Another test', err)
4309
4310    def testMissingBlob(self):
4311        """Test handling of a blob containing a missing file"""
4312        with self.assertRaises(ValueError) as e:
4313            self._DoTestFile('173_missing_blob.dts', allow_missing=True)
4314        self.assertIn("Filename 'missing' not found in input path",
4315                      str(e.exception))
4316
4317    def testEnvironment(self):
4318        """Test adding a U-Boot environment"""
4319        data = self._DoReadFile('174_env.dts')
4320        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
4321        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
4322        env = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
4323        self.assertEqual(b'\x1b\x97\x22\x7c\x01var1=1\0var2="2"\0\0\xff\xff',
4324                         env)
4325
4326    def testEnvironmentNoSize(self):
4327        """Test that a missing 'size' property is detected"""
4328        with self.assertRaises(ValueError) as e:
4329            self._DoTestFile('175_env_no_size.dts')
4330        self.assertIn("'u-boot-env' entry must have a size property",
4331                      str(e.exception))
4332
4333    def testEnvironmentTooSmall(self):
4334        """Test handling of an environment that does not fit"""
4335        with self.assertRaises(ValueError) as e:
4336            self._DoTestFile('176_env_too_small.dts')
4337
4338        # checksum, start byte, environment with \0 terminator, final \0
4339        need = 4 + 1 + len(ENV_DATA) + 1 + 1
4340        short = need - 0x8
4341        self.assertIn("too small to hold data (need %#x more bytes)" % short,
4342                      str(e.exception))
4343
4344    def testSkipAtStart(self):
4345        """Test handling of skip-at-start section"""
4346        data = self._DoReadFile('177_skip_at_start.dts')
4347        self.assertEqual(U_BOOT_DATA, data)
4348
4349        image = control.images['image']
4350        entries = image.GetEntries()
4351        section = entries['section']
4352        self.assertEqual(0, section.offset)
4353        self.assertEqual(len(U_BOOT_DATA), section.size)
4354        self.assertEqual(U_BOOT_DATA, section.GetData())
4355
4356        entry = section.GetEntries()['u-boot']
4357        self.assertEqual(16, entry.offset)
4358        self.assertEqual(len(U_BOOT_DATA), entry.size)
4359        self.assertEqual(U_BOOT_DATA, entry.data)
4360
4361    def testSkipAtStartPad(self):
4362        """Test handling of skip-at-start section with padded entry"""
4363        data = self._DoReadFile('178_skip_at_start_pad.dts')
4364        before = tools.get_bytes(0, 8)
4365        after = tools.get_bytes(0, 4)
4366        all = before + U_BOOT_DATA + after
4367        self.assertEqual(all, data)
4368
4369        image = control.images['image']
4370        entries = image.GetEntries()
4371        section = entries['section']
4372        self.assertEqual(0, section.offset)
4373        self.assertEqual(len(all), section.size)
4374        self.assertEqual(all, section.GetData())
4375
4376        entry = section.GetEntries()['u-boot']
4377        self.assertEqual(16, entry.offset)
4378        self.assertEqual(len(all), entry.size)
4379        self.assertEqual(U_BOOT_DATA, entry.data)
4380
4381    def testSkipAtStartSectionPad(self):
4382        """Test handling of skip-at-start section with padding"""
4383        data = self._DoReadFile('179_skip_at_start_section_pad.dts')
4384        before = tools.get_bytes(0, 8)
4385        after = tools.get_bytes(0, 4)
4386        all = before + U_BOOT_DATA + after
4387        self.assertEqual(all, data)
4388
4389        image = control.images['image']
4390        entries = image.GetEntries()
4391        section = entries['section']
4392        self.assertEqual(0, section.offset)
4393        self.assertEqual(len(all), section.size)
4394        self.assertEqual(U_BOOT_DATA, section.data)
4395        self.assertEqual(all, section.GetPaddedData())
4396
4397        entry = section.GetEntries()['u-boot']
4398        self.assertEqual(16, entry.offset)
4399        self.assertEqual(len(U_BOOT_DATA), entry.size)
4400        self.assertEqual(U_BOOT_DATA, entry.data)
4401
4402    def testSectionPad(self):
4403        """Testing padding with sections"""
4404        data = self._DoReadFile('180_section_pad.dts')
4405        expected = (tools.get_bytes(ord('&'), 3) +
4406                    tools.get_bytes(ord('!'), 5) +
4407                    U_BOOT_DATA +
4408                    tools.get_bytes(ord('!'), 1) +
4409                    tools.get_bytes(ord('&'), 2))
4410        self.assertEqual(expected, data)
4411
4412    def testSectionAlign(self):
4413        """Testing alignment with sections"""
4414        data = self._DoReadFileDtb('181_section_align.dts', map=True)[0]
4415        expected = (b'\0' +                         # fill section
4416                    tools.get_bytes(ord('&'), 1) +   # padding to section align
4417                    b'\0' +                         # fill section
4418                    tools.get_bytes(ord('!'), 3) +   # padding to u-boot align
4419                    U_BOOT_DATA +
4420                    tools.get_bytes(ord('!'), 4) +   # padding to u-boot size
4421                    tools.get_bytes(ord('!'), 4))    # padding to section size
4422        self.assertEqual(expected, data)
4423
4424    def testCompressImage(self):
4425        """Test compression of the entire image"""
4426        self._CheckLz4()
4427        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4428            '182_compress_image.dts', use_real_dtb=True, update_dtb=True)
4429        dtb = fdt.Fdt(out_dtb_fname)
4430        dtb.Scan()
4431        props = self._GetPropTree(dtb, ['offset', 'image-pos', 'size',
4432                                        'uncomp-size'])
4433        orig = self._decompress(data)
4434        self.assertEquals(COMPRESS_DATA + U_BOOT_DATA, orig)
4435
4436        # Do a sanity check on various fields
4437        image = control.images['image']
4438        entries = image.GetEntries()
4439        self.assertEqual(2, len(entries))
4440
4441        entry = entries['blob']
4442        self.assertEqual(COMPRESS_DATA, entry.data)
4443        self.assertEqual(len(COMPRESS_DATA), entry.size)
4444
4445        entry = entries['u-boot']
4446        self.assertEqual(U_BOOT_DATA, entry.data)
4447        self.assertEqual(len(U_BOOT_DATA), entry.size)
4448
4449        self.assertEqual(len(data), image.size)
4450        self.assertEqual(COMPRESS_DATA + U_BOOT_DATA, image.uncomp_data)
4451        self.assertEqual(len(COMPRESS_DATA + U_BOOT_DATA), image.uncomp_size)
4452        orig = self._decompress(image.data)
4453        self.assertEqual(orig, image.uncomp_data)
4454
4455        expected = {
4456            'blob:offset': 0,
4457            'blob:size': len(COMPRESS_DATA),
4458            'u-boot:offset': len(COMPRESS_DATA),
4459            'u-boot:size': len(U_BOOT_DATA),
4460            'uncomp-size': len(COMPRESS_DATA + U_BOOT_DATA),
4461            'offset': 0,
4462            'image-pos': 0,
4463            'size': len(data),
4464            }
4465        self.assertEqual(expected, props)
4466
4467    def testCompressImageLess(self):
4468        """Test compression where compression reduces the image size"""
4469        self._CheckLz4()
4470        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4471            '183_compress_image_less.dts', use_real_dtb=True, update_dtb=True)
4472        dtb = fdt.Fdt(out_dtb_fname)
4473        dtb.Scan()
4474        props = self._GetPropTree(dtb, ['offset', 'image-pos', 'size',
4475                                        'uncomp-size'])
4476        orig = self._decompress(data)
4477
4478        self.assertEquals(COMPRESS_DATA + COMPRESS_DATA + U_BOOT_DATA, orig)
4479
4480        # Do a sanity check on various fields
4481        image = control.images['image']
4482        entries = image.GetEntries()
4483        self.assertEqual(2, len(entries))
4484
4485        entry = entries['blob']
4486        self.assertEqual(COMPRESS_DATA_BIG, entry.data)
4487        self.assertEqual(len(COMPRESS_DATA_BIG), entry.size)
4488
4489        entry = entries['u-boot']
4490        self.assertEqual(U_BOOT_DATA, entry.data)
4491        self.assertEqual(len(U_BOOT_DATA), entry.size)
4492
4493        self.assertEqual(len(data), image.size)
4494        self.assertEqual(COMPRESS_DATA_BIG + U_BOOT_DATA, image.uncomp_data)
4495        self.assertEqual(len(COMPRESS_DATA_BIG + U_BOOT_DATA),
4496                         image.uncomp_size)
4497        orig = self._decompress(image.data)
4498        self.assertEqual(orig, image.uncomp_data)
4499
4500        expected = {
4501            'blob:offset': 0,
4502            'blob:size': len(COMPRESS_DATA_BIG),
4503            'u-boot:offset': len(COMPRESS_DATA_BIG),
4504            'u-boot:size': len(U_BOOT_DATA),
4505            'uncomp-size': len(COMPRESS_DATA_BIG + U_BOOT_DATA),
4506            'offset': 0,
4507            'image-pos': 0,
4508            'size': len(data),
4509            }
4510        self.assertEqual(expected, props)
4511
4512    def testCompressSectionSize(self):
4513        """Test compression of a section with a fixed size"""
4514        self._CheckLz4()
4515        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4516            '184_compress_section_size.dts', use_real_dtb=True, update_dtb=True)
4517        dtb = fdt.Fdt(out_dtb_fname)
4518        dtb.Scan()
4519        props = self._GetPropTree(dtb, ['offset', 'image-pos', 'size',
4520                                        'uncomp-size'])
4521        orig = self._decompress(data)
4522        self.assertEquals(COMPRESS_DATA + U_BOOT_DATA, orig)
4523        expected = {
4524            'section/blob:offset': 0,
4525            'section/blob:size': len(COMPRESS_DATA),
4526            'section/u-boot:offset': len(COMPRESS_DATA),
4527            'section/u-boot:size': len(U_BOOT_DATA),
4528            'section:offset': 0,
4529            'section:image-pos': 0,
4530            'section:uncomp-size': len(COMPRESS_DATA + U_BOOT_DATA),
4531            'section:size': 0x30,
4532            'offset': 0,
4533            'image-pos': 0,
4534            'size': 0x30,
4535            }
4536        self.assertEqual(expected, props)
4537
4538    def testCompressSection(self):
4539        """Test compression of a section with no fixed size"""
4540        self._CheckLz4()
4541        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4542            '185_compress_section.dts', use_real_dtb=True, update_dtb=True)
4543        dtb = fdt.Fdt(out_dtb_fname)
4544        dtb.Scan()
4545        props = self._GetPropTree(dtb, ['offset', 'image-pos', 'size',
4546                                        'uncomp-size'])
4547        orig = self._decompress(data)
4548        self.assertEquals(COMPRESS_DATA + U_BOOT_DATA, orig)
4549        expected = {
4550            'section/blob:offset': 0,
4551            'section/blob:size': len(COMPRESS_DATA),
4552            'section/u-boot:offset': len(COMPRESS_DATA),
4553            'section/u-boot:size': len(U_BOOT_DATA),
4554            'section:offset': 0,
4555            'section:image-pos': 0,
4556            'section:uncomp-size': len(COMPRESS_DATA + U_BOOT_DATA),
4557            'section:size': len(data),
4558            'offset': 0,
4559            'image-pos': 0,
4560            'size': len(data),
4561            }
4562        self.assertEqual(expected, props)
4563
4564    def testLz4Missing(self):
4565        """Test that binman still produces an image if lz4 is missing"""
4566        with test_util.capture_sys_output() as (_, stderr):
4567            self._DoTestFile('185_compress_section.dts',
4568                             force_missing_bintools='lz4')
4569        err = stderr.getvalue()
4570        self.assertRegex(err, "Image 'image'.*missing bintools.*: lz4")
4571
4572    def testCompressExtra(self):
4573        """Test compression of a section with no fixed size"""
4574        self._CheckLz4()
4575        data, _, _, out_dtb_fname = self._DoReadFileDtb(
4576            '186_compress_extra.dts', use_real_dtb=True, update_dtb=True)
4577        dtb = fdt.Fdt(out_dtb_fname)
4578        dtb.Scan()
4579        props = self._GetPropTree(dtb, ['offset', 'image-pos', 'size',
4580                                        'uncomp-size'])
4581
4582        base = data[len(U_BOOT_DATA):]
4583        self.assertEquals(U_BOOT_DATA, base[:len(U_BOOT_DATA)])
4584        rest = base[len(U_BOOT_DATA):]
4585
4586        # Check compressed data
4587        bintool = self.comp_bintools['lz4']
4588        expect1 = bintool.compress(COMPRESS_DATA + U_BOOT_DATA)
4589        data1 = rest[:len(expect1)]
4590        section1 = self._decompress(data1)
4591        self.assertEquals(expect1, data1)
4592        self.assertEquals(COMPRESS_DATA + U_BOOT_DATA, section1)
4593        rest1 = rest[len(expect1):]
4594
4595        expect2 = bintool.compress(COMPRESS_DATA + COMPRESS_DATA)
4596        data2 = rest1[:len(expect2)]
4597        section2 = self._decompress(data2)
4598        self.assertEquals(expect2, data2)
4599        self.assertEquals(COMPRESS_DATA + COMPRESS_DATA, section2)
4600        rest2 = rest1[len(expect2):]
4601
4602        expect_size = (len(U_BOOT_DATA) + len(U_BOOT_DATA) + len(expect1) +
4603                       len(expect2) + len(U_BOOT_DATA))
4604        #self.assertEquals(expect_size, len(data))
4605
4606        #self.assertEquals(U_BOOT_DATA, rest2)
4607
4608        self.maxDiff = None
4609        expected = {
4610            'u-boot:offset': 0,
4611            'u-boot:image-pos': 0,
4612            'u-boot:size': len(U_BOOT_DATA),
4613
4614            'base:offset': len(U_BOOT_DATA),
4615            'base:image-pos': len(U_BOOT_DATA),
4616            'base:size': len(data) - len(U_BOOT_DATA),
4617            'base/u-boot:offset': 0,
4618            'base/u-boot:image-pos': len(U_BOOT_DATA),
4619            'base/u-boot:size': len(U_BOOT_DATA),
4620            'base/u-boot2:offset': len(U_BOOT_DATA) + len(expect1) +
4621                len(expect2),
4622            'base/u-boot2:image-pos': len(U_BOOT_DATA) * 2 + len(expect1) +
4623                len(expect2),
4624            'base/u-boot2:size': len(U_BOOT_DATA),
4625
4626            'base/section:offset': len(U_BOOT_DATA),
4627            'base/section:image-pos': len(U_BOOT_DATA) * 2,
4628            'base/section:size': len(expect1),
4629            'base/section:uncomp-size': len(COMPRESS_DATA + U_BOOT_DATA),
4630            'base/section/blob:offset': 0,
4631            'base/section/blob:size': len(COMPRESS_DATA),
4632            'base/section/u-boot:offset': len(COMPRESS_DATA),
4633            'base/section/u-boot:size': len(U_BOOT_DATA),
4634
4635            'base/section2:offset': len(U_BOOT_DATA) + len(expect1),
4636            'base/section2:image-pos': len(U_BOOT_DATA) * 2 + len(expect1),
4637            'base/section2:size': len(expect2),
4638            'base/section2:uncomp-size': len(COMPRESS_DATA + COMPRESS_DATA),
4639            'base/section2/blob:offset': 0,
4640            'base/section2/blob:size': len(COMPRESS_DATA),
4641            'base/section2/blob2:offset': len(COMPRESS_DATA),
4642            'base/section2/blob2:size': len(COMPRESS_DATA),
4643
4644            'offset': 0,
4645            'image-pos': 0,
4646            'size': len(data),
4647            }
4648        self.assertEqual(expected, props)
4649
4650    def testSymbolsSubsection(self):
4651        """Test binman can assign symbols from a subsection"""
4652        self.checkSymbols('187_symbols_sub.dts', U_BOOT_SPL_DATA, 0x1c)
4653
4654    def testReadImageEntryArg(self):
4655        """Test reading an image that would need an entry arg to generate"""
4656        entry_args = {
4657            'cros-ec-rw-path': 'ecrw.bin',
4658        }
4659        data = self.data = self._DoReadFileDtb(
4660            '188_image_entryarg.dts',use_real_dtb=True, update_dtb=True,
4661            entry_args=entry_args)
4662
4663        image_fname = tools.get_output_filename('image.bin')
4664        orig_image = control.images['image']
4665
4666        # This should not generate an error about the missing 'cros-ec-rw-path'
4667        # since we are reading the image from a file. Compare with
4668        # testEntryArgsRequired()
4669        image = Image.FromFile(image_fname)
4670        self.assertEqual(orig_image.GetEntries().keys(),
4671                         image.GetEntries().keys())
4672
4673    def testFilesAlign(self):
4674        """Test alignment with files"""
4675        data = self._DoReadFile('190_files_align.dts')
4676
4677        # The first string is 15 bytes so will align to 16
4678        expect = FILES_DATA[:15] + b'\0' + FILES_DATA[15:]
4679        self.assertEqual(expect, data)
4680
4681    def testReadImageSkip(self):
4682        """Test reading an image and accessing its FDT map"""
4683        data = self.data = self._DoReadFileRealDtb('191_read_image_skip.dts')
4684        image_fname = tools.get_output_filename('image.bin')
4685        orig_image = control.images['image']
4686        image = Image.FromFile(image_fname)
4687        self.assertEqual(orig_image.GetEntries().keys(),
4688                         image.GetEntries().keys())
4689
4690        orig_entry = orig_image.GetEntries()['fdtmap']
4691        entry = image.GetEntries()['fdtmap']
4692        self.assertEqual(orig_entry.offset, entry.offset)
4693        self.assertEqual(orig_entry.size, entry.size)
4694        self.assertEqual(16, entry.image_pos)
4695
4696        u_boot = image.GetEntries()['section'].GetEntries()['u-boot']
4697
4698        self.assertEquals(U_BOOT_DATA, u_boot.ReadData())
4699
4700    def testTplNoDtb(self):
4701        """Test that an image with tpl/u-boot-tpl-nodtb.bin can be created"""
4702        self._SetupTplElf()
4703        data = self._DoReadFile('192_u_boot_tpl_nodtb.dts')
4704        self.assertEqual(U_BOOT_TPL_NODTB_DATA,
4705                         data[:len(U_BOOT_TPL_NODTB_DATA)])
4706
4707    def testTplBssPad(self):
4708        """Test that we can pad TPL's BSS with zeros"""
4709        # ELF file with a '__bss_size' symbol
4710        self._SetupTplElf()
4711        data = self._DoReadFile('193_tpl_bss_pad.dts')
4712        self.assertEqual(U_BOOT_TPL_DATA + tools.get_bytes(0, 10) + U_BOOT_DATA,
4713                         data)
4714
4715    def testTplBssPadMissing(self):
4716        """Test that a missing symbol is detected"""
4717        self._SetupTplElf('u_boot_ucode_ptr')
4718        with self.assertRaises(ValueError) as e:
4719            self._DoReadFile('193_tpl_bss_pad.dts')
4720        self.assertIn('Expected __bss_size symbol in tpl/u-boot-tpl',
4721                      str(e.exception))
4722
4723    def checkDtbSizes(self, data, pad_len, start):
4724        """Check the size arguments in a dtb embedded in an image
4725
4726        Args:
4727            data: The image data
4728            pad_len: Length of the pad section in the image, in bytes
4729            start: Start offset of the devicetree to examine, within the image
4730
4731        Returns:
4732            Size of the devicetree in bytes
4733        """
4734        dtb_data = data[start:]
4735        dtb = fdt.Fdt.FromData(dtb_data)
4736        fdt_size = dtb.GetFdtObj().totalsize()
4737        dtb.Scan()
4738        props = self._GetPropTree(dtb, 'size')
4739        self.assertEqual({
4740            'size': len(data),
4741            'u-boot-spl/u-boot-spl-bss-pad:size': pad_len,
4742            'u-boot-spl/u-boot-spl-dtb:size': 801,
4743            'u-boot-spl/u-boot-spl-nodtb:size': len(U_BOOT_SPL_NODTB_DATA),
4744            'u-boot-spl:size': 860,
4745            'u-boot-tpl:size': len(U_BOOT_TPL_DATA),
4746            'u-boot/u-boot-dtb:size': 781,
4747            'u-boot/u-boot-nodtb:size': len(U_BOOT_NODTB_DATA),
4748            'u-boot:size': 827,
4749            }, props)
4750        return fdt_size
4751
4752    def testExpanded(self):
4753        """Test that an expanded entry type is selected when needed"""
4754        self._SetupSplElf()
4755        self._SetupTplElf()
4756
4757        # SPL has a devicetree, TPL does not
4758        entry_args = {
4759            'spl-dtb': '1',
4760            'spl-bss-pad': 'y',
4761            'tpl-dtb': '',
4762        }
4763        self._DoReadFileDtb('194_fdt_incl.dts', use_expanded=True,
4764                            entry_args=entry_args)
4765        image = control.images['image']
4766        entries = image.GetEntries()
4767        self.assertEqual(3, len(entries))
4768
4769        # First, u-boot, which should be expanded into u-boot-nodtb and dtb
4770        self.assertIn('u-boot', entries)
4771        entry = entries['u-boot']
4772        self.assertEqual('u-boot-expanded', entry.etype)
4773        subent = entry.GetEntries()
4774        self.assertEqual(2, len(subent))
4775        self.assertIn('u-boot-nodtb', subent)
4776        self.assertIn('u-boot-dtb', subent)
4777
4778        # Second, u-boot-spl, which should be expanded into three parts
4779        self.assertIn('u-boot-spl', entries)
4780        entry = entries['u-boot-spl']
4781        self.assertEqual('u-boot-spl-expanded', entry.etype)
4782        subent = entry.GetEntries()
4783        self.assertEqual(3, len(subent))
4784        self.assertIn('u-boot-spl-nodtb', subent)
4785        self.assertIn('u-boot-spl-bss-pad', subent)
4786        self.assertIn('u-boot-spl-dtb', subent)
4787
4788        # Third, u-boot-tpl, which should be not be expanded, since TPL has no
4789        # devicetree
4790        self.assertIn('u-boot-tpl', entries)
4791        entry = entries['u-boot-tpl']
4792        self.assertEqual('u-boot-tpl', entry.etype)
4793        self.assertEqual(None, entry.GetEntries())
4794
4795    def testExpandedTpl(self):
4796        """Test that an expanded entry type is selected for TPL when needed"""
4797        self._SetupTplElf()
4798
4799        entry_args = {
4800            'tpl-bss-pad': 'y',
4801            'tpl-dtb': 'y',
4802        }
4803        self._DoReadFileDtb('195_fdt_incl_tpl.dts', use_expanded=True,
4804                            entry_args=entry_args)
4805        image = control.images['image']
4806        entries = image.GetEntries()
4807        self.assertEqual(1, len(entries))
4808
4809        # We only have u-boot-tpl, which be expanded
4810        self.assertIn('u-boot-tpl', entries)
4811        entry = entries['u-boot-tpl']
4812        self.assertEqual('u-boot-tpl-expanded', entry.etype)
4813        subent = entry.GetEntries()
4814        self.assertEqual(3, len(subent))
4815        self.assertIn('u-boot-tpl-nodtb', subent)
4816        self.assertIn('u-boot-tpl-bss-pad', subent)
4817        self.assertIn('u-boot-tpl-dtb', subent)
4818
4819    def testExpandedNoPad(self):
4820        """Test an expanded entry without BSS pad enabled"""
4821        self._SetupSplElf()
4822        self._SetupTplElf()
4823
4824        # SPL has a devicetree, TPL does not
4825        entry_args = {
4826            'spl-dtb': 'something',
4827            'spl-bss-pad': 'n',
4828            'tpl-dtb': '',
4829        }
4830        self._DoReadFileDtb('194_fdt_incl.dts', use_expanded=True,
4831                            entry_args=entry_args)
4832        image = control.images['image']
4833        entries = image.GetEntries()
4834
4835        # Just check u-boot-spl, which should be expanded into two parts
4836        self.assertIn('u-boot-spl', entries)
4837        entry = entries['u-boot-spl']
4838        self.assertEqual('u-boot-spl-expanded', entry.etype)
4839        subent = entry.GetEntries()
4840        self.assertEqual(2, len(subent))
4841        self.assertIn('u-boot-spl-nodtb', subent)
4842        self.assertIn('u-boot-spl-dtb', subent)
4843
4844    def testExpandedTplNoPad(self):
4845        """Test that an expanded entry type with padding disabled in TPL"""
4846        self._SetupTplElf()
4847
4848        entry_args = {
4849            'tpl-bss-pad': '',
4850            'tpl-dtb': 'y',
4851        }
4852        self._DoReadFileDtb('195_fdt_incl_tpl.dts', use_expanded=True,
4853                            entry_args=entry_args)
4854        image = control.images['image']
4855        entries = image.GetEntries()
4856        self.assertEqual(1, len(entries))
4857
4858        # We only have u-boot-tpl, which be expanded
4859        self.assertIn('u-boot-tpl', entries)
4860        entry = entries['u-boot-tpl']
4861        self.assertEqual('u-boot-tpl-expanded', entry.etype)
4862        subent = entry.GetEntries()
4863        self.assertEqual(2, len(subent))
4864        self.assertIn('u-boot-tpl-nodtb', subent)
4865        self.assertIn('u-boot-tpl-dtb', subent)
4866
4867    def testFdtInclude(self):
4868        """Test that an Fdt is update within all binaries"""
4869        self._SetupSplElf()
4870        self._SetupTplElf()
4871
4872        # SPL has a devicetree, TPL does not
4873        self.maxDiff = None
4874        entry_args = {
4875            'spl-dtb': '1',
4876            'spl-bss-pad': 'y',
4877            'tpl-dtb': '',
4878        }
4879        # Build the image. It includes two separate devicetree binaries, each
4880        # with their own contents, but all contain the binman definition.
4881        data = self._DoReadFileDtb(
4882            '194_fdt_incl.dts', use_real_dtb=True, use_expanded=True,
4883            update_dtb=True, entry_args=entry_args)[0]
4884        pad_len = 10
4885
4886        # Check the U-Boot dtb
4887        start = len(U_BOOT_NODTB_DATA)
4888        fdt_size = self.checkDtbSizes(data, pad_len, start)
4889
4890        # Now check SPL
4891        start += fdt_size + len(U_BOOT_SPL_NODTB_DATA) + pad_len
4892        fdt_size = self.checkDtbSizes(data, pad_len, start)
4893
4894        # TPL has no devicetree
4895        start += fdt_size + len(U_BOOT_TPL_DATA)
4896        self.assertEqual(len(data), start)
4897
4898    def testSymbolsExpanded(self):
4899        """Test binman can assign symbols in expanded entries"""
4900        entry_args = {
4901            'spl-dtb': '1',
4902        }
4903        self.checkSymbols('197_symbols_expand.dts', U_BOOT_SPL_NODTB_DATA +
4904                          U_BOOT_SPL_DTB_DATA, 0x38,
4905                          entry_args=entry_args, use_expanded=True)
4906
4907    def testCollection(self):
4908        """Test a collection"""
4909        data = self._DoReadFile('198_collection.dts')
4910        self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA +
4911                         tools.get_bytes(0xff, 2) + U_BOOT_NODTB_DATA +
4912                         tools.get_bytes(0xfe, 3) + U_BOOT_DTB_DATA,
4913                         data)
4914
4915    def testCollectionSection(self):
4916        """Test a collection where a section must be built first"""
4917        # Sections never have their contents when GetData() is called, but when
4918        # BuildSectionData() is called with required=True, a section will force
4919        # building the contents, producing an error is anything is still
4920        # missing.
4921        data = self._DoReadFile('199_collection_section.dts')
4922        section = U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA
4923        self.assertEqual(section + U_BOOT_DATA + tools.get_bytes(0xff, 2) +
4924                         section + tools.get_bytes(0xfe, 3) + U_BOOT_DATA,
4925                         data)
4926
4927    def testAlignDefault(self):
4928        """Test that default alignment works on sections"""
4929        data = self._DoReadFile('200_align_default.dts')
4930        expected = (U_BOOT_DATA + tools.get_bytes(0, 8 - len(U_BOOT_DATA)) +
4931                    U_BOOT_DATA)
4932        # Special alignment for section
4933        expected += tools.get_bytes(0, 32 - len(expected))
4934        # No alignment within the nested section
4935        expected += U_BOOT_DATA + U_BOOT_NODTB_DATA;
4936        # Now the final piece, which should be default-aligned
4937        expected += tools.get_bytes(0, 88 - len(expected)) + U_BOOT_NODTB_DATA
4938        self.assertEqual(expected, data)
4939
4940    def testPackOpenSBI(self):
4941        """Test that an image with an OpenSBI binary can be created"""
4942        data = self._DoReadFile('201_opensbi.dts')
4943        self.assertEqual(OPENSBI_DATA, data[:len(OPENSBI_DATA)])
4944
4945    def testSectionsSingleThread(self):
4946        """Test sections without multithreading"""
4947        data = self._DoReadFileDtb('055_sections.dts', threads=0)[0]
4948        expected = (U_BOOT_DATA + tools.get_bytes(ord('!'), 12) +
4949                    U_BOOT_DATA + tools.get_bytes(ord('a'), 12) +
4950                    U_BOOT_DATA + tools.get_bytes(ord('&'), 4))
4951        self.assertEqual(expected, data)
4952
4953    def testThreadTimeout(self):
4954        """Test handling a thread that takes too long"""
4955        with self.assertRaises(ValueError) as e:
4956            self._DoTestFile('202_section_timeout.dts',
4957                             test_section_timeout=True)
4958        self.assertIn("Timed out obtaining contents", str(e.exception))
4959
4960    def testTiming(self):
4961        """Test output of timing information"""
4962        data = self._DoReadFile('055_sections.dts')
4963        with test_util.capture_sys_output() as (stdout, stderr):
4964            state.TimingShow()
4965        self.assertIn('read:', stdout.getvalue())
4966        self.assertIn('compress:', stdout.getvalue())
4967
4968    def testUpdateFdtInElf(self):
4969        """Test that we can update the devicetree in an ELF file"""
4970        if not elf.ELF_TOOLS:
4971            self.skipTest('Python elftools not available')
4972        infile = elf_fname = self.ElfTestFile('u_boot_binman_embed')
4973        outfile = os.path.join(self._indir, 'u-boot.out')
4974        begin_sym = 'dtb_embed_begin'
4975        end_sym = 'dtb_embed_end'
4976        retcode = self._DoTestFile(
4977            '060_fdt_update.dts', update_dtb=True,
4978            update_fdt_in_elf=','.join([infile,outfile,begin_sym,end_sym]))
4979        self.assertEqual(0, retcode)
4980
4981        # Check that the output file does in fact contact a dtb with the binman
4982        # definition in the correct place
4983        syms = elf.GetSymbolFileOffset(infile,
4984                                       ['dtb_embed_begin', 'dtb_embed_end'])
4985        data = tools.read_file(outfile)
4986        dtb_data = data[syms['dtb_embed_begin'].offset:
4987                        syms['dtb_embed_end'].offset]
4988
4989        dtb = fdt.Fdt.FromData(dtb_data)
4990        dtb.Scan()
4991        props = self._GetPropTree(dtb, BASE_DTB_PROPS + REPACK_DTB_PROPS)
4992        self.assertEqual({
4993            'image-pos': 0,
4994            'offset': 0,
4995            '_testing:offset': 32,
4996            '_testing:size': 2,
4997            '_testing:image-pos': 32,
4998            'section@0/u-boot:offset': 0,
4999            'section@0/u-boot:size': len(U_BOOT_DATA),
5000            'section@0/u-boot:image-pos': 0,
5001            'section@0:offset': 0,
5002            'section@0:size': 16,
5003            'section@0:image-pos': 0,
5004
5005            'section@1/u-boot:offset': 0,
5006            'section@1/u-boot:size': len(U_BOOT_DATA),
5007            'section@1/u-boot:image-pos': 16,
5008            'section@1:offset': 16,
5009            'section@1:size': 16,
5010            'section@1:image-pos': 16,
5011            'size': 40
5012        }, props)
5013
5014    def testUpdateFdtInElfInvalid(self):
5015        """Test that invalid args are detected with --update-fdt-in-elf"""
5016        with self.assertRaises(ValueError) as e:
5017            self._DoTestFile('060_fdt_update.dts', update_fdt_in_elf='fred')
5018        self.assertIn("Invalid args ['fred'] to --update-fdt-in-elf",
5019                      str(e.exception))
5020
5021    def testUpdateFdtInElfNoSyms(self):
5022        """Test that missing symbols are detected with --update-fdt-in-elf"""
5023        if not elf.ELF_TOOLS:
5024            self.skipTest('Python elftools not available')
5025        infile = elf_fname = self.ElfTestFile('u_boot_binman_embed')
5026        outfile = ''
5027        begin_sym = 'wrong_begin'
5028        end_sym = 'wrong_end'
5029        with self.assertRaises(ValueError) as e:
5030            self._DoTestFile(
5031                '060_fdt_update.dts',
5032                update_fdt_in_elf=','.join([infile,outfile,begin_sym,end_sym]))
5033        self.assertIn("Expected two symbols 'wrong_begin' and 'wrong_end': got 0:",
5034                      str(e.exception))
5035
5036    def testUpdateFdtInElfTooSmall(self):
5037        """Test that an over-large dtb is detected with --update-fdt-in-elf"""
5038        if not elf.ELF_TOOLS:
5039            self.skipTest('Python elftools not available')
5040        infile = elf_fname = self.ElfTestFile('u_boot_binman_embed_sm')
5041        outfile = os.path.join(self._indir, 'u-boot.out')
5042        begin_sym = 'dtb_embed_begin'
5043        end_sym = 'dtb_embed_end'
5044        with self.assertRaises(ValueError) as e:
5045            self._DoTestFile(
5046                '060_fdt_update.dts', update_dtb=True,
5047                update_fdt_in_elf=','.join([infile,outfile,begin_sym,end_sym]))
5048        self.assertRegex(
5049            str(e.exception),
5050            "Not enough space in '.*u_boot_binman_embed_sm' for data length.*")
5051
5052    def testVersion(self):
5053        """Test we can get the binman version"""
5054        version = '(unreleased)'
5055        self.assertEqual(version, state.GetVersion(self._indir))
5056
5057        with self.assertRaises(SystemExit):
5058            with test_util.capture_sys_output() as (_, stderr):
5059                self._DoBinman('-V')
5060        self.assertEqual('Binman %s\n' % version, stderr.getvalue())
5061
5062        # Try running the tool too, just to be safe
5063        result = self._RunBinman('-V')
5064        self.assertEqual('Binman %s\n' % version, result.stderr)
5065
5066        # Set up a version file to make sure that works
5067        version = 'v2025.01-rc2'
5068        tools.write_file(os.path.join(self._indir, 'version'), version,
5069                        binary=False)
5070        self.assertEqual(version, state.GetVersion(self._indir))
5071
5072    def testAltFormat(self):
5073        """Test that alternative formats can be used to extract"""
5074        self._DoReadFileRealDtb('213_fdtmap_alt_format.dts')
5075
5076        try:
5077            tmpdir, updated_fname = self._SetupImageInTmpdir()
5078            with test_util.capture_sys_output() as (stdout, _):
5079                self._DoBinman('extract', '-i', updated_fname, '-F', 'list')
5080            self.assertEqual(
5081                '''Flag (-F)   Entry type            Description
5082fdt         fdtmap                Extract the devicetree blob from the fdtmap
5083''',
5084                stdout.getvalue())
5085
5086            dtb = os.path.join(tmpdir, 'fdt.dtb')
5087            self._DoBinman('extract', '-i', updated_fname, '-F', 'fdt', '-f',
5088                           dtb, 'fdtmap')
5089
5090            # Check that we can read it and it can be scanning, meaning it does
5091            # not have a 16-byte fdtmap header
5092            data = tools.read_file(dtb)
5093            dtb = fdt.Fdt.FromData(data)
5094            dtb.Scan()
5095
5096            # Now check u-boot which has no alt_format
5097            fname = os.path.join(tmpdir, 'fdt.dtb')
5098            self._DoBinman('extract', '-i', updated_fname, '-F', 'dummy',
5099                           '-f', fname, 'u-boot')
5100            data = tools.read_file(fname)
5101            self.assertEqual(U_BOOT_DATA, data)
5102
5103        finally:
5104            shutil.rmtree(tmpdir)
5105
5106    def testExtblobList(self):
5107        """Test an image with an external blob list"""
5108        data = self._DoReadFile('215_blob_ext_list.dts')
5109        self.assertEqual(REFCODE_DATA + FSP_M_DATA, data)
5110
5111    def testExtblobListMissing(self):
5112        """Test an image with a missing external blob"""
5113        with self.assertRaises(ValueError) as e:
5114            self._DoReadFile('216_blob_ext_list_missing.dts')
5115        self.assertIn("Filename 'missing-file' not found in input path",
5116                      str(e.exception))
5117
5118    def testExtblobListMissingOk(self):
5119        """Test an image with an missing external blob that is allowed"""
5120        with test_util.capture_sys_output() as (stdout, stderr):
5121            self._DoTestFile('216_blob_ext_list_missing.dts',
5122                             allow_missing=True)
5123        err = stderr.getvalue()
5124        self.assertRegex(err, "Image 'image'.*missing.*: blob-ext")
5125
5126    def testFip(self):
5127        """Basic test of generation of an ARM Firmware Image Package (FIP)"""
5128        data = self._DoReadFile('203_fip.dts')
5129        hdr, fents = fip_util.decode_fip(data)
5130        self.assertEqual(fip_util.HEADER_MAGIC, hdr.name)
5131        self.assertEqual(fip_util.HEADER_SERIAL, hdr.serial)
5132        self.assertEqual(0x123, hdr.flags)
5133
5134        self.assertEqual(2, len(fents))
5135
5136        fent = fents[0]
5137        self.assertEqual(
5138            bytes([0x47,  0xd4, 0x08, 0x6d, 0x4c, 0xfe, 0x98, 0x46,
5139                  0x9b, 0x95, 0x29, 0x50, 0xcb, 0xbd, 0x5a, 0x0]), fent.uuid)
5140        self.assertEqual('soc-fw', fent.fip_type)
5141        self.assertEqual(0x88, fent.offset)
5142        self.assertEqual(len(ATF_BL31_DATA), fent.size)
5143        self.assertEqual(0x123456789abcdef, fent.flags)
5144        self.assertEqual(ATF_BL31_DATA, fent.data)
5145        self.assertEqual(True, fent.valid)
5146
5147        fent = fents[1]
5148        self.assertEqual(
5149            bytes([0x65, 0x92, 0x27, 0x03, 0x2f, 0x74, 0xe6, 0x44,
5150             0x8d, 0xff, 0x57, 0x9a, 0xc1, 0xff, 0x06, 0x10]), fent.uuid)
5151        self.assertEqual('scp-fwu-cfg', fent.fip_type)
5152        self.assertEqual(0x8c, fent.offset)
5153        self.assertEqual(len(ATF_BL31_DATA), fent.size)
5154        self.assertEqual(0, fent.flags)
5155        self.assertEqual(ATF_BL2U_DATA, fent.data)
5156        self.assertEqual(True, fent.valid)
5157
5158    def testFipOther(self):
5159        """Basic FIP with something that isn't a external blob"""
5160        data = self._DoReadFile('204_fip_other.dts')
5161        hdr, fents = fip_util.decode_fip(data)
5162
5163        self.assertEqual(2, len(fents))
5164        fent = fents[1]
5165        self.assertEqual('rot-cert', fent.fip_type)
5166        self.assertEqual(b'aa', fent.data)
5167
5168    def testFipNoType(self):
5169        """FIP with an entry of an unknown type"""
5170        with self.assertRaises(ValueError) as e:
5171            self._DoReadFile('205_fip_no_type.dts')
5172        self.assertIn("Must provide a fip-type (node name 'u-boot' is not a known FIP type)",
5173                      str(e.exception))
5174
5175    def testFipUuid(self):
5176        """Basic FIP with a manual uuid"""
5177        data = self._DoReadFile('206_fip_uuid.dts')
5178        hdr, fents = fip_util.decode_fip(data)
5179
5180        self.assertEqual(2, len(fents))
5181        fent = fents[1]
5182        self.assertEqual(None, fent.fip_type)
5183        self.assertEqual(
5184            bytes([0xfc, 0x65, 0x13, 0x92, 0x4a, 0x5b, 0x11, 0xec,
5185                   0x94, 0x35, 0xff, 0x2d, 0x1c, 0xfc, 0x79, 0x9c]),
5186            fent.uuid)
5187        self.assertEqual(U_BOOT_DATA, fent.data)
5188
5189    def testFipLs(self):
5190        """Test listing a FIP"""
5191        data = self._DoReadFileRealDtb('207_fip_ls.dts')
5192        hdr, fents = fip_util.decode_fip(data)
5193
5194        tmpdir = None
5195        try:
5196            tmpdir, updated_fname = self._SetupImageInTmpdir()
5197            with test_util.capture_sys_output() as (stdout, stderr):
5198                self._DoBinman('ls', '-i', updated_fname)
5199        finally:
5200            if tmpdir:
5201                shutil.rmtree(tmpdir)
5202        lines = stdout.getvalue().splitlines()
5203        expected = [
5204'Name        Image-pos  Size  Entry-type  Offset  Uncomp-size',
5205'--------------------------------------------------------------',
5206'image               0   2d3  section          0',
5207'  atf-fip           0    90  atf-fip          0',
5208'    soc-fw         88     4  blob-ext        88',
5209'    u-boot         8c     4  u-boot          8c',
5210'  fdtmap           90   243  fdtmap          90',
5211]
5212        self.assertEqual(expected, lines)
5213
5214        image = control.images['image']
5215        entries = image.GetEntries()
5216        fdtmap = entries['fdtmap']
5217
5218        fdtmap_data = data[fdtmap.image_pos:fdtmap.image_pos + fdtmap.size]
5219        magic = fdtmap_data[:8]
5220        self.assertEqual(b'_FDTMAP_', magic)
5221        self.assertEqual(tools.get_bytes(0, 8), fdtmap_data[8:16])
5222
5223        fdt_data = fdtmap_data[16:]
5224        dtb = fdt.Fdt.FromData(fdt_data)
5225        dtb.Scan()
5226        props = self._GetPropTree(dtb, BASE_DTB_PROPS, prefix='/')
5227        self.assertEqual({
5228            'atf-fip/soc-fw:image-pos': 136,
5229            'atf-fip/soc-fw:offset': 136,
5230            'atf-fip/soc-fw:size': 4,
5231            'atf-fip/u-boot:image-pos': 140,
5232            'atf-fip/u-boot:offset': 140,
5233            'atf-fip/u-boot:size': 4,
5234            'atf-fip:image-pos': 0,
5235            'atf-fip:offset': 0,
5236            'atf-fip:size': 144,
5237            'image-pos': 0,
5238            'offset': 0,
5239            'fdtmap:image-pos': fdtmap.image_pos,
5240            'fdtmap:offset': fdtmap.offset,
5241            'fdtmap:size': len(fdtmap_data),
5242            'size': len(data),
5243        }, props)
5244
5245    def testFipExtractOneEntry(self):
5246        """Test extracting a single entry fron an FIP"""
5247        self._DoReadFileRealDtb('207_fip_ls.dts')
5248        image_fname = tools.get_output_filename('image.bin')
5249        fname = os.path.join(self._indir, 'output.extact')
5250        control.ExtractEntries(image_fname, fname, None, ['atf-fip/u-boot'])
5251        data = tools.read_file(fname)
5252        self.assertEqual(U_BOOT_DATA, data)
5253
5254    def testFipReplace(self):
5255        """Test replacing a single file in a FIP"""
5256        expected = U_BOOT_DATA + tools.get_bytes(0x78, 50)
5257        data = self._DoReadFileRealDtb('208_fip_replace.dts')
5258        updated_fname = tools.get_output_filename('image-updated.bin')
5259        tools.write_file(updated_fname, data)
5260        entry_name = 'atf-fip/u-boot'
5261        control.WriteEntry(updated_fname, entry_name, expected,
5262                           allow_resize=True)
5263        actual = control.ReadEntry(updated_fname, entry_name)
5264        self.assertEqual(expected, actual)
5265
5266        new_data = tools.read_file(updated_fname)
5267        hdr, fents = fip_util.decode_fip(new_data)
5268
5269        self.assertEqual(2, len(fents))
5270
5271        # Check that the FIP entry is updated
5272        fent = fents[1]
5273        self.assertEqual(0x8c, fent.offset)
5274        self.assertEqual(len(expected), fent.size)
5275        self.assertEqual(0, fent.flags)
5276        self.assertEqual(expected, fent.data)
5277        self.assertEqual(True, fent.valid)
5278
5279    def testFipMissing(self):
5280        with test_util.capture_sys_output() as (stdout, stderr):
5281            self._DoTestFile('209_fip_missing.dts', allow_missing=True)
5282        err = stderr.getvalue()
5283        self.assertRegex(err, "Image 'image'.*missing.*: rmm-fw")
5284
5285    def testFipSize(self):
5286        """Test a FIP with a size property"""
5287        data = self._DoReadFile('210_fip_size.dts')
5288        self.assertEqual(0x100 + len(U_BOOT_DATA), len(data))
5289        hdr, fents = fip_util.decode_fip(data)
5290        self.assertEqual(fip_util.HEADER_MAGIC, hdr.name)
5291        self.assertEqual(fip_util.HEADER_SERIAL, hdr.serial)
5292
5293        self.assertEqual(1, len(fents))
5294
5295        fent = fents[0]
5296        self.assertEqual('soc-fw', fent.fip_type)
5297        self.assertEqual(0x60, fent.offset)
5298        self.assertEqual(len(ATF_BL31_DATA), fent.size)
5299        self.assertEqual(ATF_BL31_DATA, fent.data)
5300        self.assertEqual(True, fent.valid)
5301
5302        rest = data[0x60 + len(ATF_BL31_DATA):0x100]
5303        self.assertEqual(tools.get_bytes(0xff, len(rest)), rest)
5304
5305    def testFipBadAlign(self):
5306        """Test that an invalid alignment value in a FIP is detected"""
5307        with self.assertRaises(ValueError) as e:
5308            self._DoTestFile('211_fip_bad_align.dts')
5309        self.assertIn(
5310            "Node \'/binman/atf-fip\': FIP alignment 31 must be a power of two",
5311            str(e.exception))
5312
5313    def testFipCollection(self):
5314        """Test using a FIP in a collection"""
5315        data = self._DoReadFile('212_fip_collection.dts')
5316        entry1 = control.images['image'].GetEntries()['collection']
5317        data1 = data[:entry1.size]
5318        hdr1, fents2 = fip_util.decode_fip(data1)
5319
5320        entry2 = control.images['image'].GetEntries()['atf-fip']
5321        data2 = data[entry2.offset:entry2.offset + entry2.size]
5322        hdr1, fents2 = fip_util.decode_fip(data2)
5323
5324        # The 'collection' entry should have U-Boot included at the end
5325        self.assertEqual(entry1.size - len(U_BOOT_DATA), entry2.size)
5326        self.assertEqual(data1, data2 + U_BOOT_DATA)
5327        self.assertEqual(U_BOOT_DATA, data1[-4:])
5328
5329        # There should be a U-Boot after the final FIP
5330        self.assertEqual(U_BOOT_DATA, data[-4:])
5331
5332    def testFakeBlob(self):
5333        """Test handling of faking an external blob"""
5334        with test_util.capture_sys_output() as (stdout, stderr):
5335            self._DoTestFile('217_fake_blob.dts', allow_missing=True,
5336                             allow_fake_blobs=True)
5337        err = stderr.getvalue()
5338        self.assertRegex(
5339            err,
5340            "Image '.*' has faked external blobs and is non-functional: .*")
5341
5342    def testExtblobListFaked(self):
5343        """Test an extblob with missing external blob that are faked"""
5344        with test_util.capture_sys_output() as (stdout, stderr):
5345            self._DoTestFile('216_blob_ext_list_missing.dts',
5346                             allow_fake_blobs=True)
5347        err = stderr.getvalue()
5348        self.assertRegex(err, "Image 'image'.*faked.*: blob-ext-list")
5349
5350    def testListBintools(self):
5351        args = ['tool', '--list']
5352        with test_util.capture_sys_output() as (stdout, _):
5353            self._DoBinman(*args)
5354        out = stdout.getvalue().splitlines()
5355        self.assertTrue(len(out) >= 2)
5356
5357    def testFetchBintools(self):
5358        def fail_download(url):
5359            """Take the tools.download() function by raising an exception"""
5360            raise urllib.error.URLError('my error')
5361
5362        args = ['tool']
5363        with self.assertRaises(ValueError) as e:
5364            self._DoBinman(*args)
5365        self.assertIn("Invalid arguments to 'tool' subcommand",
5366                      str(e.exception))
5367
5368        args = ['tool', '--fetch']
5369        with self.assertRaises(ValueError) as e:
5370            self._DoBinman(*args)
5371        self.assertIn('Please specify bintools to fetch', str(e.exception))
5372
5373        args = ['tool', '--fetch', '_testing']
5374        with unittest.mock.patch.object(tools, 'download',
5375                                        side_effect=fail_download):
5376            with test_util.capture_sys_output() as (stdout, _):
5377                self._DoBinman(*args)
5378        self.assertIn('failed to fetch with all methods', stdout.getvalue())
5379
5380    def testBintoolDocs(self):
5381        """Test for creation of bintool documentation"""
5382        with test_util.capture_sys_output() as (stdout, stderr):
5383            control.write_bintool_docs(control.bintool.Bintool.get_tool_list())
5384        self.assertTrue(len(stdout.getvalue()) > 0)
5385
5386    def testBintoolDocsMissing(self):
5387        """Test handling of missing bintool documentation"""
5388        with self.assertRaises(ValueError) as e:
5389            with test_util.capture_sys_output() as (stdout, stderr):
5390                control.write_bintool_docs(
5391                    control.bintool.Bintool.get_tool_list(), 'mkimage')
5392        self.assertIn('Documentation is missing for modules: mkimage',
5393                      str(e.exception))
5394
5395    def testListWithGenNode(self):
5396        """Check handling of an FDT map when the section cannot be found"""
5397        entry_args = {
5398            'of-list': 'test-fdt1 test-fdt2',
5399        }
5400        data = self._DoReadFileDtb(
5401            '219_fit_gennode.dts',
5402            entry_args=entry_args,
5403            use_real_dtb=True,
5404            extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])
5405
5406        tmpdir = None
5407        try:
5408            tmpdir, updated_fname = self._SetupImageInTmpdir()
5409            with test_util.capture_sys_output() as (stdout, stderr):
5410                self._RunBinman('ls', '-i', updated_fname)
5411        finally:
5412            if tmpdir:
5413                shutil.rmtree(tmpdir)
5414
5415    def testFitSubentryUsesBintool(self):
5416        """Test that binman FIT subentries can use bintools"""
5417        command.test_result = self._HandleGbbCommand
5418        entry_args = {
5419            'keydir': 'devkeys',
5420            'bmpblk': 'bmpblk.bin',
5421        }
5422        data, _, _, _ = self._DoReadFileDtb('220_fit_subentry_bintool.dts',
5423                entry_args=entry_args)
5424
5425        expected = (GBB_DATA + GBB_DATA + tools.get_bytes(0, 8) +
5426                    tools.get_bytes(0, 0x2180 - 16))
5427        self.assertIn(expected, data)
5428
5429    def testFitSubentryMissingBintool(self):
5430        """Test that binman reports missing bintools for FIT subentries"""
5431        entry_args = {
5432            'keydir': 'devkeys',
5433        }
5434        with test_util.capture_sys_output() as (_, stderr):
5435            self._DoTestFile('220_fit_subentry_bintool.dts',
5436                    force_missing_bintools='futility', entry_args=entry_args)
5437        err = stderr.getvalue()
5438        self.assertRegex(err, "Image 'image'.*missing bintools.*: futility")
5439
5440    def testFitSubentryHashSubnode(self):
5441        """Test an image with a FIT inside"""
5442        self._SetupSplElf()
5443        data, _, _, out_dtb_name = self._DoReadFileDtb(
5444            '221_fit_subentry_hash.dts', use_real_dtb=True, update_dtb=True)
5445
5446        mkimage_dtb = fdt.Fdt.FromData(data)
5447        mkimage_dtb.Scan()
5448        binman_dtb = fdt.Fdt(out_dtb_name)
5449        binman_dtb.Scan()
5450
5451        # Check that binman didn't add hash values
5452        fnode = binman_dtb.GetNode('/binman/fit/images/kernel/hash')
5453        self.assertNotIn('value', fnode.props)
5454
5455        fnode = binman_dtb.GetNode('/binman/fit/images/fdt-1/hash')
5456        self.assertNotIn('value', fnode.props)
5457
5458        # Check that mkimage added hash values
5459        fnode = mkimage_dtb.GetNode('/images/kernel/hash')
5460        self.assertIn('value', fnode.props)
5461
5462        fnode = mkimage_dtb.GetNode('/images/fdt-1/hash')
5463        self.assertIn('value', fnode.props)
5464
5465    def testPackTeeOs(self):
5466        """Test that an image with an TEE binary can be created"""
5467        data = self._DoReadFile('222_tee_os.dts')
5468        self.assertEqual(TEE_OS_DATA, data[:len(TEE_OS_DATA)])
5469
5470    def testPackTiDm(self):
5471        """Test that an image with a TI DM binary can be created"""
5472        data = self._DoReadFile('225_ti_dm.dts')
5473        self.assertEqual(TI_DM_DATA, data[:len(TI_DM_DATA)])
5474
5475    def testFitFdtOper(self):
5476        """Check handling of a specified FIT operation"""
5477        entry_args = {
5478            'of-list': 'test-fdt1 test-fdt2',
5479            'default-dt': 'test-fdt2',
5480        }
5481        self._DoReadFileDtb(
5482            '223_fit_fdt_oper.dts',
5483            entry_args=entry_args,
5484            extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
5485
5486    def testFitFdtBadOper(self):
5487        """Check handling of an FDT map when the section cannot be found"""
5488        with self.assertRaises(ValueError) as exc:
5489            self._DoReadFileDtb('224_fit_bad_oper.dts')
5490        self.assertIn("Node '/binman/fit': subnode 'images/@fdt-SEQ': Unknown operation 'unknown'",
5491                      str(exc.exception))
5492
5493    def test_uses_expand_size(self):
5494        """Test that the 'expand-size' property cannot be used anymore"""
5495        with self.assertRaises(ValueError) as e:
5496           data = self._DoReadFile('225_expand_size_bad.dts')
5497        self.assertIn(
5498            "Node '/binman/u-boot': Please use 'extend-size' instead of 'expand-size'",
5499            str(e.exception))
5500
5501    def testFitSplitElf(self):
5502        """Test an image with an FIT with an split-elf operation"""
5503        if not elf.ELF_TOOLS:
5504            self.skipTest('Python elftools not available')
5505        entry_args = {
5506            'of-list': 'test-fdt1 test-fdt2',
5507            'default-dt': 'test-fdt2',
5508            'atf-bl31-path': 'bl31.elf',
5509            'tee-os-path': 'tee.elf',
5510        }
5511        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
5512        data = self._DoReadFileDtb(
5513            '226_fit_split_elf.dts',
5514            entry_args=entry_args,
5515            extra_indirs=[test_subdir])[0]
5516
5517        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
5518        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
5519
5520        base_keys = {'description', 'type', 'arch', 'os', 'compression',
5521                     'data', 'load'}
5522        dtb = fdt.Fdt.FromData(fit_data)
5523        dtb.Scan()
5524
5525        elf_data = tools.read_file(os.path.join(self._indir, 'bl31.elf'))
5526        segments, entry = elf.read_loadable_segments(elf_data)
5527
5528        # We assume there are two segments
5529        self.assertEquals(2, len(segments))
5530
5531        atf1 = dtb.GetNode('/images/atf-1')
5532        _, start, data = segments[0]
5533        self.assertEqual(base_keys | {'entry'}, atf1.props.keys())
5534        self.assertEqual(entry,
5535                         fdt_util.fdt32_to_cpu(atf1.props['entry'].value))
5536        self.assertEqual(start,
5537                         fdt_util.fdt32_to_cpu(atf1.props['load'].value))
5538        self.assertEqual(data, atf1.props['data'].bytes)
5539
5540        hash_node = atf1.FindNode('hash')
5541        self.assertIsNotNone(hash_node)
5542        self.assertEqual({'algo', 'value'}, hash_node.props.keys())
5543
5544        atf2 = dtb.GetNode('/images/atf-2')
5545        self.assertEqual(base_keys, atf2.props.keys())
5546        _, start, data = segments[1]
5547        self.assertEqual(start,
5548                         fdt_util.fdt32_to_cpu(atf2.props['load'].value))
5549        self.assertEqual(data, atf2.props['data'].bytes)
5550
5551        hash_node = atf2.FindNode('hash')
5552        self.assertIsNotNone(hash_node)
5553        self.assertEqual({'algo', 'value'}, hash_node.props.keys())
5554
5555        hash_node = dtb.GetNode('/images/tee-1/hash-1')
5556        self.assertIsNotNone(hash_node)
5557        self.assertEqual({'algo', 'value'}, hash_node.props.keys())
5558
5559        conf = dtb.GetNode('/configurations')
5560        self.assertEqual({'default'}, conf.props.keys())
5561
5562        for subnode in conf.subnodes:
5563            self.assertEqual({'description', 'fdt', 'loadables'},
5564                             subnode.props.keys())
5565            self.assertEqual(
5566                ['atf-1', 'atf-2', 'tee-1', 'tee-2'],
5567                fdt_util.GetStringList(subnode, 'loadables'))
5568
5569    def _check_bad_fit(self, dts):
5570        """Check a bad FIT
5571
5572        This runs with the given dts and returns the assertion raised
5573
5574        Args:
5575            dts (str): dts filename to use
5576
5577        Returns:
5578            str: Assertion string raised
5579        """
5580        entry_args = {
5581            'of-list': 'test-fdt1 test-fdt2',
5582            'default-dt': 'test-fdt2',
5583            'atf-bl31-path': 'bl31.elf',
5584            'tee-os-path': 'tee.elf',
5585        }
5586        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
5587        with self.assertRaises(ValueError) as exc:
5588            self._DoReadFileDtb(dts, entry_args=entry_args,
5589                                extra_indirs=[test_subdir])[0]
5590        return str(exc.exception)
5591
5592    def testFitSplitElfBadElf(self):
5593        """Test a FIT split-elf operation with an invalid ELF file"""
5594        if not elf.ELF_TOOLS:
5595            self.skipTest('Python elftools not available')
5596        TestFunctional._MakeInputFile('bad.elf', tools.get_bytes(100, 100))
5597        entry_args = {
5598            'of-list': 'test-fdt1 test-fdt2',
5599            'default-dt': 'test-fdt2',
5600            'atf-bl31-path': 'bad.elf',
5601            'tee-os-path': 'tee.elf',
5602        }
5603        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
5604        with self.assertRaises(ValueError) as exc:
5605            self._DoReadFileDtb(
5606                '226_fit_split_elf.dts',
5607                entry_args=entry_args,
5608                extra_indirs=[test_subdir])[0]
5609        self.assertIn(
5610            "Node '/binman/fit': subnode 'images/@atf-SEQ': Failed to read ELF file: Magic number does not match",
5611            str(exc.exception))
5612
5613    def checkFitSplitElf(self, **kwargs):
5614        """Test an split-elf FIT with a missing ELF file
5615
5616        Args:
5617            kwargs (dict of str): Arguments to pass to _DoTestFile()
5618
5619        Returns:
5620            tuple:
5621                str: stdout result
5622                str: stderr result
5623        """
5624        entry_args = {
5625            'of-list': 'test-fdt1 test-fdt2',
5626            'default-dt': 'test-fdt2',
5627            'atf-bl31-path': 'bl31.elf',
5628            'tee-os-path': 'missing.elf',
5629        }
5630        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
5631        with test_util.capture_sys_output() as (stdout, stderr):
5632            self._DoTestFile(
5633                '226_fit_split_elf.dts', entry_args=entry_args,
5634                extra_indirs=[test_subdir], verbosity=3, **kwargs)
5635            out = stdout.getvalue()
5636            err = stderr.getvalue()
5637        return out, err
5638
5639    def testFitSplitElfBadDirective(self):
5640        """Test a FIT split-elf invalid fit,xxx directive in an image node"""
5641        if not elf.ELF_TOOLS:
5642            self.skipTest('Python elftools not available')
5643        err = self._check_bad_fit('227_fit_bad_dir.dts')
5644        self.assertIn(
5645            "Node '/binman/fit': subnode 'images/@atf-SEQ': Unknown directive 'fit,something'",
5646            err)
5647
5648    def testFitSplitElfBadDirectiveConfig(self):
5649        """Test a FIT split-elf with invalid fit,xxx directive in config"""
5650        if not elf.ELF_TOOLS:
5651            self.skipTest('Python elftools not available')
5652        err = self._check_bad_fit('228_fit_bad_dir_config.dts')
5653        self.assertEqual(
5654            "Node '/binman/fit': subnode 'configurations/@config-SEQ': Unknown directive 'fit,config'",
5655            err)
5656
5657
5658    def testFitSplitElfMissing(self):
5659        """Test an split-elf FIT with a missing ELF file"""
5660        if not elf.ELF_TOOLS:
5661            self.skipTest('Python elftools not available')
5662        out, err = self.checkFitSplitElf(allow_missing=True)
5663        self.assertRegex(
5664            err,
5665            "Image '.*' is missing external blobs and is non-functional: .*")
5666        self.assertNotRegex(out, '.*Faked blob.*')
5667        fname = tools.get_output_filename('binman-fake/missing.elf')
5668        self.assertFalse(os.path.exists(fname))
5669
5670    def testFitSplitElfFaked(self):
5671        """Test an split-elf FIT with faked ELF file"""
5672        if not elf.ELF_TOOLS:
5673            self.skipTest('Python elftools not available')
5674        out, err = self.checkFitSplitElf(allow_missing=True, allow_fake_blobs=True)
5675        self.assertRegex(
5676            err,
5677            "Image '.*' is missing external blobs and is non-functional: .*")
5678        self.assertRegex(
5679            out,
5680            "Entry '/binman/fit/images/@tee-SEQ/tee-os': Faked blob '.*binman-fake/missing.elf")
5681        fname = tools.get_output_filename('binman-fake/missing.elf')
5682        self.assertTrue(os.path.exists(fname))
5683
5684    def testMkimageMissingBlob(self):
5685        """Test using mkimage to build an image"""
5686        with test_util.capture_sys_output() as (stdout, stderr):
5687            self._DoTestFile('229_mkimage_missing.dts', allow_missing=True,
5688                             allow_fake_blobs=True)
5689        err = stderr.getvalue()
5690        self.assertRegex(
5691            err,
5692            "Image '.*' has faked external blobs and is non-functional: .*")
5693
5694    def testPreLoad(self):
5695        """Test an image with a pre-load header"""
5696        entry_args = {
5697            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5698        }
5699        data = self._DoReadFileDtb(
5700            '230_pre_load.dts', entry_args=entry_args,
5701            extra_indirs=[os.path.join(self._binman_dir, 'test')])[0]
5702        self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)])
5703        self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)])
5704        self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)])
5705
5706    def testPreLoadNoKey(self):
5707        """Test an image with a pre-load heade0r with missing key"""
5708        with self.assertRaises(FileNotFoundError) as exc:
5709            self._DoReadFile('230_pre_load.dts')
5710        self.assertIn("No such file or directory: 'dev.key'",
5711                      str(exc.exception))
5712
5713    def testPreLoadPkcs(self):
5714        """Test an image with a pre-load header with padding pkcs"""
5715        entry_args = {
5716            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5717        }
5718        data = self._DoReadFileDtb('231_pre_load_pkcs.dts',
5719                                   entry_args=entry_args)[0]
5720        self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)])
5721        self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)])
5722        self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)])
5723
5724    def testPreLoadPss(self):
5725        """Test an image with a pre-load header with padding pss"""
5726        entry_args = {
5727            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5728        }
5729        data = self._DoReadFileDtb('232_pre_load_pss.dts',
5730                                   entry_args=entry_args)[0]
5731        self.assertEqual(PRE_LOAD_MAGIC, data[:len(PRE_LOAD_MAGIC)])
5732        self.assertEqual(PRE_LOAD_VERSION, data[4:4 + len(PRE_LOAD_VERSION)])
5733        self.assertEqual(PRE_LOAD_HDR_SIZE, data[8:8 + len(PRE_LOAD_HDR_SIZE)])
5734
5735    def testPreLoadInvalidPadding(self):
5736        """Test an image with a pre-load header with an invalid padding"""
5737        entry_args = {
5738            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5739        }
5740        with self.assertRaises(ValueError) as e:
5741            self._DoReadFileDtb('233_pre_load_invalid_padding.dts',
5742                                entry_args=entry_args)
5743
5744    def testPreLoadInvalidSha(self):
5745        """Test an image with a pre-load header with an invalid hash"""
5746        entry_args = {
5747            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5748        }
5749        with self.assertRaises(ValueError) as e:
5750            self._DoReadFileDtb('234_pre_load_invalid_sha.dts',
5751                                entry_args=entry_args)
5752
5753    def testPreLoadInvalidAlgo(self):
5754        """Test an image with a pre-load header with an invalid algo"""
5755        with self.assertRaises(ValueError) as e:
5756            data = self._DoReadFile('235_pre_load_invalid_algo.dts')
5757
5758    def testPreLoadInvalidKey(self):
5759        """Test an image with a pre-load header with an invalid key"""
5760        entry_args = {
5761            'pre-load-key-path': os.path.join(self._binman_dir, 'test'),
5762        }
5763        with self.assertRaises(ValueError) as e:
5764            data = self._DoReadFileDtb('236_pre_load_invalid_key.dts',
5765                                       entry_args=entry_args)
5766
5767    def _CheckSafeUniqueNames(self, *images):
5768        """Check all entries of given images for unsafe unique names"""
5769        for image in images:
5770            entries = {}
5771            image._CollectEntries(entries, {}, image)
5772            for entry in entries.values():
5773                uniq = entry.GetUniqueName()
5774
5775                # Used as part of a filename, so must not be absolute paths.
5776                self.assertFalse(os.path.isabs(uniq))
5777
5778    def testSafeUniqueNames(self):
5779        """Test entry unique names are safe in single image configuration"""
5780        data = self._DoReadFileRealDtb('237_unique_names.dts')
5781
5782        orig_image = control.images['image']
5783        image_fname = tools.get_output_filename('image.bin')
5784        image = Image.FromFile(image_fname)
5785
5786        self._CheckSafeUniqueNames(orig_image, image)
5787
5788    def testSafeUniqueNamesMulti(self):
5789        """Test entry unique names are safe with multiple images"""
5790        data = self._DoReadFileRealDtb('238_unique_names_multi.dts')
5791
5792        orig_image = control.images['image']
5793        image_fname = tools.get_output_filename('image.bin')
5794        image = Image.FromFile(image_fname)
5795
5796        self._CheckSafeUniqueNames(orig_image, image)
5797
5798    def testReplaceCmdWithBintool(self):
5799        """Test replacing an entry that needs a bintool to pack"""
5800        data = self._DoReadFileRealDtb('239_replace_with_bintool.dts')
5801        expected = U_BOOT_DATA + b'aa'
5802        self.assertEqual(expected, data[:len(expected)])
5803
5804        try:
5805            tmpdir, updated_fname = self._SetupImageInTmpdir()
5806            fname = os.path.join(tmpdir, 'update-testing.bin')
5807            tools.write_file(fname, b'zz')
5808            self._DoBinman('replace', '-i', updated_fname,
5809                           '_testing', '-f', fname)
5810
5811            data = tools.read_file(updated_fname)
5812            expected = U_BOOT_DATA + b'zz'
5813            self.assertEqual(expected, data[:len(expected)])
5814        finally:
5815            shutil.rmtree(tmpdir)
5816
5817    def testReplaceCmdOtherWithBintool(self):
5818        """Test replacing an entry when another needs a bintool to pack"""
5819        data = self._DoReadFileRealDtb('239_replace_with_bintool.dts')
5820        expected = U_BOOT_DATA + b'aa'
5821        self.assertEqual(expected, data[:len(expected)])
5822
5823        try:
5824            tmpdir, updated_fname = self._SetupImageInTmpdir()
5825            fname = os.path.join(tmpdir, 'update-u-boot.bin')
5826            tools.write_file(fname, b'x' * len(U_BOOT_DATA))
5827            self._DoBinman('replace', '-i', updated_fname,
5828                           'u-boot', '-f', fname)
5829
5830            data = tools.read_file(updated_fname)
5831            expected = b'x' * len(U_BOOT_DATA) + b'aa'
5832            self.assertEqual(expected, data[:len(expected)])
5833        finally:
5834            shutil.rmtree(tmpdir)
5835
5836    def testReplaceResizeNoRepackSameSize(self):
5837        """Test replacing entries with same-size data without repacking"""
5838        expected = b'x' * len(U_BOOT_DATA)
5839        data, expected_fdtmap, _ = self._RunReplaceCmd('u-boot', expected)
5840        self.assertEqual(expected, data)
5841
5842        path, fdtmap = state.GetFdtContents('fdtmap')
5843        self.assertIsNotNone(path)
5844        self.assertEqual(expected_fdtmap, fdtmap)
5845
5846    def testReplaceResizeNoRepackSmallerSize(self):
5847        """Test replacing entries with smaller-size data without repacking"""
5848        new_data = b'x'
5849        data, expected_fdtmap, _ = self._RunReplaceCmd('u-boot', new_data)
5850        expected = new_data.ljust(len(U_BOOT_DATA), b'\0')
5851        self.assertEqual(expected, data)
5852
5853        path, fdtmap = state.GetFdtContents('fdtmap')
5854        self.assertIsNotNone(path)
5855        self.assertEqual(expected_fdtmap, fdtmap)
5856
5857    def testExtractFit(self):
5858        """Test extracting a FIT section"""
5859        self._DoReadFileRealDtb('240_fit_extract_replace.dts')
5860        image_fname = tools.get_output_filename('image.bin')
5861
5862        fit_data = control.ReadEntry(image_fname, 'fit')
5863        fit = fdt.Fdt.FromData(fit_data)
5864        fit.Scan()
5865
5866        # Check subentry data inside the extracted fit
5867        for node_path, expected in [
5868            ('/images/kernel', U_BOOT_DATA),
5869            ('/images/fdt-1', U_BOOT_NODTB_DATA),
5870            ('/images/scr-1', COMPRESS_DATA),
5871        ]:
5872            node = fit.GetNode(node_path)
5873            data = fit.GetProps(node)['data'].bytes
5874            self.assertEqual(expected, data)
5875
5876    def testExtractFitSubentries(self):
5877        """Test extracting FIT section subentries"""
5878        self._DoReadFileRealDtb('240_fit_extract_replace.dts')
5879        image_fname = tools.get_output_filename('image.bin')
5880
5881        for entry_path, expected in [
5882            ('fit/kernel', U_BOOT_DATA),
5883            ('fit/kernel/u-boot', U_BOOT_DATA),
5884            ('fit/fdt-1', U_BOOT_NODTB_DATA),
5885            ('fit/fdt-1/u-boot-nodtb', U_BOOT_NODTB_DATA),
5886            ('fit/scr-1', COMPRESS_DATA),
5887            ('fit/scr-1/blob', COMPRESS_DATA),
5888        ]:
5889            data = control.ReadEntry(image_fname, entry_path)
5890            self.assertEqual(expected, data)
5891
5892    def testReplaceFitSubentryLeafSameSize(self):
5893        """Test replacing a FIT leaf subentry with same-size data"""
5894        new_data = b'x' * len(U_BOOT_DATA)
5895        data, expected_fdtmap, _ = self._RunReplaceCmd(
5896            'fit/kernel/u-boot', new_data,
5897            dts='240_fit_extract_replace.dts')
5898        self.assertEqual(new_data, data)
5899
5900        path, fdtmap = state.GetFdtContents('fdtmap')
5901        self.assertIsNotNone(path)
5902        self.assertEqual(expected_fdtmap, fdtmap)
5903
5904    def testReplaceFitSubentryLeafBiggerSize(self):
5905        """Test replacing a FIT leaf subentry with bigger-size data"""
5906        new_data = b'ub' * len(U_BOOT_NODTB_DATA)
5907        data, expected_fdtmap, _ = self._RunReplaceCmd(
5908            'fit/fdt-1/u-boot-nodtb', new_data,
5909            dts='240_fit_extract_replace.dts')
5910        self.assertEqual(new_data, data)
5911
5912        # Will be repacked, so fdtmap must change
5913        path, fdtmap = state.GetFdtContents('fdtmap')
5914        self.assertIsNotNone(path)
5915        self.assertNotEqual(expected_fdtmap, fdtmap)
5916
5917    def testReplaceFitSubentryLeafSmallerSize(self):
5918        """Test replacing a FIT leaf subentry with smaller-size data"""
5919        new_data = b'x'
5920        expected = new_data.ljust(len(U_BOOT_NODTB_DATA), b'\0')
5921        data, expected_fdtmap, _ = self._RunReplaceCmd(
5922            'fit/fdt-1/u-boot-nodtb', new_data,
5923            dts='240_fit_extract_replace.dts')
5924        self.assertEqual(expected, data)
5925
5926        path, fdtmap = state.GetFdtContents('fdtmap')
5927        self.assertIsNotNone(path)
5928        self.assertEqual(expected_fdtmap, fdtmap)
5929
5930    def testReplaceSectionSimple(self):
5931        """Test replacing a simple section with same-sized data"""
5932        new_data = b'w' * len(COMPRESS_DATA + U_BOOT_DATA)
5933        data, expected_fdtmap, image = self._RunReplaceCmd('section',
5934            new_data, dts='241_replace_section_simple.dts')
5935        self.assertEqual(new_data, data)
5936
5937        entries = image.GetEntries()
5938        self.assertIn('section', entries)
5939        entry = entries['section']
5940        self.assertEqual(len(new_data), entry.size)
5941
5942    def testReplaceSectionLarger(self):
5943        """Test replacing a simple section with larger data"""
5944        new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) + 1)
5945        data, expected_fdtmap, image = self._RunReplaceCmd('section',
5946            new_data, dts='241_replace_section_simple.dts')
5947        self.assertEqual(new_data, data)
5948
5949        entries = image.GetEntries()
5950        self.assertIn('section', entries)
5951        entry = entries['section']
5952        self.assertEqual(len(new_data), entry.size)
5953        fentry = entries['fdtmap']
5954        self.assertEqual(entry.offset + entry.size, fentry.offset)
5955
5956    def testReplaceSectionSmaller(self):
5957        """Test replacing a simple section with smaller data"""
5958        new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) - 1) + b'\0'
5959        data, expected_fdtmap, image = self._RunReplaceCmd('section',
5960            new_data, dts='241_replace_section_simple.dts')
5961        self.assertEqual(new_data, data)
5962
5963        # The new size is the same as the old, just with a pad byte at the end
5964        entries = image.GetEntries()
5965        self.assertIn('section', entries)
5966        entry = entries['section']
5967        self.assertEqual(len(new_data), entry.size)
5968
5969    def testReplaceSectionSmallerAllow(self):
5970        """Test failing to replace a simple section with smaller data"""
5971        new_data = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) - 1)
5972        try:
5973            state.SetAllowEntryContraction(True)
5974            with self.assertRaises(ValueError) as exc:
5975                self._RunReplaceCmd('section', new_data,
5976                                    dts='241_replace_section_simple.dts')
5977        finally:
5978            state.SetAllowEntryContraction(False)
5979
5980        # Since we have no information about the position of things within the
5981        # section, we cannot adjust the position of /section-u-boot so it ends
5982        # up outside the section
5983        self.assertIn(
5984            "Node '/section/u-boot': Offset 0x24 (36) size 0x4 (4) is outside "
5985            "the section '/section' starting at 0x0 (0) of size 0x27 (39)",
5986            str(exc.exception))
5987
5988    def testMkimageImagename(self):
5989        """Test using mkimage with -n holding the data too"""
5990        self._SetupSplElf()
5991        data = self._DoReadFile('242_mkimage_name.dts')
5992
5993        # Check that the data appears in the file somewhere
5994        self.assertIn(U_BOOT_SPL_DATA, data)
5995
5996        # Get struct legacy_img_hdr -> ih_name
5997        name = data[0x20:0x40]
5998
5999        # Build the filename that we expect to be placed in there, by virtue of
6000        # the -n paraameter
6001        expect = os.path.join(tools.get_output_dir(), 'mkimage.mkimage')
6002
6003        # Check that the image name is set to the temporary filename used
6004        self.assertEqual(expect.encode('utf-8')[:0x20], name)
6005
6006    def testMkimageImage(self):
6007        """Test using mkimage with -n holding the data too"""
6008        self._SetupSplElf()
6009        data = self._DoReadFile('243_mkimage_image.dts')
6010
6011        # Check that the data appears in the file somewhere
6012        self.assertIn(U_BOOT_SPL_DATA, data)
6013
6014        # Get struct legacy_img_hdr -> ih_name
6015        name = data[0x20:0x40]
6016
6017        # Build the filename that we expect to be placed in there, by virtue of
6018        # the -n paraameter
6019        expect = os.path.join(tools.get_output_dir(), 'mkimage-n.mkimage')
6020
6021        # Check that the image name is set to the temporary filename used
6022        self.assertEqual(expect.encode('utf-8')[:0x20], name)
6023
6024        # Check the corect data is in the imagename file
6025        self.assertEqual(U_BOOT_DATA, tools.read_file(expect))
6026
6027    def testMkimageImageNoContent(self):
6028        """Test using mkimage with -n and no data"""
6029        self._SetupSplElf()
6030        with self.assertRaises(ValueError) as exc:
6031            self._DoReadFile('244_mkimage_image_no_content.dts')
6032        self.assertIn('Could not complete processing of contents',
6033                      str(exc.exception))
6034
6035    def testMkimageImageBad(self):
6036        """Test using mkimage with imagename node and data-to-imagename"""
6037        self._SetupSplElf()
6038        with self.assertRaises(ValueError) as exc:
6039            self._DoReadFile('245_mkimage_image_bad.dts')
6040        self.assertIn('Cannot use both imagename node and data-to-imagename',
6041                      str(exc.exception))
6042
6043    def testCollectionOther(self):
6044        """Test a collection where the data comes from another section"""
6045        data = self._DoReadFile('246_collection_other.dts')
6046        self.assertEqual(U_BOOT_NODTB_DATA + U_BOOT_DTB_DATA +
6047                         tools.get_bytes(0xff, 2) + U_BOOT_NODTB_DATA +
6048                         tools.get_bytes(0xfe, 3) + U_BOOT_DTB_DATA,
6049                         data)
6050
6051    def testMkimageCollection(self):
6052        """Test using a collection referring to an entry in a mkimage entry"""
6053        self._SetupSplElf()
6054        data = self._DoReadFile('247_mkimage_coll.dts')
6055        expect = U_BOOT_SPL_DATA + U_BOOT_DATA
6056        self.assertEqual(expect, data[:len(expect)])
6057
6058    def testCompressDtbPrependInvalid(self):
6059        """Test that invalid header is detected"""
6060        with self.assertRaises(ValueError) as e:
6061            self._DoReadFileDtb('248_compress_dtb_prepend_invalid.dts')
6062        self.assertIn("Node '/binman/u-boot-dtb': Invalid prepend in "
6063                      "'u-boot-dtb': 'invalid'", str(e.exception))
6064
6065    def testCompressDtbPrependLength(self):
6066        """Test that compress with length header works as expected"""
6067        data = self._DoReadFileRealDtb('249_compress_dtb_prepend_length.dts')
6068        image = control.images['image']
6069        entries = image.GetEntries()
6070        self.assertIn('u-boot-dtb', entries)
6071        u_boot_dtb = entries['u-boot-dtb']
6072        self.assertIn('fdtmap', entries)
6073        fdtmap = entries['fdtmap']
6074
6075        image_fname = tools.get_output_filename('image.bin')
6076        orig = control.ReadEntry(image_fname, 'u-boot-dtb')
6077        dtb = fdt.Fdt.FromData(orig)
6078        dtb.Scan()
6079        props = self._GetPropTree(dtb, ['size', 'uncomp-size'])
6080        expected = {
6081            'u-boot:size': len(U_BOOT_DATA),
6082            'u-boot-dtb:uncomp-size': len(orig),
6083            'u-boot-dtb:size': u_boot_dtb.size,
6084            'fdtmap:size': fdtmap.size,
6085            'size': len(data),
6086            }
6087        self.assertEqual(expected, props)
6088
6089        # Check implementation
6090        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
6091        rest = data[len(U_BOOT_DATA):]
6092        comp_data_len = struct.unpack('<I', rest[:4])[0]
6093        comp_data = rest[4:4 + comp_data_len]
6094        orig2 = self._decompress(comp_data)
6095        self.assertEqual(orig, orig2)
6096
6097    def testInvalidCompress(self):
6098        """Test that invalid compress algorithm is detected"""
6099        with self.assertRaises(ValueError) as e:
6100            self._DoTestFile('250_compress_dtb_invalid.dts')
6101        self.assertIn("Unknown algorithm 'invalid'", str(e.exception))
6102
6103    def testCompUtilCompressions(self):
6104        """Test compression algorithms"""
6105        for bintool in self.comp_bintools.values():
6106            self._CheckBintool(bintool)
6107            data = bintool.compress(COMPRESS_DATA)
6108            self.assertNotEqual(COMPRESS_DATA, data)
6109            orig = bintool.decompress(data)
6110            self.assertEquals(COMPRESS_DATA, orig)
6111
6112    def testCompUtilVersions(self):
6113        """Test tool version of compression algorithms"""
6114        for bintool in self.comp_bintools.values():
6115            self._CheckBintool(bintool)
6116            version = bintool.version()
6117            self.assertRegex(version, '^v?[0-9]+[0-9.]*')
6118
6119    def testCompUtilPadding(self):
6120        """Test padding of compression algorithms"""
6121        # Skip zstd because it doesn't support padding
6122        for bintool in [v for k,v in self.comp_bintools.items() if k != 'zstd']:
6123            self._CheckBintool(bintool)
6124            data = bintool.compress(COMPRESS_DATA)
6125            self.assertNotEqual(COMPRESS_DATA, data)
6126            data += tools.get_bytes(0, 64)
6127            orig = bintool.decompress(data)
6128            self.assertEquals(COMPRESS_DATA, orig)
6129
6130    def testCompressDtbZstd(self):
6131        """Test that zstd compress of device-tree files failed"""
6132        with self.assertRaises(ValueError) as e:
6133            self._DoTestFile('251_compress_dtb_zstd.dts')
6134        self.assertIn("Node '/binman/u-boot-dtb': The zstd compression "
6135                      "requires a length header", str(e.exception))
6136
6137    def testMkimageMultipleDataFiles(self):
6138        """Test passing multiple files to mkimage in a mkimage entry"""
6139        self._SetupSplElf()
6140        self._SetupTplElf()
6141        data = self._DoReadFile('252_mkimage_mult_data.dts')
6142        # Size of files are packed in their 4B big-endian format
6143        expect = struct.pack('>I', len(U_BOOT_TPL_DATA))
6144        expect += struct.pack('>I', len(U_BOOT_SPL_DATA))
6145        # Size info is always followed by a 4B zero value.
6146        expect += tools.get_bytes(0, 4)
6147        expect += U_BOOT_TPL_DATA
6148        # All but last files are 4B-aligned
6149        align_pad = len(U_BOOT_TPL_DATA) % 4
6150        if align_pad:
6151            expect += tools.get_bytes(0, align_pad)
6152        expect += U_BOOT_SPL_DATA
6153        self.assertEqual(expect, data[-len(expect):])
6154
6155    def testMkimageMultipleExpanded(self):
6156        """Test passing multiple files to mkimage in a mkimage entry"""
6157        self._SetupSplElf()
6158        self._SetupTplElf()
6159        entry_args = {
6160            'spl-bss-pad': 'y',
6161            'spl-dtb': 'y',
6162        }
6163        data = self._DoReadFileDtb('252_mkimage_mult_data.dts',
6164                                   use_expanded=True, entry_args=entry_args)[0]
6165        pad_len = 10
6166        tpl_expect = U_BOOT_TPL_DATA
6167        spl_expect = U_BOOT_SPL_NODTB_DATA + tools.get_bytes(0, pad_len)
6168        spl_expect += U_BOOT_SPL_DTB_DATA
6169
6170        content = data[0x40:]
6171        lens = struct.unpack('>III', content[:12])
6172
6173        # Size of files are packed in their 4B big-endian format
6174        # Size info is always followed by a 4B zero value.
6175        self.assertEqual(len(tpl_expect), lens[0])
6176        self.assertEqual(len(spl_expect), lens[1])
6177        self.assertEqual(0, lens[2])
6178
6179        rest = content[12:]
6180        self.assertEqual(tpl_expect, rest[:len(tpl_expect)])
6181
6182        rest = rest[len(tpl_expect):]
6183        align_pad = len(tpl_expect) % 4
6184        self.assertEqual(tools.get_bytes(0, align_pad), rest[:align_pad])
6185        rest = rest[align_pad:]
6186        self.assertEqual(spl_expect, rest)
6187
6188    def testMkimageMultipleNoContent(self):
6189        """Test passing multiple data files to mkimage with one data file having no content"""
6190        self._SetupSplElf()
6191        with self.assertRaises(ValueError) as exc:
6192            self._DoReadFile('253_mkimage_mult_no_content.dts')
6193        self.assertIn('Could not complete processing of contents',
6194                      str(exc.exception))
6195
6196    def testMkimageFilename(self):
6197        """Test using mkimage to build a binary with a filename"""
6198        self._SetupSplElf()
6199        retcode = self._DoTestFile('254_mkimage_filename.dts')
6200        self.assertEqual(0, retcode)
6201        fname = tools.get_output_filename('mkimage-test.bin')
6202        self.assertTrue(os.path.exists(fname))
6203
6204    def testVpl(self):
6205        """Test that an image with VPL and its device tree can be created"""
6206        # ELF file with a '__bss_size' symbol
6207        self._SetupVplElf()
6208        data = self._DoReadFile('255_u_boot_vpl.dts')
6209        self.assertEqual(U_BOOT_VPL_DATA + U_BOOT_VPL_DTB_DATA, data)
6210
6211    def testVplNoDtb(self):
6212        """Test that an image with vpl/u-boot-vpl-nodtb.bin can be created"""
6213        self._SetupVplElf()
6214        data = self._DoReadFile('256_u_boot_vpl_nodtb.dts')
6215        self.assertEqual(U_BOOT_VPL_NODTB_DATA,
6216                         data[:len(U_BOOT_VPL_NODTB_DATA)])
6217
6218    def testExpandedVpl(self):
6219        """Test that an expanded entry type is selected for TPL when needed"""
6220        self._SetupVplElf()
6221
6222        entry_args = {
6223            'vpl-bss-pad': 'y',
6224            'vpl-dtb': 'y',
6225        }
6226        self._DoReadFileDtb('257_fdt_incl_vpl.dts', use_expanded=True,
6227                            entry_args=entry_args)
6228        image = control.images['image']
6229        entries = image.GetEntries()
6230        self.assertEqual(1, len(entries))
6231
6232        # We only have u-boot-vpl, which be expanded
6233        self.assertIn('u-boot-vpl', entries)
6234        entry = entries['u-boot-vpl']
6235        self.assertEqual('u-boot-vpl-expanded', entry.etype)
6236        subent = entry.GetEntries()
6237        self.assertEqual(3, len(subent))
6238        self.assertIn('u-boot-vpl-nodtb', subent)
6239        self.assertIn('u-boot-vpl-bss-pad', subent)
6240        self.assertIn('u-boot-vpl-dtb', subent)
6241
6242    def testVplBssPadMissing(self):
6243        """Test that a missing symbol is detected"""
6244        self._SetupVplElf('u_boot_ucode_ptr')
6245        with self.assertRaises(ValueError) as e:
6246            self._DoReadFile('258_vpl_bss_pad.dts')
6247        self.assertIn('Expected __bss_size symbol in vpl/u-boot-vpl',
6248                      str(e.exception))
6249
6250    def testSymlink(self):
6251        """Test that image files can be symlinked"""
6252        retcode = self._DoTestFile('259_symlink.dts', debug=True, map=True)
6253        self.assertEqual(0, retcode)
6254        image = control.images['test_image']
6255        fname = tools.get_output_filename('test_image.bin')
6256        sname = tools.get_output_filename('symlink_to_test.bin')
6257        self.assertTrue(os.path.islink(sname))
6258        self.assertEqual(os.readlink(sname), fname)
6259
6260    def testSymlinkOverwrite(self):
6261        """Test that symlinked images can be overwritten"""
6262        testdir = TestFunctional._MakeInputDir('symlinktest')
6263        self._DoTestFile('259_symlink.dts', debug=True, map=True, output_dir=testdir)
6264        # build the same image again in the same directory so that existing symlink is present
6265        self._DoTestFile('259_symlink.dts', debug=True, map=True, output_dir=testdir)
6266        fname = tools.get_output_filename('test_image.bin')
6267        sname = tools.get_output_filename('symlink_to_test.bin')
6268        self.assertTrue(os.path.islink(sname))
6269        self.assertEqual(os.readlink(sname), fname)
6270
6271    def testSymbolsElf(self):
6272        """Test binman can assign symbols embedded in an ELF file"""
6273        if not elf.ELF_TOOLS:
6274            self.skipTest('Python elftools not available')
6275        self._SetupTplElf('u_boot_binman_syms')
6276        self._SetupVplElf('u_boot_binman_syms')
6277        self._SetupSplElf('u_boot_binman_syms')
6278        data = self._DoReadFileDtb('260_symbols_elf.dts')[0]
6279        image_fname = tools.get_output_filename('image.bin')
6280
6281        image = control.images['image']
6282        entries = image.GetEntries()
6283
6284        for entry in entries.values():
6285            # No symbols in u-boot and it has faked contents anyway
6286            if entry.name == 'u-boot':
6287                continue
6288            edata = data[entry.image_pos:entry.image_pos + entry.size]
6289            efname = tools.get_output_filename(f'edata-{entry.name}')
6290            tools.write_file(efname, edata)
6291
6292            syms = elf.GetSymbolFileOffset(efname, ['_binman_u_boot'])
6293            re_name = re.compile('_binman_(u_boot_(.*))_prop_(.*)')
6294            for name, sym in syms.items():
6295                msg = 'test'
6296                val = elf.GetSymbolValue(sym, edata, msg)
6297                entry_m = re_name.match(name)
6298                if entry_m:
6299                    ename, prop = entry_m.group(1), entry_m.group(3)
6300                entry, entry_name, prop_name = image.LookupEntry(entries,
6301                                                                 name, msg)
6302                if prop_name == 'offset':
6303                    expect_val = entry.offset
6304                elif prop_name == 'image_pos':
6305                    expect_val = entry.image_pos
6306                elif prop_name == 'size':
6307                    expect_val = entry.size
6308                self.assertEqual(expect_val, val)
6309
6310    def testSymbolsElfBad(self):
6311        """Check error when trying to write symbols without the elftools lib"""
6312        if not elf.ELF_TOOLS:
6313            self.skipTest('Python elftools not available')
6314        self._SetupTplElf('u_boot_binman_syms')
6315        self._SetupVplElf('u_boot_binman_syms')
6316        self._SetupSplElf('u_boot_binman_syms')
6317        try:
6318            elf.ELF_TOOLS = False
6319            with self.assertRaises(ValueError) as exc:
6320                self._DoReadFileDtb('260_symbols_elf.dts')
6321        finally:
6322            elf.ELF_TOOLS = True
6323        self.assertIn(
6324            "Section '/binman': entry '/binman/u-boot-spl-elf': "
6325            'Cannot write symbols to an ELF file without Python elftools',
6326            str(exc.exception))
6327
6328    def testSectionFilename(self):
6329        """Check writing of section contents to a file"""
6330        data = self._DoReadFile('261_section_fname.dts')
6331        expected = (b'&&' + U_BOOT_DATA + b'&&&' +
6332                    tools.get_bytes(ord('!'), 7) +
6333                    U_BOOT_DATA + tools.get_bytes(ord('&'), 12))
6334        self.assertEqual(expected, data)
6335
6336        sect_fname = tools.get_output_filename('outfile.bin')
6337        self.assertTrue(os.path.exists(sect_fname))
6338        sect_data = tools.read_file(sect_fname)
6339        self.assertEqual(U_BOOT_DATA, sect_data)
6340
6341    def testAbsent(self):
6342        """Check handling of absent entries"""
6343        data = self._DoReadFile('262_absent.dts')
6344        self.assertEqual(U_BOOT_DATA + U_BOOT_IMG_DATA, data)
6345
6346    def testPackTeeOsOptional(self):
6347        """Test that an image with an optional TEE binary can be created"""
6348        entry_args = {
6349            'tee-os-path': 'tee.elf',
6350        }
6351        data = self._DoReadFileDtb('263_tee_os_opt.dts',
6352                                   entry_args=entry_args)[0]
6353        self.assertEqual(U_BOOT_DATA + U_BOOT_IMG_DATA, data)
6354
6355    def checkFitTee(self, dts, tee_fname):
6356        """Check that a tee-os entry works and returns data
6357
6358        Args:
6359            dts (str): Device tree filename to use
6360            tee_fname (str): filename containing tee-os
6361
6362        Returns:
6363            bytes: Image contents
6364        """
6365        if not elf.ELF_TOOLS:
6366            self.skipTest('Python elftools not available')
6367        entry_args = {
6368            'of-list': 'test-fdt1 test-fdt2',
6369            'default-dt': 'test-fdt2',
6370            'tee-os-path': tee_fname,
6371        }
6372        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
6373        data = self._DoReadFileDtb(dts, entry_args=entry_args,
6374                                   extra_indirs=[test_subdir])[0]
6375        return data
6376
6377    def testFitTeeOsOptionalFit(self):
6378        """Test an image with a FIT with an optional OP-TEE binary"""
6379        data = self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bin')
6380
6381        # There should be only one node, holding the data set up in SetUpClass()
6382        # for tee.bin
6383        dtb = fdt.Fdt.FromData(data)
6384        dtb.Scan()
6385        node = dtb.GetNode('/images/tee-1')
6386        self.assertEqual(TEE_ADDR,
6387                         fdt_util.fdt32_to_cpu(node.props['load'].value))
6388        self.assertEqual(TEE_ADDR,
6389                         fdt_util.fdt32_to_cpu(node.props['entry'].value))
6390        self.assertEqual(U_BOOT_DATA, node.props['data'].bytes)
6391
6392        with test_util.capture_sys_output() as (stdout, stderr):
6393            self.checkFitTee('264_tee_os_opt_fit.dts', '')
6394        err = stderr.getvalue()
6395        self.assertRegex(
6396            err,
6397            "Image '.*' is missing optional external blobs but is still functional: tee-os")
6398
6399    def testFitTeeOsOptionalFitBad(self):
6400        """Test an image with a FIT with an optional OP-TEE binary"""
6401        with self.assertRaises(ValueError) as exc:
6402            self.checkFitTee('265_tee_os_opt_fit_bad.dts', 'tee.bin')
6403        self.assertIn(
6404            "Node '/binman/fit': subnode 'images/@tee-SEQ': Failed to read ELF file: Magic number does not match",
6405            str(exc.exception))
6406
6407    def testFitTeeOsBad(self):
6408        """Test an OP-TEE binary with wrong formats"""
6409        self.make_tee_bin('tee.bad1', 123)
6410        with self.assertRaises(ValueError) as exc:
6411            self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bad1')
6412        self.assertIn(
6413            "Node '/binman/fit/images/@tee-SEQ/tee-os': OP-TEE paged mode not supported",
6414            str(exc.exception))
6415
6416        self.make_tee_bin('tee.bad2', 0, b'extra data')
6417        with self.assertRaises(ValueError) as exc:
6418            self.checkFitTee('264_tee_os_opt_fit.dts', 'tee.bad2')
6419        self.assertIn(
6420            "Node '/binman/fit/images/@tee-SEQ/tee-os': Invalid OP-TEE file: size mismatch (expected 0x4, have 0xe)",
6421            str(exc.exception))
6422
6423    def testExtblobOptional(self):
6424        """Test an image with an external blob that is optional"""
6425        with test_util.capture_sys_output() as (stdout, stderr):
6426            data = self._DoReadFile('266_blob_ext_opt.dts')
6427        self.assertEqual(REFCODE_DATA, data)
6428        err = stderr.getvalue()
6429        self.assertRegex(
6430            err,
6431            "Image '.*' is missing optional external blobs but is still functional: missing")
6432
6433    def testSectionInner(self):
6434        """Test an inner section with a size"""
6435        data = self._DoReadFile('267_section_inner.dts')
6436        expected = U_BOOT_DATA + tools.get_bytes(0, 12)
6437        self.assertEqual(expected, data)
6438
6439    def testNull(self):
6440        """Test an image with a null entry"""
6441        data = self._DoReadFile('268_null.dts')
6442        self.assertEqual(U_BOOT_DATA + b'\xff\xff\xff\xff' + U_BOOT_IMG_DATA, data)
6443
6444    def testOverlap(self):
6445        """Test an image with a overlapping entry"""
6446        data = self._DoReadFile('269_overlap.dts')
6447        self.assertEqual(U_BOOT_DATA[:1] + b'aa' + U_BOOT_DATA[3:], data)
6448
6449        image = control.images['image']
6450        entries = image.GetEntries()
6451
6452        self.assertIn('inset', entries)
6453        inset = entries['inset']
6454        self.assertEqual(1, inset.offset);
6455        self.assertEqual(1, inset.image_pos);
6456        self.assertEqual(2, inset.size);
6457
6458    def testOverlapNull(self):
6459        """Test an image with a null overlap"""
6460        data = self._DoReadFile('270_overlap_null.dts')
6461        self.assertEqual(U_BOOT_DATA, data[:len(U_BOOT_DATA)])
6462
6463        # Check the FMAP
6464        fhdr, fentries = fmap_util.DecodeFmap(data[len(U_BOOT_DATA):])
6465        self.assertEqual(4, fhdr.nareas)
6466        fiter = iter(fentries)
6467
6468        fentry = next(fiter)
6469        self.assertEqual(b'SECTION', fentry.name)
6470        self.assertEqual(0, fentry.offset)
6471        self.assertEqual(len(U_BOOT_DATA), fentry.size)
6472        self.assertEqual(0, fentry.flags)
6473
6474        fentry = next(fiter)
6475        self.assertEqual(b'U_BOOT', fentry.name)
6476        self.assertEqual(0, fentry.offset)
6477        self.assertEqual(len(U_BOOT_DATA), fentry.size)
6478        self.assertEqual(0, fentry.flags)
6479
6480        # Make sure that the NULL entry appears in the FMAP
6481        fentry = next(fiter)
6482        self.assertEqual(b'NULL', fentry.name)
6483        self.assertEqual(1, fentry.offset)
6484        self.assertEqual(2, fentry.size)
6485        self.assertEqual(0, fentry.flags)
6486
6487        fentry = next(fiter)
6488        self.assertEqual(b'FMAP', fentry.name)
6489        self.assertEqual(len(U_BOOT_DATA), fentry.offset)
6490
6491    def testOverlapBad(self):
6492        """Test an image with a bad overlapping entry"""
6493        with self.assertRaises(ValueError) as exc:
6494            self._DoReadFile('271_overlap_bad.dts')
6495        self.assertIn(
6496            "Node '/binman/inset': Offset 0x10 (16) ending at 0x12 (18) must overlap with existing entries",
6497            str(exc.exception))
6498
6499    def testOverlapNoOffset(self):
6500        """Test an image with a bad overlapping entry"""
6501        with self.assertRaises(ValueError) as exc:
6502            self._DoReadFile('272_overlap_no_size.dts')
6503        self.assertIn(
6504            "Node '/binman/inset': 'fill' entry is missing properties: size",
6505            str(exc.exception))
6506
6507    def testBlobSymbol(self):
6508        """Test a blob with symbols read from an ELF file"""
6509        elf_fname = self.ElfTestFile('blob_syms')
6510        TestFunctional._MakeInputFile('blob_syms', tools.read_file(elf_fname))
6511        TestFunctional._MakeInputFile('blob_syms.bin',
6512            tools.read_file(self.ElfTestFile('blob_syms.bin')))
6513
6514        data = self._DoReadFile('273_blob_symbol.dts')
6515
6516        syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
6517        addr = elf.GetSymbolAddress(elf_fname, '__my_start_sym')
6518        self.assertEqual(syms['_binman_sym_magic'].address, addr)
6519        self.assertEqual(syms['_binman_inset_prop_offset'].address, addr + 4)
6520        self.assertEqual(syms['_binman_inset_prop_size'].address, addr + 8)
6521
6522        sym_values = struct.pack('<LLL', elf.BINMAN_SYM_MAGIC_VALUE, 4, 8)
6523        expected = sym_values
6524        self.assertEqual(expected, data[:len(expected)])
6525
6526    def testOffsetFromElf(self):
6527        """Test a blob with symbols read from an ELF file"""
6528        elf_fname = self.ElfTestFile('blob_syms')
6529        TestFunctional._MakeInputFile('blob_syms', tools.read_file(elf_fname))
6530        TestFunctional._MakeInputFile('blob_syms.bin',
6531            tools.read_file(self.ElfTestFile('blob_syms.bin')))
6532
6533        data = self._DoReadFile('274_offset_from_elf.dts')
6534
6535        syms = elf.GetSymbols(elf_fname, ['binman', 'image'])
6536        base = elf.GetSymbolAddress(elf_fname, '__my_start_sym')
6537
6538        image = control.images['image']
6539        entries = image.GetEntries()
6540
6541        self.assertIn('inset', entries)
6542        inset = entries['inset']
6543
6544        self.assertEqual(base + 4, inset.offset);
6545        self.assertEqual(base + 4, inset.image_pos);
6546        self.assertEqual(4, inset.size);
6547
6548        self.assertIn('inset2', entries)
6549        inset = entries['inset2']
6550        self.assertEqual(base + 8, inset.offset);
6551        self.assertEqual(base + 8, inset.image_pos);
6552        self.assertEqual(4, inset.size);
6553
6554    def testFitAlign(self):
6555        """Test an image with an FIT with aligned external data"""
6556        data = self._DoReadFile('275_fit_align.dts')
6557        self.assertEqual(4096, len(data))
6558
6559        dtb = fdt.Fdt.FromData(data)
6560        dtb.Scan()
6561
6562        props = self._GetPropTree(dtb, ['data-position'])
6563        expected = {
6564            'u-boot:data-position': 1024,
6565            'fdt-1:data-position': 2048,
6566            'fdt-2:data-position': 3072,
6567        }
6568        self.assertEqual(expected, props)
6569
6570    def testFitFirmwareLoadables(self):
6571        """Test an image with an FIT that use fit,firmware"""
6572        if not elf.ELF_TOOLS:
6573            self.skipTest('Python elftools not available')
6574        entry_args = {
6575            'of-list': 'test-fdt1',
6576            'default-dt': 'test-fdt1',
6577            'atf-bl31-path': 'bl31.elf',
6578            'tee-os-path': 'missing.bin',
6579        }
6580        test_subdir = os.path.join(self._indir, TEST_FDT_SUBDIR)
6581        with test_util.capture_sys_output() as (stdout, stderr):
6582            data = self._DoReadFileDtb(
6583                '276_fit_firmware_loadables.dts',
6584                entry_args=entry_args,
6585                extra_indirs=[test_subdir])[0]
6586
6587        dtb = fdt.Fdt.FromData(data)
6588        dtb.Scan()
6589
6590        node = dtb.GetNode('/configurations/conf-uboot-1')
6591        self.assertEqual('u-boot', node.props['firmware'].value)
6592        self.assertEqual(['atf-1', 'atf-2'],
6593                         fdt_util.GetStringList(node, 'loadables'))
6594
6595        node = dtb.GetNode('/configurations/conf-atf-1')
6596        self.assertEqual('atf-1', node.props['firmware'].value)
6597        self.assertEqual(['u-boot', 'atf-2'],
6598                         fdt_util.GetStringList(node, 'loadables'))
6599
6600        node = dtb.GetNode('/configurations/conf-missing-uboot-1')
6601        self.assertEqual('u-boot', node.props['firmware'].value)
6602        self.assertEqual(['atf-1', 'atf-2'],
6603                         fdt_util.GetStringList(node, 'loadables'))
6604
6605        node = dtb.GetNode('/configurations/conf-missing-atf-1')
6606        self.assertEqual('atf-1', node.props['firmware'].value)
6607        self.assertEqual(['u-boot', 'atf-2'],
6608                         fdt_util.GetStringList(node, 'loadables'))
6609
6610        node = dtb.GetNode('/configurations/conf-missing-tee-1')
6611        self.assertEqual('atf-1', node.props['firmware'].value)
6612        self.assertEqual(['u-boot', 'atf-2'],
6613                         fdt_util.GetStringList(node, 'loadables'))
6614
6615    def testTooldir(self):
6616        """Test that we can specify the tooldir"""
6617        with test_util.capture_sys_output() as (stdout, stderr):
6618            self.assertEqual(0, self._DoBinman('--tooldir', 'fred',
6619                                               'tool', '-l'))
6620        self.assertEqual('fred', bintool.Bintool.tooldir)
6621
6622        # Check that the toolpath is updated correctly
6623        self.assertEqual(['fred'], tools.tool_search_paths)
6624
6625        # Try with a few toolpaths; the tooldir should be at the end
6626        with test_util.capture_sys_output() as (stdout, stderr):
6627            self.assertEqual(0, self._DoBinman(
6628                '--toolpath', 'mary', '--toolpath', 'anna', '--tooldir', 'fred',
6629                'tool', '-l'))
6630        self.assertEqual(['mary', 'anna', 'fred'], tools.tool_search_paths)
6631
6632    def testReplaceSectionEntry(self):
6633        """Test replacing an entry in a section"""
6634        expect_data = b'w' * len(U_BOOT_DATA + COMPRESS_DATA)
6635        entry_data, expected_fdtmap, image = self._RunReplaceCmd('section/blob',
6636            expect_data, dts='241_replace_section_simple.dts')
6637        self.assertEqual(expect_data, entry_data)
6638
6639        entries = image.GetEntries()
6640        self.assertIn('section', entries)
6641        section = entries['section']
6642
6643        sect_entries = section.GetEntries()
6644        self.assertIn('blob', sect_entries)
6645        entry = sect_entries['blob']
6646        self.assertEqual(len(expect_data), entry.size)
6647
6648        fname = tools.get_output_filename('image-updated.bin')
6649        data = tools.read_file(fname)
6650
6651        new_blob_data = data[entry.image_pos:entry.image_pos + len(expect_data)]
6652        self.assertEqual(expect_data, new_blob_data)
6653
6654        self.assertEqual(U_BOOT_DATA,
6655                         data[entry.image_pos + len(expect_data):]
6656                         [:len(U_BOOT_DATA)])
6657
6658    def testReplaceSectionDeep(self):
6659        """Test replacing an entry in two levels of sections"""
6660        expect_data = b'w' * len(U_BOOT_DATA + COMPRESS_DATA)
6661        entry_data, expected_fdtmap, image = self._RunReplaceCmd(
6662            'section/section/blob', expect_data,
6663            dts='278_replace_section_deep.dts')
6664        self.assertEqual(expect_data, entry_data)
6665
6666        entries = image.GetEntries()
6667        self.assertIn('section', entries)
6668        section = entries['section']
6669
6670        subentries = section.GetEntries()
6671        self.assertIn('section', subentries)
6672        section = subentries['section']
6673
6674        sect_entries = section.GetEntries()
6675        self.assertIn('blob', sect_entries)
6676        entry = sect_entries['blob']
6677        self.assertEqual(len(expect_data), entry.size)
6678
6679        fname = tools.get_output_filename('image-updated.bin')
6680        data = tools.read_file(fname)
6681
6682        new_blob_data = data[entry.image_pos:entry.image_pos + len(expect_data)]
6683        self.assertEqual(expect_data, new_blob_data)
6684
6685        self.assertEqual(U_BOOT_DATA,
6686                         data[entry.image_pos + len(expect_data):]
6687                         [:len(U_BOOT_DATA)])
6688
6689    def testReplaceFitSibling(self):
6690        """Test an image with a FIT inside where we replace its sibling"""
6691        self._SetupSplElf()
6692        fname = TestFunctional._MakeInputFile('once', b'available once')
6693        self._DoReadFileRealDtb('277_replace_fit_sibling.dts')
6694        os.remove(fname)
6695
6696        try:
6697            tmpdir, updated_fname = self._SetupImageInTmpdir()
6698
6699            fname = os.path.join(tmpdir, 'update-blob')
6700            expected = b'w' * (len(COMPRESS_DATA + U_BOOT_DATA) + 1)
6701            tools.write_file(fname, expected)
6702
6703            self._DoBinman('replace', '-i', updated_fname, 'blob', '-f', fname)
6704            data = tools.read_file(updated_fname)
6705            start = len(U_BOOT_DTB_DATA)
6706            self.assertEqual(expected, data[start:start + len(expected)])
6707            map_fname = os.path.join(tmpdir, 'image-updated.map')
6708            self.assertFalse(os.path.exists(map_fname))
6709        finally:
6710            shutil.rmtree(tmpdir)
6711
6712    def testX509Cert(self):
6713        """Test creating an X509 certificate"""
6714        keyfile = self.TestFile('key.key')
6715        entry_args = {
6716            'keyfile': keyfile,
6717        }
6718        data = self._DoReadFileDtb('279_x509_cert.dts',
6719                                   entry_args=entry_args)[0]
6720        cert = data[:-4]
6721        self.assertEqual(U_BOOT_DATA, data[-4:])
6722
6723        # TODO: verify the signature
6724
6725    def testX509CertMissing(self):
6726        """Test that binman still produces an image if openssl is missing"""
6727        keyfile = self.TestFile('key.key')
6728        entry_args = {
6729            'keyfile': 'keyfile',
6730        }
6731        with test_util.capture_sys_output() as (_, stderr):
6732            self._DoTestFile('279_x509_cert.dts',
6733                             force_missing_bintools='openssl',
6734                             entry_args=entry_args)
6735        err = stderr.getvalue()
6736        self.assertRegex(err, "Image 'image'.*missing bintools.*: openssl")
6737
6738    def testPackRockchipTpl(self):
6739        """Test that an image with a Rockchip TPL binary can be created"""
6740        data = self._DoReadFile('291_rockchip_tpl.dts')
6741        self.assertEqual(ROCKCHIP_TPL_DATA, data[:len(ROCKCHIP_TPL_DATA)])
6742
6743    def testMkimageMissingBlobMultiple(self):
6744        """Test missing blob with mkimage entry and multiple-data-files"""
6745        with test_util.capture_sys_output() as (stdout, stderr):
6746            self._DoTestFile('292_mkimage_missing_multiple.dts', allow_missing=True)
6747        err = stderr.getvalue()
6748        self.assertIn("is missing external blobs and is non-functional", err)
6749
6750        with self.assertRaises(ValueError) as e:
6751            self._DoTestFile('292_mkimage_missing_multiple.dts', allow_missing=False)
6752        self.assertIn("not found in input path", str(e.exception))
6753
6754    def _PrepareSignEnv(self, dts='280_fit_sign.dts'):
6755        """Prepare sign environment
6756
6757        Create private and public keys, add pubkey into dtb.
6758
6759        Returns:
6760            Tuple:
6761                FIT container
6762                Image name
6763                Private key
6764                DTB
6765        """
6766        self._SetupSplElf()
6767        data = self._DoReadFileRealDtb(dts)
6768        updated_fname = tools.get_output_filename('image-updated.bin')
6769        tools.write_file(updated_fname, data)
6770        dtb = tools.get_output_filename('source.dtb')
6771        private_key = tools.get_output_filename('test_key.key')
6772        public_key = tools.get_output_filename('test_key.crt')
6773        fit = tools.get_output_filename('fit.fit')
6774        key_dir = tools.get_output_dir()
6775
6776        tools.run('openssl', 'req', '-batch' , '-newkey', 'rsa:4096',
6777                  '-sha256', '-new',  '-nodes',  '-x509', '-keyout',
6778                  private_key, '-out', public_key)
6779        tools.run('fdt_add_pubkey', '-a', 'sha256,rsa4096', '-k', key_dir,
6780                  '-n', 'test_key', '-r', 'conf', dtb)
6781
6782        return fit, updated_fname, private_key, dtb
6783
6784    def testSignSimple(self):
6785        """Test that a FIT container can be signed in image"""
6786        is_signed = False
6787        fit, fname, private_key, dtb = self._PrepareSignEnv()
6788
6789        # do sign with private key
6790        control.SignEntries(fname, None, private_key, 'sha256,rsa4096',
6791                            ['fit'])
6792        is_signed = self._CheckSign(fit, dtb)
6793
6794        self.assertEqual(is_signed, True)
6795
6796    def testSignExactFIT(self):
6797        """Test that a FIT container can be signed and replaced in image"""
6798        is_signed = False
6799        fit, fname, private_key, dtb = self._PrepareSignEnv()
6800
6801        # Make sure we propagate the toolpath, since mkimage may not be on PATH
6802        args = []
6803        if self.toolpath:
6804            for path in self.toolpath:
6805                args += ['--toolpath', path]
6806
6807        # do sign with private key
6808        self._DoBinman(*args, 'sign', '-i', fname, '-k', private_key, '-a',
6809                       'sha256,rsa4096', '-f', fit, 'fit')
6810        is_signed = self._CheckSign(fit, dtb)
6811
6812        self.assertEqual(is_signed, True)
6813
6814    def testSignNonFit(self):
6815        """Test a non-FIT entry cannot be signed"""
6816        is_signed = False
6817        fit, fname, private_key, _ = self._PrepareSignEnv(
6818            '281_sign_non_fit.dts')
6819
6820        # do sign with private key
6821        with self.assertRaises(ValueError) as e:
6822            self._DoBinman('sign', '-i', fname, '-k', private_key, '-a',
6823                       'sha256,rsa4096', '-f', fit, 'u-boot')
6824        self.assertIn(
6825            "Node '/u-boot': Updating signatures is not supported with this entry type",
6826            str(e.exception))
6827
6828    def testSignMissingMkimage(self):
6829        """Test that FIT signing handles a missing mkimage tool"""
6830        fit, fname, private_key, _ = self._PrepareSignEnv()
6831
6832        # try to sign with a missing mkimage tool
6833        bintool.Bintool.set_missing_list(['mkimage'])
6834        with self.assertRaises(ValueError) as e:
6835            control.SignEntries(fname, None, private_key, 'sha256,rsa4096',
6836                                ['fit'])
6837        self.assertIn("Node '/fit': Missing tool: 'mkimage'", str(e.exception))
6838
6839    def testSymbolNoWrite(self):
6840        """Test disabling of symbol writing"""
6841        self._SetupSplElf()
6842        self.checkSymbols('282_symbols_disable.dts', U_BOOT_SPL_DATA, 0x1c,
6843                          no_write_symbols=True)
6844
6845    def testSymbolNoWriteExpanded(self):
6846        """Test disabling of symbol writing in expanded entries"""
6847        entry_args = {
6848            'spl-dtb': '1',
6849        }
6850        self.checkSymbols('282_symbols_disable.dts', U_BOOT_SPL_NODTB_DATA +
6851                          U_BOOT_SPL_DTB_DATA, 0x38,
6852                          entry_args=entry_args, use_expanded=True,
6853                          no_write_symbols=True)
6854
6855    def testMkimageSpecial(self):
6856        """Test mkimage ignores special hash-1 node"""
6857        data = self._DoReadFile('283_mkimage_special.dts')
6858
6859        # Just check that the data appears in the file somewhere
6860        self.assertIn(U_BOOT_DATA, data)
6861
6862    def testFitFdtList(self):
6863        """Test an image with an FIT with the fit,fdt-list-val option"""
6864        entry_args = {
6865            'default-dt': 'test-fdt2',
6866        }
6867        data = self._DoReadFileDtb(
6868            '284_fit_fdt_list.dts',
6869            entry_args=entry_args,
6870            extra_indirs=[os.path.join(self._indir, TEST_FDT_SUBDIR)])[0]
6871        self.assertEqual(U_BOOT_NODTB_DATA, data[-len(U_BOOT_NODTB_DATA):])
6872        fit_data = data[len(U_BOOT_DATA):-len(U_BOOT_NODTB_DATA)]
6873
6874    def testSplEmptyBss(self):
6875        """Test an expanded SPL with a zero-size BSS"""
6876        # ELF file with a '__bss_size' symbol
6877        self._SetupSplElf(src_fname='bss_data_zero')
6878
6879        entry_args = {
6880            'spl-bss-pad': 'y',
6881            'spl-dtb': 'y',
6882        }
6883        data = self._DoReadFileDtb('285_spl_expand.dts',
6884                                   use_expanded=True, entry_args=entry_args)[0]
6885
6886    def testTemplate(self):
6887        """Test using a template"""
6888        TestFunctional._MakeInputFile('vga2.bin', b'#' + VGA_DATA)
6889        data = self._DoReadFile('286_template.dts')
6890        first = U_BOOT_DATA + VGA_DATA + U_BOOT_DTB_DATA
6891        second = U_BOOT_DATA + b'#' + VGA_DATA + U_BOOT_DTB_DATA
6892        self.assertEqual(U_BOOT_IMG_DATA + first + second, data)
6893
6894        dtb_fname1 = tools.get_output_filename('u-boot.dtb.tmpl1')
6895        self.assertTrue(os.path.exists(dtb_fname1))
6896        dtb = fdt.Fdt.FromData(tools.read_file(dtb_fname1))
6897        dtb.Scan()
6898        node1 = dtb.GetNode('/binman/template')
6899        self.assertTrue(node1)
6900        vga = dtb.GetNode('/binman/first/intel-vga')
6901        self.assertTrue(vga)
6902
6903        dtb_fname2 = tools.get_output_filename('u-boot.dtb.tmpl2')
6904        self.assertTrue(os.path.exists(dtb_fname2))
6905        dtb2 = fdt.Fdt.FromData(tools.read_file(dtb_fname2))
6906        dtb2.Scan()
6907        node2 = dtb2.GetNode('/binman/template')
6908        self.assertFalse(node2)
6909
6910    def testTemplateBlobMulti(self):
6911        """Test using a template with 'multiple-images' enabled"""
6912        TestFunctional._MakeInputFile('my-blob.bin', b'blob')
6913        TestFunctional._MakeInputFile('my-blob2.bin', b'other')
6914        retcode = self._DoTestFile('287_template_multi.dts')
6915
6916        self.assertEqual(0, retcode)
6917        image = control.images['image']
6918        image_fname = tools.get_output_filename('my-image.bin')
6919        data = tools.read_file(image_fname)
6920        self.assertEqual(b'blob@@@@other', data)
6921
6922    def testTemplateFit(self):
6923        """Test using a template in a FIT"""
6924        fit_data = self._DoReadFile('288_template_fit.dts')
6925        fname = os.path.join(self._indir, 'fit_data.fit')
6926        tools.write_file(fname, fit_data)
6927        out = tools.run('dumpimage', '-l', fname)
6928
6929    def testTemplateSection(self):
6930        """Test using a template in a section (not at top level)"""
6931        TestFunctional._MakeInputFile('vga2.bin', b'#' + VGA_DATA)
6932        data = self._DoReadFile('289_template_section.dts')
6933        first = U_BOOT_DATA + VGA_DATA + U_BOOT_DTB_DATA
6934        second = U_BOOT_DATA + b'#' + VGA_DATA + U_BOOT_DTB_DATA
6935        self.assertEqual(U_BOOT_IMG_DATA + first + second + first, data)
6936
6937    def testMkimageSymbols(self):
6938        """Test using mkimage to build an image with symbols in it"""
6939        self._SetupSplElf('u_boot_binman_syms')
6940        data = self._DoReadFile('290_mkimage_sym.dts')
6941
6942        image = control.images['image']
6943        entries = image.GetEntries()
6944        self.assertIn('u-boot', entries)
6945        u_boot = entries['u-boot']
6946
6947        mkim = entries['mkimage']
6948        mkim_entries = mkim.GetEntries()
6949        self.assertIn('u-boot-spl', mkim_entries)
6950        spl = mkim_entries['u-boot-spl']
6951        self.assertIn('u-boot-spl2', mkim_entries)
6952        spl2 = mkim_entries['u-boot-spl2']
6953
6954        # skip the mkimage header and the area sizes
6955        mk_data = data[mkim.offset + 0x40:]
6956        size, term = struct.unpack('>LL', mk_data[:8])
6957
6958        # There should be only one image, so check that the zero terminator is
6959        # present
6960        self.assertEqual(0, term)
6961
6962        content = mk_data[8:8 + size]
6963
6964        # The image should contain the symbols from u_boot_binman_syms.c
6965        # Note that image_pos is adjusted by the base address of the image,
6966        # which is 0x10 in our test image
6967        spl_data = content[:0x18]
6968        content = content[0x1b:]
6969
6970        # After the header is a table of offsets for each image. There should
6971        # only be one image, then a 0 terminator, so figure out the real start
6972        # of the image data
6973        base = 0x40 + 8
6974
6975        # Check symbols in both u-boot-spl and u-boot-spl2
6976        for i in range(2):
6977            vals = struct.unpack('<LLQLL', spl_data)
6978
6979            # The image should contain the symbols from u_boot_binman_syms.c
6980            # Note that image_pos is adjusted by the base address of the image,
6981            # which is 0x10 in our 'u_boot_binman_syms' test image
6982            self.assertEqual(elf.BINMAN_SYM_MAGIC_VALUE, vals[0])
6983            self.assertEqual(base, vals[1])
6984            self.assertEqual(spl2.offset, vals[2])
6985            # figure out the internal positions of its components
6986            self.assertEqual(0x10 + u_boot.image_pos, vals[3])
6987
6988            # Check that spl and spl2 are actually at the indicated positions
6989            self.assertEqual(
6990                elf.BINMAN_SYM_MAGIC_VALUE,
6991                struct.unpack('<I', data[spl.image_pos:spl.image_pos + 4])[0])
6992            self.assertEqual(
6993                elf.BINMAN_SYM_MAGIC_VALUE,
6994                struct.unpack('<I', data[spl2.image_pos:spl2.image_pos + 4])[0])
6995
6996            self.assertEqual(len(U_BOOT_DATA), vals[4])
6997
6998            # Move to next
6999            spl_data = content[:0x18]
7000
7001    def testTemplatePhandle(self):
7002        """Test using a template in a node containing a phandle"""
7003        entry_args = {
7004            'atf-bl31-path': 'bl31.elf',
7005        }
7006        data = self._DoReadFileDtb('309_template_phandle.dts',
7007                                   entry_args=entry_args)
7008        fname = tools.get_output_filename('image.bin')
7009        out = tools.run('dumpimage', '-l', fname)
7010
7011        # We should see the FIT description and one for each of the two images
7012        lines = out.splitlines()
7013        descs = [line.split()[-1] for line in lines if 'escription' in line]
7014        self.assertEqual(['test-desc', 'atf', 'fdt'], descs)
7015
7016    def testTemplatePhandleDup(self):
7017        """Test using a template in a node containing a phandle"""
7018        entry_args = {
7019            'atf-bl31-path': 'bl31.elf',
7020        }
7021        with self.assertRaises(ValueError) as e:
7022            self._DoReadFileDtb('310_template_phandle_dup.dts',
7023                                entry_args=entry_args)
7024        self.assertIn(
7025            'Duplicate phandle 1 in nodes /binman/image/fit/images/atf/atf-bl31 and /binman/image-2/fit/images/atf/atf-bl31',
7026            str(e.exception))
7027
7028    def testTIBoardConfig(self):
7029        """Test that a schema validated board config file can be generated"""
7030        data = self._DoReadFile('293_ti_board_cfg.dts')
7031        self.assertEqual(TI_BOARD_CONFIG_DATA, data)
7032
7033    def testTIBoardConfigLint(self):
7034        """Test that an incorrectly linted config file would generate error"""
7035        with self.assertRaises(ValueError) as e:
7036            data = self._DoReadFile('323_ti_board_cfg_phony.dts')
7037        self.assertIn("Yamllint error", str(e.exception))
7038
7039    def testTIBoardConfigCombined(self):
7040        """Test that a schema validated combined board config file can be generated"""
7041        data = self._DoReadFile('294_ti_board_cfg_combined.dts')
7042        configlen_noheader = TI_BOARD_CONFIG_DATA * 4
7043        self.assertGreater(data, configlen_noheader)
7044
7045    def testTIBoardConfigNoDataType(self):
7046        """Test that error is thrown when data type is not supported"""
7047        with self.assertRaises(ValueError) as e:
7048            data = self._DoReadFile('295_ti_board_cfg_no_type.dts')
7049        self.assertIn("Schema validation error", str(e.exception))
7050
7051    def testPackTiSecure(self):
7052        """Test that an image with a TI secured binary can be created"""
7053        keyfile = self.TestFile('key.key')
7054        entry_args = {
7055            'keyfile': keyfile,
7056        }
7057        data = self._DoReadFileDtb('296_ti_secure.dts',
7058                                   entry_args=entry_args)[0]
7059        self.assertGreater(len(data), len(TI_UNSECURE_DATA))
7060
7061    def testPackTiSecureFirewall(self):
7062        """Test that an image with a TI secured binary can be created"""
7063        keyfile = self.TestFile('key.key')
7064        entry_args = {
7065            'keyfile': keyfile,
7066        }
7067        data_no_firewall = self._DoReadFileDtb('296_ti_secure.dts',
7068                                   entry_args=entry_args)[0]
7069        data_firewall = self._DoReadFileDtb('324_ti_secure_firewall.dts',
7070                                   entry_args=entry_args)[0]
7071        self.assertGreater(len(data_firewall),len(data_no_firewall))
7072
7073    def testPackTiSecureFirewallMissingProperty(self):
7074        """Test that an image with a TI secured binary can be created"""
7075        keyfile = self.TestFile('key.key')
7076        entry_args = {
7077            'keyfile': keyfile,
7078        }
7079        with self.assertRaises(ValueError) as e:
7080            data_firewall = self._DoReadFileDtb('325_ti_secure_firewall_missing_property.dts',
7081                                       entry_args=entry_args)[0]
7082        self.assertRegex(str(e.exception), "Node '/binman/ti-secure': Subnode 'firewall-0-2' is missing properties: id,region")
7083
7084    def testPackTiSecureMissingTool(self):
7085        """Test that an image with a TI secured binary (non-functional) can be created
7086        when openssl is missing"""
7087        keyfile = self.TestFile('key.key')
7088        entry_args = {
7089            'keyfile': keyfile,
7090        }
7091        with test_util.capture_sys_output() as (_, stderr):
7092            self._DoTestFile('296_ti_secure.dts',
7093                             force_missing_bintools='openssl',
7094                             entry_args=entry_args)
7095        err = stderr.getvalue()
7096        self.assertRegex(err, "Image 'image'.*missing bintools.*: openssl")
7097
7098    def testPackTiSecureROM(self):
7099        """Test that a ROM image with a TI secured binary can be created"""
7100        keyfile = self.TestFile('key.key')
7101        entry_args = {
7102            'keyfile': keyfile,
7103        }
7104        data = self._DoReadFileDtb('297_ti_secure_rom.dts',
7105                                entry_args=entry_args)[0]
7106        data_a = self._DoReadFileDtb('299_ti_secure_rom_a.dts',
7107                                entry_args=entry_args)[0]
7108        data_b = self._DoReadFileDtb('300_ti_secure_rom_b.dts',
7109                                entry_args=entry_args)[0]
7110        self.assertGreater(len(data), len(TI_UNSECURE_DATA))
7111        self.assertGreater(len(data_a), len(TI_UNSECURE_DATA))
7112        self.assertGreater(len(data_b), len(TI_UNSECURE_DATA))
7113
7114    def testPackTiSecureROMCombined(self):
7115        """Test that a ROM image with a TI secured binary can be created"""
7116        keyfile = self.TestFile('key.key')
7117        entry_args = {
7118            'keyfile': keyfile,
7119        }
7120        data = self._DoReadFileDtb('298_ti_secure_rom_combined.dts',
7121                                entry_args=entry_args)[0]
7122        self.assertGreater(len(data), len(TI_UNSECURE_DATA))
7123
7124    def testEncryptedNoAlgo(self):
7125        """Test encrypted node with missing required properties"""
7126        with self.assertRaises(ValueError) as e:
7127            self._DoReadFileDtb('301_encrypted_no_algo.dts')
7128        self.assertIn(
7129            "Node '/binman/fit/images/u-boot/encrypted': 'encrypted' entry is missing properties: algo iv-filename",
7130            str(e.exception))
7131
7132    def testEncryptedInvalidIvfile(self):
7133        """Test encrypted node with invalid iv file"""
7134        with self.assertRaises(ValueError) as e:
7135            self._DoReadFileDtb('302_encrypted_invalid_iv_file.dts')
7136        self.assertIn("Filename 'invalid-iv-file' not found in input path",
7137                      str(e.exception))
7138
7139    def testEncryptedMissingKey(self):
7140        """Test encrypted node with missing key properties"""
7141        with self.assertRaises(ValueError) as e:
7142            self._DoReadFileDtb('303_encrypted_missing_key.dts')
7143        self.assertIn(
7144            "Node '/binman/fit/images/u-boot/encrypted': Provide either 'key-filename' or 'key-source'",
7145            str(e.exception))
7146
7147    def testEncryptedKeySource(self):
7148        """Test encrypted node with key-source property"""
7149        data = self._DoReadFileDtb('304_encrypted_key_source.dts')[0]
7150
7151        dtb = fdt.Fdt.FromData(data)
7152        dtb.Scan()
7153
7154        node = dtb.GetNode('/images/u-boot/cipher')
7155        self.assertEqual('algo-name', node.props['algo'].value)
7156        self.assertEqual('key-source-value', node.props['key-source'].value)
7157        self.assertEqual(ENCRYPTED_IV_DATA,
7158                         tools.to_bytes(''.join(node.props['iv'].value)))
7159        self.assertNotIn('key', node.props)
7160
7161    def testEncryptedKeyFile(self):
7162        """Test encrypted node with key-filename property"""
7163        data = self._DoReadFileDtb('305_encrypted_key_file.dts')[0]
7164
7165        dtb = fdt.Fdt.FromData(data)
7166        dtb.Scan()
7167
7168        node = dtb.GetNode('/images/u-boot/cipher')
7169        self.assertEqual('algo-name', node.props['algo'].value)
7170        self.assertEqual(ENCRYPTED_IV_DATA,
7171                         tools.to_bytes(''.join(node.props['iv'].value)))
7172        self.assertEqual(ENCRYPTED_KEY_DATA,
7173                         tools.to_bytes(''.join(node.props['key'].value)))
7174        self.assertNotIn('key-source', node.props)
7175
7176
7177    def testSplPubkeyDtb(self):
7178         """Test u_boot_spl_pubkey_dtb etype"""
7179         data = tools.read_file(self.TestFile("key.pem"))
7180         self._MakeInputFile("key.crt", data)
7181         self._DoReadFileRealDtb('306_spl_pubkey_dtb.dts')
7182         image = control.images['image']
7183         entries = image.GetEntries()
7184         dtb_entry = entries['u-boot-spl-pubkey-dtb']
7185         dtb_data = dtb_entry.GetData()
7186         dtb = fdt.Fdt.FromData(dtb_data)
7187         dtb.Scan()
7188
7189         signature_node = dtb.GetNode('/signature')
7190         self.assertIsNotNone(signature_node)
7191         key_node = signature_node.FindNode("key-key")
7192         self.assertIsNotNone(key_node)
7193         self.assertEqual(fdt_util.GetString(key_node, "required"),
7194                          "conf")
7195         self.assertEqual(fdt_util.GetString(key_node, "algo"),
7196                          "sha384,rsa4096")
7197         self.assertEqual(fdt_util.GetString(key_node, "key-name-hint"),
7198                          "key")
7199
7200    def testXilinxBootgenSigning(self):
7201        """Test xilinx-bootgen etype"""
7202        bootgen = bintool.Bintool.create('bootgen')
7203        self._CheckBintool(bootgen)
7204        data = tools.read_file(self.TestFile("key.key"))
7205        self._MakeInputFile("psk.pem", data)
7206        self._MakeInputFile("ssk.pem", data)
7207        self._SetupPmuFwlElf()
7208        self._SetupSplElf()
7209        self._DoReadFileRealDtb('307_xilinx_bootgen_sign.dts')
7210        image_fname = tools.get_output_filename('image.bin')
7211
7212        # Read partition header table and check if authentication is enabled
7213        bootgen_out = bootgen.run_cmd("-arch", "zynqmp",
7214                                      "-read", image_fname, "pht").splitlines()
7215        attributes = {"authentication": None,
7216                      "core": None,
7217                      "encryption": None}
7218
7219        for l in bootgen_out:
7220            for a in attributes.keys():
7221                if a in l:
7222                   m = re.match(fr".*{a} \[([^]]+)\]", l)
7223                   attributes[a] = m.group(1)
7224
7225        self.assertTrue(attributes['authentication'] == "rsa")
7226        self.assertTrue(attributes['core'] == "a53-0")
7227        self.assertTrue(attributes['encryption'] == "no")
7228
7229    def testXilinxBootgenSigningEncryption(self):
7230        """Test xilinx-bootgen etype"""
7231        bootgen = bintool.Bintool.create('bootgen')
7232        self._CheckBintool(bootgen)
7233        data = tools.read_file(self.TestFile("key.key"))
7234        self._MakeInputFile("psk.pem", data)
7235        self._MakeInputFile("ssk.pem", data)
7236        self._SetupPmuFwlElf()
7237        self._SetupSplElf()
7238        self._DoReadFileRealDtb('308_xilinx_bootgen_sign_enc.dts')
7239        image_fname = tools.get_output_filename('image.bin')
7240
7241        # Read boot header in order to verify encryption source and
7242        # encryption parameter
7243        bootgen_out = bootgen.run_cmd("-arch", "zynqmp",
7244                                      "-read", image_fname, "bh").splitlines()
7245        attributes = {"auth_only":
7246                        {"re": r".*auth_only \[([^]]+)\]", "value": None},
7247                      "encryption_keystore":
7248                        {"re": r" *encryption_keystore \(0x28\) : (.*)",
7249                            "value": None},
7250                     }
7251
7252        for l in bootgen_out:
7253            for a in attributes.keys():
7254                if a in l:
7255                   m = re.match(attributes[a]['re'], l)
7256                   attributes[a] = m.group(1)
7257
7258        # Check if fsbl-attribute is set correctly
7259        self.assertTrue(attributes['auth_only'] == "true")
7260        # Check if key is stored in efuse
7261        self.assertTrue(attributes['encryption_keystore'] == "0xa5c3c5a3")
7262
7263    def testXilinxBootgenMissing(self):
7264        """Test that binman still produces an image if bootgen is missing"""
7265        data = tools.read_file(self.TestFile("key.key"))
7266        self._MakeInputFile("psk.pem", data)
7267        self._MakeInputFile("ssk.pem", data)
7268        self._SetupPmuFwlElf()
7269        self._SetupSplElf()
7270        with test_util.capture_sys_output() as (_, stderr):
7271            self._DoTestFile('307_xilinx_bootgen_sign.dts',
7272                             force_missing_bintools='bootgen')
7273        err = stderr.getvalue()
7274        self.assertRegex(err,
7275                         "Image 'image'.*missing bintools.*: bootgen")
7276
7277    def _GetCapsuleHeaders(self, data):
7278        """Get the capsule header contents
7279
7280        Args:
7281            data: Capsule file contents
7282
7283        Returns:
7284            Dict:
7285                key: Capsule Header name (str)
7286                value: Header field value (str)
7287        """
7288        capsule_file = os.path.join(self._indir, 'test.capsule')
7289        tools.write_file(capsule_file, data)
7290
7291        out = tools.run('mkeficapsule', '--dump-capsule', capsule_file)
7292        lines = out.splitlines()
7293
7294        re_line = re.compile(r'^([^:\-\t]*)(?:\t*\s*:\s*(.*))?$')
7295        vals = {}
7296        for line in lines:
7297            mat = re_line.match(line)
7298            if mat:
7299                vals[mat.group(1)] = mat.group(2)
7300
7301        return vals
7302
7303    def _CheckCapsule(self, data, signed_capsule=False, version_check=False,
7304                      capoemflags=False):
7305        fmp_signature = "3153534D" # 'M', 'S', 'S', '1'
7306        fmp_size = "00000010"
7307        fmp_fw_version = "00000002"
7308        capsule_image_index = "00000001"
7309        oemflag = "00018000"
7310        auth_hdr_revision = "00000200"
7311        auth_hdr_cert_type = "00000EF1"
7312
7313        payload_data_len = len(EFI_CAPSULE_DATA)
7314
7315        hdr = self._GetCapsuleHeaders(data)
7316
7317        self.assertEqual(FW_MGMT_GUID.upper(), hdr['EFI_CAPSULE_HDR.CAPSULE_GUID'])
7318
7319        self.assertEqual(CAPSULE_IMAGE_GUID.upper(),
7320                         hdr['FMP_CAPSULE_IMAGE_HDR.UPDATE_IMAGE_TYPE_ID'])
7321        self.assertEqual(capsule_image_index,
7322                         hdr['FMP_CAPSULE_IMAGE_HDR.UPDATE_IMAGE_INDEX'])
7323
7324        if capoemflags:
7325            self.assertEqual(oemflag, hdr['EFI_CAPSULE_HDR.FLAGS'])
7326
7327        if signed_capsule:
7328            self.assertEqual(auth_hdr_revision,
7329                             hdr['EFI_FIRMWARE_IMAGE_AUTH.AUTH_INFO.HDR.wREVISION'])
7330            self.assertEqual(auth_hdr_cert_type,
7331                             hdr['EFI_FIRMWARE_IMAGE_AUTH.AUTH_INFO.HDR.wCERTTYPE'])
7332            self.assertEqual(WIN_CERT_TYPE_EFI_GUID.upper(),
7333                             hdr['EFI_FIRMWARE_IMAGE_AUTH.AUTH_INFO.CERT_TYPE'])
7334
7335        if version_check:
7336            self.assertEqual(fmp_signature,
7337                             hdr['FMP_PAYLOAD_HDR.SIGNATURE'])
7338            self.assertEqual(fmp_size,
7339                             hdr['FMP_PAYLOAD_HDR.HEADER_SIZE'])
7340            self.assertEqual(fmp_fw_version,
7341                             hdr['FMP_PAYLOAD_HDR.FW_VERSION'])
7342
7343        self.assertEqual(payload_data_len, int(hdr['Payload Image Size']))
7344
7345    def _CheckEmptyCapsule(self, data, accept_capsule=False):
7346        if accept_capsule:
7347            capsule_hdr_guid = EMPTY_CAPSULE_ACCEPT_GUID
7348        else:
7349            capsule_hdr_guid = EMPTY_CAPSULE_REVERT_GUID
7350
7351        hdr = self._GetCapsuleHeaders(data)
7352
7353        self.assertEqual(capsule_hdr_guid.upper(),
7354                         hdr['EFI_CAPSULE_HDR.CAPSULE_GUID'])
7355
7356        if accept_capsule:
7357            capsule_size = "0000002C"
7358        else:
7359            capsule_size = "0000001C"
7360        self.assertEqual(capsule_size,
7361                         hdr['EFI_CAPSULE_HDR.CAPSULE_IMAGE_SIZE'])
7362
7363        if accept_capsule:
7364            self.assertEqual(CAPSULE_IMAGE_GUID.upper(), hdr['ACCEPT_IMAGE_GUID'])
7365
7366    def testCapsuleGen(self):
7367        """Test generation of EFI capsule"""
7368        data = self._DoReadFile('311_capsule.dts')
7369
7370        self._CheckCapsule(data)
7371
7372    def testSignedCapsuleGen(self):
7373        """Test generation of EFI capsule"""
7374        data = tools.read_file(self.TestFile("key.key"))
7375        self._MakeInputFile("key.key", data)
7376        data = tools.read_file(self.TestFile("key.pem"))
7377        self._MakeInputFile("key.crt", data)
7378
7379        data = self._DoReadFile('312_capsule_signed.dts')
7380
7381        self._CheckCapsule(data, signed_capsule=True)
7382
7383    def testCapsuleGenVersionSupport(self):
7384        """Test generation of EFI capsule with version support"""
7385        data = self._DoReadFile('313_capsule_version.dts')
7386
7387        self._CheckCapsule(data, version_check=True)
7388
7389    def testCapsuleGenSignedVer(self):
7390        """Test generation of signed EFI capsule with version information"""
7391        data = tools.read_file(self.TestFile("key.key"))
7392        self._MakeInputFile("key.key", data)
7393        data = tools.read_file(self.TestFile("key.pem"))
7394        self._MakeInputFile("key.crt", data)
7395
7396        data = self._DoReadFile('314_capsule_signed_ver.dts')
7397
7398        self._CheckCapsule(data, signed_capsule=True, version_check=True)
7399
7400    def testCapsuleGenCapOemFlags(self):
7401        """Test generation of EFI capsule with OEM Flags set"""
7402        data = self._DoReadFile('315_capsule_oemflags.dts')
7403
7404        self._CheckCapsule(data, capoemflags=True)
7405
7406    def testCapsuleGenKeyMissing(self):
7407        """Test that binman errors out on missing key"""
7408        with self.assertRaises(ValueError) as e:
7409            self._DoReadFile('316_capsule_missing_key.dts')
7410
7411        self.assertIn("Both private key and public key certificate need to be provided",
7412                      str(e.exception))
7413
7414    def testCapsuleGenIndexMissing(self):
7415        """Test that binman errors out on missing image index"""
7416        with self.assertRaises(ValueError) as e:
7417            self._DoReadFile('317_capsule_missing_index.dts')
7418
7419        self.assertIn("entry is missing properties: image-index",
7420                      str(e.exception))
7421
7422    def testCapsuleGenGuidMissing(self):
7423        """Test that binman errors out on missing image GUID"""
7424        with self.assertRaises(ValueError) as e:
7425            self._DoReadFile('318_capsule_missing_guid.dts')
7426
7427        self.assertIn("entry is missing properties: image-guid",
7428                      str(e.exception))
7429
7430    def testCapsuleGenAcceptCapsule(self):
7431        """Test generationg of accept EFI capsule"""
7432        data = self._DoReadFile('319_capsule_accept.dts')
7433
7434        self._CheckEmptyCapsule(data, accept_capsule=True)
7435
7436    def testCapsuleGenRevertCapsule(self):
7437        """Test generationg of revert EFI capsule"""
7438        data = self._DoReadFile('320_capsule_revert.dts')
7439
7440        self._CheckEmptyCapsule(data)
7441
7442    def testCapsuleGenAcceptGuidMissing(self):
7443        """Test that binman errors out on missing image GUID for accept capsule"""
7444        with self.assertRaises(ValueError) as e:
7445            self._DoReadFile('321_capsule_accept_missing_guid.dts')
7446
7447        self.assertIn("Image GUID needed for generating accept capsule",
7448                      str(e.exception))
7449
7450    def testCapsuleGenEmptyCapsuleTypeMissing(self):
7451        """Test that capsule-type is specified"""
7452        with self.assertRaises(ValueError) as e:
7453            self._DoReadFile('322_empty_capsule_type_missing.dts')
7454
7455        self.assertIn("entry is missing properties: capsule-type",
7456                      str(e.exception))
7457
7458    def testCapsuleGenAcceptOrRevertMissing(self):
7459        """Test that both accept and revert capsule are not specified"""
7460        with self.assertRaises(ValueError) as e:
7461            self._DoReadFile('323_capsule_accept_revert_missing.dts')
7462
7463if __name__ == "__main__":
7464    unittest.main()
7465