Merge tag 'u-boot-imx-20191009' of https://gitlab.denx.de/u-boot/custodians/u-boot-imx
[oweals/u-boot.git] / tools / binman / image.py
index 24c4f6f578a6a61ee59041f6bae86b834c1662ad..2beab7fd4d2d50ebdf9b2627005a1944cea78631 100644 (file)
@@ -1,18 +1,29 @@
+# SPDX-License-Identifier: GPL-2.0+
 # Copyright (c) 2016 Google, Inc
 # Written by Simon Glass <sjg@chromium.org>
 #
-# SPDX-License-Identifier:      GPL-2.0+
-#
 # Class for an image, the output of binman
 #
 
+from __future__ import print_function
+
 from collections import OrderedDict
+import fnmatch
 from operator import attrgetter
-
+import os
+import re
+import sys
+
+from entry import Entry
+from etype import fdtmap
+from etype import image_header
+from etype import section
+import fdt
 import fdt_util
 import tools
+import tout
 
-class Image:
+class Image(section.Entry_section):
     """A Image, representing an output from binman
 
     An image is comprised of a collection of entries each containing binary
@@ -20,213 +31,298 @@ class Image:
 
     This class implements the various operations needed for images.
 
-    Atrtributes:
-        _node: Node object that contains the image definition in device tree
-        _name: Image name
-        _size: Image size in bytes, or None if not known yet
-        _align_size: Image size alignment, or None
-        _pad_before: Number of bytes before the first entry starts. This
-            effectively changes the place where entry position 0 starts
-        _pad_after: Number of bytes after the last entry ends. The last
-            entry will finish on or before this boundary
-        _pad_byte: Byte to use to pad the image where there is no entry
-        _filename: Output filename for image
-        _sort: True if entries should be sorted by position, False if they
-            must be in-order in the device tree description
-        _skip_at_start: Number of bytes before the first entry starts. These
-            effecively adjust the starting position of entries. For example,
-            if _pad_before is 16, then the first entry would start at 16.
-            An entry with pos = 20 would in fact be written at position 4
-            in the image file.
-        _end_4gb: Indicates that the image ends at the 4GB boundary. This is
-            used for x86 images, which want to use positions such that a
-             memory address (like 0xff800000) is the first entry position.
-             This causes _skip_at_start to be set to the starting memory
-             address.
-        _entries: OrderedDict() of entries
+    Attributes:
+        filename: Output filename for image
+        image_node: Name of node containing the description for this image
+        fdtmap_dtb: Fdt object for the fdtmap when loading from a file
+        fdtmap_data: Contents of the fdtmap when loading from a file
+        allow_repack: True to add properties to allow the image to be safely
+            repacked later
+
+    Args:
+        copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
+            from the device tree
+        test: True if this is being called from a test of Images. This this case
+            there is no device tree defining the structure of the section, so
+            we create a section manually.
     """
-    def __init__(self, name, node):
-        global entry
-        global Entry
-        import entry
-        from entry import Entry
-
-        self._node = node
-        self._name = name
-        self._size = None
-        self._align_size = None
-        self._pad_before = 0
-        self._pad_after = 0
-        self._pad_byte = 0
-        self._filename = '%s.bin' % self._name
-        self._sort = False
-        self._skip_at_start = 0
-        self._end_4gb = False
-        self._entries = OrderedDict()
-
-        self._ReadNode()
-        self._ReadEntries()
-
-    def _ReadNode(self):
-        """Read properties from the image node"""
-        self._size = fdt_util.GetInt(self._node, 'size')
-        self._align_size = fdt_util.GetInt(self._node, 'align-size')
-        if tools.NotPowerOfTwo(self._align_size):
-            self._Raise("Alignment size %s must be a power of two" %
-                        self._align_size)
-        self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
-        self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
-        self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
+    def __init__(self, name, node, copy_to_orig=True, test=False):
+        section.Entry_section.__init__(self, None, 'section', node, test=test)
+        self.copy_to_orig = copy_to_orig
+        self.name = 'main-section'
+        self.image_name = name
+        self._filename = '%s.bin' % self.image_name
+        self.fdtmap_dtb = None
+        self.fdtmap_data = None
+        self.allow_repack = False
+        if not test:
+            self.ReadNode()
+
+    def ReadNode(self):
+        section.Entry_section.ReadNode(self)
         filename = fdt_util.GetString(self._node, 'filename')
         if filename:
             self._filename = filename
