]>
Commit | Line | Data |
---|---|---|
bf7fd50b SG |
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) |