binman: Avoid calculated section data repeatedly
[platform/kernel/u-boot.git] / tools / binman / etype / section.py
index cdd8618..3dd5f58 100644 (file)
@@ -8,15 +8,15 @@ Sections are entries which can contain other entries. This allows hierarchical
 images to be created.
 """
 
-from __future__ import print_function
-
 from collections import OrderedDict
 import re
 import sys
 
-from entry import Entry
-import fdt_util
-import tools
+from binman.entry import Entry
+from dtoc import fdt_util
+from patman import tools
+from patman import tout
+from patman.tools import ToHexSize
 
 
 class Entry_section(Entry):
@@ -35,6 +35,11 @@ class Entry_section(Entry):
         name-prefix: Adds a prefix to the name of every entry in the section
             when writing out the map
 
+    Properties:
+        allow_missing: True if this section permits external blobs to be
+            missing their contents. The second will produce an image but of
+            course it will not work.
+
     Since a section is also an entry, it inherits all the properies of entries
     too.
 
@@ -44,30 +49,16 @@ class Entry_section(Entry):
     """
     def __init__(self, section, etype, node, test=False):
         if not test:
-            Entry.__init__(self, section, etype, node)
-        if section:
-            self.image = section.image
+            super().__init__(section, etype, node)
         self._entries = OrderedDict()
         self._pad_byte = 0
         self._sort = False
         self._skip_at_start = None
         self._end_4gb = False
-        if not test:
-            self._ReadNode()
-            self._ReadEntries()
-
-    def _Raise(self, msg):
-        """Raises an error for this section
-
-        Args:
-            msg: Error message to use in the raise string
-        Raises:
-            ValueError()
-        """
-        raise ValueError("Section '%s': %s" % (self._node.path, msg))
 
-    def _ReadNode(self):
-        """Read properties from the image node"""
+    def ReadNode(self):
+        """Read properties from the section node"""
+        super().ReadNode()
         self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
         self._sort = fdt_util.GetBool(self._node, 'sort-by-offset')
         self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
@@ -87,14 +78,27 @@ class Entry_section(Entry):
         if filename:
             self._filename = filename
 
+        self._ReadEntries()
+
     def _ReadEntries(self):
         for node in self._node.subnodes:
-            if node.name == 'hash':
+            if node.name.startswith('hash') or node.name.startswith('signature'):
                 continue
             entry = Entry.Create(self, node)
+            entry.ReadNode()
             entry.SetPrefix(self._name_prefix)
             self._entries[node.name] = entry
 
+    def _Raise(self, msg):
+        """Raises an error for this section
+
+        Args:
+            msg: Error message to use in the raise string
+        Raises:
+            ValueError()
+        """
+        raise ValueError("Section '%s': %s" % (self._node.path, msg))
+
     def GetFdts(self):
         fdts = {}
         for entry in self._entries.values():
