741630f0912afc1ec9cfe5ca835c01ceb7ed226c
[platform/kernel/u-boot.git] / tools / binman / image.py
1 # 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
9 from __future__ import print_function
10
11 from collections import OrderedDict
12 from operator import attrgetter
13 import re
14 import sys
15
16 import fdt_util
17 import tools
18
19 class 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     """
52     def __init__(self, name, node, test=False):
53         global entry
54         global Entry
55         import entry
56         from entry import Entry
57
58         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
71         if not test:
72             self._ReadNode()
73             self._ReadEntries()
74
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
127     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
135     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
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)
240
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)
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)
251
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))