1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
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.
67 def __init__(self, name, node, copy_to_orig=True, test=False,
68 ignore_missing=False, use_expanded=False):
69 super().__init__(None, 'section', node, test=test)
70 self.copy_to_orig = copy_to_orig
71 self.name = 'main-section'
72 self.image_name = name
73 self._filename = '%s.bin' % self.image_name
74 self.fdtmap_dtb = None
75 self.fdtmap_data = None
76 self.allow_repack = False
77 self._ignore_missing = ignore_missing
78 self.use_expanded = use_expanded
79 self.test_section_timeout = False
85 filename = fdt_util.GetString(self._node, 'filename')
87 self._filename = filename
88 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
91 def FromFile(cls, fname):
92 """Convert an image file into an Image for use in binman
95 fname: Filename of image file to read
98 Image object on success
101 ValueError if something goes wrong
103 data = tools.ReadFile(fname)
106 # First look for an image header
107 pos = image_header.LocateHeaderOffset(data)
109 # Look for the FDT map
110 pos = fdtmap.LocateFdtmap(data)
112 raise ValueError('Cannot find FDT map in image')
114 # We don't know the FDT size, so check its header first
115 probe_dtb = fdt.Fdt.FromData(
116 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
117 dtb_size = probe_dtb.GetFdtObj().totalsize()
118 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
119 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
120 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
121 tools.WriteFile(out_fname, fdt_data)
122 dtb = fdt.Fdt(out_fname)
125 # Return an Image with the associated nodes
127 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
129 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
130 image.fdtmap_dtb = dtb
131 image.fdtmap_data = fdtmap_data
133 image._filename = fname
134 image.image_name, _ = os.path.splitext(fname)
137 def Raise(self, msg):
138 """Convenience function to raise an error referencing an image"""
139 raise ValueError("Image '%s': %s" % (self._node.path, msg))
141 def PackEntries(self):
142 """Pack all entries into the image"""
145 def SetImagePos(self):
146 # This first section in the image so it starts at 0
147 super().SetImagePos(0)
149 def ProcessEntryContents(self):
150 """Call the ProcessContents() method for each entry
152 This is intended to adjust the contents as needed by the entry type.
155 True if the new data size is OK, False if expansion is needed
157 return super().ProcessContents()
159 def WriteSymbols(self):
160 """Write symbol values into binary files for access at run time"""
161 super().WriteSymbols(self)
163 def BuildImage(self):
164 """Write the image to a file"""
165 fname = tools.GetOutputFilename(self._filename)
166 tout.Info("Writing image to '%s'" % fname)
167 with open(fname, 'wb') as fd:
168 data = self.GetPaddedData()
170 tout.Info("Wrote %#x bytes" % len(data))
173 """Write a map of the image to a .map file
176 Filename of map file written
178 filename = '%s.map' % self.image_name
179 fname = tools.GetOutputFilename(filename)
180 with open(fname, 'w') as fd:
181 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
183 super().WriteMap(fd, 0)
186 def BuildEntryList(self):
187 """List the files in an image
190 List of entry.EntryInfo objects describing all entries in the image
193 self.ListEntries(entries, 0)
196 def FindEntryPath(self, entry_path):
197 """Find an entry at a given path in the image
200 entry_path: Path to entry (e.g. /ro-section/u-boot')
203 Entry object corresponding to that past
206 ValueError if no entry found
208 parts = entry_path.split('/')
209 entries = self.GetEntries()
212 entry = entries.get(part)
214 raise ValueError("Entry '%s' not found in '%s'" %
216 parent = entry.GetPath()
217 entries = entry.GetEntries()
220 def ReadData(self, decomp=True):
221 tout.Debug("Image '%s' ReadData(), size=%#x" %
222 (self.GetPath(), len(self._data)))
225 def GetListEntries(self, entry_paths):
226 """List the entries in an image
228 This decodes the supplied image and returns a list of entries from that
229 image, preceded by a header.
232 entry_paths: List of paths to match (each can have wildcards). Only
233 entries whose names match one of these paths will be printed
236 String error message if something went wrong, otherwise
238 List of EntryInfo objects
240 List of text columns, each a string
241 List of widths of each column
243 def _EntryToStrings(entry):
244 """Convert an entry to a list of strings, one for each column
247 entry: EntryInfo object containing information to output
250 List of strings, one for each field in entry
253 """Append a hex value, or an empty string if val is None
256 val: Integer value, or None if none
258 args.append('' if val is None else '>%x' % val)
260 args = [' ' * entry.indent + entry.name]
261 _AppendHex(entry.image_pos)
262 _AppendHex(entry.size)
263 args.append(entry.etype)
264 _AppendHex(entry.offset)
265 _AppendHex(entry.uncomp_size)
268 def _DoLine(lines, line):
269 """Add a line to the output list
271 This adds a line (a list of columns) to the output list. It also updates
272 the widths[] array with the maximum width of each column
275 lines: List of lines to add to
276 line: List of strings, one for each column
278 for i, item in enumerate(line):
279 widths[i] = max(widths[i], len(item))
282 def _NameInPaths(fname, entry_paths):
283 """Check if a filename is in a list of wildcarded paths
286 fname: Filename to check
287 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
291 True if any wildcard matches the filename (using Unix filename
292 pattern matching, not regular expressions)
295 for path in entry_paths:
296 if fnmatch.fnmatch(fname, path):
300 entries = self.BuildEntryList()
302 # This is our list of lines. Each item in the list is a list of strings, one
305 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
307 num_columns = len(HEADER)
309 # This records the width of each column, calculated as the maximum width of
310 # all the strings in that column
311 widths = [0] * num_columns
312 _DoLine(lines, HEADER)
314 # We won't print anything unless it has at least this indent. So at the
315 # start we will print nothing, unless a path matches (or there are no
318 min_indent = MAX_INDENT
322 selected_entries = []
323 for entry in entries:
324 if entry.indent > indent:
325 path_stack.append(path)
326 elif entry.indent < indent:
329 path = path_stack[-1] + '/' + entry.name
330 indent = entry.indent
332 # If there are entry paths to match and we are not looking at a
333 # sub-entry of a previously matched entry, we need to check the path
334 if entry_paths and indent <= min_indent:
335 if _NameInPaths(path[1:], entry_paths):
336 # Print this entry and all sub-entries (=higher indent)
339 # Don't print this entry, nor any following entries until we get
341 min_indent = MAX_INDENT
343 _DoLine(lines, _EntryToStrings(entry))
344 selected_entries.append(entry)
345 return selected_entries, lines, widths
347 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
348 """Look up a symbol in an ELF file
350 Looks up a symbol in an ELF file. Only entry types which come from an
351 ELF image can be used by this function.
353 This searches through this image including all of its subsections.
355 At present the only entry properties supported are:
357 image_pos - 'base_addr' is added if this is not an end-at-4gb image
361 sym_name: Symbol name in the ELF file to look up in the format
362 _binman_<entry>_prop_<property> where <entry> is the name of
363 the entry and <property> is the property to find (e.g.
364 _binman_u_boot_prop_offset). As a special case, you can append
365 _any to <entry> to have it search for any matching entry. E.g.
366 _binman_u_boot_any_prop_offset will match entries called u-boot,
367 u-boot-img and u-boot-nodtb)
368 optional: True if the symbol is optional. If False this function
369 will raise if the symbol is not found
370 msg: Message to display if an error occurs
371 base_addr: Base address of image. This is added to the returned
372 image_pos in most cases so that the returned position indicates
373 where the targeted entry/binary has actually been loaded. But
374 if end-at-4gb is used, this is not done, since the binary is
375 already assumed to be linked to the ROM position and using
376 execute-in-place (XIP).
379 Value that should be assigned to that symbol, or None if it was
380 optional and not found
383 ValueError if the symbol is invalid or not found, or references a
384 property which is not supported
386 entries = OrderedDict()
388 self._CollectEntries(entries, entries_by_name, self)
389 return self.LookupSymbol(sym_name, optional, msg, base_addr,