@@ -128,28 +132,117 @@ class Entry_section(Entry):
         a section containing a list of files. Process these entries so that
         this information is added to the device tree.
         """
-        Entry.ExpandEntries(self)
+        super().ExpandEntries()
         for entry in self._entries.values():
             entry.ExpandEntries()
 
-    def AddMissingProperties(self):
+    def AddMissingProperties(self, have_image_pos):
         """Add new properties to the device tree as needed for this entry"""
-        Entry.AddMissingProperties(self)
+        super().AddMissingProperties(have_image_pos)
+        if self.compress != 'none':
+            have_image_pos = False
         for entry in self._entries.values():
-            entry.AddMissingProperties()
+            entry.AddMissingProperties(have_image_pos)
 
     def ObtainContents(self):
         return self.GetEntryContents()
 
-    def GetData(self):
-        section_data = tools.GetBytes(self._pad_byte, self.size)
+    def GetPaddedDataForEntry(self, entry, entry_data):
+        """Get the data for an entry including any padding
+
+        Gets the entry data and uses the section pad-byte value to add padding
+        before and after as defined by the pad-before and pad-after properties.
+        This does not consider alignment.
+
+        Args:
+            entry: Entry to check
+
+        Returns:
+            Contents of the entry along with any pad bytes before and
+            after it (bytes)
+        """
+        pad_byte = (entry._pad_byte if isinstance(entry, Entry_section)
+                    else self._pad_byte)
+
+        data = b''
+        # Handle padding before the entry
+        if entry.pad_before:
+            data += tools.GetBytes(self._pad_byte, entry.pad_before)
+
+        # Add in the actual entry data
+        data += entry_data
+
+        # Handle padding after the entry
+        if entry.pad_after:
+            data += tools.GetBytes(self._pad_byte, entry.pad_after)
+
+        if entry.size:
+            data += tools.GetBytes(pad_byte, entry.size - len(data))
+
+        self.Detail('GetPaddedDataForEntry: size %s' % ToHexSize(self.data))
+
+        return data
+
+    def _BuildSectionData(self):
+        """Build the contents of a section
+
+        This places all entries at the right place, dealing with padding before
+        and after entries. It does not do padding for the section itself (the
+        pad-before and pad-after properties in the section items) since that is
+        handled by the parent section.
+
+        Returns:
+            Contents of the section (bytes)
+        """
+        section_data = b''
 
         for entry in self._entries.values():
-            data = entry.GetData()
-            base = self.pad_before + entry.offset - self._skip_at_start
-            section_data = (section_data[:base] + data +
-                            section_data[base + len(data):])
-        return section_data
+            data = self.GetPaddedDataForEntry(entry, entry.GetData())
+            # Handle empty space before the entry
+            pad = (entry.offset or 0) - self._skip_at_start - len(section_data)
+            if pad > 0:
+                section_data += tools.GetBytes(self._pad_byte, pad)
+
+            # Add in the actual entry data
+            section_data += data
+
+        self.Detail('GetData: %d entries, total size %#x' %
+                    (len(self._entries), len(section_data)))
+        return self.CompressData(section_data)
+
+    def GetPaddedData(self, data=None):
+        """Get the data for a section including any padding
+
+        Gets the section data and uses the parent section's pad-byte value to
+        add padding before and after as defined by the pad-before and pad-after
+        properties. If this is a top-level section (i.e. an image), this is the
+        same as GetData(), since padding is not supported.
+
+        This does not consider alignment.
+
+        Returns:
+            Contents of the section along with any pad bytes before and
+            after it (bytes)
+        """
+        section = self.section or self
+        if data is None:
+            data = self.GetData()
+        return section.GetPaddedDataForEntry(self, data)
+
+    def GetData(self):
+        """Get the contents of an entry
+
+        This builds the contents of the section, stores this as the contents of
+        the section and returns it
+
+        Returns:
+            bytes content of the section, made up for all all of its subentries.
+            This excludes any padding. If the section is compressed, the
+            compressed data is returned
+        """
+        data = self._BuildSectionData()
+        self.SetContents(data)
+        return data
 
     def GetOffsets(self):
         """Handle entries that want to set the offset/size of other entries
@@ -162,21 +255,32 @@ class Entry_section(Entry):
 
     def ResetForPack(self):
         """Reset offset/size fields so that packing can be done again"""
-        Entry.ResetForPack(self)
+        super().ResetForPack()
         for entry in self._entries.values():
             entry.ResetForPack()
 
     def Pack(self, offset):
         """Pack all entries into the section"""
         self._PackEntries()
-        return Entry.Pack(self, offset)
+        if self._sort:
+            self._SortEntries()
+        self._ExpandEntries()
+
+        data = self._BuildSectionData()
+        self.SetContents(data)
+
+        self.CheckSize()
+
+        offset = super().Pack(offset)
+        self.CheckEntries()
+        return offset
 
     def _PackEntries(self):
-        """Pack all entries into the image"""
+        """Pack all entries into the section"""
         offset = self._skip_at_start
         for entry in self._entries.values():
             offset = entry.Pack(offset)
-        self.size = self.CheckSize()
+        return offset
 
     def _ExpandEntries(self):
         """Expand any entries that are permitted to"""
@@ -198,22 +302,23 @@ class Entry_section(Entry):
             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()