-        self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
-        self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
-        if self._end_4gb and not self._size:
-            self._Raise("Image size must be provided when using end-at-4gb")
-        if self._end_4gb:
-            self._skip_at_start = 0x100000000 - self._size
-
-    def CheckSize(self):
-        """Check that the image contents does not exceed its size, etc."""
-        contents_size = 0
-        for entry in self._entries.values():
-            contents_size = max(contents_size, entry.pos + entry.size)
-
-        contents_size -= self._skip_at_start
+        self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
 
-        size = self._size
-        if not size:
-            size = self._pad_before + contents_size + self._pad_after
-            size = tools.Align(size, self._align_size)
+    @classmethod
+    def FromFile(cls, fname):
+        """Convert an image file into an Image for use in binman
 
-        if self._size and contents_size > self._size:
-            self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
-                       (contents_size, contents_size, self._size, self._size))
-        if not self._size:
-            self._size = size
-        if self._size != tools.Align(self._size, self._align_size):
-            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
-                  (self._size, self._size, self._align_size, self._align_size))
+        Args:
+            fname: Filename of image file to read
 
-    def _Raise(self, msg):
-        """Raises an error for this image
+        Returns:
+            Image object on success
 
-        Args:
-            msg: Error message to use in the raise string
         Raises:
-            ValueError()
+            ValueError if something goes wrong
         """
+        data = tools.ReadFile(fname)
+        size = len(data)
+
+        # First look for an image header
+        pos = image_header.LocateHeaderOffset(data)
+        if pos is None:
+            # Look for the FDT map
+            pos = fdtmap.LocateFdtmap(data)
+        if pos is None:
+            raise ValueError('Cannot find FDT map in image')
+
+        # We don't know the FDT size, so check its header first
+        probe_dtb = fdt.Fdt.FromData(
+            data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
+        dtb_size = probe_dtb.GetFdtObj().totalsize()
+        fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
+        fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
+        out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
+        tools.WriteFile(out_fname, fdt_data)
+        dtb = fdt.Fdt(out_fname)
+        dtb.Scan()
+
+        # Return an Image with the associated nodes
+        root = dtb.GetRoot()
+        image = Image('image', root, copy_to_orig=False)
+
+        image.image_node = fdt_util.GetString(root, 'image-node', 'image')
+        image.fdtmap_dtb = dtb
+        image.fdtmap_data = fdtmap_data
+        image._data = data
+        image._filename = fname
+        image.image_name, _ = os.path.splitext(fname)
+        return image
+
+    def Raise(self, msg):
+        """Convenience function to raise an error referencing an image"""
         raise ValueError("Image '%s': %s" % (self._node.path, msg))
 
-    def _ReadEntries(self):
-        for node in self._node.subnodes:
-            self._entries[node.name] = Entry.Create(self, node)
+    def PackEntries(self):
+        """Pack all entries into the image"""
+        section.Entry_section.Pack(self, 0)
 
-    def FindEntryType(self, etype):
-        """Find an entry type in the image
+    def SetImagePos(self):
+        # This first section in the image so it starts at 0
+        section.Entry_section.SetImagePos(self, 0)
+
+    def ProcessEntryContents(self):
+        """Call the ProcessContents() method for each entry
+
+        This is intended to adjust the contents as needed by the entry type.
 
-        Args:
-            etype: Entry type to find
         Returns:
-            entry matching that type, or None if not found
+            True if the new data size is OK, False if expansion is needed
         """
+        sizes_ok = True
         for entry in self._entries.values():
-            if entry.etype == etype:
-                return entry
-        return None
+            if not entry.ProcessContents():
+                sizes_ok = False
+                tout.Debug("Entry '%s' size change" % self._node.path)
+        return sizes_ok
 
