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 __future__ import print_function
11 from collections import OrderedDict
12 from operator import attrgetter
20 """A Image, representing an output from binman
22 An image is comprised of a collection of entries each containing binary
23 data. The image size must be large enough to hold all of this data.
25 This class implements the various operations needed for images.
28 _node: Node object that contains the image definition in device tree
30 _size: Image size in bytes, or None if not known yet
31 _align_size: Image size alignment, or None
32 _pad_before: Number of bytes before the first entry starts. This
33 effectively changes the place where entry position 0 starts
34 _pad_after: Number of bytes after the last entry ends. The last
35 entry will finish on or before this boundary
36 _pad_byte: Byte to use to pad the image where there is no entry
37 _filename: Output filename for image
38 _sort: True if entries should be sorted by position, False if they
39 must be in-order in the device tree description
40 _skip_at_start: Number of bytes before the first entry starts. These
41 effecively adjust the starting position of entries. For example,
42 if _pad_before is 16, then the first entry would start at 16.
43 An entry with pos = 20 would in fact be written at position 4
45 _end_4gb: Indicates that the image ends at the 4GB boundary. This is
46 used for x86 images, which want to use positions such that a
47 memory address (like 0xff800000) is the first entry position.
48 This causes _skip_at_start to be set to the starting memory
50 _entries: OrderedDict() of entries
52 def __init__(self, name, node, test=False):
56 from entry import Entry
61 self._align_size = None
65 self._filename = '%s.bin' % self._name
67 self._skip_at_start = 0
69 self._entries = OrderedDict()
76 """Read properties from the image node"""
77 self._size = fdt_util.GetInt(self._node, 'size')
78 self._align_size = fdt_util.GetInt(self._node, 'align-size')
79 if tools.NotPowerOfTwo(self._align_size):
80 self._Raise("Alignment size %s must be a power of two" %
82 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
83 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
84 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
85 filename = fdt_util.GetString(self._node, 'filename')
87 self._filename = filename
88 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
89 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
90 if self._end_4gb and not self._size:
91 self._Raise("Image size must be provided when using end-at-4gb")
93 self._skip_at_start = 0x100000000 - self._size
96 """Check that the image contents does not exceed its size, etc."""
98 for entry in self._entries.values():
99 contents_size = max(contents_size, entry.pos + entry.size)
101 contents_size -= self._skip_at_start
105 size = self._pad_before + contents_size + self._pad_after
106 size = tools.Align(size, self._align_size)
108 if self._size and contents_size > self._size:
109 self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
110 (contents_size, contents_size, self._size, self._size))
113 if self._size != tools.Align(self._size, self._align_size):
114 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
115 (self._size, self._size, self._align_size, self._align_size))
117 def _Raise(self, msg):
118 """Raises an error for this image
121 msg: Error message to use in the raise string
125 raise ValueError("Image '%s': %s" % (self._node.path, msg))
128 """Get the path of an image (in the FDT)
131 Full path of the node for this image
133 return self._node.path
135 def _ReadEntries(self):
136 for node in self._node.subnodes:
137 self._entries[node.name] = Entry.Create(self, node)
139 def FindEntryType(self, etype):
140 """Find an entry type in the image
143 etype: Entry type to find
145 entry matching that type, or None if not found
147 for entry in self._entries.values():
148 if entry.etype == etype:
152 def GetEntryContents(self):
153 """Call ObtainContents() for each entry
155 This calls each entry's ObtainContents() a few times until they all
156 return True. We stop calling an entry's function once it returns
157 True. This allows the contents of one entry to depend on another.
159 After 3 rounds we give up since it's likely an error.
161 todo = self._entries.values()
162 for passnum in range(3):
165 if not entry.ObtainContents():
166 next_todo.append(entry)
171 def _SetEntryPosSize(self, name, pos, size):
172 """Set the position and size of an entry
175 name: Entry name to update
179 entry = self._entries.get(name)
181 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
182 entry.SetPositionSize(self._skip_at_start + pos, size)
184 def GetEntryPositions(self):
185 """Handle entries that want to set the position/size of other entries
187 This calls each entry's GetPositions() method. If it returns a list
188 of entries to update, it updates them.
190 for entry in self._entries.values():
191 pos_dict = entry.GetPositions()
192 for name, info in pos_dict.iteritems():
193 self._SetEntryPosSize(name, *info)
195 def PackEntries(self):
196 """Pack all entries into the image"""
197 pos = self._skip_at_start
198 for entry in self._entries.values():
199 pos = entry.Pack(pos)
201 def _SortEntries(self):
202 """Sort entries by position"""
203 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
204 self._entries.clear()
205 for entry in entries:
206 self._entries[entry._node.name] = entry
208 def CheckEntries(self):
209 """Check that entries do not overlap or extend outside the image"""
214 for entry in self._entries.values():
215 if (entry.pos < self._skip_at_start or
216 entry.pos >= self._skip_at_start + self._size):
217 entry.Raise("Position %#x (%d) is outside the image starting "
219 (entry.pos, entry.pos, self._skip_at_start,
220 self._skip_at_start))
222 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
223 "ending at %#x (%d)" %
224 (entry.pos, entry.pos, prev_name, pos, pos))
225 pos = entry.pos + entry.size
226 prev_name = entry.GetPath()
228 def ProcessEntryContents(self):
229 """Call the ProcessContents() method for each entry
231 This is intended to adjust the contents as needed by the entry type.
233 for entry in self._entries.values():
234 entry.ProcessContents()
236 def WriteSymbols(self):
237 """Write symbol values into binary files for access at run time"""
238 for entry in self._entries.values():
239 entry.WriteSymbols(self)
241 def BuildImage(self):
242 """Write the image to a file"""
243 fname = tools.GetOutputFilename(self._filename)
244 with open(fname, 'wb') as fd:
245 fd.write(chr(self._pad_byte) * self._size)
247 for entry in self._entries.values():
248 data = entry.GetData()
249 fd.seek(self._pad_before + entry.pos - self._skip_at_start)
252 def LookupSymbol(self, sym_name, optional, msg):
253 """Look up a symbol in an ELF file
255 Looks up a symbol in an ELF file. Only entry types which come from an
256 ELF image can be used by this function.
258 At present the only entry property supported is pos.
261 sym_name: Symbol name in the ELF file to look up in the format
262 _binman_<entry>_prop_<property> where <entry> is the name of
263 the entry and <property> is the property to find (e.g.
264 _binman_u_boot_prop_pos). As a special case, you can append
265 _any to <entry> to have it search for any matching entry. E.g.
266 _binman_u_boot_any_prop_pos will match entries called u-boot,
267 u-boot-img and u-boot-nodtb)
268 optional: True if the symbol is optional. If False this function
269 will raise if the symbol is not found
270 msg: Message to display if an error occurs
273 Value that should be assigned to that symbol, or None if it was
274 optional and not found
277 ValueError if the symbol is invalid or not found, or references a
278 property which is not supported
280 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
282 raise ValueError("%s: Symbol '%s' has invalid format" %
284 entry_name, prop_name = m.groups()
285 entry_name = entry_name.replace('_', '-')
286 entry = self._entries.get(entry_name)
288 if entry_name.endswith('-any'):
289 root = entry_name[:-4]
290 for name in self._entries:
291 if name.startswith(root):
292 rest = name[len(root):]
293 if rest in ['', '-img', '-nodtb']:
294 entry = self._entries[name]
296 err = ("%s: Entry '%s' not found in list (%s)" %
297 (msg, entry_name, ','.join(self._entries.keys())))
299 print('Warning: %s' % err, file=sys.stderr)
301 raise ValueError(err)
302 if prop_name == 'pos':
305 raise ValueError("%s: No such property '%s'" % (msg, prop_name))