Skip to content
Snippets Groups Projects
bsection.py 11.4 KiB
Newer Older
  • Learn to ignore specific revisions
  • # SPDX-License-Identifier: GPL-2.0+
    # Copyright (c) 2018 Google, Inc
    # Written by Simon Glass <sjg@chromium.org>
    #
    # Base class for sections (collections of entries)
    #
    
    from __future__ import print_function
    
    from collections import OrderedDict
    import sys
    
    import fdt_util
    import re
    import tools
    
    class Section(object):
        """A section which contains multiple entries
    
        A section represents a collection of entries. There must be one or more
        sections in an image. Sections are used to group entries together.
    
        Attributes:
            _node: Node object that contains the section definition in device tree
            _size: Section size in bytes, or None if not known yet
            _align_size: Section 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 section where there is no entry
            _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
                effectively 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 section 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
        """
        def __init__(self, name, node, test=False):
            global entry
            global Entry
            import entry
            from entry import Entry
    
            self._node = node
            self._size = None
            self._align_size = None
            self._pad_before = 0
            self._pad_after = 0
            self._pad_byte = 0
            self._sort = False
            self._skip_at_start = 0
            self._end_4gb = False
            self._entries = OrderedDict()
            if not test:
                self._ReadNode()
                self._ReadEntries()
    
        def _ReadNode(self):
            """Read properties from the section 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)
            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("Section size must be provided when using end-at-4gb")
            if self._end_4gb:
                self._skip_at_start = 0x100000000 - self._size
    
        def _ReadEntries(self):
            for node in self._node.subnodes:
                self._entries[node.name] = Entry.Create(self, node)
    
        def CheckSize(self):
            """Check that the section 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
    
            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 _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 GetPath(self):
            """Get the path of an image (in the FDT)
    
            Returns:
                Full path of the node for this image
            """
            return self._node.path
    
        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 each entry
    
            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.
    
            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
    
            Args:
                name: Entry name to update
                pos: New position
                size: New size
            """
            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)
    
        def GetEntryPositions(self):
            """Handle entries that want to set the position/size of other entries
    
            This calls each entry's GetPositions() method. If it returns a list
            of entries to update, it updates them.
            """
            for entry in self._entries.values():
                pos_dict = entry.GetPositions()
                for name, info in pos_dict.iteritems():
                    self._SetEntryPosSize(name, *info)
    
        def PackEntries(self):
            """Pack all entries into the section"""
            pos = self._skip_at_start
            for entry in self._entries.values():
                pos = entry.Pack(pos)
    
        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 section"""
            if self._sort:
                self._SortEntries()
            pos = 0
            prev_name = 'None'
            for entry in self._entries.values():
    
                entry.CheckPosition()
    
                if (entry.pos < self._skip_at_start or
                    entry.pos >= self._skip_at_start + self._size):
                    entry.Raise("Position %#x (%d) is outside the section 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()
    
        def ProcessEntryContents(self):
            """Call the ProcessContents() method for each entry
    
            This is intended to adjust the contents as needed by the entry type.
            """
            for entry in self._entries.values():
                entry.ProcessContents()
    
        def WriteSymbols(self):
            """Write symbol values into binary files for access at run time"""
            for entry in self._entries.values():
                entry.WriteSymbols(self)
    
        def BuildSection(self, fd, base_pos):
            """Write the section to a file"""
            fd.seek(base_pos)
            fd.write(self.GetData())
    
        def GetData(self):
            """Write the section to a file"""
            section_data = chr(self._pad_byte) * self._size
    
            for entry in self._entries.values():
                data = entry.GetData()
                base = self._pad_before + entry.pos - self._skip_at_start
                section_data = (section_data[:base] + data +
                                section_data[base + len(data):])
            return section_data
    
        def LookupSymbol(self, sym_name, optional, msg):
            """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 pos.
    
            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_pos). 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_pos 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
    
            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 == 'pos':
                return entry.pos
            else:
                raise ValueError("%s: No such property '%s'" % (msg, prop_name))
    
        def GetEntries(self):
            return self._entries