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