]> git.sur5r.net Git - u-boot/blob - tools/binman/image.py
binman: Set up 'entry' to permit full test coverage
[u-boot] / 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 fdt_util
13 import tools
14
15 class 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):
49         global entry
50         global Entry
51         import entry
52         from entry import Entry
53
54         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)