1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Class for an image, the output of binman
8 from __future__ import print_function
10 from collections import OrderedDict
11 from operator import attrgetter
19 """A Image, representing an output from binman
21 An image is comprised of a collection of entries each containing binary
22 data. The image size must be large enough to hold all of this data.
24 This class implements the various operations needed for images.
27 _node: Node object that contains the image definition in device tree
29 _size: Image size in bytes, or None if not known yet
30 _align_size: Image size alignment, or None
31 _pad_before: Number of bytes before the first entry starts. This
32 effectively changes the place where entry position 0 starts
33 _pad_after: Number of bytes after the last entry ends. The last
34 entry will finish on or before this boundary
35 _pad_byte: Byte to use to pad the image where there is no entry
36 _filename: Output filename for image
37 _sort: True if entries should be sorted by position, False if they
38 must be in-order in the device tree description
39 _skip_at_start: Number of bytes before the first entry starts. These
40 effecively adjust the starting position of entries. For example,
41 if _pad_before is 16, then the first entry would start at 16.
42 An entry with pos = 20 would in fact be written at position 4
44 _end_4gb: Indicates that the image ends at the 4GB boundary. This is
45 used for x86 images, which want to use positions such that a
46 memory address (like 0xff800000) is the first entry position.
47 This causes _skip_at_start to be set to the starting memory
49 _entries: OrderedDict() of entries
51 def __init__(self, name, node, test=False):
55 from entry import Entry
60 self._align_size = None
64 self._filename = '%s.bin' % self._name
66 self._skip_at_start = 0
68 self._entries = OrderedDict()
75 """Read properties from the image node"""
76 self._size = fdt_util.GetInt(self._node, 'size')
77 self._align_size = fdt_util.GetInt(self._node, 'align-size')
78 if tools.NotPowerOfTwo(self._align_size):
79 self._Raise("Alignment size %s must be a power of two" %
81 self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
82 self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
83 self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
84 filename = fdt_util.GetString(self._node, 'filename')
86 self._filename = filename
87 self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
88 self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
89 if self._end_4gb and not self._size:
90 self._Raise("Image size must be provided when using end-at-4gb")
92 self._skip_at_start = 0x100000000 - self._size
95 """Check that the image contents does not exceed its size, etc."""
97 for entry in self._entries.values():
98 contents_size = max(contents_size, entry.pos + entry.size)
100 contents_size -= self._skip_at_start
104 size = self._pad_before + contents_size + self._pad_after
105 size = tools.Align(size, self._align_size)
107 if self._size and contents_size > self._size:
108 self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
109 (contents_size, contents_size, self._size, self._size))
112 if self._size != tools.Align(self._size, self._align_size):
113 self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
114 (self._size, self._size, self._align_size, self._align_size))
116 def _Raise(self, msg):
117 """Raises an error for this image
120 msg: Error message to use in the raise string
124 raise ValueError("Image '%s': %s" % (self._node.path, msg))
127 """Get the path of an image (in the FDT)
130 Full path of the node for this image
132 return self._node.path
134 def _ReadEntries(self):
135 for node in self._node.subnodes:
136 self._entries[node.name] = Entry.Create(self, node)
138 def FindEntryType(self, etype):
139 """Find an entry type in the image
142 etype: Entry type to find
144 entry matching that type, or None if not found
146 for entry in self._entries.values():
147 if entry.etype == etype:
151 def GetEntryContents(self):
152 """Call ObtainContents() for each entry
154 This calls each entry's ObtainContents() a few times until they all
155 return True. We stop calling an entry's function once it returns
156 True. This allows the contents of one entry to depend on another.
158 After 3 rounds we give up since it's likely an error.
160 todo = self._entries.values()
161 for passnum in range(3):
164 if not entry.ObtainContents():
165 next_todo.append(entry)
170 def _SetEntryPosSize(self, name, pos, size):
171 """Set the position and size of an entry
174 name: Entry name to update
178 entry = self._entries.get(name)
180 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
181 entry.SetPositionSize(self._skip_at_start + pos, size)
183 def GetEntryPositions(self):
184 """Handle entries that want to set the position/size of other entries
186 This calls each entry's GetPositions() method. If it returns a list
187 of entries to update, it updates them.
189 for entry in self._entries.values():
190 pos_dict = entry.GetPositions()
191 for name, info in pos_dict.iteritems():
192 self._SetEntryPosSize(name, *info)
194 def PackEntries(self):
195 """Pack all entries into the image"""
196 pos = self._skip_at_start
197 for entry in self._entries.values():
198 pos = entry.Pack(pos)
200 def _SortEntries(self):
201 """Sort entries by position"""
202 entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
203 self._entries.clear()
204 for entry in entries:
205 self._entries[entry._node.name] = entry
207 def CheckEntries(self):
208 """Check that entries do not overlap or extend outside the image"""
213 for entry in self._entries.values():
214 if (entry.pos < self._skip_at_start or
215 entry.pos >= self._skip_at_start + self._size):
216 entry.Raise("Position %#x (%d) is outside the image starting "
218 (entry.pos, entry.pos, self._skip_at_start,
219 self._skip_at_start))
221 entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
222 "ending at %#x (%d)" %
223 (entry.pos, entry.pos, prev_name, pos, pos))
224 pos = entry.pos + entry.size
225 prev_name = entry.GetPath()
227 def ProcessEntryContents(self):
228 """Call the ProcessContents() method for each entry
230 This is intended to adjust the contents as needed by the entry type.
232 for entry in self._entries.values():
233 entry.ProcessContents()
235 def WriteSymbols(self):
236 """Write symbol values into binary files for access at run time"""
237 for entry in self._entries.values():
238 entry.WriteSymbols(self)
240 def BuildImage(self):
241 """Write the image to a file"""
242 fname = tools.GetOutputFilename(self._filename)
243 with open(fname, 'wb') as fd:
244 fd.write(chr(self._pad_byte) * self._size)
246 for entry in self._entries.values():
247 data = entry.GetData()
248 fd.seek(self._pad_before + entry.pos - self._skip_at_start)
251 def LookupSymbol(self, sym_name, optional, msg):
252 """Look up a symbol in an ELF file
254 Looks up a symbol in an ELF file. Only entry types which come from an
255 ELF image can be used by this function.
257 At present the only entry property supported is pos.
260 sym_name: Symbol name in the ELF file to look up in the format
261 _binman_<entry>_prop_<property> where <entry> is the name of
262 the entry and <property> is the property to find (e.g.
263 _binman_u_boot_prop_pos). As a special case, you can append
264 _any to <entry> to have it search for any matching entry. E.g.
265 _binman_u_boot_any_prop_pos will match entries called u-boot,
266 u-boot-img and u-boot-nodtb)
267 optional: True if the symbol is optional. If False this function
268 will raise if the symbol is not found
269 msg: Message to display if an error occurs
272 Value that should be assigned to that symbol, or None if it was
273 optional and not found
276 ValueError if the symbol is invalid or not found, or references a
277 property which is not supported
279 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
281 raise ValueError("%s: Symbol '%s' has invalid format" %
283 entry_name, prop_name = m.groups()
284 entry_name = entry_name.replace('_', '-')
285 entry = self._entries.get(entry_name)
287 if entry_name.endswith('-any'):
288 root = entry_name[:-4]
289 for name in self._entries:
290 if name.startswith(root):
291 rest = name[len(root):]
292 if rest in ['', '-img', '-nodtb']:
293 entry = self._entries[name]
295 err = ("%s: Entry '%s' not found in list (%s)" %
296 (msg, entry_name, ','.join(self._entries.keys())))
298 print('Warning: %s' % err, file=sys.stderr)
300 raise ValueError(err)
301 if prop_name == 'pos':
304 raise ValueError("%s: No such property '%s'" % (msg, prop_name))