1 # Copyright (c) 2016 Google, Inc
2 # Written by Simon Glass <sjg@chromium.org>
4 # SPDX-License-Identifier: GPL-2.0+
6 # Class for an image, the output of binman
9 from collections import OrderedDict
10 from operator import attrgetter
16 """A Image, representing an output from binman
18 An image is comprised of a collection of entries each containing binary
19 data. The image size must be large enough to hold all of this data.
21 This class implements the various operations needed for images.
24 _node: Node object that contains the image definition in device tree
26 _size: Image size in bytes, or None if not known yet
27 _align_size: Image size alignment, or None
28 _pad_before: Number of bytes before the first entry starts. This
29 effectively changes the place where entry position 0 starts
30 _pad_after: Number of bytes after the last entry ends. The last
31 entry will finish on or before this boundary
32 _pad_byte: Byte to use to pad the image where there is no entry
33 _filename: Output filename for image
34 _sort: True if entries should be sorted by position, False if they
35 must be in-order in the device tree description
36 _skip_at_start: Number of bytes before the first entry starts. These
37 effecively adjust the starting position of entries. For example,
38 if _pad_before is 16, then the first entry would start at 16.
39 An entry with pos = 20 would in fact be written at position 4
41 _end_4gb: Indicates that the image ends at the 4GB boundary. This is
42 used for x86 images, which want to use positions such that a
43 memory address (like 0xff800000) is the first entry position.
44 This causes _skip_at_start to be set to the starting memory
46 _entries: OrderedDict() of entries
48 def __init__(self, name, node):
52 from entry import Entry
57 self._align_size = None
61 self._filename = '%s.bin' % self._name
63 self._skip_at_start = 0
65 self._entries = OrderedDict()
71 """Read properties from the image node"""
72 self._size = fdt_util.GetInt(self._node, 'size')
73 self._align_size = fdt_util.GetInt(self._node, 'align-size')
74 if tools.NotPowerOfTwo(self._align_size):
75 self._Raise("Alignment size %s must be a power of two" %
77 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
78 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
79 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
80 filename = fdt_util.GetString(self._node, 'filename')
82 self._filename = filename
83 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
84 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
85 if self._end_4gb and not self._size:
86 self._Raise("Image size must be provided when using end-at-4gb")
88 self._skip_at_start = 0x100000000 - self._size
91 """Check that the image contents does not exceed its size, etc."""
93 for entry in self._entries.values():
94 contents_size = max(contents_size, entry.pos + entry.size)
96 contents_size -= self._skip_at_start
100 size = self._pad_before + contents_size + self._pad_after
101 size = tools.Align(size, self._align_size)
103 if self._size and contents_size > self._size:
104 self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
105 (contents_size, contents_size, self._size, self._size))
108 if self._size != tools.Align(self._size, self._align_size):
109 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
110 (self._size, self._size, self._align_size, self._align_size))
112 def _Raise(self, msg):
113 """Raises an error for this image
116 msg: Error message to use in the raise string
120 raise ValueError("Image '%s': %s" % (self._node.path, msg))
122 def _ReadEntries(self):
123 for node in self._node.subnodes:
124 self._entries[node.name] = Entry.Create(self, node)
126 def FindEntryType(self, etype):
127 """Find an entry type in the image
130 etype: Entry type to find
132 entry matching that type, or None if not found
134 for entry in self._entries.values():
135 if entry.etype == etype:
139 def GetEntryContents(self):
140 """Call ObtainContents() for each entry
142 This calls each entry's ObtainContents() a few times until they all
143 return True. We stop calling an entry's function once it returns
144 True. This allows the contents of one entry to depend on another.
146 After 3 rounds we give up since it's likely an error.
148 todo = self._entries.values()
149 for passnum in range(3):
152 if not entry.ObtainContents():
153 next_todo.append(entry)
158 def _SetEntryPosSize(self, name, pos, size):
159 """Set the position and size of an entry
162 name: Entry name to update
166 entry = self._entries.get(name)
168 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
169 entry.SetPositionSize(self._skip_at_start + pos, size)
171 def GetEntryPositions(self):
172 """Handle entries that want to set the position/size of other entries
174 This calls each entry's GetPositions() method. If it returns a list
175 of entries to update, it updates them.
177 for entry in self._entries.values():
178 pos_dict = entry.GetPositions()
179 for name, info in pos_dict.iteritems():
180 self._SetEntryPosSize(name, *info)
182 def PackEntries(self):
183 """Pack all entries into the image"""
184 pos = self._skip_at_start
185 for entry in self._entries.values():
186 pos = entry.Pack(pos)
188 def _SortEntries(self):
189 """Sort entries by position"""
190 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
191 self._entries.clear()
192 for entry in entries:
193 self._entries[entry._node.name] = entry
195 def CheckEntries(self):
196 """Check that entries do not overlap or extend outside the image"""
201 for entry in self._entries.values():
202 if (entry.pos < self._skip_at_start or
203 entry.pos >= self._skip_at_start + self._size):
204 entry.Raise("Position %#x (%d) is outside the image starting "
206 (entry.pos, entry.pos, self._skip_at_start,
207 self._skip_at_start))
209 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
210 "ending at %#x (%d)" %
211 (entry.pos, entry.pos, prev_name, pos, pos))
212 pos = entry.pos + entry.size
213 prev_name = entry.GetPath()
215 def ProcessEntryContents(self):
216 """Call the ProcessContents() method for each entry
218 This is intended to adjust the contents as needed by the entry type.
220 for entry in self._entries.values():
221 entry.ProcessContents()
223 def BuildImage(self):
224 """Write the image to a file"""
225 fname = tools.GetOutputFilename(self._filename)
226 with open(fname, 'wb') as fd:
227 fd.write(chr(self._pad_byte) * self._size)
229 for entry in self._entries.values():
230 data = entry.GetData()
231 fd.seek(self._pad_before + entry.pos - self._skip_at_start)