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
43 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
45 test: True if this is being called from a test of Images. This this case
46 there is no device tree defining the structure of the section, so
47 we create a section manually.
48 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
49 exception). This should be used if the Image is being loaded from
50 a file rather than generated. In that case we obviously don't need
51 the entry arguments since the contents already exists.
52 use_expanded: True if we are updating the FDT wth entry offsets, etc.
53 and should use the expanded versions of the U-Boot entries.
54 Any entry type that includes a devicetree must put it in a
55 separate entry so that it will be updated. For example. 'u-boot'
56 normally just picks up 'u-boot.bin' which includes the
57 devicetree, but this is not updateable, since it comes into
58 binman as one piece and binman doesn't know that it is actually
59 an executable followed by a devicetree. Of course it could be
60 taught this, but then when reading an image (e.g. 'binman ls')
61 it may need to be able to split the devicetree out of the image
62 in order to determine the location of things. Instead we choose
63 to ignore 'u-boot-bin' in this case, and build it ourselves in
64 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
65 Entry_u_boot_expanded and Entry_blob_phase for details.
66 missing_etype: Use a default entry type ('blob') if the requested one
67 does not exist in binman. This is useful if an image was created by
68 binman a newer version of binman but we want to list it in an older
69 version which does not support all the entry types.
70 generate: If true, generator nodes are processed. If false they are
71 ignored which is useful when an existing image is read back from a
74 def __init__(self, name, node, copy_to_orig=True, test=False,
75 ignore_missing=False, use_expanded=False, missing_etype=False,
77 super().__init__(None, 'section', node, test=test)
78 self.copy_to_orig = copy_to_orig
79 self.name = 'main-section'
80 self.image_name = name
81 self._filename = '%s.bin' % self.image_name
82 self.fdtmap_dtb = None
83 self.fdtmap_data = None
84 self.allow_repack = False
85 self._ignore_missing = ignore_missing
86 self.missing_etype = missing_etype
87 self.use_expanded = use_expanded
88 self.test_section_timeout = False
90 self.generate = generate
96 filename = fdt_util.GetString(self._node, 'filename')
98 self._filename = filename
99 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
102 def FromFile(cls, fname):
103 """Convert an image file into an Image for use in binman
106 fname: Filename of image file to read
109 Image object on success
112 ValueError if something goes wrong
114 data = tools.read_file(fname)
117 # First look for an image header
118 pos = image_header.LocateHeaderOffset(data)
120 # Look for the FDT map
121 pos = fdtmap.LocateFdtmap(data)
123 raise ValueError('Cannot find FDT map in image')
125 # We don't know the FDT size, so check its header first
126 probe_dtb = fdt.Fdt.FromData(
127 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
128 dtb_size = probe_dtb.GetFdtObj().totalsize()
129 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
130 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
131 out_fname = tools.get_output_filename('fdtmap.in.dtb')
132 tools.write_file(out_fname, fdt_data)
133 dtb = fdt.Fdt(out_fname)
136 # Return an Image with the associated nodes
138 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
139 missing_etype=True, generate=False)
141 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
142 image.fdtmap_dtb = dtb
143 image.fdtmap_data = fdtmap_data
145 image._filename = fname
146 image.image_name, _ = os.path.splitext(fname)
149 def Raise(self, msg):
150 """Convenience function to raise an error referencing an image"""
151 raise ValueError("Image '%s': %s" % (self._node.path, msg))
153 def PackEntries(self):
154 """Pack all entries into the image"""
157 def SetImagePos(self):
158 # This first section in the image so it starts at 0
159 super().SetImagePos(0)
161 def ProcessEntryContents(self):
162 """Call the ProcessContents() method for each entry
164 This is intended to adjust the contents as needed by the entry type.
167 True if the new data size is OK, False if expansion is needed
169 return super().ProcessContents()
171 def WriteSymbols(self):
172 """Write symbol values into binary files for access at run time"""
173 super().WriteSymbols(self)
175 def BuildImage(self):
176 """Write the image to a file"""
177 fname = tools.get_output_filename(self._filename)
178 tout.Info("Writing image to '%s'" % fname)
179 with open(fname, 'wb') as fd:
180 data = self.GetPaddedData()
182 tout.Info("Wrote %#x bytes" % len(data))
185 """Write a map of the image to a .map file
188 Filename of map file written
190 filename = '%s.map' % self.image_name
191 fname = tools.get_output_filename(filename)
192 with open(fname, 'w') as fd:
193 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
195 super().WriteMap(fd, 0)
198 def BuildEntryList(self):
199 """List the files in an image
202 List of entry.EntryInfo objects describing all entries in the image
205 self.ListEntries(entries, 0)
208 def FindEntryPath(self, entry_path):
209 """Find an entry at a given path in the image
212 entry_path: Path to entry (e.g. /ro-section/u-boot')
215 Entry object corresponding to that past
218 ValueError if no entry found
220 parts = entry_path.split('/')
221 entries = self.GetEntries()
224 entry = entries.get(part)
226 raise ValueError("Entry '%s' not found in '%s'" %
228 parent = entry.GetPath()
229 entries = entry.GetEntries()
232 def ReadData(self, decomp=True, alt_format=None):
233 tout.Debug("Image '%s' ReadData(), size=%#x" %
234 (self.GetPath(), len(self._data)))
237 def GetListEntries(self, entry_paths):
238 """List the entries in an image
240 This decodes the supplied image and returns a list of entries from that
241 image, preceded by a header.
244 entry_paths: List of paths to match (each can have wildcards). Only
245 entries whose names match one of these paths will be printed
248 String error message if something went wrong, otherwise
250 List of EntryInfo objects
252 List of text columns, each a string
253 List of widths of each column
255 def _EntryToStrings(entry):
256 """Convert an entry to a list of strings, one for each column
259 entry: EntryInfo object containing information to output
262 List of strings, one for each field in entry
265 """Append a hex value, or an empty string if val is None
268 val: Integer value, or None if none
270 args.append('' if val is None else '>%x' % val)
272 args = [' ' * entry.indent + entry.name]
273 _AppendHex(entry.image_pos)
274 _AppendHex(entry.size)
275 args.append(entry.etype)
276 _AppendHex(entry.offset)
277 _AppendHex(entry.uncomp_size)
280 def _DoLine(lines, line):
281 """Add a line to the output list
283 This adds a line (a list of columns) to the output list. It also updates
284 the widths[] array with the maximum width of each column
287 lines: List of lines to add to
288 line: List of strings, one for each column
290 for i, item in enumerate(line):
291 widths[i] = max(widths[i], len(item))
294 def _NameInPaths(fname, entry_paths):
295 """Check if a filename is in a list of wildcarded paths
298 fname: Filename to check
299 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
303 True if any wildcard matches the filename (using Unix filename
304 pattern matching, not regular expressions)
307 for path in entry_paths:
308 if fnmatch.fnmatch(fname, path):
312 entries = self.BuildEntryList()
314 # This is our list of lines. Each item in the list is a list of strings, one
317 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
319 num_columns = len(HEADER)
321 # This records the width of each column, calculated as the maximum width of
322 # all the strings in that column
323 widths = [0] * num_columns
324 _DoLine(lines, HEADER)
326 # We won't print anything unless it has at least this indent. So at the
327 # start we will print nothing, unless a path matches (or there are no
330 min_indent = MAX_INDENT
334 selected_entries = []
335 for entry in entries:
336 if entry.indent > indent:
337 path_stack.append(path)
338 elif entry.indent < indent:
341 path = path_stack[-1] + '/' + entry.name
342 indent = entry.indent
344 # If there are entry paths to match and we are not looking at a
345 # sub-entry of a previously matched entry, we need to check the path
346 if entry_paths and indent <= min_indent:
347 if _NameInPaths(path[1:], entry_paths):
348 # Print this entry and all sub-entries (=higher indent)
351 # Don't print this entry, nor any following entries until we get
353 min_indent = MAX_INDENT
355 _DoLine(lines, _EntryToStrings(entry))
356 selected_entries.append(entry)
357 return selected_entries, lines, widths
359 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
360 """Look up a symbol in an ELF file
362 Looks up a symbol in an ELF file. Only entry types which come from an
363 ELF image can be used by this function.
365 This searches through this image including all of its subsections.
367 At present the only entry properties supported are:
369 image_pos - 'base_addr' is added if this is not an end-at-4gb image
373 sym_name: Symbol name in the ELF file to look up in the format
374 _binman_<entry>_prop_<property> where <entry> is the name of
375 the entry and <property> is the property to find (e.g.
376 _binman_u_boot_prop_offset). As a special case, you can append
377 _any to <entry> to have it search for any matching entry. E.g.
378 _binman_u_boot_any_prop_offset will match entries called u-boot,
379 u-boot-img and u-boot-nodtb)
380 optional: True if the symbol is optional. If False this function
381 will raise if the symbol is not found
382 msg: Message to display if an error occurs
383 base_addr: Base address of image. This is added to the returned
384 image_pos in most cases so that the returned position indicates
385 where the targeted entry/binary has actually been loaded. But
386 if end-at-4gb is used, this is not done, since the binary is
387 already assumed to be linked to the ROM position and using
388 execute-in-place (XIP).
391 Value that should be assigned to that symbol, or None if it was
392 optional and not found
395 ValueError if the symbol is invalid or not found, or references a
396 property which is not supported
398 entries = OrderedDict()
400 self._CollectEntries(entries, entries_by_name, self)
401 return self.LookupSymbol(sym_name, optional, msg, base_addr,
404 def CollectBintools(self):
405 """Collect all the bintools used by this image
410 value: Bintool object
413 super().AddBintools(bintools)
414 self.bintools = bintools