Merge branch 'master' of git://git.denx.de/u-boot-spi
[oweals/u-boot.git] / tools / binman / image.py
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
9 from collections import OrderedDict
10 from operator import attrgetter
11
12 import entry
13 from entry import Entry
14 import fdt_util
15 import tools
16
17 class 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)