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