1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Class for an image, the output of binman
8 from collections import OrderedDict
10 from operator import attrgetter
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
20 from dtoc import fdt_util
21 from patman import tools
22 from patman import tout
24 class Image(section.Entry_section):
25 """A Image, representing an output from binman
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.
30 This class implements the various operations needed for images.
33 filename: Output filename for image
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
37 allow_repack: True to add properties to allow the image to be safely
39 test_section_timeout: Use a zero timeout for section multi-threading
41 symlink: Name of symlink to image
44 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
46 test: True if this is being called from a test of Images. This this case
47 there is no device tree defining the structure of the section, so
48 we create a section manually.
49 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
50 exception). This should be used if the Image is being loaded from
51 a file rather than generated. In that case we obviously don't need
52 the entry arguments since the contents already exists.
53 use_expanded: True if we are updating the FDT wth entry offsets, etc.
54 and should use the expanded versions of the U-Boot entries.
55 Any entry type that includes a devicetree must put it in a
56 separate entry so that it will be updated. For example. 'u-boot'
57 normally just picks up 'u-boot.bin' which includes the
58 devicetree, but this is not updateable, since it comes into
59 binman as one piece and binman doesn't know that it is actually
60 an executable followed by a devicetree. Of course it could be
61 taught this, but then when reading an image (e.g. 'binman ls')
62 it may need to be able to split the devicetree out of the image
63 in order to determine the location of things. Instead we choose
64 to ignore 'u-boot-bin' in this case, and build it ourselves in
65 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
66 Entry_u_boot_expanded and Entry_blob_phase for details.
67 missing_etype: Use a default entry type ('blob') if the requested one
68 does not exist in binman. This is useful if an image was created by
69 binman a newer version of binman but we want to list it in an older
70 version which does not support all the entry types.
71 generate: If true, generator nodes are processed. If false they are
72 ignored which is useful when an existing image is read back from a
75 def __init__(self, name, node, copy_to_orig=True, test=False,
76 ignore_missing=False, use_expanded=False, missing_etype=False,
78 super().__init__(None, 'section', node, test=test)
79 self.copy_to_orig = copy_to_orig
80 self.name = 'main-section'
81 self.image_name = name
82 self._filename = '%s.bin' % self.image_name
83 self.fdtmap_dtb = None
84 self.fdtmap_data = None
85 self.allow_repack = False
86 self._ignore_missing = ignore_missing
87 self.missing_etype = missing_etype
88 self.use_expanded = use_expanded
89 self.test_section_timeout = False
91 self.generate = generate
97 filename = fdt_util.GetString(self._node, 'filename')
99 self._filename = filename
100 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
101 self._symlink = fdt_util.GetString(self._node, 'symlink')
104 def FromFile(cls, fname):
105 """Convert an image file into an Image for use in binman
108 fname: Filename of image file to read
111 Image object on success
114 ValueError if something goes wrong
116 data = tools.read_file(fname)
119 # First look for an image header
120 pos = image_header.LocateHeaderOffset(data)
122 # Look for the FDT map
123 pos = fdtmap.LocateFdtmap(data)
125 raise ValueError('Cannot find FDT map in image')
127 # We don't know the FDT size, so check its header first
128 probe_dtb = fdt.Fdt.FromData(
129 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
130 dtb_size = probe_dtb.GetFdtObj().totalsize()
131 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
132 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
133 out_fname = tools.get_output_filename('fdtmap.in.dtb')
134 tools.write_file(out_fname, fdt_data)
135 dtb = fdt.Fdt(out_fname)
138 # Return an Image with the associated nodes
140 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
141 missing_etype=True, generate=False)
143 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
144 image.fdtmap_dtb = dtb
145 image.fdtmap_data = fdtmap_data
147 image._filename = fname
148 image.image_name, _ = os.path.splitext(fname)
151 def Raise(self, msg):
152 """Convenience function to raise an error referencing an image"""
153 raise ValueError("Image '%s': %s" % (self._node.path, msg))
155 def PackEntries(self):
156 """Pack all entries into the image"""
159 def SetImagePos(self):
160 # This first section in the image so it starts at 0
161 super().SetImagePos(0)
163 def ProcessEntryContents(self):
164 """Call the ProcessContents() method for each entry
166 This is intended to adjust the contents as needed by the entry type.
169 True if the new data size is OK, False if expansion is needed
171 return super().ProcessContents()
173 def WriteSymbols(self):
174 """Write symbol values into binary files for access at run time"""
175 super().WriteSymbols(self)
177 def BuildImage(self):
178 """Write the image to a file"""
179 fname = tools.get_output_filename(self._filename)
180 tout.info("Writing image to '%s'" % fname)
181 with open(fname, 'wb') as fd:
182 data = self.GetPaddedData()
184 tout.info("Wrote %#x bytes" % len(data))
185 # Create symlink to file if symlink given
186 if self._symlink is not None:
187 sname = tools.get_output_filename(self._symlink)
188 os.symlink(fname, sname)
191 """Write a map of the image to a .map file
194 Filename of map file written
196 filename = '%s.map' % self.image_name
197 fname = tools.get_output_filename(filename)
198 with open(fname, 'w') as fd:
199 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
201 super().WriteMap(fd, 0)
204 def BuildEntryList(self):
205 """List the files in an image
208 List of entry.EntryInfo objects describing all entries in the image
211 self.ListEntries(entries, 0)
214 def FindEntryPath(self, entry_path):
215 """Find an entry at a given path in the image
218 entry_path: Path to entry (e.g. /ro-section/u-boot')
221 Entry object corresponding to that past
224 ValueError if no entry found
226 parts = entry_path.split('/')
227 entries = self.GetEntries()
230 entry = entries.get(part)
232 raise ValueError("Entry '%s' not found in '%s'" %
234 parent = entry.GetPath()
235 entries = entry.GetEntries()
238 def ReadData(self, decomp=True, alt_format=None):
239 tout.debug("Image '%s' ReadData(), size=%#x" %
240 (self.GetPath(), len(self._data)))
243 def GetListEntries(self, entry_paths):
244 """List the entries in an image
246 This decodes the supplied image and returns a list of entries from that
247 image, preceded by a header.
250 entry_paths: List of paths to match (each can have wildcards). Only
251 entries whose names match one of these paths will be printed
254 String error message if something went wrong, otherwise
256 List of EntryInfo objects
258 List of text columns, each a string
259 List of widths of each column
261 def _EntryToStrings(entry):
262 """Convert an entry to a list of strings, one for each column
265 entry: EntryInfo object containing information to output
268 List of strings, one for each field in entry
271 """Append a hex value, or an empty string if val is None
274 val: Integer value, or None if none
276 args.append('' if val is None else '>%x' % val)
278 args = [' ' * entry.indent + entry.name]
279 _AppendHex(entry.image_pos)
280 _AppendHex(entry.size)
281 args.append(entry.etype)
282 _AppendHex(entry.offset)
283 _AppendHex(entry.uncomp_size)
286 def _DoLine(lines, line):
287 """Add a line to the output list
289 This adds a line (a list of columns) to the output list. It also updates
290 the widths[] array with the maximum width of each column
293 lines: List of lines to add to
294 line: List of strings, one for each column
296 for i, item in enumerate(line):
297 widths[i] = max(widths[i], len(item))
300 def _NameInPaths(fname, entry_paths):
301 """Check if a filename is in a list of wildcarded paths
304 fname: Filename to check
305 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
309 True if any wildcard matches the filename (using Unix filename
310 pattern matching, not regular expressions)
313 for path in entry_paths:
314 if fnmatch.fnmatch(fname, path):
318 entries = self.BuildEntryList()
320 # This is our list of lines. Each item in the list is a list of strings, one
323 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
325 num_columns = len(HEADER)
327 # This records the width of each column, calculated as the maximum width of
328 # all the strings in that column
329 widths = [0] * num_columns
330 _DoLine(lines, HEADER)
332 # We won't print anything unless it has at least this indent. So at the
333 # start we will print nothing, unless a path matches (or there are no
336 min_indent = MAX_INDENT
340 selected_entries = []
341 for entry in entries:
342 if entry.indent > indent:
343 path_stack.append(path)
344 elif entry.indent < indent:
347 path = path_stack[-1] + '/' + entry.name
348 indent = entry.indent
350 # If there are entry paths to match and we are not looking at a
351 # sub-entry of a previously matched entry, we need to check the path
352 if entry_paths and indent <= min_indent:
353 if _NameInPaths(path[1:], entry_paths):
354 # Print this entry and all sub-entries (=higher indent)
357 # Don't print this entry, nor any following entries until we get
359 min_indent = MAX_INDENT
361 _DoLine(lines, _EntryToStrings(entry))
362 selected_entries.append(entry)
363 return selected_entries, lines, widths
365 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
366 """Look up a symbol in an ELF file
368 Looks up a symbol in an ELF file. Only entry types which come from an
369 ELF image can be used by this function.
371 This searches through this image including all of its subsections.
373 At present the only entry properties supported are:
375 image_pos - 'base_addr' is added if this is not an end-at-4gb image
379 sym_name: Symbol name in the ELF file to look up in the format
380 _binman_<entry>_prop_<property> where <entry> is the name of
381 the entry and <property> is the property to find (e.g.
382 _binman_u_boot_prop_offset). As a special case, you can append
383 _any to <entry> to have it search for any matching entry. E.g.
384 _binman_u_boot_any_prop_offset will match entries called u-boot,
385 u-boot-img and u-boot-nodtb)
386 optional: True if the symbol is optional. If False this function
387 will raise if the symbol is not found
388 msg: Message to display if an error occurs
389 base_addr: Base address of image. This is added to the returned
390 image_pos in most cases so that the returned position indicates
391 where the targeted entry/binary has actually been loaded. But
392 if end-at-4gb is used, this is not done, since the binary is
393 already assumed to be linked to the ROM position and using
394 execute-in-place (XIP).
397 Value that should be assigned to that symbol, or None if it was
398 optional and not found
401 ValueError if the symbol is invalid or not found, or references a
402 property which is not supported
404 entries = OrderedDict()
406 self._CollectEntries(entries, entries_by_name, self)
407 return self.LookupSymbol(sym_name, optional, msg, base_addr,
410 def CollectBintools(self):
411 """Collect all the bintools used by this image
416 value: Bintool object
419 super().AddBintools(bintools)
420 self.bintools = bintools