-    def GetEntryContents(self):
-        """Call ObtainContents() for each entry
+    def WriteSymbols(self):
+        """Write symbol values into binary files for access at run time"""
+        section.Entry_section.WriteSymbols(self, self)
 
-        This calls each entry's ObtainContents() a few times until they all
-        return True. We stop calling an entry's function once it returns
-        True. This allows the contents of one entry to depend on another.
+    def BuildImage(self):
+        """Write the image to a file"""
+        fname = tools.GetOutputFilename(self._filename)
+        tout.Info("Writing image to '%s'" % fname)
+        with open(fname, 'wb') as fd:
+            data = self.GetData()
+            fd.write(data)
+        tout.Info("Wrote %#x bytes" % len(data))
 
-        After 3 rounds we give up since it's likely an error.
-        """
-        todo = self._entries.values()
-        for passnum in range(3):
-            next_todo = []
-            for entry in todo:
-                if not entry.ObtainContents():
-                    next_todo.append(entry)
-            todo = next_todo
-            if not todo:
-                break
-
-    def _SetEntryPosSize(self, name, pos, size):
-        """Set the position and size of an entry
+    def WriteMap(self):
+        """Write a map of the image to a .map file
 
-        Args:
-            name: Entry name to update
-            pos: New position
-            size: New size
+        Returns:
+            Filename of map file written
         """
-        entry = self._entries.get(name)
-        if not entry:
-            self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
-        entry.SetPositionSize(self._skip_at_start + pos, size)
+        filename = '%s.map' % self.image_name
+        fname = tools.GetOutputFilename(filename)
+        with open(fname, 'w') as fd:
+            print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
+                  file=fd)
+            section.Entry_section.WriteMap(self, fd, 0)
+        return fname
 
-    def GetEntryPositions(self):
-        """Handle entries that want to set the position/size of other entries
+    def BuildEntryList(self):
+        """List the files in an image
 
-        This calls each entry's GetPositions() method. If it returns a list
-        of entries to update, it updates them.
+        Returns:
+            List of entry.EntryInfo objects describing all entries in the image
         """
-        for entry in self._entries.values():
-            pos_dict = entry.GetPositions()
-            for name, info in pos_dict.iteritems():
-                self._SetEntryPosSize(name, *info)
+        entries = []
+        self.ListEntries(entries, 0)
+        return entries
 
-    def PackEntries(self):
-        """Pack all entries into the image"""
-        pos = self._skip_at_start
-        for entry in self._entries.values():
-            pos = entry.Pack(pos)
+    def FindEntryPath(self, entry_path):
+        """Find an entry at a given path in the image
 
-    def _SortEntries(self):
-        """Sort entries by position"""
-        entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
-        self._entries.clear()
-        for entry in entries:
-            self._entries[entry._node.name] = entry
-
-    def CheckEntries(self):
-        """Check that entries do not overlap or extend outside the image"""
-        if self._sort:
-            self._SortEntries()
-        pos = 0
-        prev_name = 'None'
-        for entry in self._entries.values():
-            if (entry.pos < self._skip_at_start or
-                entry.pos >= self._skip_at_start + self._size):
-                entry.Raise("Position %#x (%d) is outside the image starting "
-                            "at %#x (%d)" %
-                            (entry.pos, entry.pos, self._skip_at_start,
-                             self._skip_at_start))
-            if entry.pos < pos:
-                entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
-                            "ending at %#x (%d)" %
-                            (entry.pos, entry.pos, prev_name, pos, pos))
-            pos = entry.pos + entry.size
-            prev_name = entry.GetPath()
+        Args:
+            entry_path: Path to entry (e.g. /ro-section/u-boot')
 
-    def ProcessEntryContents(self):
-        """Call the ProcessContents() method for each entry
+        Returns:
+            Entry object corresponding to that past
 
-        This is intended to adjust the contents as needed by the entry type.
+        Raises:
+            ValueError if no entry found
         """
-        for entry in self._entries.values():
-            entry.ProcessContents()
+        parts = entry_path.split('/')
+        entries = self.GetEntries()
+        parent = '/'
+        for part in parts:
+            entry = entries.get(part)
+            if not entry:
+                raise ValueError("Entry '%s' not found in '%s'" %
+                                 (part, parent))
+            parent = entry.GetPath()
+            entries = entry.GetEntries()
+        return entry
+
+    def ReadData(self, decomp=True):
+        tout.Debug("Image '%s' ReadData(), size=%#x" %
+                   (self.GetPath(), len(self._data)))
+        return self._data
+
+    def GetListEntries(self, entry_paths):
+        """List the entries in an image
+
+        This decodes the supplied image and returns a list of entries from that
+        image, preceded by a header.
 
