# SPDX-License-Identifier: GPL-2.0+ # Copyright (c) 2018 Google, Inc # Written by Simon Glass # # 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__prop_ where is the name of the entry and is the property to find (e.g. _binman_u_boot_prop_offset). As a special case, you can append _any to 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