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
41 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
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.
46 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
47 exception). This should be used if the Image is being loaded from
48 a file rather than generated. In that case we obviously don't need
49 the entry arguments since the contents already exists.
50 use_expanded: True if we are updating the FDT wth entry offsets, etc.
51 and should use the expanded versions of the U-Boot entries.
52 Any entry type that includes a devicetree must put it in a
53 separate entry so that it will be updated. For example. 'u-boot'
54 normally just picks up 'u-boot.bin' which includes the
55 devicetree, but this is not updateable, since it comes into
56 binman as one piece and binman doesn't know that it is actually
57 an executable followed by a devicetree. Of course it could be
58 taught this, but then when reading an image (e.g. 'binman ls')
59 it may need to be able to split the devicetree out of the image
60 in order to determine the location of things. Instead we choose
61 to ignore 'u-boot-bin' in this case, and build it ourselves in
62 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
63 Entry_u_boot_expanded and Entry_blob_phase for details.
65 def __init__(self, name, node, copy_to_orig=True, test=False,
66 ignore_missing=False, use_expanded=False):
67 super().__init__(None, 'section', node, test=test)
68 self.copy_to_orig = copy_to_orig
69 self.name = 'main-section'
70 self.image_name = name
71 self._filename = '%s.bin' % self.image_name
72 self.fdtmap_dtb = None
73 self.fdtmap_data = None
74 self.allow_repack = False
75 self._ignore_missing = ignore_missing
76 self.use_expanded = use_expanded
82 filename = fdt_util.GetString(self._node, 'filename')
84 self._filename = filename
85 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
88 def FromFile(cls, fname):
89 """Convert an image file into an Image for use in binman
92 fname: Filename of image file to read
95 Image object on success
98 ValueError if something goes wrong
100 data = tools.ReadFile(fname)
103 # First look for an image header
104 pos = image_header.LocateHeaderOffset(data)
106 # Look for the FDT map
107 pos = fdtmap.LocateFdtmap(data)
109 raise ValueError('Cannot find FDT map in image')
111 # We don't know the FDT size, so check its header first
112 probe_dtb = fdt.Fdt.FromData(
113 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
114 dtb_size = probe_dtb.GetFdtObj().totalsize()
115 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
116 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
117 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
118 tools.WriteFile(out_fname, fdt_data)
119 dtb = fdt.Fdt(out_fname)
122 # Return an Image with the associated nodes
124 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
126 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
127 image.fdtmap_dtb = dtb
128 image.fdtmap_data = fdtmap_data
130 image._filename = fname
131 image.image_name, _ = os.path.splitext(fname)
134 def Raise(self, msg):
135 """Convenience function to raise an error referencing an image"""
136 raise ValueError("Image '%s': %s" % (self._node.path, msg))
138 def PackEntries(self):
139 """Pack all entries into the image"""
142 def SetImagePos(self):
143 # This first section in the image so it starts at 0
144 super().SetImagePos(0)
146 def ProcessEntryContents(self):
147 """Call the ProcessContents() method for each entry
149 This is intended to adjust the contents as needed by the entry type.
152 True if the new data size is OK, False if expansion is needed
154 return super().ProcessContents()
156 def WriteSymbols(self):
157 """Write symbol values into binary files for access at run time"""
158 super().WriteSymbols(self)
160 def BuildImage(self):
161 """Write the image to a file"""
162 fname = tools.GetOutputFilename(self._filename)
163 tout.Info("Writing image to '%s'" % fname)
164 with open(fname, 'wb') as fd:
165 data = self.GetPaddedData()
167 tout.Info("Wrote %#x bytes" % len(data))
170 """Write a map of the image to a .map file
173 Filename of map file written
175 filename = '%s.map' % self.image_name
176 fname = tools.GetOutputFilename(filename)
177 with open(fname, 'w') as fd:
178 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
180 super().WriteMap(fd, 0)
183 def BuildEntryList(self):
184 """List the files in an image
187 List of entry.EntryInfo objects describing all entries in the image
190 self.ListEntries(entries, 0)
193 def FindEntryPath(self, entry_path):
194 """Find an entry at a given path in the image
197 entry_path: Path to entry (e.g. /ro-section/u-boot')
200 Entry object corresponding to that past
203 ValueError if no entry found
205 parts = entry_path.split('/')
206 entries = self.GetEntries()
209 entry = entries.get(part)
211 raise ValueError("Entry '%s' not found in '%s'" %
213 parent = entry.GetPath()
214 entries = entry.GetEntries()
217 def ReadData(self, decomp=True):
218 tout.Debug("Image '%s' ReadData(), size=%#x" %
219 (self.GetPath(), len(self._data)))
222 def GetListEntries(self, entry_paths):
223 """List the entries in an image
225 This decodes the supplied image and returns a list of entries from that
226 image, preceded by a header.
229 entry_paths: List of paths to match (each can have wildcards). Only
230 entries whose names match one of these paths will be printed
233 String error message if something went wrong, otherwise
235 List of EntryInfo objects
237 List of text columns, each a string
238 List of widths of each column
240 def _EntryToStrings(entry):
241 """Convert an entry to a list of strings, one for each column
244 entry: EntryInfo object containing information to output
247 List of strings, one for each field in entry
250 """Append a hex value, or an empty string if val is None
253 val: Integer value, or None if none
255 args.append('' if val is None else '>%x' % val)
257 args = [' ' * entry.indent + entry.name]
258 _AppendHex(entry.image_pos)
259 _AppendHex(entry.size)
260 args.append(entry.etype)
261 _AppendHex(entry.offset)
262 _AppendHex(entry.uncomp_size)
265 def _DoLine(lines, line):
266 """Add a line to the output list
268 This adds a line (a list of columns) to the output list. It also updates
269 the widths[] array with the maximum width of each column
272 lines: List of lines to add to
273 line: List of strings, one for each column
275 for i, item in enumerate(line):
276 widths[i] = max(widths[i], len(item))
279 def _NameInPaths(fname, entry_paths):
280 """Check if a filename is in a list of wildcarded paths
283 fname: Filename to check
284 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
288 True if any wildcard matches the filename (using Unix filename
289 pattern matching, not regular expressions)
292 for path in entry_paths:
293 if fnmatch.fnmatch(fname, path):
297 entries = self.BuildEntryList()
299 # This is our list of lines. Each item in the list is a list of strings, one
302 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
304 num_columns = len(HEADER)
306 # This records the width of each column, calculated as the maximum width of
307 # all the strings in that column
308 widths = [0] * num_columns
309 _DoLine(lines, HEADER)
311 # We won't print anything unless it has at least this indent. So at the
312 # start we will print nothing, unless a path matches (or there are no
315 min_indent = MAX_INDENT
319 selected_entries = []
320 for entry in entries:
321 if entry.indent > indent:
322 path_stack.append(path)
323 elif entry.indent < indent:
326 path = path_stack[-1] + '/' + entry.name
327 indent = entry.indent
329 # If there are entry paths to match and we are not looking at a
330 # sub-entry of a previously matched entry, we need to check the path
331 if entry_paths and indent <= min_indent:
332 if _NameInPaths(path[1:], entry_paths):
333 # Print this entry and all sub-entries (=higher indent)
336 # Don't print this entry, nor any following entries until we get
338 min_indent = MAX_INDENT
340 _DoLine(lines, _EntryToStrings(entry))
341 selected_entries.append(entry)
342 return selected_entries, lines, widths
344 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
345 """Look up a symbol in an ELF file
347 Looks up a symbol in an ELF file. Only entry types which come from an
348 ELF image can be used by this function.
350 This searches through this image including all of its subsections.
352 At present the only entry properties supported are:
354 image_pos - 'base_addr' is added if this is not an end-at-4gb image
358 sym_name: Symbol name in the ELF file to look up in the format
359 _binman_<entry>_prop_<property> where <entry> is the name of
360 the entry and <property> is the property to find (e.g.
361 _binman_u_boot_prop_offset). As a special case, you can append
362 _any to <entry> to have it search for any matching entry. E.g.
363 _binman_u_boot_any_prop_offset will match entries called u-boot,
364 u-boot-img and u-boot-nodtb)
365 optional: True if the symbol is optional. If False this function
366 will raise if the symbol is not found
367 msg: Message to display if an error occurs
368 base_addr: Base address of image. This is added to the returned
369 image_pos in most cases so that the returned position indicates
370 where the targeted entry/binary has actually been loaded. But
371 if end-at-4gb is used, this is not done, since the binary is
372 already assumed to be linked to the ROM position and using
373 execute-in-place (XIP).
376 Value that should be assigned to that symbol, or None if it was
377 optional and not found
380 ValueError if the symbol is invalid or not found, or references a
381 property which is not supported
383 entries = OrderedDict()
385 self._CollectEntries(entries, entries_by_name, self)
386 return self.LookupSymbol(sym_name, optional, msg, base_addr,