blob: 07fc9306659eb1be21a55931724098742179e036 [file] [log] [blame]
Simon Glassbf7fd502016-11-25 20:15:51 -07001# 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)