rockchip: rk3399: Add Nanopi M4 2GB board support
[platform/kernel/u-boot.git] / tools / binman / etype / section.py
index b90b80e..89b7bf6 100644 (file)
 # SPDX-License-Identifier:      GPL-2.0+
 # Copyright (c) 2018 Google, Inc
 # Written by Simon Glass <sjg@chromium.org>
-#
-# Entry-type module for sections, which are entries which can contain other
-# entries.
-#
+
+"""Entry-type module for sections (groups of entries)
+
+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
+import tout
 
-import bsection
 
 class Entry_section(Entry):
-    def __init__(self, image, etype, node):
-        Entry.__init__(self, image, etype, node)
-        self._section = bsection.Section(node.name, node)
+    """Entry that contains other entries
+
+    Properties / Entry arguments: (see binman README for more information)
+        pad-byte: Pad byte to use when padding
+        sort-by-offset: True if entries should be sorted by offset, False if
+            they must be in-order in the device tree description
+        end-at-4gb: Used to build an x86 ROM which ends at 4GB (2^32)
+        skip-at-start: Number of bytes before the first entry starts. These
+            effectively adjust the starting offset of entries. For example,
+            if this is 16, then the first entry would start at 16. An entry
+            with offset = 20 would in fact be written at offset 4 in the image
+            file, since the first 16 bytes are skipped when writing.
+        name-prefix: Adds a prefix to the name of every entry in the section
+            when writing out the map
+
+    Since a section is also an entry, it inherits all the properies of entries
+    too.
+
+    A section is an entry which can contain other entries, thus allowing
+    hierarchical images to be created. See 'Sections and hierarchical images'
+    in the binman README for more information.
+    """
+    def __init__(self, section, etype, node, test=False):
+        if not test:
+            Entry.__init__(self, section, etype, node)
+        self._entries = OrderedDict()
+        self._pad_byte = 0
+        self._sort = False
+        self._skip_at_start = None
+        self._end_4gb = False
+
+    def ReadNode(self):
+        """Read properties from the image node"""
+        Entry.ReadNode(self)
+        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')
+        self._skip_at_start = fdt_util.GetInt(self._node, 'skip-at-start')
+        if self._end_4gb:
+            if not self.size:
+                self.Raise("Section size must be provided when using end-at-4gb")
+            if self._skip_at_start is not None:
+                self.Raise("Provide either 'end-at-4gb' or 'skip-at-start'")
+            else:
+                self._skip_at_start = 0x100000000 - self.size
+        else:
+            if self._skip_at_start is None:
+                self._skip_at_start = 0
+        self._name_prefix = fdt_util.GetString(self._node, 'name-prefix')
+        filename = fdt_util.GetString(self._node, 'filename')
+        if filename:
+            self._filename = filename
+
+        self._ReadEntries()
+
+    def _ReadEntries(self):
+        for node in self._node.subnodes:
+            if node.name == 'hash':
+                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():
+            fdts.update(entry.GetFdts())
+        return fdts
 
     def ProcessFdt(self, fdt):
-        return self._section.ProcessFdt(fdt)
+        """Allow entries to adjust the device tree
+
+        Some entries need to adjust the device tree for their purposes. This
+        may involve adding or deleting properties.
+        """
+        todo = self._entries.values()
+        for passnum in range(3):
+            next_todo = []
+            for entry in todo:
+                if not entry.ProcessFdt(fdt):
+                    next_todo.append(entry)
+            todo = next_todo
+            if not todo:
+                break
+        if todo:
+            self.Raise('Internal error: Could not complete processing of Fdt: remaining %s' %
+                       todo)
+        return True
+
+    def ExpandEntries(self):
+        """Expand out any entries which have calculated sub-entries
+
+        Some entries are expanded out at runtime, e.g. 'files', which produces
+        a section containing a list of files. Process these entries so that
+        this information is added to the device tree.
+        """
+        Entry.ExpandEntries(self)
+        for entry in self._entries.values():
+            entry.ExpandEntries()
 
     def AddMissingProperties(self):
+        """Add new properties to the device tree as needed for this entry"""
         Entry.AddMissingProperties(self)
-        self._section.AddMissingProperties()
+        for entry in self._entries.values():
+            entry.AddMissingProperties()
 
     def ObtainContents(self):
-        return self._section.GetEntryContents()
+        return self.GetEntryContents()
 
     def GetData(self):
-        return self._section.GetData()
+        section_data = b''
+
+        for entry in self._entries.values():
+            data = entry.GetData()
+            base = self.pad_before + (entry.offset or 0) - self._skip_at_start
+            pad = base - len(section_data)
+            if pad > 0:
+                section_data += tools.GetBytes(self._pad_byte, pad)
+            section_data += data
+        if self.size:
+            pad = self.size - len(section_data)
+            if pad > 0:
+                section_data += tools.GetBytes(self._pad_byte, pad)
+        self.Detail('GetData: %d entries, total size %#x' %
+                    (len(self._entries), len(section_data)))
+        return section_data
 
     def GetOffsets(self):
         """Handle entries that want to set the offset/size of other entries
