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