xref: /openbmc/u-boot/tools/binman/image.py (revision a3b36c84)
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 collections import OrderedDict
10from operator import attrgetter
11
12import fdt_util
13import tools
14
15class Image:
16    """A Image, representing an output from binman
17
18    An image is comprised of a collection of entries each containing binary
19    data. The image size must be large enough to hold all of this data.
20
21    This class implements the various operations needed for images.
22
23    Atrtributes:
24        _node: Node object that contains the image definition in device tree
25        _name: Image name
26        _size: Image size in bytes, or None if not known yet
27        _align_size: Image size alignment, or None
28        _pad_before: Number of bytes before the first entry starts. This
29            effectively changes the place where entry position 0 starts
30        _pad_after: Number of bytes after the last entry ends. The last
31            entry will finish on or before this boundary
32        _pad_byte: Byte to use to pad the image where there is no entry
33        _filename: Output filename for image
34        _sort: True if entries should be sorted by position, False if they
35            must be in-order in the device tree description
36        _skip_at_start: Number of bytes before the first entry starts. These
37            effecively adjust the starting position of entries. For example,
38            if _pad_before is 16, then the first entry would start at 16.
39            An entry with pos = 20 would in fact be written at position 4
40            in the image file.
41        _end_4gb: Indicates that the image ends at the 4GB boundary. This is
42            used for x86 images, which want to use positions such that a
43             memory address (like 0xff800000) is the first entry position.
44             This causes _skip_at_start to be set to the starting memory
45             address.
46        _entries: OrderedDict() of entries
47    """
48    def __init__(self, name, node):
49        global entry
50        global Entry
51        import entry
52        from entry import Entry
53
54        self._node = node
55        self._name = name
56        self._size = None
57        self._align_size = None
58        self._pad_before = 0
59        self._pad_after = 0
60        self._pad_byte = 0
61        self._filename = '%s.bin' % self._name
62        self._sort = False
63        self._skip_at_start = 0
64        self._end_4gb = False
65        self._entries = OrderedDict()
66
67        self._ReadNode()
68        self._ReadEntries()
69
70    def _ReadNode(self):
71        """Read properties from the image node"""
72        self._size = fdt_util.GetInt(self._node, 'size')
73        self._align_size = fdt_util.GetInt(self._node, 'align-size')
74        if tools.NotPowerOfTwo(self._align_size):
75            self._Raise("Alignment size %s must be a power of two" %
76                        self._align_size)
77        self._pad_before = fdt_util.GetInt(self._node, 'pad-before', 0)
78        self._pad_after = fdt_util.GetInt(self._node, 'pad-after', 0)
79        self._pad_byte = fdt_util.GetInt(self._node, 'pad-byte', 0)
80        filename = fdt_util.GetString(self._node, 'filename')
81        if filename:
82            self._filename = filename
83        self._sort = fdt_util.GetBool(self._node, 'sort-by-pos')
84        self._end_4gb = fdt_util.GetBool(self._node, 'end-at-4gb')
85        if self._end_4gb and not self._size:
86            self._Raise("Image size must be provided when using end-at-4gb")
87        if self._end_4gb:
88            self._skip_at_start = 0x100000000 - self._size
89
90    def CheckSize(self):
91        """Check that the image contents does not exceed its size, etc."""
92        contents_size = 0
93        for entry in self._entries.values():
94            contents_size = max(contents_size, entry.pos + entry.size)
95
96        contents_size -= self._skip_at_start
97
98        size = self._size
99        if not size:
100            size = self._pad_before + contents_size + self._pad_after
101            size = tools.Align(size, self._align_size)
102
103        if self._size and contents_size > self._size:
104            self._Raise("contents size %#x (%d) exceeds image size %#x (%d)" %
105                       (contents_size, contents_size, self._size, self._size))
106        if not self._size:
107            self._size = size
108        if self._size != tools.Align(self._size, self._align_size):
109            self._Raise("Size %#x (%d) does not match align-size %#x (%d)" %
110                  (self._size, self._size, self._align_size, self._align_size))
111
112    def _Raise(self, msg):
113        """Raises an error for this image
114
115        Args:
116            msg: Error message to use in the raise string
117        Raises:
118            ValueError()
119        """
120        raise ValueError("Image '%s': %s" % (self._node.path, msg))
121
122    def _ReadEntries(self):
123        for node in self._node.subnodes:
124            self._entries[node.name] = Entry.Create(self, node)
125
126    def FindEntryType(self, etype):
127        """Find an entry type in the image
128
129        Args:
130            etype: Entry type to find
131        Returns:
132            entry matching that type, or None if not found
133        """
134        for entry in self._entries.values():
135            if entry.etype == etype:
136                return entry
137        return None
138
139    def GetEntryContents(self):
140        """Call ObtainContents() for each entry
141
142        This calls each entry's ObtainContents() a few times until they all
143        return True. We stop calling an entry's function once it returns
144        True. This allows the contents of one entry to depend on another.
145
146        After 3 rounds we give up since it's likely an error.
147        """
148        todo = self._entries.values()
149        for passnum in range(3):
150            next_todo = []
151            for entry in todo:
152                if not entry.ObtainContents():
153                    next_todo.append(entry)
154            todo = next_todo
155            if not todo:
156                break
157
158    def _SetEntryPosSize(self, name, pos, size):
159        """Set the position and size of an entry
160
161        Args:
162            name: Entry name to update
163            pos: New position
164            size: New size
165        """
166        entry = self._entries.get(name)
167        if not entry:
168            self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
169        entry.SetPositionSize(self._skip_at_start + pos, size)
170
171    def GetEntryPositions(self):
172        """Handle entries that want to set the position/size of other entries
173
174        This calls each entry's GetPositions() method. If it returns a list
175        of entries to update, it updates them.
176        """
177        for entry in self._entries.values():
178            pos_dict = entry.GetPositions()
179            for name, info in pos_dict.iteritems():
180                self._SetEntryPosSize(name, *info)
181
182    def PackEntries(self):
183        """Pack all entries into the image"""
184        pos = self._skip_at_start
185        for entry in self._entries.values():
186            pos = entry.Pack(pos)
187
188    def _SortEntries(self):
189        """Sort entries by position"""
190        entries = sorted(self._entries.values(), key=lambda entry: entry.pos)
191        self._entries.clear()
192        for entry in entries:
193            self._entries[entry._node.name] = entry
194
195    def CheckEntries(self):
196        """Check that entries do not overlap or extend outside the image"""
197        if self._sort:
198            self._SortEntries()
199        pos = 0
200        prev_name = 'None'
201        for entry in self._entries.values():
202            if (entry.pos < self._skip_at_start or
203                entry.pos >= self._skip_at_start + self._size):
204                entry.Raise("Position %#x (%d) is outside the image starting "
205                            "at %#x (%d)" %
206                            (entry.pos, entry.pos, self._skip_at_start,
207                             self._skip_at_start))
208            if entry.pos < pos:
209                entry.Raise("Position %#x (%d) overlaps with previous entry '%s' "
210                            "ending at %#x (%d)" %
211                            (entry.pos, entry.pos, prev_name, pos, pos))
212            pos = entry.pos + entry.size
213            prev_name = entry.GetPath()
214
215    def ProcessEntryContents(self):
216        """Call the ProcessContents() method for each entry
217
218        This is intended to adjust the contents as needed by the entry type.
219        """
220        for entry in self._entries.values():
221            entry.ProcessContents()
222
223    def BuildImage(self):
224        """Write the image to a file"""
225        fname = tools.GetOutputFilename(self._filename)
226        with open(fname, 'wb') as fd:
227            fd.write(chr(self._pad_byte) * self._size)
228
229            for entry in self._entries.values():
230                data = entry.GetData()
231                fd.seek(self._pad_before + entry.pos - self._skip_at_start)
232                fd.write(data)
233