@@ -36,34 +165,94 @@ class Entry_section(Entry):
         This calls each entry's GetOffsets() method. If it returns a list
         of entries to update, it updates them.
         """
-        self._section.GetEntryOffsets()
+        self.GetEntryOffsets()
         return {}
 
+    def ResetForPack(self):
+        """Reset offset/size fields so that packing can be done again"""
+        Entry.ResetForPack(self)
+        for entry in self._entries.values():
+            entry.ResetForPack()
+
     def Pack(self, offset):
         """Pack all entries into the section"""
-        self._section.PackEntries()
-        self._section.SetOffset(offset)
-        self.size = self._section.GetSize()
-        return super(Entry_section, self).Pack(offset)
+        self._PackEntries()
+        return Entry.Pack(self, offset)
 
-    def SetImagePos(self, image_pos):
-        Entry.SetImagePos(self, image_pos)
-        self._section.SetImagePos(image_pos + self.offset)
+    def _PackEntries(self):
+        """Pack all entries into the image"""
+        offset = self._skip_at_start
+        for entry in self._entries.values():
+            offset = entry.Pack(offset)
+        self.size = self.CheckSize()
+
+    def _ExpandEntries(self):
+        """Expand any entries that are permitted to"""
+        exp_entry = None
+        for entry in self._entries.values():
+            if exp_entry:
+                exp_entry.ExpandToLimit(entry.offset)
+                exp_entry = None
+            if entry.expand_size:
+                exp_entry = entry
+        if exp_entry:
+            exp_entry.ExpandToLimit(self.size)
+
+    def _SortEntries(self):
+        """Sort entries by offset"""
+        entries = sorted(self._entries.values(), key=lambda entry: entry.offset)
+        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()
+        self._ExpandEntries()
+        offset = 0
+        prev_name = 'None'
+        for entry in self._entries.values():
+            entry.CheckOffset()
+            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:
+                entry.Raise("Offset %#x (%d) overlaps with previous entry '%s' "
+                            "ending at %#x (%d)" %
+                            (entry.offset, entry.offset, prev_name, offset, offset))
+            offset = entry.offset + entry.size
+            prev_name = entry.GetPath()
 
     def WriteSymbols(self, section):
         """Write symbol values into binary files for access at run time"""
-        self._section.WriteSymbols()
+        for entry in self._entries.values():
+            entry.WriteSymbols(self)
 
     def SetCalculatedProperties(self):
         Entry.SetCalculatedProperties(self)
-        self._section.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)
 
     def ProcessContents(self):
-        self._section.ProcessEntryContents()
-        super(Entry_section, self).ProcessContents()
+        sizes_ok_base = super(Entry_section, self).ProcessContents()
+        sizes_ok = True
+        for entry in self._entries.values():
+            if not entry.ProcessContents():
+                sizes_ok = False
+        return sizes_ok and sizes_ok_base
 
     def CheckOffset(self):
-        self._section.CheckEntries()
+        self.CheckEntries()
 
     def WriteMap(self, fd, indent):
         """Write a map of the section to a .map file