-        self._ExpandEntries()
+        """Check that entries do not overlap or extend outside the section"""
+        max_size = self.size if self.uncomp_size is None else self.uncomp_size
+
         offset = 0
         prev_name = 'None'
         for entry in self._entries.values():
-            entry.CheckOffset()
+            entry.CheckEntries()
             if (entry.offset < self._skip_at_start or
                     entry.offset + entry.size > self._skip_at_start +
-                    self.size):
-                entry.Raise("Offset %#x (%d) is outside the section starting "
-                            "at %#x (%d)" %
-                            (entry.offset, entry.offset, self._skip_at_start,
-                             self._skip_at_start))
-            if entry.offset < offset:
+                    max_size):
+                entry.Raise('Offset %#x (%d) size %#x (%d) is outside the '
+                            "section '%s' starting at %#x (%d) "
+                            'of size %#x (%d)' %
+                            (entry.offset, entry.offset, entry.size, entry.size,
+                             self._node.path, self._skip_at_start,
+                             self._skip_at_start, max_size, max_size))
+            if entry.offset < offset and entry.size:
                 entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
                             "ending at %#x (%d)" %
                             (entry.offset, entry.offset, prev_name, offset, offset))
@@ -226,14 +331,15 @@ class Entry_section(Entry):
             entry.WriteSymbols(self)
 
     def SetCalculatedProperties(self):
-        Entry.SetCalculatedProperties(self)
+        super().SetCalculatedProperties()
         for entry in self._entries.values():
             entry.SetCalculatedProperties()
 
     def SetImagePos(self, image_pos):
-        Entry.SetImagePos(self, image_pos)
-        for entry in self._entries.values():
-            entry.SetImagePos(image_pos + self.offset)
+        super().SetImagePos(image_pos)
+        if self.compress == 'none':
+            for entry in self._entries.values():
+                entry.SetImagePos(image_pos + self.offset)
 
     def ProcessContents(self):
         sizes_ok_base = super(Entry_section, self).ProcessContents()
@@ -243,9 +349,6 @@ class Entry_section(Entry):
                 sizes_ok = False
         return sizes_ok and sizes_ok_base
 
-    def CheckOffset(self):
-        self.CheckEntries()
-
     def WriteMap(self, fd, indent):
         """Write a map of the section to a .map file
 
@@ -282,13 +385,16 @@ class Entry_section(Entry):
                 return entry.GetData()
         source_entry.Raise("Cannot find entry for node '%s'" % node.name)
 
-    def LookupSymbol(self, sym_name, optional, msg):
+    def LookupSymbol(self, sym_name, optional, msg, base_addr):
         """Look up a symbol in an ELF file
 
         Looks up a symbol in an ELF file. Only entry types which come from an
         ELF image can be used by this function.
 
-        At present the only entry property supported is offset.
+        At present the only entry properties supported are:
+            offset
+            image_pos - 'base_addr' is added if this is not an end-at-4gb image
+            size
 
         Args:
             sym_name: Symbol name in the ELF file to look up in the format
@@ -301,6 +407,12 @@ class Entry_section(Entry):
             optional: True if the symbol is optional. If False this function
                 will raise if the symbol is not found
             msg: Message to display if an error occurs
+            base_addr: Base address of image. This is added to the returned
+                image_pos in most cases so that the returned position indicates
+                where the targetted entry/binary has actually been loaded. But
+                if end-at-4gb is used, this is not done, since the binary is
+                already assumed to be linked to the ROM position and using
+                execute-in-place (XIP).
 
         Returns:
             Value that should be assigned to that symbol, or None if it was
@@ -335,7 +447,12 @@ class Entry_section(Entry):
         if prop_name == 'offset':
             return entry.offset
         elif prop_name == 'image_pos':
-            return entry.image_pos
+            value = entry.image_pos
+            if not self.GetImage()._end_4gb:
+                value += base_addr
+            return value
+        if prop_name == 'size':
+            return entry.size
         else:
             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
 
@@ -371,7 +488,7 @@ class Entry_section(Entry):
             Image size as an integer number of bytes, which may be None if the
                 image size is dynamic and its sections have not yet been packed
         """
-        return self.image.size
+        return self.GetImage().size
 
     def FindEntryType(self, etype):
         """Find an entry type in the section
