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