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