]>
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 | |
4583c002 SG |
21 | from u_boot_pylib import tools |
22 | from u_boot_pylib import tout | |
bf7fd50b | 23 | |
2e3697b9 SG |
24 | # This is imported if needed |
25 | state = None | |
26 | ||
8beb11ea | 27 | class Image(section.Entry_section): |
bf7fd50b SG |
28 | """A Image, representing an output from binman |
29 | ||
30 | An image is comprised of a collection of entries each containing binary | |
31 | data. The image size must be large enough to hold all of this data. | |
32 | ||
33 | This class implements the various operations needed for images. | |
34 | ||
8beb11ea SG |
35 | Attributes: |
36 | filename: Output filename for image | |
589d8f91 SG |
37 | image_node: Name of node containing the description for this image |
38 | fdtmap_dtb: Fdt object for the fdtmap when loading from a file | |
39 | fdtmap_data: Contents of the fdtmap when loading from a file | |
12bb1a99 SG |
40 | allow_repack: True to add properties to allow the image to be safely |
41 | repacked later | |
c69d19c8 SG |
42 | test_section_timeout: Use a zero timeout for section multi-threading |
43 | (for testing) | |
3545e859 | 44 | symlink: Name of symlink to image |
7ae5f315 SG |
45 | |
46 | Args: | |
12bb1a99 SG |
47 | copy_to_orig: Copy offset/size to orig_offset/orig_size after reading |
48 | from the device tree | |
7ae5f315 SG |
49 | test: True if this is being called from a test of Images. This this case |
50 | there is no device tree defining the structure of the section, so | |
51 | we create a section manually. | |
939d1062 SG |
52 | ignore_missing: Ignore any missing entry arguments (i.e. don't raise an |
53 | exception). This should be used if the Image is being loaded from | |
54 | a file rather than generated. In that case we obviously don't need | |
55 | the entry arguments since the contents already exists. | |
0b6023ee SG |
56 | use_expanded: True if we are updating the FDT wth entry offsets, etc. |
57 | and should use the expanded versions of the U-Boot entries. | |
58 | Any entry type that includes a devicetree must put it in a | |
59 | separate entry so that it will be updated. For example. 'u-boot' | |
60 | normally just picks up 'u-boot.bin' which includes the | |
61 | devicetree, but this is not updateable, since it comes into | |
62 | binman as one piece and binman doesn't know that it is actually | |
63 | an executable followed by a devicetree. Of course it could be | |
64 | taught this, but then when reading an image (e.g. 'binman ls') | |
65 | it may need to be able to split the devicetree out of the image | |
66 | in order to determine the location of things. Instead we choose | |
67 | to ignore 'u-boot-bin' in this case, and build it ourselves in | |
68 | binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See | |
69 | Entry_u_boot_expanded and Entry_blob_phase for details. | |
858436df SG |
70 | missing_etype: Use a default entry type ('blob') if the requested one |
71 | does not exist in binman. This is useful if an image was created by | |
72 | binman a newer version of binman but we want to list it in an older | |
73 | version which does not support all the entry types. | |
fcc87efd JK |
74 | generate: If true, generator nodes are processed. If false they are |
75 | ignored which is useful when an existing image is read back from a | |
76 | file. | |
bf7fd50b | 77 | """ |
939d1062 | 78 | def __init__(self, name, node, copy_to_orig=True, test=False, |
fcc87efd JK |
79 | ignore_missing=False, use_expanded=False, missing_etype=False, |
80 | generate=True): | |
2e3697b9 SG |
81 | # Put this here to allow entry-docs and help to work without libfdt |
82 | global state | |
83 | from binman import state | |
84 | ||
34861d50 | 85 | super().__init__(None, 'section', node, test=test) |
12bb1a99 | 86 | self.copy_to_orig = copy_to_orig |
193d3dbd | 87 | self.name = name |
8beb11ea SG |
88 | self.image_name = name |
89 | self._filename = '%s.bin' % self.image_name | |
589d8f91 SG |
90 | self.fdtmap_dtb = None |
91 | self.fdtmap_data = None | |
12bb1a99 | 92 | self.allow_repack = False |
939d1062 | 93 | self._ignore_missing = ignore_missing |
858436df | 94 | self.missing_etype = missing_etype |
0b6023ee | 95 | self.use_expanded = use_expanded |
c69d19c8 | 96 | self.test_section_timeout = False |
386c63cf | 97 | self.bintools = {} |
fcc87efd | 98 | self.generate = generate |
8beb11ea | 99 | if not test: |
c6bd6e23 SG |
100 | self.ReadNode() |
101 | ||
102 | def ReadNode(self): | |
34861d50 | 103 | super().ReadNode() |
12bb1a99 | 104 | self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack') |
3545e859 | 105 | self._symlink = fdt_util.GetString(self._node, 'symlink') |
bf7fd50b | 106 | |
ffded752 SG |
107 | @classmethod |
108 | def FromFile(cls, fname): | |
109 | """Convert an image file into an Image for use in binman | |
110 | ||
111 | Args: | |
112 | fname: Filename of image file to read | |
113 | ||
114 | Returns: | |
115 | Image object on success | |
116 | ||
117 | Raises: | |
118 | ValueError if something goes wrong | |
119 | """ | |
c1aa66e7 | 120 | data = tools.read_file(fname) |
ffded752 SG |
121 | size = len(data) |
122 | ||
123 | # First look for an image header | |
124 | pos = image_header.LocateHeaderOffset(data) | |
125 | if pos is None: | |
126 | # Look for the FDT map | |
127 | pos = fdtmap.LocateFdtmap(data) | |
128 | if pos is None: | |
129 | raise ValueError('Cannot find FDT map in image') | |
130 | ||
131 | # We don't know the FDT size, so check its header first | |
132 | probe_dtb = fdt.Fdt.FromData( | |
133 | data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256]) | |
134 | dtb_size = probe_dtb.GetFdtObj().totalsize() | |
135 | fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN] | |
96b6c506 | 136 | fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:] |
c1aa66e7 SG |
137 | out_fname = tools.get_output_filename('fdtmap.in.dtb') |
138 | tools.write_file(out_fname, fdt_data) | |
51014aab | 139 | dtb = fdt.Fdt(out_fname) |
ffded752 SG |
140 | dtb.Scan() |
141 | ||
142 | # Return an Image with the associated nodes | |
589d8f91 | 143 | root = dtb.GetRoot() |
858436df | 144 | image = Image('image', root, copy_to_orig=False, ignore_missing=True, |
fcc87efd | 145 | missing_etype=True, generate=False) |
51014aab | 146 | |
589d8f91 SG |
147 | image.image_node = fdt_util.GetString(root, 'image-node', 'image') |
148 | image.fdtmap_dtb = dtb | |
149 | image.fdtmap_data = fdtmap_data | |
f667e45b | 150 | image._data = data |
10f9d006 SG |
151 | image._filename = fname |
152 | image.image_name, _ = os.path.splitext(fname) | |
f667e45b | 153 | return image |
ffded752 | 154 | |
c52c9e7d SG |
155 | def Raise(self, msg): |
156 | """Convenience function to raise an error referencing an image""" | |
157 | raise ValueError("Image '%s': %s" % (self._node.path, msg)) | |
158 | ||
bf7fd50b SG |
159 | def PackEntries(self): |
160 | """Pack all entries into the image""" | |
34861d50 | 161 | super().Pack(0) |
078ab1a2 | 162 | |
dbf6be9f | 163 | def SetImagePos(self): |
8beb11ea | 164 | # This first section in the image so it starts at 0 |
34861d50 | 165 | super().SetImagePos(0) |
dbf6be9f | 166 | |
bf7fd50b SG |
167 | def ProcessEntryContents(self): |
168 | """Call the ProcessContents() method for each entry | |
169 | ||
170 | This is intended to adjust the contents as needed by the entry type. | |
a0dcaf20 SG |
171 | |
172 | Returns: | |
173 | True if the new data size is OK, False if expansion is needed | |
bf7fd50b | 174 | """ |
5af9ebc4 | 175 | return super().ProcessContents() |
bf7fd50b | 176 | |
19790632 SG |
177 | def WriteSymbols(self): |
178 | """Write symbol values into binary files for access at run time""" | |
34861d50 | 179 | super().WriteSymbols(self) |
8beb11ea | 180 | |
bf7fd50b SG |
181 | def BuildImage(self): |
182 | """Write the image to a file""" | |
c1aa66e7 | 183 | fname = tools.get_output_filename(self._filename) |
f3385a5b | 184 | tout.info("Writing image to '%s'" % fname) |
bf7fd50b | 185 | with open(fname, 'wb') as fd: |
4a655c9b | 186 | data = self.GetPaddedData() |
7400107e | 187 | fd.write(data) |
f3385a5b | 188 | tout.info("Wrote %#x bytes" % len(data)) |
3545e859 NMF |
189 | # Create symlink to file if symlink given |
190 | if self._symlink is not None: | |
191 | sname = tools.get_output_filename(self._symlink) | |
15432ea6 AD |
192 | if os.path.islink(sname): |
193 | os.remove(sname) | |
3545e859 | 194 | os.symlink(fname, sname) |
3b0c3821 | 195 | |
7081a94e SG |
196 | def WriteAlternates(self): |
197 | """Write out alternative devicetree blobs, each in its own file""" | |
198 | alt_entry = self.FindEntryType('alternates-fdt') | |
199 | if not alt_entry: | |
200 | return | |
201 | ||
202 | for alt in alt_entry.alternates: | |
203 | fname, data = alt_entry.ProcessWithFdt(alt) | |
204 | pathname = tools.get_output_filename(fname) | |
205 | tout.info(f"Writing alternate '{alt}' to '{pathname}'") | |
206 | tools.write_file(pathname, data) | |
207 | tout.info("Wrote %#x bytes" % len(data)) | |
208 | ||
3b0c3821 | 209 | def WriteMap(self): |
163ed6c3 SG |
210 | """Write a map of the image to a .map file |
211 | ||
212 | Returns: | |
213 | Filename of map file written | |
214 | """ | |
8beb11ea | 215 | filename = '%s.map' % self.image_name |
c1aa66e7 | 216 | fname = tools.get_output_filename(filename) |
3b0c3821 | 217 | with open(fname, 'w') as fd: |
1be70d20 SG |
218 | print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'), |
219 | file=fd) | |
34861d50 | 220 | super().WriteMap(fd, 0) |
163ed6c3 | 221 | return fname |
41b8ba09 SG |
222 | |
223 | def BuildEntryList(self): | |
224 | """List the files in an image | |
225 | ||
226 | Returns: | |
227 | List of entry.EntryInfo objects describing all entries in the image | |
228 | """ | |
229 | entries = [] | |
8beb11ea | 230 | self.ListEntries(entries, 0) |
41b8ba09 | 231 | return entries |
61f564d1 SG |
232 | |
233 | def FindEntryPath(self, entry_path): | |
234 | """Find an entry at a given path in the image | |
235 | ||
236 | Args: | |
237 | entry_path: Path to entry (e.g. /ro-section/u-boot') | |
238 | ||
239 | Returns: | |
240 | Entry object corresponding to that past | |
241 | ||
242 | Raises: | |
243 | ValueError if no entry found | |
244 | """ | |
245 | parts = entry_path.split('/') | |
246 | entries = self.GetEntries() | |
247 | parent = '/' | |
248 | for part in parts: | |
249 | entry = entries.get(part) | |
250 | if not entry: | |
251 | raise ValueError("Entry '%s' not found in '%s'" % | |
252 | (part, parent)) | |
253 | parent = entry.GetPath() | |
254 | entries = entry.GetEntries() | |
255 | return entry | |
256 | ||
943bf78a | 257 | def ReadData(self, decomp=True, alt_format=None): |
f3385a5b | 258 | tout.debug("Image '%s' ReadData(), size=%#x" % |
2d553c00 | 259 | (self.GetPath(), len(self._data))) |
61f564d1 SG |
260 | return self._data |
261 | ||
262 | def GetListEntries(self, entry_paths): | |
263 | """List the entries in an image | |
264 | ||
265 | This decodes the supplied image and returns a list of entries from that | |
266 | image, preceded by a header. | |
267 | ||
268 | Args: | |
269 | entry_paths: List of paths to match (each can have wildcards). Only | |
270 | entries whose names match one of these paths will be printed | |
271 | ||
272 | Returns: | |
273 | String error message if something went wrong, otherwise | |
274 | 3-Tuple: | |
275 | List of EntryInfo objects | |
276 | List of lines, each | |
277 | List of text columns, each a string | |
278 | List of widths of each column | |
279 | """ | |
280 | def _EntryToStrings(entry): | |
281 | """Convert an entry to a list of strings, one for each column | |
282 | ||
283 | Args: | |
284 | entry: EntryInfo object containing information to output | |
285 | ||
286 | Returns: | |
287 | List of strings, one for each field in entry | |
288 | """ | |
289 | def _AppendHex(val): | |
290 | """Append a hex value, or an empty string if val is None | |
291 | ||
292 | Args: | |
293 | val: Integer value, or None if none | |
294 | """ | |
295 | args.append('' if val is None else '>%x' % val) | |
296 | ||
297 | args = [' ' * entry.indent + entry.name] | |
298 | _AppendHex(entry.image_pos) | |
299 | _AppendHex(entry.size) | |
300 | args.append(entry.etype) | |
301 | _AppendHex(entry.offset) | |
302 | _AppendHex(entry.uncomp_size) | |
303 | return args | |
304 | ||
305 | def _DoLine(lines, line): | |
306 | """Add a line to the output list | |
307 | ||
308 | This adds a line (a list of columns) to the output list. It also updates | |
309 | the widths[] array with the maximum width of each column | |
310 | ||
311 | Args: | |
312 | lines: List of lines to add to | |
313 | line: List of strings, one for each column | |
314 | """ | |
315 | for i, item in enumerate(line): | |
316 | widths[i] = max(widths[i], len(item)) | |
317 | lines.append(line) | |
318 | ||
319 | def _NameInPaths(fname, entry_paths): | |
320 | """Check if a filename is in a list of wildcarded paths | |
321 | ||
322 | Args: | |
323 | fname: Filename to check | |
324 | entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*', | |
325 | 'section/u-boot']) | |
326 | ||
327 | Returns: | |
328 | True if any wildcard matches the filename (using Unix filename | |
329 | pattern matching, not regular expressions) | |
330 | False if not | |
331 | """ | |
332 | for path in entry_paths: | |
333 | if fnmatch.fnmatch(fname, path): | |
334 | return True | |
335 | return False | |
336 | ||
337 | entries = self.BuildEntryList() | |
338 | ||
339 | # This is our list of lines. Each item in the list is a list of strings, one | |
340 | # for each column | |
341 | lines = [] | |
342 | HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset', | |
343 | 'Uncomp-size'] | |
344 | num_columns = len(HEADER) | |
345 | ||
346 | # This records the width of each column, calculated as the maximum width of | |
347 | # all the strings in that column | |
348 | widths = [0] * num_columns | |
349 | _DoLine(lines, HEADER) | |
350 | ||
351 | # We won't print anything unless it has at least this indent. So at the | |
352 | # start we will print nothing, unless a path matches (or there are no | |
353 | # entry paths) | |
354 | MAX_INDENT = 100 | |
355 | min_indent = MAX_INDENT | |
356 | path_stack = [] | |
357 | path = '' | |
358 | indent = 0 | |
359 | selected_entries = [] | |
360 | for entry in entries: | |
361 | if entry.indent > indent: | |
362 | path_stack.append(path) | |
363 | elif entry.indent < indent: | |
364 | path_stack.pop() | |
365 | if path_stack: | |
366 | path = path_stack[-1] + '/' + entry.name | |
367 | indent = entry.indent | |
368 | ||
369 | # If there are entry paths to match and we are not looking at a | |
370 | # sub-entry of a previously matched entry, we need to check the path | |
371 | if entry_paths and indent <= min_indent: | |
372 | if _NameInPaths(path[1:], entry_paths): | |
373 | # Print this entry and all sub-entries (=higher indent) | |
374 | min_indent = indent | |
375 | else: | |
376 | # Don't print this entry, nor any following entries until we get | |
377 | # a path match | |
378 | min_indent = MAX_INDENT | |
379 | continue | |
380 | _DoLine(lines, _EntryToStrings(entry)) | |
381 | selected_entries.append(entry) | |
382 | return selected_entries, lines, widths | |
870a9ead | 383 | |
f2154c30 SG |
384 | def GetImageSymbolValue(self, sym_name, optional, msg, base_addr): |
385 | """Get the value of a Binman symbol | |
870a9ead | 386 | |
f2154c30 | 387 | Look up a Binman symbol and obtain its value. |
870a9ead SG |
388 | |
389 | This searches through this image including all of its subsections. | |
390 | ||
391 | At present the only entry properties supported are: | |
392 | offset | |
393 | image_pos - 'base_addr' is added if this is not an end-at-4gb image | |
394 | size | |
395 | ||
396 | Args: | |
397 | sym_name: Symbol name in the ELF file to look up in the format | |
398 | _binman_<entry>_prop_<property> where <entry> is the name of | |
399 | the entry and <property> is the property to find (e.g. | |
400 | _binman_u_boot_prop_offset). As a special case, you can append | |
401 | _any to <entry> to have it search for any matching entry. E.g. | |
402 | _binman_u_boot_any_prop_offset will match entries called u-boot, | |
403 | u-boot-img and u-boot-nodtb) | |
404 | optional: True if the symbol is optional. If False this function | |
405 | will raise if the symbol is not found | |
406 | msg: Message to display if an error occurs | |
b73d0bb5 SG |
407 | base_addr (int): Base address of image. This is added to the |
408 | returned value of image-pos so that the returned position | |
409 | indicates where the targeted entry/binary has actually been | |
410 | loaded | |
870a9ead SG |
411 | |
412 | Returns: | |
413 | Value that should be assigned to that symbol, or None if it was | |
414 | optional and not found | |
415 | ||
416 | Raises: | |
417 | ValueError if the symbol is invalid or not found, or references a | |
418 | property which is not supported | |
419 | """ | |
420 | entries = OrderedDict() | |
421 | entries_by_name = {} | |
422 | self._CollectEntries(entries, entries_by_name, self) | |
f2154c30 SG |
423 | return self.GetSymbolValue(sym_name, optional, msg, base_addr, |
424 | entries_by_name) | |
386c63cf SG |
425 | |
426 | def CollectBintools(self): | |
427 | """Collect all the bintools used by this image | |
428 | ||
429 | Returns: | |
430 | Dict of bintools: | |
431 | key: name of tool | |
432 | value: Bintool object | |
433 | """ | |
434 | bintools = {} | |
435 | super().AddBintools(bintools) | |
436 | self.bintools = bintools | |
437 | return bintools | |
daed9b42 SG |
438 | |
439 | def FdtContents(self, fdt_etype): | |
440 | """This base-class implementation simply calls the state function""" | |
441 | return state.GetFdtContents(fdt_etype) |