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.
51 def __init__(self, name, node, copy_to_orig=True, test=False,
52 ignore_missing=False):
53 super().__init__(None, 'section', node, test=test)
54 self.copy_to_orig = copy_to_orig
55 self.name = 'main-section'
56 self.image_name = name
57 self._filename = '%s.bin' % self.image_name
58 self.fdtmap_dtb = None
59 self.fdtmap_data = None
60 self.allow_repack = False
61 self._ignore_missing = ignore_missing
67 filename = fdt_util.GetString(self._node, 'filename')
69 self._filename = filename
70 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
73 def FromFile(cls, fname):
74 """Convert an image file into an Image for use in binman
77 fname: Filename of image file to read
80 Image object on success
83 ValueError if something goes wrong
85 data = tools.ReadFile(fname)
88 # First look for an image header
89 pos = image_header.LocateHeaderOffset(data)
91 # Look for the FDT map
92 pos = fdtmap.LocateFdtmap(data)
94 raise ValueError('Cannot find FDT map in image')
96 # We don't know the FDT size, so check its header first
97 probe_dtb = fdt.Fdt.FromData(
98 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
99 dtb_size = probe_dtb.GetFdtObj().totalsize()
100 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
101 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
102 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
103 tools.WriteFile(out_fname, fdt_data)
104 dtb = fdt.Fdt(out_fname)
107 # Return an Image with the associated nodes
109 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
111 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
112 image.fdtmap_dtb = dtb
113 image.fdtmap_data = fdtmap_data
115 image._filename = fname
116 image.image_name, _ = os.path.splitext(fname)
119 def Raise(self, msg):
120 """Convenience function to raise an error referencing an image"""
121 raise ValueError("Image '%s': %s" % (self._node.path, msg))
123 def PackEntries(self):
124 """Pack all entries into the image"""
127 def SetImagePos(self):
128 # This first section in the image so it starts at 0
129 super().SetImagePos(0)
131 def ProcessEntryContents(self):
132 """Call the ProcessContents() method for each entry
134 This is intended to adjust the contents as needed by the entry type.
137 True if the new data size is OK, False if expansion is needed
140 for entry in self._entries.values():
141 if not entry.ProcessContents():
143 tout.Debug("Entry '%s' size change" % self._node.path)
146 def WriteSymbols(self):
147 """Write symbol values into binary files for access at run time"""
148 super().WriteSymbols(self)
150 def BuildImage(self):
151 """Write the image to a file"""
152 fname = tools.GetOutputFilename(self._filename)
153 tout.Info("Writing image to '%s'" % fname)
154 with open(fname, 'wb') as fd:
155 data = self.GetPaddedData()
157 tout.Info("Wrote %#x bytes" % len(data))
160 """Write a map of the image to a .map file
163 Filename of map file written
165 filename = '%s.map' % self.image_name
166 fname = tools.GetOutputFilename(filename)
167 with open(fname, 'w') as fd:
168 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
170 super().WriteMap(fd, 0)
173 def BuildEntryList(self):
174 """List the files in an image
177 List of entry.EntryInfo objects describing all entries in the image
180 self.ListEntries(entries, 0)
183 def FindEntryPath(self, entry_path):
184 """Find an entry at a given path in the image
187 entry_path: Path to entry (e.g. /ro-section/u-boot')
190 Entry object corresponding to that past
193 ValueError if no entry found
195 parts = entry_path.split('/')
196 entries = self.GetEntries()
199 entry = entries.get(part)
201 raise ValueError("Entry '%s' not found in '%s'" %
203 parent = entry.GetPath()
204 entries = entry.GetEntries()
207 def ReadData(self, decomp=True):
208 tout.Debug("Image '%s' ReadData(), size=%#x" %
209 (self.GetPath(), len(self._data)))
212 def GetListEntries(self, entry_paths):
213 """List the entries in an image
215 This decodes the supplied image and returns a list of entries from that
216 image, preceded by a header.
219 entry_paths: List of paths to match (each can have wildcards). Only
220 entries whose names match one of these paths will be printed
223 String error message if something went wrong, otherwise
225 List of EntryInfo objects
227 List of text columns, each a string
228 List of widths of each column
230 def _EntryToStrings(entry):
231 """Convert an entry to a list of strings, one for each column
234 entry: EntryInfo object containing information to output
237 List of strings, one for each field in entry
240 """Append a hex value, or an empty string if val is None
243 val: Integer value, or None if none
245 args.append('' if val is None else '>%x' % val)
247 args = [' ' * entry.indent + entry.name]
248 _AppendHex(entry.image_pos)
249 _AppendHex(entry.size)
250 args.append(entry.etype)
251 _AppendHex(entry.offset)
252 _AppendHex(entry.uncomp_size)
255 def _DoLine(lines, line):
256 """Add a line to the output list
258 This adds a line (a list of columns) to the output list. It also updates
259 the widths[] array with the maximum width of each column
262 lines: List of lines to add to
263 line: List of strings, one for each column
265 for i, item in enumerate(line):
266 widths[i] = max(widths[i], len(item))
269 def _NameInPaths(fname, entry_paths):
270 """Check if a filename is in a list of wildcarded paths
273 fname: Filename to check
274 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
278 True if any wildcard matches the filename (using Unix filename
279 pattern matching, not regular expressions)
282 for path in entry_paths:
283 if fnmatch.fnmatch(fname, path):
287 entries = self.BuildEntryList()
289 # This is our list of lines. Each item in the list is a list of strings, one
292 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
294 num_columns = len(HEADER)
296 # This records the width of each column, calculated as the maximum width of
297 # all the strings in that column
298 widths = [0] * num_columns
299 _DoLine(lines, HEADER)
301 # We won't print anything unless it has at least this indent. So at the
302 # start we will print nothing, unless a path matches (or there are no
305 min_indent = MAX_INDENT
309 selected_entries = []
310 for entry in entries:
311 if entry.indent > indent:
312 path_stack.append(path)
313 elif entry.indent < indent:
316 path = path_stack[-1] + '/' + entry.name
317 indent = entry.indent
319 # If there are entry paths to match and we are not looking at a
320 # sub-entry of a previously matched entry, we need to check the path
321 if entry_paths and indent <= min_indent:
322 if _NameInPaths(path[1:], entry_paths):
323 # Print this entry and all sub-entries (=higher indent)
326 # Don't print this entry, nor any following entries until we get
328 min_indent = MAX_INDENT
330 _DoLine(lines, _EntryToStrings(entry))
331 selected_entries.append(entry)
332 return selected_entries, lines, widths
334 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
335 """Look up a symbol in an ELF file
337 Looks up a symbol in an ELF file. Only entry types which come from an
338 ELF image can be used by this function.
340 This searches through this image including all of its subsections.
342 At present the only entry properties supported are:
344 image_pos - 'base_addr' is added if this is not an end-at-4gb image
348 sym_name: Symbol name in the ELF file to look up in the format
349 _binman_<entry>_prop_<property> where <entry> is the name of
350 the entry and <property> is the property to find (e.g.
351 _binman_u_boot_prop_offset). As a special case, you can append
352 _any to <entry> to have it search for any matching entry. E.g.
353 _binman_u_boot_any_prop_offset will match entries called u-boot,
354 u-boot-img and u-boot-nodtb)
355 optional: True if the symbol is optional. If False this function
356 will raise if the symbol is not found
357 msg: Message to display if an error occurs
358 base_addr: Base address of image. This is added to the returned
359 image_pos in most cases so that the returned position indicates
360 where the targeted entry/binary has actually been loaded. But
361 if end-at-4gb is used, this is not done, since the binary is
362 already assumed to be linked to the ROM position and using
363 execute-in-place (XIP).
366 Value that should be assigned to that symbol, or None if it was
367 optional and not found
370 ValueError if the symbol is invalid or not found, or references a
371 property which is not supported
373 entries = OrderedDict()
375 self._CollectEntries(entries, entries_by_name, self)
376 return self.LookupSymbol(sym_name, optional, msg, base_addr,