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 __future__ import print_function
10 from collections import OrderedDict
12 from operator import attrgetter
16 from entry import Entry
17 from etype import fdtmap
18 from etype import image_header
19 from etype import section
25 class Image(section.Entry_section):
26 """A Image, representing an output from binman
28 An image is comprised of a collection of entries each containing binary
29 data. The image size must be large enough to hold all of this data.
31 This class implements the various operations needed for images.
34 filename: Output filename for image
37 test: True if this is being called from a test of Images. This this case
38 there is no device tree defining the structure of the section, so
39 we create a section manually.
41 def __init__(self, name, node, test=False):
43 section.Entry_section.__init__(self, None, 'section', node, test)
44 self.name = 'main-section'
45 self.image_name = name
46 self._filename = '%s.bin' % self.image_name
48 filename = fdt_util.GetString(self._node, 'filename')
50 self._filename = filename
53 def FromFile(cls, fname):
54 """Convert an image file into an Image for use in binman
57 fname: Filename of image file to read
60 Image object on success
63 ValueError if something goes wrong
65 data = tools.ReadFile(fname)
68 # First look for an image header
69 pos = image_header.LocateHeaderOffset(data)
71 # Look for the FDT map
72 pos = fdtmap.LocateFdtmap(data)
74 raise ValueError('Cannot find FDT map in image')
76 # We don't know the FDT size, so check its header first
77 probe_dtb = fdt.Fdt.FromData(
78 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
79 dtb_size = probe_dtb.GetFdtObj().totalsize()
80 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
81 dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
84 # Return an Image with the associated nodes
85 image = Image('image', dtb.GetRoot())
90 """Convenience function to raise an error referencing an image"""
91 raise ValueError("Image '%s': %s" % (self._node.path, msg))
93 def PackEntries(self):
94 """Pack all entries into the image"""
95 section.Entry_section.Pack(self, 0)
97 def SetImagePos(self):
98 # This first section in the image so it starts at 0
99 section.Entry_section.SetImagePos(self, 0)
101 def ProcessEntryContents(self):
102 """Call the ProcessContents() method for each entry
104 This is intended to adjust the contents as needed by the entry type.
107 True if the new data size is OK, False if expansion is needed
110 for entry in self._entries.values():
111 if not entry.ProcessContents():
113 tout.Debug("Entry '%s' size change" % self._node.path)
116 def WriteSymbols(self):
117 """Write symbol values into binary files for access at run time"""
118 section.Entry_section.WriteSymbols(self, self)
120 def BuildSection(self, fd, base_offset):
121 """Write the section to a file"""
123 fd.write(self.GetData())
125 def BuildImage(self):
126 """Write the image to a file"""
127 fname = tools.GetOutputFilename(self._filename)
128 with open(fname, 'wb') as fd:
129 self.BuildSection(fd, 0)
132 """Write a map of the image to a .map file
135 Filename of map file written
137 filename = '%s.map' % self.image_name
138 fname = tools.GetOutputFilename(filename)
139 with open(fname, 'w') as fd:
140 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
142 section.Entry_section.WriteMap(self, fd, 0)
145 def BuildEntryList(self):
146 """List the files in an image
149 List of entry.EntryInfo objects describing all entries in the image
152 self.ListEntries(entries, 0)
155 def FindEntryPath(self, entry_path):
156 """Find an entry at a given path in the image
159 entry_path: Path to entry (e.g. /ro-section/u-boot')
162 Entry object corresponding to that past
165 ValueError if no entry found
167 parts = entry_path.split('/')
168 entries = self.GetEntries()
171 entry = entries.get(part)
173 raise ValueError("Entry '%s' not found in '%s'" %
175 parent = entry.GetPath()
176 entries = entry.GetEntries()
179 def ReadData(self, decomp=True):
182 def GetListEntries(self, entry_paths):
183 """List the entries in an image
185 This decodes the supplied image and returns a list of entries from that
186 image, preceded by a header.
189 entry_paths: List of paths to match (each can have wildcards). Only
190 entries whose names match one of these paths will be printed
193 String error message if something went wrong, otherwise
195 List of EntryInfo objects
197 List of text columns, each a string
198 List of widths of each column
200 def _EntryToStrings(entry):
201 """Convert an entry to a list of strings, one for each column
204 entry: EntryInfo object containing information to output
207 List of strings, one for each field in entry
210 """Append a hex value, or an empty string if val is None
213 val: Integer value, or None if none
215 args.append('' if val is None else '>%x' % val)
217 args = [' ' * entry.indent + entry.name]
218 _AppendHex(entry.image_pos)
219 _AppendHex(entry.size)
220 args.append(entry.etype)
221 _AppendHex(entry.offset)
222 _AppendHex(entry.uncomp_size)
225 def _DoLine(lines, line):
226 """Add a line to the output list
228 This adds a line (a list of columns) to the output list. It also updates
229 the widths[] array with the maximum width of each column
232 lines: List of lines to add to
233 line: List of strings, one for each column
235 for i, item in enumerate(line):
236 widths[i] = max(widths[i], len(item))
239 def _NameInPaths(fname, entry_paths):
240 """Check if a filename is in a list of wildcarded paths
243 fname: Filename to check
244 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
248 True if any wildcard matches the filename (using Unix filename
249 pattern matching, not regular expressions)
252 for path in entry_paths:
253 if fnmatch.fnmatch(fname, path):
257 entries = self.BuildEntryList()
259 # This is our list of lines. Each item in the list is a list of strings, one
262 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
264 num_columns = len(HEADER)
266 # This records the width of each column, calculated as the maximum width of
267 # all the strings in that column
268 widths = [0] * num_columns
269 _DoLine(lines, HEADER)
271 # We won't print anything unless it has at least this indent. So at the
272 # start we will print nothing, unless a path matches (or there are no
275 min_indent = MAX_INDENT
279 selected_entries = []
280 for entry in entries:
281 if entry.indent > indent:
282 path_stack.append(path)
283 elif entry.indent < indent:
286 path = path_stack[-1] + '/' + entry.name
287 indent = entry.indent
289 # If there are entry paths to match and we are not looking at a
290 # sub-entry of a previously matched entry, we need to check the path
291 if entry_paths and indent <= min_indent:
292 if _NameInPaths(path[1:], entry_paths):
293 # Print this entry and all sub-entries (=higher indent)
296 # Don't print this entry, nor any following entries until we get
298 min_indent = MAX_INDENT
300 _DoLine(lines, _EntryToStrings(entry))
301 selected_entries.append(entry)
302 return selected_entries, lines, widths