@@ -71,4 +260,280 @@ class Entry_section(Entry):
         Args:
             fd: File to write the map to
         """
-        self._section.WriteMap(fd, indent)
+        Entry.WriteMapLine(fd, indent, self.name, self.offset or 0,
+                           self.size, self.image_pos)
+        for entry in self._entries.values():
+            entry.WriteMap(fd, indent + 1)
+
+    def GetEntries(self):
+        return self._entries
+
+    def GetContentsByPhandle(self, phandle, source_entry):
+        """Get the data contents of an entry specified by a phandle
+
+        This uses a phandle to look up a node and and find the entry
+        associated with it. Then it returnst he contents of that entry.
+
+        Args:
+            phandle: Phandle to look up (integer)
+            source_entry: Entry containing that phandle (used for error
+                reporting)
+
+        Returns:
+            data from associated entry (as a string), or None if not found
+        """
+        node = self._node.GetFdt().LookupPhandle(phandle)
+        if not node:
+            source_entry.Raise("Cannot find node for phandle %d" % phandle)
+        for entry in self._entries.values():
+            if entry._node == node:
+                return entry.GetData()
+        source_entry.Raise("Cannot find entry for node '%s'" % node.name)
+
+    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 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
+                _binman_<entry>_prop_<property> where <entry> is the name of
+                the entry and <property> is the property to find (e.g.
+                _binman_u_boot_prop_offset). As a special case, you can append
+                _any to <entry> to have it search for any matching entry. E.g.
+                _binman_u_boot_any_prop_offset will match entries called u-boot,
+                u-boot-img and u-boot-nodtb)
+            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
+                optional and not found
+
+        Raises:
+            ValueError if the symbol is invalid or not found, or references a
+                property which is not supported
+        """
+        m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
+        if not m:
+            raise ValueError("%s: Symbol '%s' has invalid format" %
+                             (msg, sym_name))
+        entry_name, prop_name = m.groups()
+        entry_name = entry_name.replace('_', '-')
+        entry = self._entries.get(entry_name)
+        if not entry:
+            if entry_name.endswith('-any'):
+                root = entry_name[:-4]
+                for name in self._entries:
+                    if name.startswith(root):
+                        rest = name[len(root):]
+                        if rest in ['', '-img', '-nodtb']:
+                            entry = self._entries[name]
+        if not entry:
+            err = ("%s: Entry '%s' not found in list (%s)" %
+                   (msg, entry_name, ','.join(self._entries.keys())))
+            if optional:
+                print('Warning: %s' % err, file=sys.stderr)
+                return None
+            raise ValueError(err)
+        if prop_name == 'offset':
+            return entry.offset
+        elif prop_name == '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))
+
+    def GetRootSkipAtStart(self):
+        """Get the skip-at-start value for the top-level section
+
+        This is used to find out the starting offset for root section that
+        contains this section. If this is a top-level section then it returns
+        the skip-at-start offset for this section.
+
+        This is used to get the absolute position of section within the image.
+
+        Returns:
+            Integer skip-at-start value for the root section containing this
+                section
+        """
+        if self.section:
+            return self.section.GetRootSkipAtStart()
+        return self._skip_at_start
+
+    def GetStartOffset(self):
+        """Get the start offset for this section
+
+        Returns:
+            The first available offset in this section (typically 0)
+        """
+        return self._skip_at_start
+
+    def GetImageSize(self):
+        """Get the size of the image containing this section
+
+        Returns:
+            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.GetImage().size
+
+    def FindEntryType(self, etype):
+        """Find an entry type in the section
+
+        Args:
+            etype: Entry type to find
+        Returns:
+            entry matching that type, or None if not found
+        """
+        for entry in self._entries.values():
+            if entry.etype == etype:
+                return entry
+        return None
+
+    def GetEntryContents(self):
+        """Call ObtainContents() for the section
+        """
+        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
+        if todo:
+            self.Raise('Internal error: Could not complete processing of contents: remaining %s' %
+                       todo)
+        return True
+
+    def _SetEntryOffsetSize(self, name, offset, size):
+        """Set the offset and size of an entry
+
+        Args:
+            name: Entry name to update
+            offset: New offset, or None to leave alone
+            size: New size, or None to leave alone
+        """
+        entry = self._entries.get(name)
+        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)
+
+    def GetEntryOffsets(self):
+        """Handle entries that want to set the offset/size of other entries
+
+        This calls each entry's GetOffsets() method. If it returns a list
+        of entries to update, it updates them.
+        """
+        for entry in self._entries.values():
+            offset_dict = entry.GetOffsets()
+            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
+
+        size = self.size
+        if not size:
+            size = self.pad_before + contents_size + self.pad_after
+            size = tools.Align(size, self.align_size)
+
+        if self.size and contents_size > self.size:
+            self._Raise("contents size %#x (%d) exceeds section 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))
+        return size
+
+    def ListEntries(self, entries, indent):
+        """List the files in the section"""
+        Entry.AddEntryInfo(entries, indent, self.name, 'section', self.size,
+                           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