xref: /openbmc/u-boot/tools/binman/image.py (revision 46b1e54b)
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
9from __future__ import print_function
10
11from collections import OrderedDict
12from operator import attrgetter
13import re
14import sys
15
16import 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    """
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))
306