1 # Copyright (c) 2016 Google, Inc
2 # Written by Simon Glass <sjg@chromium.org>
4 # SPDX-License-Identifier: GPL-2.0+
6 # Class for an image, the output of binman
9 from collections import OrderedDict
10 from operator import attrgetter
13 from entry import Entry
18 """A Image, representing an output from binman
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.
23 This class implements the various operations needed for images.
26 _node: Node object that contains the image definition in device tree
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
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
48 _entries: OrderedDict() of entries
50 def __init__(self, name, node):
54 self._align_size = None
58 self._filename = '%s.bin' % self._name
60 self._skip_at_start = 0
62 self._entries = OrderedDict()
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" %
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')
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")
85 self._skip_at_start = 0x100000000 - self._size
88 """Check that the image contents does not exceed its size, etc."""
90 for entry in self._entries.values():
91 contents_size = max(contents_size, entry.pos + entry.size)
93 contents_size -= self._skip_at_start
97 size = self._pad_before + contents_size + self._pad_after
98 size = tools.Align(size, self._align_size)
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))
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))
109 def _Raise(self, msg):
110 """Raises an error for this image
113 msg: Error message to use in the raise string
117 raise ValueError("Image '%s': %s" % (self._node.path, msg))
119 def _ReadEntries(self):
120 for node in self._node.subnodes:
121 self._entries[node.name] = Entry.Create(self, node)
123 def FindEntryType(self, etype):
124 """Find an entry type in the image
127 etype: Entry type to find
129 entry matching that type, or None if not found
131 for entry in self._entries.values():
132 if entry.etype == etype:
136 def GetEntryContents(self):
137 """Call ObtainContents() for each entry
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.
143 After 3 rounds we give up since it's likely an error.
145 todo = self._entries.values()
146 for passnum in range(3):
149 if not entry.ObtainContents():
150 next_todo.append(entry)
155 def _SetEntryPosSize(self, name, pos, size):
156 """Set the position and size of an entry
159 name: Entry name to update
163 entry = self._entries.get(name)
165 self._Raise("Unable to set pos/size for unknown entry '%s'" % name)
166 entry.SetPositionSize(self._skip_at_start + pos, size)
168 def GetEntryPositions(self):
169 """Handle entries that want to set the position/size of other entries
171 This calls each entry's GetPositions() method. If it returns a list
172 of entries to update, it updates them.
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)
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)
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
192 def CheckEntries(self):
193 """Check that entries do not overlap or extend outside the image"""
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 "
203 (entry.pos, entry.pos, self._skip_at_start,
204 self._skip_at_start))
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()
212 def ProcessEntryContents(self):
213 """Call the ProcessContents() method for each entry
215 This is intended to adjust the contents as needed by the entry type.
217 for entry in self._entries.values():
218 entry.ProcessContents()
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)
226 for entry in self._entries.values():
227 data = entry.GetData()
228 fd.seek(self._pad_before + entry.pos - self._skip_at_start)