| # Copyright (c) 2016 Google, Inc |
| # Written by Simon Glass <sjg@chromium.org> |
| # |
| # SPDX-License-Identifier: GPL-2.0+ |
| # |
| # Class for an image, the output of binman |
| # |
| |
| from collections import OrderedDict |
| from operator import attrgetter |
| |
| import entry |
| from entry import Entry |
| import fdt_util |
| import tools |
| |
| class Image: |
| """A Image, representing an output from binman |
| |
| An image is comprised of a collection of entries each containing binary |
| data. The image size must be large enough to hold all of this data. |
| |
| This class implements the various operations needed for images. |
| |
| Atrtributes: |
| _node: Node object that contains the image definition in device tree |
| _name: Image name |
| _size: Image size in bytes, or None if not known yet |
| _align_size: Image 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 image where there is no entry |
| _filename: Output filename for image |
| _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 |
| effecively 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 image 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): |
| self._node = node |
| self._name = name |
| self._size = None |
| self._align_size = None |
| self._pad_before = 0 |
| self._pad_after = 0 |
| self._pad_byte = 0 |
| self._filename = '%s.bin' % self._name |
| self._sort = False |
| self._skip_at_start = 0 |
| self._end_4gb = False |
| self._entries = OrderedDict() |
| |
| self._ReadNode() |
| self._ReadEntries() |
| |
| def _ReadNode(self): |
| """Read properties from the image 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) |
| filename = fdt_util.GetString(self._node, 'filename') |
| if filename: |
| self._filename = filename |
| 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("Image size must be provided when using end-at-4gb") |
| if self._end_4gb: |
| self._skip_at_start = 0x100000000 - self._size |
| |
| 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.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 image 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)) |
| |
| def _Raise(self, msg): |
| """Raises an error for this image |
| |
| Args: |
| msg: Error message to use in the raise string |
| Raises: |
| ValueError() |
| """ |
| raise ValueError("Image '%s': %s" % (self._node.path, msg)) |
| |
| def _ReadEntries(self): |
| for node in self._node.subnodes: |
| self._entries[node.name] = Entry.Create(self, node) |
| |
| def FindEntryType(self, etype): |
| """Find an entry type in the image |
| |
| 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 image""" |
| 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 image""" |
| if self._sort: |
| self._SortEntries() |
| pos = 0 |
| prev_name = 'None' |
| for entry in self._entries.values(): |
| if (entry.pos < self._skip_at_start or |
| entry.pos >= self._skip_at_start + self._size): |
| entry.Raise("Position %#x (%d) is outside the image 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 BuildImage(self): |
| """Write the image to a file""" |
| fname = tools.GetOutputFilename(self._filename) |
| with open(fname, 'wb') as fd: |
| fd.write(chr(self._pad_byte) * self._size) |
| |
| for entry in self._entries.values(): |
| data = entry.GetData() |
| fd.seek(self._pad_before + entry.pos - self._skip_at_start) |
| fd.write(data) |