mirror of
				https://github.com/smaeul/u-boot.git
				synced 2025-10-31 03:58:17 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			465 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			465 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # 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
 | |
| from sets import Set
 | |
| import sys
 | |
| 
 | |
| import fdt_util
 | |
| import re
 | |
| import state
 | |
| 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
 | |
|         _parent_section: Parent Section object which created this Section
 | |
|         _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 offset 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 offset, 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 offset of entries. For example,
 | |
|             if _pad_before 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.
 | |
|         _end_4gb: Indicates that the section ends at the 4GB boundary. This is
 | |
|             used for x86 images, which want to use offsets such that a memory
 | |
|             address (like 0xff800000) is the first entry offset. This causes
 | |
|             _skip_at_start to be set to the starting memory address.
 | |
|         _name_prefix: Prefix to add to the name of all entries within this
 | |
|             section
 | |
|         _entries: OrderedDict() of entries
 | |
|     """
 | |
|     def __init__(self, name, parent_section, node, image, test=False):
 | |
|         global entry
 | |
|         global Entry
 | |
|         import entry
 | |
|         from entry import Entry
 | |
| 
 | |
|         self._parent_section = parent_section
 | |
|         self._name = name
 | |
|         self._node = node
 | |
|         self._image = image
 | |
|         self._offset = 0
 | |
|         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 = None
 | |
|         self._end_4gb = False
 | |
|         self._name_prefix = ''
 | |
|         self._entries = OrderedDict()
 | |
|         self._image_pos = None
 | |
|         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-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')
 | |
| 
 | |
|     def _ReadEntries(self):
 | |
|         for node in self._node.subnodes:
 | |
|             if node.name == 'hash':
 | |
|                 continue
 | |
|             entry = Entry.Create(self, node)
 | |
|             entry.SetPrefix(self._name_prefix)
 | |
|             self._entries[node.name] = entry
 | |
| 
 | |
|     def GetFdtSet(self):
 | |
|         """Get the set of device tree files used by this image"""
 | |
|         fdt_set = Set()
 | |
|         for entry in self._entries.values():
 | |
|             fdt_set.update(entry.GetFdtSet())
 | |
|         return fdt_set
 | |
| 
 | |
|     def SetOffset(self, offset):
 | |
|         self._offset = offset
 | |
| 
 | |
|     def 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"""
 | |
|         for prop in ['offset', 'size', 'image-pos']:
 | |
|             if not prop in self._node.props:
 | |
|                 state.AddZeroProp(self._node, prop)
 | |
|         state.CheckAddHashProp(self._node)
 | |
|         for entry in self._entries.values():
 | |
|             entry.AddMissingProperties()
 | |
| 
 | |
|     def SetCalculatedProperties(self):
 | |
|         state.SetInt(self._node, 'offset', self._offset)
 | |
|         state.SetInt(self._node, 'size', self._size)
 | |
|         image_pos = self._image_pos
 | |
|         if self._parent_section:
 | |
|             image_pos -= self._parent_section.GetRootSkipAtStart()
 | |
|         state.SetInt(self._node, 'image-pos', image_pos)
 | |
|         for entry in self._entries.values():
 | |
|             entry.SetCalculatedProperties()
 | |
| 
 | |
|     def ProcessFdt(self, fdt):
 | |
|         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 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.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 _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
 | |
|         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
 | |
|             size: New size
 | |
|         """
 | |
|         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, 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.iteritems():
 | |
|                 self._SetEntryOffsetSize(name, *info)
 | |
| 
 | |
|     def PackEntries(self):
 | |
|         """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()
 | |
| 
 | |
|     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 _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 CheckEntries(self):
 | |
|         """Check that entries do not overlap or extend outside the section
 | |
| 
 | |
|         This also sorts entries, if needed and expands
 | |
|         """
 | |
|         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 SetImagePos(self, image_pos):
 | |
|         self._image_pos = image_pos
 | |
|         for entry in self._entries.values():
 | |
|             entry.SetImagePos(image_pos)
 | |
| 
 | |
|     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_offset):
 | |
|         """Write the section to a file"""
 | |
|         fd.seek(base_offset)
 | |
|         fd.write(self.GetData())
 | |
| 
 | |
|     def GetData(self):
 | |
|         """Get the contents of the section"""
 | |
|         section_data = chr(self._pad_byte) * self._size
 | |
| 
 | |
|         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
 | |
| 
 | |
|     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 offset.
 | |
| 
 | |
|         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
 | |
| 
 | |
|         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':
 | |
|             return entry.image_pos
 | |
|         else:
 | |
|             raise ValueError("%s: No such property '%s'" % (msg, prop_name))
 | |
| 
 | |
|     def GetEntries(self):
 | |
|         """Get the number of entries in a section
 | |
| 
 | |
|         Returns:
 | |
|             Number of entries in a section
 | |
|         """
 | |
|         return self._entries
 | |
| 
 | |
|     def GetSize(self):
 | |
|         """Get the size of a section in bytes
 | |
| 
 | |
|         This is only meaningful if the section has a pre-defined size, or the
 | |
|         entries within it have been packed, so that the size has been
 | |
|         calculated.
 | |
| 
 | |
|         Returns:
 | |
|             Entry size in bytes
 | |
|         """
 | |
|         return self._size
 | |
| 
 | |
|     def WriteMap(self, fd, indent):
 | |
|         """Write a map of the section to a .map file
 | |
| 
 | |
|         Args:
 | |
|             fd: File to write the map to
 | |
|         """
 | |
|         Entry.WriteMapLine(fd, indent, self._name, self._offset, self._size,
 | |
|                            self._image_pos)
 | |
|         for entry in self._entries.values():
 | |
|             entry.WriteMap(fd, indent + 1)
 | |
| 
 | |
|     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 ExpandSize(self, size):
 | |
|         if size != self._size:
 | |
|             self._size = size
 | |
| 
 | |
|     def GetRootSkipAtStart(self):
 | |
|         if self._parent_section:
 | |
|             return self._parent_section.GetRootSkipAtStart()
 | |
|         return self._skip_at_start
 | |
| 
 | |
|     def GetImageSize(self):
 | |
|         return self._image._size
 |