uboot/tools/binman/image.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# Class for an image, the output of binman
   6#
   7
   8from collections import OrderedDict
   9import fnmatch
  10from operator import attrgetter
  11import os
  12import re
  13import sys
  14
  15from binman.entry import Entry
  16from binman.etype import fdtmap
  17from binman.etype import image_header
  18from binman.etype import section
  19from dtoc import fdt
  20from dtoc import fdt_util
  21from patman import tools
  22from patman import tout
  23
  24class Image(section.Entry_section):
  25    """A Image, representing an output from binman
  26
  27    An image is comprised of a collection of entries each containing binary
  28    data. The image size must be large enough to hold all of this data.
  29
  30    This class implements the various operations needed for images.
  31
  32    Attributes:
  33        filename: Output filename for image
  34        image_node: Name of node containing the description for this image
  35        fdtmap_dtb: Fdt object for the fdtmap when loading from a file
  36        fdtmap_data: Contents of the fdtmap when loading from a file
  37        allow_repack: True to add properties to allow the image to be safely
  38            repacked later
  39        test_section_timeout: Use a zero timeout for section multi-threading
  40            (for testing)
  41
  42    Args:
  43        copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
  44            from the device tree
  45        test: True if this is being called from a test of Images. This this case
  46            there is no device tree defining the structure of the section, so
  47            we create a section manually.
  48        ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
  49            exception). This should be used if the Image is being loaded from
  50            a file rather than generated. In that case we obviously don't need
  51            the entry arguments since the contents already exists.
  52        use_expanded: True if we are updating the FDT wth entry offsets, etc.
  53            and should use the expanded versions of the U-Boot entries.
  54            Any entry type that includes a devicetree must put it in a
  55            separate entry so that it will be updated. For example. 'u-boot'
  56            normally just picks up 'u-boot.bin' which includes the
  57            devicetree, but this is not updateable, since it comes into
  58            binman as one piece and binman doesn't know that it is actually
  59            an executable followed by a devicetree. Of course it could be
  60            taught this, but then when reading an image (e.g. 'binman ls')
  61            it may need to be able to split the devicetree out of the image
  62            in order to determine the location of things. Instead we choose
  63            to ignore 'u-boot-bin' in this case, and build it ourselves in
  64            binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
  65            Entry_u_boot_expanded and Entry_blob_phase for details.
  66    """
  67    def __init__(self, name, node, copy_to_orig=True, test=False,
  68                 ignore_missing=False, use_expanded=False):
  69        super().__init__(None, 'section', node, test=test)
  70        self.copy_to_orig = copy_to_orig
  71        self.name = 'main-section'
  72        self.image_name = name
  73        self._filename = '%s.bin' % self.image_name
  74        self.fdtmap_dtb = None
  75        self.fdtmap_data = None
  76        self.allow_repack = False
  77        self._ignore_missing = ignore_missing
  78        self.use_expanded = use_expanded
  79        self.test_section_timeout = False
  80        if not test:
  81            self.ReadNode()
  82
  83    def ReadNode(self):
  84        super().ReadNode()
  85        filename = fdt_util.GetString(self._node, 'filename')
  86        if filename:
  87            self._filename = filename
  88        self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
  89
  90    @classmethod
  91    def FromFile(cls, fname):
  92        """Convert an image file into an Image for use in binman
  93
  94        Args:
  95            fname: Filename of image file to read
  96
  97        Returns:
  98            Image object on success
  99
 100        Raises:
 101            ValueError if something goes wrong
 102        """
 103        data = tools.ReadFile(fname)
 104        size = len(data)
 105
 106        # First look for an image header
 107        pos = image_header.LocateHeaderOffset(data)
 108        if pos is None:
 109            # Look for the FDT map
 110            pos = fdtmap.LocateFdtmap(data)
 111        if pos is None:
 112            raise ValueError('Cannot find FDT map in image')
 113
 114        # We don't know the FDT size, so check its header first
 115        probe_dtb = fdt.Fdt.FromData(
 116            data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
 117        dtb_size = probe_dtb.GetFdtObj().totalsize()
 118        fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
 119        fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
 120        out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
 121        tools.WriteFile(out_fname, fdt_data)
 122        dtb = fdt.Fdt(out_fname)
 123        dtb.Scan()
 124
 125        # Return an Image with the associated nodes
 126        root = dtb.GetRoot()
 127        image = Image('image', root, copy_to_orig=False, ignore_missing=True)
 128
 129        image.image_node = fdt_util.GetString(root, 'image-node', 'image')
 130        image.fdtmap_dtb = dtb
 131        image.fdtmap_data = fdtmap_data
 132        image._data = data
 133        image._filename = fname
 134        image.image_name, _ = os.path.splitext(fname)
 135        return image
 136
 137    def Raise(self, msg):
 138        """Convenience function to raise an error referencing an image"""
 139        raise ValueError("Image '%s': %s" % (self._node.path, msg))
 140
 141    def PackEntries(self):
 142        """Pack all entries into the image"""
 143        super().Pack(0)
 144
 145    def SetImagePos(self):
 146        # This first section in the image so it starts at 0
 147        super().SetImagePos(0)
 148
 149    def ProcessEntryContents(self):
 150        """Call the ProcessContents() method for each entry
 151
 152        This is intended to adjust the contents as needed by the entry type.
 153
 154        Returns:
 155            True if the new data size is OK, False if expansion is needed
 156        """
 157        return super().ProcessContents()
 158
 159    def WriteSymbols(self):
 160        """Write symbol values into binary files for access at run time"""
 161        super().WriteSymbols(self)
 162
 163    def BuildImage(self):
 164        """Write the image to a file"""
 165        fname = tools.GetOutputFilename(self._filename)
 166        tout.Info("Writing image to '%s'" % fname)
 167        with open(fname, 'wb') as fd:
 168            data = self.GetPaddedData()
 169            fd.write(data)
 170        tout.Info("Wrote %#x bytes" % len(data))
 171
 172    def WriteMap(self):
 173        """Write a map of the image to a .map file
 174
 175        Returns:
 176            Filename of map file written
 177        """
 178        filename = '%s.map' % self.image_name
 179        fname = tools.GetOutputFilename(filename)
 180        with open(fname, 'w') as fd:
 181            print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
 182                  file=fd)
 183            super().WriteMap(fd, 0)
 184        return fname
 185
 186    def BuildEntryList(self):
 187        """List the files in an image
 188
 189        Returns:
 190            List of entry.EntryInfo objects describing all entries in the image
 191        """
 192        entries = []
 193        self.ListEntries(entries, 0)
 194        return entries
 195
 196    def FindEntryPath(self, entry_path):
 197        """Find an entry at a given path in the image
 198
 199        Args:
 200            entry_path: Path to entry (e.g. /ro-section/u-boot')
 201
 202        Returns:
 203            Entry object corresponding to that past
 204
 205        Raises:
 206            ValueError if no entry found
 207        """
 208        parts = entry_path.split('/')
 209        entries = self.GetEntries()
 210        parent = '/'
 211        for part in parts:
 212            entry = entries.get(part)
 213            if not entry:
 214                raise ValueError("Entry '%s' not found in '%s'" %
 215                                 (part, parent))
 216            parent = entry.GetPath()
 217            entries = entry.GetEntries()
 218        return entry
 219
 220    def ReadData(self, decomp=True):
 221        tout.Debug("Image '%s' ReadData(), size=%#x" %
 222                   (self.GetPath(), len(self._data)))
 223        return self._data
 224
 225    def GetListEntries(self, entry_paths):
 226        """List the entries in an image
 227
 228        This decodes the supplied image and returns a list of entries from that
 229        image, preceded by a header.
 230
 231        Args:
 232            entry_paths: List of paths to match (each can have wildcards). Only
 233                entries whose names match one of these paths will be printed
 234
 235        Returns:
 236            String error message if something went wrong, otherwise
 237            3-Tuple:
 238                List of EntryInfo objects
 239                List of lines, each
 240                    List of text columns, each a string
 241                List of widths of each column
 242        """
 243        def _EntryToStrings(entry):
 244            """Convert an entry to a list of strings, one for each column
 245
 246            Args:
 247                entry: EntryInfo object containing information to output
 248
 249            Returns:
 250                List of strings, one for each field in entry
 251            """
 252            def _AppendHex(val):
 253                """Append a hex value, or an empty string if val is None
 254
 255                Args:
 256                    val: Integer value, or None if none
 257                """
 258                args.append('' if val is None else '>%x' % val)
 259
 260            args = ['  ' * entry.indent + entry.name]
 261            _AppendHex(entry.image_pos)
 262            _AppendHex(entry.size)
 263            args.append(entry.etype)
 264            _AppendHex(entry.offset)
 265            _AppendHex(entry.uncomp_size)
 266            return args
 267
 268        def _DoLine(lines, line):
 269            """Add a line to the output list
 270
 271            This adds a line (a list of columns) to the output list. It also updates
 272            the widths[] array with the maximum width of each column
 273
 274            Args:
 275                lines: List of lines to add to
 276                line: List of strings, one for each column
 277            """
 278            for i, item in enumerate(line):
 279                widths[i] = max(widths[i], len(item))
 280            lines.append(line)
 281
 282        def _NameInPaths(fname, entry_paths):
 283            """Check if a filename is in a list of wildcarded paths
 284
 285            Args:
 286                fname: Filename to check
 287                entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
 288                                                             'section/u-boot'])
 289
 290            Returns:
 291                True if any wildcard matches the filename (using Unix filename
 292                    pattern matching, not regular expressions)
 293                False if not
 294            """
 295            for path in entry_paths:
 296                if fnmatch.fnmatch(fname, path):
 297                    return True
 298            return False
 299
 300        entries = self.BuildEntryList()
 301
 302        # This is our list of lines. Each item in the list is a list of strings, one
 303        # for each column
 304        lines = []
 305        HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
 306                  'Uncomp-size']
 307        num_columns = len(HEADER)
 308
 309        # This records the width of each column, calculated as the maximum width of
 310        # all the strings in that column
 311        widths = [0] * num_columns
 312        _DoLine(lines, HEADER)
 313
 314        # We won't print anything unless it has at least this indent. So at the
 315        # start we will print nothing, unless a path matches (or there are no
 316        # entry paths)
 317        MAX_INDENT = 100
 318        min_indent = MAX_INDENT
 319        path_stack = []
 320        path = ''
 321        indent = 0
 322        selected_entries = []
 323        for entry in entries:
 324            if entry.indent > indent:
 325                path_stack.append(path)
 326            elif entry.indent < indent:
 327                path_stack.pop()
 328            if path_stack:
 329                path = path_stack[-1] + '/' + entry.name
 330            indent = entry.indent
 331
 332            # If there are entry paths to match and we are not looking at a
 333            # sub-entry of a previously matched entry, we need to check the path
 334            if entry_paths and indent <= min_indent:
 335                if _NameInPaths(path[1:], entry_paths):
 336                    # Print this entry and all sub-entries (=higher indent)
 337                    min_indent = indent
 338                else:
 339                    # Don't print this entry, nor any following entries until we get
 340                    # a path match
 341                    min_indent = MAX_INDENT
 342                    continue
 343            _DoLine(lines, _EntryToStrings(entry))
 344            selected_entries.append(entry)
 345        return selected_entries, lines, widths
 346
 347    def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
 348        """Look up a symbol in an ELF file
 349
 350        Looks up a symbol in an ELF file. Only entry types which come from an
 351        ELF image can be used by this function.
 352
 353        This searches through this image including all of its subsections.
 354
 355        At present the only entry properties supported are:
 356            offset
 357            image_pos - 'base_addr' is added if this is not an end-at-4gb image
 358            size
 359
 360        Args:
 361            sym_name: Symbol name in the ELF file to look up in the format
 362                _binman_<entry>_prop_<property> where <entry> is the name of
 363                the entry and <property> is the property to find (e.g.
 364                _binman_u_boot_prop_offset). As a special case, you can append
 365                _any to <entry> to have it search for any matching entry. E.g.
 366                _binman_u_boot_any_prop_offset will match entries called u-boot,
 367                u-boot-img and u-boot-nodtb)
 368            optional: True if the symbol is optional. If False this function
 369                will raise if the symbol is not found
 370            msg: Message to display if an error occurs
 371            base_addr: Base address of image. This is added to the returned
 372                image_pos in most cases so that the returned position indicates
 373                where the targeted entry/binary has actually been loaded. But
 374                if end-at-4gb is used, this is not done, since the binary is
 375                already assumed to be linked to the ROM position and using
 376                execute-in-place (XIP).
 377
 378        Returns:
 379            Value that should be assigned to that symbol, or None if it was
 380                optional and not found
 381
 382        Raises:
 383            ValueError if the symbol is invalid or not found, or references a
 384                property which is not supported
 385        """
 386        entries = OrderedDict()
 387        entries_by_name = {}
 388        self._CollectEntries(entries, entries_by_name, self)
 389        return self.LookupSymbol(sym_name, optional, msg, base_addr,
 390                                 entries_by_name)
 391