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