]> git.sur5r.net Git - u-boot/blob - tools/binman/image.py
Merge git://git.denx.de/u-boot-dm
[u-boot] / tools / binman / image.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Class for an image, the output of binman
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 from operator import attrgetter
12 import re
13 import sys
14
15 import fdt_util
16 import tools
17
18 class 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     """
51     def __init__(self, name, node, test=False):
52         global entry
53         global Entry
54         import entry
55         from entry import Entry
56
57         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
70         if not test:
71             self._ReadNode()
72             self._ReadEntries()
73
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
126     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
134     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
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)
239
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)
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)
250
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))