]>
Commit | Line | Data |
---|---|---|
83d290c5 | 1 | # SPDX-License-Identifier: GPL-2.0+ |
bf7fd50b SG |
2 | # Copyright (c) 2016 Google, Inc |
3 | # Written by Simon Glass <[email protected]> | |
4 | # | |
bf7fd50b SG |
5 | # Class for an image, the output of binman |
6 | # | |
7 | ||
19790632 SG |
8 | from __future__ import print_function |
9 | ||
bf7fd50b | 10 | from collections import OrderedDict |
61f564d1 | 11 | import fnmatch |
bf7fd50b | 12 | from operator import attrgetter |
19790632 SG |
13 | import re |
14 | import sys | |
bf7fd50b | 15 | |
8beb11ea | 16 | from entry import Entry |
ffded752 SG |
17 | from etype import fdtmap |
18 | from etype import image_header | |
8beb11ea | 19 | from etype import section |
ffded752 | 20 | import fdt |
bf7fd50b SG |
21 | import fdt_util |
22 | import tools | |
eea264ea | 23 | import tout |
bf7fd50b | 24 | |
8beb11ea | 25 | class Image(section.Entry_section): |
bf7fd50b SG |
26 | """A Image, representing an output from binman |
27 | ||
28 | An image is comprised of a collection of entries each containing binary | |
29 | data. The image size must be large enough to hold all of this data. | |
30 | ||
31 | This class implements the various operations needed for images. | |
32 | ||
8beb11ea SG |
33 | Attributes: |
34 | filename: Output filename for image | |
7ae5f315 SG |
35 | |
36 | Args: | |
37 | test: True if this is being called from a test of Images. This this case | |
38 | there is no device tree defining the structure of the section, so | |
39 | we create a section manually. | |
bf7fd50b | 40 | """ |
19790632 | 41 | def __init__(self, name, node, test=False): |
8beb11ea SG |
42 | self.image = self |
43 | section.Entry_section.__init__(self, None, 'section', node, test) | |
44 | self.name = 'main-section' | |
45 | self.image_name = name | |
46 | self._filename = '%s.bin' % self.image_name | |
47 | if not test: | |
48 | filename = fdt_util.GetString(self._node, 'filename') | |
49 | if filename: | |
50 | self._filename = filename | |
bf7fd50b | 51 | |
ffded752 SG |
52 | @classmethod |
53 | def FromFile(cls, fname): | |
54 | """Convert an image file into an Image for use in binman | |
55 | ||
56 | Args: | |
57 | fname: Filename of image file to read | |
58 | ||
59 | Returns: | |
60 | Image object on success | |
61 | ||
62 | Raises: | |
63 | ValueError if something goes wrong | |
64 | """ | |
65 | data = tools.ReadFile(fname) | |
66 | size = len(data) | |
67 | ||
68 | # First look for an image header | |
69 | pos = image_header.LocateHeaderOffset(data) | |
70 | if pos is None: | |
71 | # Look for the FDT map | |
72 | pos = fdtmap.LocateFdtmap(data) | |
73 | if pos is None: | |
74 | raise ValueError('Cannot find FDT map in image') | |
75 | ||
76 | # We don't know the FDT size, so check its header first | |
77 | probe_dtb = fdt.Fdt.FromData( | |
78 | data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256]) | |
79 | dtb_size = probe_dtb.GetFdtObj().totalsize() | |
80 | fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN] | |
81 | dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]) | |
82 | dtb.Scan() | |
83 | ||
84 | # Return an Image with the associated nodes | |
85 | return Image('image', dtb.GetRoot()) | |
86 | ||
c52c9e7d SG |
87 | def Raise(self, msg): |
88 | """Convenience function to raise an error referencing an image""" | |
89 | raise ValueError("Image '%s': %s" % (self._node.path, msg)) | |
90 | ||
bf7fd50b SG |
91 | def PackEntries(self): |
92 | """Pack all entries into the image""" | |
8beb11ea | 93 | section.Entry_section.Pack(self, 0) |
078ab1a2 | 94 | |
dbf6be9f | 95 | def SetImagePos(self): |
8beb11ea SG |
96 | # This first section in the image so it starts at 0 |
97 | section.Entry_section.SetImagePos(self, 0) | |
dbf6be9f | 98 | |
bf7fd50b SG |
99 | def ProcessEntryContents(self): |
100 | """Call the ProcessContents() method for each entry | |
101 | ||
102 | This is intended to adjust the contents as needed by the entry type. | |
a0dcaf20 SG |
103 | |
104 | Returns: | |
105 | True if the new data size is OK, False if expansion is needed | |
bf7fd50b | 106 | """ |
8beb11ea SG |
107 | sizes_ok = True |
108 | for entry in self._entries.values(): | |
109 | if not entry.ProcessContents(): | |
110 | sizes_ok = False | |
eea264ea | 111 | tout.Debug("Entry '%s' size change" % self._node.path) |
8beb11ea | 112 | return sizes_ok |
bf7fd50b | 113 | |
19790632 SG |
114 | def WriteSymbols(self): |
115 | """Write symbol values into binary files for access at run time""" | |
8beb11ea SG |
116 | section.Entry_section.WriteSymbols(self, self) |
117 | ||
118 | def BuildSection(self, fd, base_offset): | |
119 | """Write the section to a file""" | |
120 | fd.seek(base_offset) | |
121 | fd.write(self.GetData()) | |
19790632 | 122 | |
bf7fd50b SG |
123 | def BuildImage(self): |
124 | """Write the image to a file""" | |
125 | fname = tools.GetOutputFilename(self._filename) | |
126 | with open(fname, 'wb') as fd: | |
8beb11ea | 127 | self.BuildSection(fd, 0) |
3b0c3821 SG |
128 | |
129 | def WriteMap(self): | |
163ed6c3 SG |
130 | """Write a map of the image to a .map file |
131 | ||
132 | Returns: | |
133 | Filename of map file written | |
134 | """ | |
8beb11ea | 135 | filename = '%s.map' % self.image_name |
3b0c3821 SG |
136 | fname = tools.GetOutputFilename(filename) |
137 | with open(fname, 'w') as fd: | |
1be70d20 SG |
138 | print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'), |
139 | file=fd) | |
8beb11ea | 140 | section.Entry_section.WriteMap(self, fd, 0) |
163ed6c3 | 141 | return fname |
41b8ba09 SG |
142 | |
143 | def BuildEntryList(self): | |
144 | """List the files in an image | |
145 | ||
146 | Returns: | |
147 | List of entry.EntryInfo objects describing all entries in the image | |
148 | """ | |
149 | entries = [] | |
8beb11ea | 150 | self.ListEntries(entries, 0) |
41b8ba09 | 151 | return entries |
61f564d1 SG |
152 | |
153 | def FindEntryPath(self, entry_path): | |
154 | """Find an entry at a given path in the image | |
155 | ||
156 | Args: | |
157 | entry_path: Path to entry (e.g. /ro-section/u-boot') | |
158 | ||
159 | Returns: | |
160 | Entry object corresponding to that past | |
161 | ||
162 | Raises: | |
163 | ValueError if no entry found | |
164 | """ | |
165 | parts = entry_path.split('/') | |
166 | entries = self.GetEntries() | |
167 | parent = '/' | |
168 | for part in parts: | |
169 | entry = entries.get(part) | |
170 | if not entry: | |
171 | raise ValueError("Entry '%s' not found in '%s'" % | |
172 | (part, parent)) | |
173 | parent = entry.GetPath() | |
174 | entries = entry.GetEntries() | |
175 | return entry | |
176 | ||
177 | def ReadData(self, decomp=True): | |
178 | return self._data | |
179 | ||
180 | def GetListEntries(self, entry_paths): | |
181 | """List the entries in an image | |
182 | ||
183 | This decodes the supplied image and returns a list of entries from that | |
184 | image, preceded by a header. | |
185 | ||
186 | Args: | |
187 | entry_paths: List of paths to match (each can have wildcards). Only | |
188 | entries whose names match one of these paths will be printed | |
189 | ||
190 | Returns: | |
191 | String error message if something went wrong, otherwise | |
192 | 3-Tuple: | |
193 | List of EntryInfo objects | |
194 | List of lines, each | |
195 | List of text columns, each a string | |
196 | List of widths of each column | |
197 | """ | |
198 | def _EntryToStrings(entry): | |
199 | """Convert an entry to a list of strings, one for each column | |
200 | ||
201 | Args: | |
202 | entry: EntryInfo object containing information to output | |
203 | ||
204 | Returns: | |
205 | List of strings, one for each field in entry | |
206 | """ | |
207 | def _AppendHex(val): | |
208 | """Append a hex value, or an empty string if val is None | |
209 | ||
210 | Args: | |
211 | val: Integer value, or None if none | |
212 | """ | |
213 | args.append('' if val is None else '>%x' % val) | |
214 | ||
215 | args = [' ' * entry.indent + entry.name] | |
216 | _AppendHex(entry.image_pos) | |
217 | _AppendHex(entry.size) | |
218 | args.append(entry.etype) | |
219 | _AppendHex(entry.offset) | |
220 | _AppendHex(entry.uncomp_size) | |
221 | return args | |
222 | ||
223 | def _DoLine(lines, line): | |
224 | """Add a line to the output list | |
225 | ||
226 | This adds a line (a list of columns) to the output list. It also updates | |
227 | the widths[] array with the maximum width of each column | |
228 | ||
229 | Args: | |
230 | lines: List of lines to add to | |
231 | line: List of strings, one for each column | |
232 | """ | |
233 | for i, item in enumerate(line): | |
234 | widths[i] = max(widths[i], len(item)) | |
235 | lines.append(line) | |
236 | ||
237 | def _NameInPaths(fname, entry_paths): | |
238 | """Check if a filename is in a list of wildcarded paths | |
239 | ||
240 | Args: | |
241 | fname: Filename to check | |
242 | entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*', | |
243 | 'section/u-boot']) | |
244 | ||
245 | Returns: | |
246 | True if any wildcard matches the filename (using Unix filename | |
247 | pattern matching, not regular expressions) | |
248 | False if not | |
249 | """ | |
250 | for path in entry_paths: | |
251 | if fnmatch.fnmatch(fname, path): | |
252 | return True | |
253 | return False | |
254 | ||
255 | entries = self.BuildEntryList() | |
256 | ||
257 | # This is our list of lines. Each item in the list is a list of strings, one | |
258 | # for each column | |
259 | lines = [] | |
260 | HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset', | |
261 | 'Uncomp-size'] | |
262 | num_columns = len(HEADER) | |
263 | ||
264 | # This records the width of each column, calculated as the maximum width of | |
265 | # all the strings in that column | |
266 | widths = [0] * num_columns | |
267 | _DoLine(lines, HEADER) | |
268 | ||
269 | # We won't print anything unless it has at least this indent. So at the | |
270 | # start we will print nothing, unless a path matches (or there are no | |
271 | # entry paths) | |
272 | MAX_INDENT = 100 | |
273 | min_indent = MAX_INDENT | |
274 | path_stack = [] | |
275 | path = '' | |
276 | indent = 0 | |
277 | selected_entries = [] | |
278 | for entry in entries: | |
279 | if entry.indent > indent: | |
280 | path_stack.append(path) | |
281 | elif entry.indent < indent: | |
282 | path_stack.pop() | |
283 | if path_stack: | |
284 | path = path_stack[-1] + '/' + entry.name | |
285 | indent = entry.indent | |
286 | ||
287 | # If there are entry paths to match and we are not looking at a | |
288 | # sub-entry of a previously matched entry, we need to check the path | |
289 | if entry_paths and indent <= min_indent: | |
290 | if _NameInPaths(path[1:], entry_paths): | |
291 | # Print this entry and all sub-entries (=higher indent) | |
292 | min_indent = indent | |
293 | else: | |
294 | # Don't print this entry, nor any following entries until we get | |
295 | # a path match | |
296 | min_indent = MAX_INDENT | |
297 | continue | |
298 | _DoLine(lines, _EntryToStrings(entry)) | |
299 | selected_entries.append(entry) | |
300 | return selected_entries, lines, widths |