-    def BuildImage(self):
-        """Write the image to a file"""
-        fname = tools.GetOutputFilename(self._filename)
-        with open(fname, 'wb') as fd:
-            fd.write(chr(self._pad_byte) * self._size)
+        Args:
+            entry_paths: List of paths to match (each can have wildcards). Only
+                entries whose names match one of these paths will be printed
 
-            for entry in self._entries.values():
-                data = entry.GetData()
-                fd.seek(self._pad_before + entry.pos - self._skip_at_start)
-                fd.write(data)
+        Returns:
+            String error message if something went wrong, otherwise
+            3-Tuple:
+                List of EntryInfo objects
+                List of lines, each
+                    List of text columns, each a string
+                List of widths of each column
+        """
+        def _EntryToStrings(entry):
+            """Convert an entry to a list of strings, one for each column
+
+            Args:
+                entry: EntryInfo object containing information to output
+
+            Returns:
+                List of strings, one for each field in entry
+            """
+            def _AppendHex(val):
+                """Append a hex value, or an empty string if val is None
+
+                Args:
+                    val: Integer value, or None if none
+                """
+                args.append('' if val is None else '>%x' % val)
+
+            args = ['  ' * entry.indent + entry.name]
+            _AppendHex(entry.image_pos)
+            _AppendHex(entry.size)
+            args.append(entry.etype)
+            _AppendHex(entry.offset)
+            _AppendHex(entry.uncomp_size)
+            return args
+
+        def _DoLine(lines, line):
+            """Add a line to the output list
+
+            This adds a line (a list of columns) to the output list. It also updates
+            the widths[] array with the maximum width of each column
+
+            Args:
+                lines: List of lines to add to
+                line: List of strings, one for each column
+            """
+            for i, item in enumerate(line):
+                widths[i] = max(widths[i], len(item))
+            lines.append(line)
+
+        def _NameInPaths(fname, entry_paths):
+            """Check if a filename is in a list of wildcarded paths
+
+            Args:
+                fname: Filename to check
+                entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
+                                                             'section/u-boot'])
+
+            Returns:
+                True if any wildcard matches the filename (using Unix filename
+                    pattern matching, not regular expressions)
+                False if not
+            """
+            for path in entry_paths:
+                if fnmatch.fnmatch(fname, path):
+                    return True
+            return False
+
+        entries = self.BuildEntryList()
+
+        # This is our list of lines. Each item in the list is a list of strings, one
+        # for each column
+        lines = []
+        HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
+                  'Uncomp-size']
+        num_columns = len(HEADER)
+
+        # This records the width of each column, calculated as the maximum width of
+        # all the strings in that column
+        widths = [0] * num_columns
+        _DoLine(lines, HEADER)
+
+        # We won't print anything unless it has at least this indent. So at the
+        # start we will print nothing, unless a path matches (or there are no
+        # entry paths)
+        MAX_INDENT = 100
+        min_indent = MAX_INDENT
+        path_stack = []
+        path = ''
+        indent = 0
+        selected_entries = []
+        for entry in entries:
+            if entry.indent > indent:
+                path_stack.append(path)
+            elif entry.indent < indent:
+                path_stack.pop()
+            if path_stack:
+                path = path_stack[-1] + '/' + entry.name
+            indent = entry.indent
+
+            # If there are entry paths to match and we are not looking at a
+            # sub-entry of a previously matched entry, we need to check the path
+            if entry_paths and indent <= min_indent:
+                if _NameInPaths(path[1:], entry_paths):
+                    # Print this entry and all sub-entries (=higher indent)
+                    min_indent = indent
+                else:
+                    # Don't print this entry, nor any following entries until we get
+                    # a path match
+                    min_indent = MAX_INDENT
+                    continue
+            _DoLine(lines, _EntryToStrings(entry))
+            selected_entries.append(entry)
+        return selected_entries, lines, widths