blob: 741630f0912afc1ec9cfe5ca835c01ceb7ed226c [file] [log] [blame]
Simon Glass2574ef62016-11-25 20:15:51 -07001# Copyright (c) 2016 Google, Inc
2# Written by Simon Glass <sjg@chromium.org>
3#
4# SPDX-License-Identifier: GPL-2.0+
5#
6# Class for an image, the output of binman
7#
8
Simon Glass4ca8e042017-11-13 18:55:01 -07009from __future__ import print_function
10
Simon Glass2574ef62016-11-25 20:15:51 -070011from collections import OrderedDict
12from operator import attrgetter
Simon Glass4ca8e042017-11-13 18:55:01 -070013import re
14import sys
Simon Glass2574ef62016-11-25 20:15:51 -070015
Simon Glass2574ef62016-11-25 20:15:51 -070016import fdt_util
17import tools
18
19class Image:
20 """A Image, representing an output from binman
21
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.
24
25 This class implements the various operations needed for images.
26
27 Atrtributes:
28 _node: Node object that contains the image definition in device tree
29 _name: Image name
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
44 in the image file.
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
49 address.
50 _entries: OrderedDict() of entries
51 """
Simon Glass4ca8e042017-11-13 18:55:01 -070052 def __init__(self, name, node, test=False):
Simon Glassb3393262017-11-12 21:52:20 -070053 global entry
54 global Entry
55 import entry
56 from entry import Entry
57
Simon Glass2574ef62016-11-25 20:15:51 -070058 self._node = node
59 self._name = name
60 self._size = None
61 self._align_size = None
62 self._pad_before = 0
63 self._pad_after = 0
64 self._pad_byte = 0
65 self._filename = '%s.bin' % self._name
66 self._sort = False
67 self._skip_at_start = 0
68 self._end_4gb = False
69 self._entries = OrderedDict()
70
Simon Glass4ca8e042017-11-13 18:55:01 -070071 if not test:
72 self._ReadNode()
73 self._ReadEntries()
Simon Glass2574ef62016-11-25 20:15:51 -070074
75 def _ReadNode(self):
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" %
81 self._align_size)
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')
86 if 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")
92 if self._end_4gb:
93 self._skip_at_start = 0x100000000 - self._size
94
95 def CheckSize(self):
96 """Check that the image contents does not exceed its size, etc."""
97 contents_size = 0
98 for entry in self._entries.values():
99 contents_size = max(contents_size, entry.pos + entry.size)
100
101 contents_size -= self._skip_at_start
102
103 size = self._size
104 if not size:
105 size = self._pad_before + contents_size + self._pad_after
106 size = tools.Align(size, self._align_size)
107
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))
111 if not self._size:
112 self._size = 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))
116
117 def _Raise(self, msg):
118 """Raises an error for this image
119
120 Args:
121 msg: Error message to use in the raise string
122 Raises:
123 ValueError()
124 """
125 raise ValueError("Image '%s': %s" % (self._node.path, msg))
126
Simon Glass4ca8e042017-11-13 18:55:01 -0700127 def GetPath(self):
128 """Get the path of an image (in the FDT)
129
130 Returns:
131 Full path of the node for this image
132 """
133 return self._node.path
134
Simon Glass2574ef62016-11-25 20:15:51 -0700135 def _ReadEntries(self):
136 for node in self._node.subnodes:
137 self._entries[node.name] = Entry.Create(self, node)
138
139 def FindEntryType(self, etype):
140 """Find an entry type in the image
141
142 Args:
143 etype: Entry type to find
144 Returns:
145 entry matching that type, or None if not found
146 """
147 for entry in self._entries.values():
148 if entry.etype == etype:
149 return entry
150 return None
151
152 def GetEntryContents(self):
153 """Call ObtainContents() for each entry
154
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.
158
159 After 3 rounds we give up since it's likely an error.
160 """
161 todo = self._entries.values()
162 for passnum in range(3):
163 next_todo = []
164 for entry in todo:
165 if not entry.ObtainContents():
166 next_todo.append(entry)
167 todo = next_todo
168 if not todo:
169 break
170
171 def _SetEntryPosSize(self, name, pos, size):
172 """Set the position and size of an entry
173
174 Args:
175 name: Entry name to update
176 pos: New position
177 size: New size
178 """
179 entry = self._entries.get(name)
180 if not entry:
181 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
182 entry.SetPositionSize(self._skip_at_start + pos, size)
183
184 def GetEntryPositions(self):
185 """Handle entries that want to set the position/size of other entries
186
187 This calls each entry's GetPositions() method. If it returns a list
188 of entries to update, it updates them.
189 """
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)
194
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)
200
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
207
208 def CheckEntries(self):
209 """Check that entries do not overlap or extend outside the image"""
210 if self._sort:
211 self._SortEntries()
212 pos = 0
213 prev_name = 'None'
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 "
218 "at %#x (%d)" %
219 (entry.pos, entry.pos, self._skip_at_start,
220 self._skip_at_start))
221 if entry.pos < pos:
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()
227
228 def ProcessEntryContents(self):
229 """Call the ProcessContents() method for each entry
230
231 This is intended to adjust the contents as needed by the entry type.
232 """
233 for entry in self._entries.values():
234 entry.ProcessContents()
235
Simon Glass4ca8e042017-11-13 18:55:01 -0700236 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)
240
Simon Glass2574ef62016-11-25 20:15:51 -0700241 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)
246
247 for entry in self._entries.values():
248 data = entry.GetData()
249 fd.seek(self._pad_before + entry.pos - self._skip_at_start)
250 fd.write(data)
Simon Glass4ca8e042017-11-13 18:55:01 -0700251
252 def LookupSymbol(self, sym_name, optional, msg):
253 """Look up a symbol in an ELF file
254
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.
257
258 At present the only entry property supported is pos.
259
260 Args:
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
271
272 Returns:
273 Value that should be assigned to that symbol, or None if it was
274 optional and not found
275
276 Raises:
277 ValueError if the symbol is invalid or not found, or references a
278 property which is not supported
279 """
280 m = re.match(r'^_binman_(\w+)_prop_(\w+)$', sym_name)
281 if not m:
282 raise ValueError("%s: Symbol '%s' has invalid format" %
283 (msg, sym_name))
284 entry_name, prop_name = m.groups()
285 entry_name = entry_name.replace('_', '-')
286 entry = self._entries.get(entry_name)
287 if not entry:
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]
295 if not entry:
296 err = ("%s: Entry '%s' not found in list (%s)" %
297 (msg, entry_name, ','.join(self._entries.keys())))
298 if optional:
299 print('Warning: %s' % err, file=sys.stderr)
300 return None
301 raise ValueError(err)
302 if prop_name == 'pos':
303 return entry.pos
304 else:
305 raise ValueError("%s: No such property '%s'" % (msg, prop_name))