@@ -387,7 +504,7 @@ class Entry_section(Entry):
         return None
 
     def GetEntryContents(self):
-        """Call ObtainContents() for the section
+        """Call ObtainContents() for each entry in the section
         """
         todo = self._entries.values()
         for passnum in range(3):
@@ -415,8 +532,8 @@ class Entry_section(Entry):
         if not entry:
             self._Raise("Unable to set offset/size for unknown entry '%s'" %
                         name)
-        entry.SetOffsetSize(self._skip_at_start + offset if offset else None,
-                            size)
+        entry.SetOffsetSize(self._skip_at_start + offset if offset is not None
+                            else None, size)
 
     def GetEntryOffsets(self):
         """Handle entries that want to set the offset/size of other entries
@@ -429,18 +546,13 @@ class Entry_section(Entry):
             for name, info in offset_dict.items():
                 self._SetEntryOffsetSize(name, *info)
 
-
     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.offset + entry.size)
-
-        contents_size -= self._skip_at_start
+        contents_size = len(self.data)
 
         size = self.size
         if not size:
-            size = self.pad_before + contents_size + self.pad_after
+            data = self.GetPaddedData(self.data)
+            size = len(data)
             size = tools.Align(size, self.align_size)
 
         if self.size and contents_size > self.size:
@@ -460,3 +572,79 @@ class Entry_section(Entry):
                            self.image_pos, None, self.offset, self)
         for entry in self._entries.values():
             entry.ListEntries(entries, indent + 1)
+
+    def LoadData(self, decomp=True):
+        for entry in self._entries.values():
+            entry.LoadData(decomp)
+        self.Detail('Loaded data')
+
+    def GetImage(self):
+        """Get the image containing this section
+
+        Note that a top-level section is actually an Image, so this function may
+        return self.
+
+        Returns:
+            Image object containing this section
+        """
+        if not self.section:
+            return self
+        return self.section.GetImage()
+
+    def GetSort(self):
+        """Check if the entries in this section will be sorted
+
+        Returns:
+            True if to be sorted, False if entries will be left in the order
+                they appear in the device tree
+        """
+        return self._sort
+
+    def ReadData(self, decomp=True):
+        tout.Info("ReadData path='%s'" % self.GetPath())
+        parent_data = self.section.ReadData(True)
+        tout.Info('%s: Reading data from offset %#x-%#x, size %#x' %
+                  (self.GetPath(), self.offset, self.offset + self.size,
+                   self.size))
+        data = parent_data[self.offset:self.offset + self.size]
+        return data
+
+    def ReadChildData(self, child, decomp=True):
+        tout.Debug("ReadChildData for child '%s'" % child.GetPath())
+        parent_data = self.ReadData(True)
+        offset = child.offset - self._skip_at_start
+        tout.Debug("Extract for child '%s': offset %#x, skip_at_start %#x, result %#x" %
+                   (child.GetPath(), child.offset, self._skip_at_start, offset))
+        data = parent_data[offset:offset + child.size]
+        if decomp:
+            indata = data
+            data = tools.Decompress(indata, child.compress)
+            if child.uncomp_size:
+                tout.Info("%s: Decompressing data size %#x with algo '%s' to data size %#x" %
+                            (child.GetPath(), len(indata), child.compress,
+                            len(data)))
+        return data
+
+    def WriteChildData(self, child):
+        return True
+
+    def SetAllowMissing(self, allow_missing):
+        """Set whether a section allows missing external blobs
+
+        Args:
+            allow_missing: True if allowed, False if not allowed
+        """
+        self.allow_missing = allow_missing
+        for entry in self._entries.values():
+            entry.SetAllowMissing(allow_missing)
+
+    def CheckMissing(self, missing_list):
+        """Check if any entries in this section have missing external blobs
+
+        If there are missing blobs, the entries are added to the list
+
+        Args:
+            missing_list: List of Entry objects to be added to
+        """
+        for entry in self._entries.values():
+            entry.CheckMissing(missing_list)