diff options
Diffstat (limited to 'tools/binman/image.py')
-rw-r--r-- | tools/binman/image.py | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/tools/binman/image.py b/tools/binman/image.py new file mode 100644 index 0000000000..07fc930665 --- /dev/null +++ b/tools/binman/image.py @@ -0,0 +1,229 @@ +# 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) |