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
17 from entry import Entry
18 from etype import fdtmap
19 from etype import image_header
20 from etype import section
26 class Image(section.Entry_section):
27 """A Image, representing an output from binman
29 An image is comprised of a collection of entries each containing binary
30 data. The image size must be large enough to hold all of this data.
32 This class implements the various operations needed for images.
35 filename: Output filename for image
36 image_node: Name of node containing the description for this image
37 fdtmap_dtb: Fdt object for the fdtmap when loading from a file
38 fdtmap_data: Contents of the fdtmap when loading from a file
39 allow_repack: True to add properties to allow the image to be safely
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.
49 def __init__(self, name, node, copy_to_orig=True, test=False):
50 section.Entry_section.__init__(self, None, 'section', node, test=test)
51 self.copy_to_orig = copy_to_orig
52 self.name = 'main-section'
53 self.image_name = name
54 self._filename = '%s.bin' % self.image_name
55 self.fdtmap_dtb = None
56 self.fdtmap_data = None
57 self.allow_repack = False
62 section.Entry_section.ReadNode(self)
63 filename = fdt_util.GetString(self._node, 'filename')
65 self._filename = filename
66 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
69 def FromFile(cls, fname):
70 """Convert an image file into an Image for use in binman
73 fname: Filename of image file to read
76 Image object on success
79 ValueError if something goes wrong
81 data = tools.ReadFile(fname)
84 # First look for an image header
85 pos = image_header.LocateHeaderOffset(data)
87 # Look for the FDT map
88 pos = fdtmap.LocateFdtmap(data)
90 raise ValueError('Cannot find FDT map in image')
92 # We don't know the FDT size, so check its header first
93 probe_dtb = fdt.Fdt.FromData(
94 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
95 dtb_size = probe_dtb.GetFdtObj().totalsize()
96 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
97 dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
100 # Return an Image with the associated nodes
102 image = Image('image', root, copy_to_orig=False)
103 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
104 image.fdtmap_dtb = dtb
105 image.fdtmap_data = fdtmap_data
107 image._filename = fname
108 image.image_name, _ = os.path.splitext(fname)
111 def Raise(self, msg):
112 """Convenience function to raise an error referencing an image"""
113 raise ValueError("Image '%s': %s" % (self._node.path, msg))
115 def PackEntries(self):
116 """Pack all entries into the image"""
117 section.Entry_section.Pack(self, 0)
119 def SetImagePos(self):
120 # This first section in the image so it starts at 0
121 section.Entry_section.SetImagePos(self, 0)
123 def ProcessEntryContents(self):
124 """Call the ProcessContents() method for each entry
126 This is intended to adjust the contents as needed by the entry type.
129 True if the new data size is OK, False if expansion is needed
132 for entry in self._entries.values():
133 if not entry.ProcessContents():
135 tout.Debug("Entry '%s' size change" % self._node.path)
138 def WriteSymbols(self):
139 """Write symbol values into binary files for access at run time"""
140 section.Entry_section.WriteSymbols(self, self)
142 def BuildSection(self, fd, base_offset):
143 """Write the section to a file"""
145 fd.write(self.GetData())
147 def BuildImage(self):
148 """Write the image to a file"""
149 fname = tools.GetOutputFilename(self._filename)
150 with open(fname, 'wb') as fd:
151 self.BuildSection(fd, 0)
154 """Write a map of the image to a .map file
157 Filename of map file written
159 filename = '%s.map' % self.image_name
160 fname = tools.GetOutputFilename(filename)
161 with open(fname, 'w') as fd:
162 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
164 section.Entry_section.WriteMap(self, fd, 0)
167 def BuildEntryList(self):
168 """List the files in an image
171 List of entry.EntryInfo objects describing all entries in the image
174 self.ListEntries(entries, 0)
177 def FindEntryPath(self, entry_path):
178 """Find an entry at a given path in the image
181 entry_path: Path to entry (e.g. /ro-section/u-boot')
184 Entry object corresponding to that past
187 ValueError if no entry found
189 parts = entry_path.split('/')
190 entries = self.GetEntries()
193 entry = entries.get(part)
195 raise ValueError("Entry '%s' not found in '%s'" %
197 parent = entry.GetPath()
198 entries = entry.GetEntries()
201 def ReadData(self, decomp=True):
204 def GetListEntries(self, entry_paths):
205 """List the entries in an image
207 This decodes the supplied image and returns a list of entries from that
208 image, preceded by a header.
211 entry_paths: List of paths to match (each can have wildcards). Only
212 entries whose names match one of these paths will be printed
215 String error message if something went wrong, otherwise
217 List of EntryInfo objects
219 List of text columns, each a string
220 List of widths of each column
222 def _EntryToStrings(entry):
223 """Convert an entry to a list of strings, one for each column
226 entry: EntryInfo object containing information to output
229 List of strings, one for each field in entry
232 """Append a hex value, or an empty string if val is None
235 val: Integer value, or None if none
237 args.append('' if val is None else '>%x' % val)
239 args = [' ' * entry.indent + entry.name]
240 _AppendHex(entry.image_pos)
241 _AppendHex(entry.size)
242 args.append(entry.etype)
243 _AppendHex(entry.offset)
244 _AppendHex(entry.uncomp_size)
247 def _DoLine(lines, line):
248 """Add a line to the output list
250 This adds a line (a list of columns) to the output list. It also updates
251 the widths[] array with the maximum width of each column
254 lines: List of lines to add to
255 line: List of strings, one for each column
257 for i, item in enumerate(line):
258 widths[i] = max(widths[i], len(item))
261 def _NameInPaths(fname, entry_paths):
262 """Check if a filename is in a list of wildcarded paths
265 fname: Filename to check
266 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
270 True if any wildcard matches the filename (using Unix filename
271 pattern matching, not regular expressions)
274 for path in entry_paths:
275 if fnmatch.fnmatch(fname, path):
279 entries = self.BuildEntryList()
281 # This is our list of lines. Each item in the list is a list of strings, one
284 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
286 num_columns = len(HEADER)
288 # This records the width of each column, calculated as the maximum width of
289 # all the strings in that column
290 widths = [0] * num_columns
291 _DoLine(lines, HEADER)
293 # We won't print anything unless it has at least this indent. So at the
294 # start we will print nothing, unless a path matches (or there are no
297 min_indent = MAX_INDENT
301 selected_entries = []
302 for entry in entries:
303 if entry.indent > indent:
304 path_stack.append(path)
305 elif entry.indent < indent:
308 path = path_stack[-1] + '/' + entry.name
309 indent = entry.indent
311 # If there are entry paths to match and we are not looking at a
312 # sub-entry of a previously matched entry, we need to check the path
313 if entry_paths and indent <= min_indent:
314 if _NameInPaths(path[1:], entry_paths):
315 # Print this entry and all sub-entries (=higher indent)
318 # Don't print this entry, nor any following entries until we get
320 min_indent = MAX_INDENT
322 _DoLine(lines, _EntryToStrings(entry))
323 selected_entries.append(entry)
324 return selected_entries, lines, widths