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 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
98 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
99 tools.WriteFile(out_fname, fdt_data)
100 dtb = fdt.Fdt(out_fname)
103 # Return an Image with the associated nodes
105 image = Image('image', root, copy_to_orig=False)
107 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
108 image.fdtmap_dtb = dtb
109 image.fdtmap_data = fdtmap_data
111 image._filename = fname
112 image.image_name, _ = os.path.splitext(fname)
115 def Raise(self, msg):
116 """Convenience function to raise an error referencing an image"""
117 raise ValueError("Image '%s': %s" % (self._node.path, msg))
119 def PackEntries(self):
120 """Pack all entries into the image"""
121 section.Entry_section.Pack(self, 0)
123 def SetImagePos(self):
124 # This first section in the image so it starts at 0
125 section.Entry_section.SetImagePos(self, 0)
127 def ProcessEntryContents(self):
128 """Call the ProcessContents() method for each entry
130 This is intended to adjust the contents as needed by the entry type.
133 True if the new data size is OK, False if expansion is needed
136 for entry in self._entries.values():
137 if not entry.ProcessContents():
139 tout.Debug("Entry '%s' size change" % self._node.path)
142 def WriteSymbols(self):
143 """Write symbol values into binary files for access at run time"""
144 section.Entry_section.WriteSymbols(self, self)
146 def BuildImage(self):
147 """Write the image to a file"""
148 fname = tools.GetOutputFilename(self._filename)
149 tout.Info("Writing image to '%s'" % fname)
150 with open(fname, 'wb') as fd:
151 data = self.GetData()
153 tout.Info("Wrote %#x bytes" % len(data))
156 """Write a map of the image to a .map file
159 Filename of map file written
161 filename = '%s.map' % self.image_name
162 fname = tools.GetOutputFilename(filename)
163 with open(fname, 'w') as fd:
164 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
166 section.Entry_section.WriteMap(self, fd, 0)
169 def BuildEntryList(self):
170 """List the files in an image
173 List of entry.EntryInfo objects describing all entries in the image
176 self.ListEntries(entries, 0)
179 def FindEntryPath(self, entry_path):
180 """Find an entry at a given path in the image
183 entry_path: Path to entry (e.g. /ro-section/u-boot')
186 Entry object corresponding to that past
189 ValueError if no entry found
191 parts = entry_path.split('/')
192 entries = self.GetEntries()
195 entry = entries.get(part)
197 raise ValueError("Entry '%s' not found in '%s'" %
199 parent = entry.GetPath()
200 entries = entry.GetEntries()
203 def ReadData(self, decomp=True):
204 tout.Debug("Image '%s' ReadData(), size=%#x" %
205 (self.GetPath(), len(self._data)))
208 def GetListEntries(self, entry_paths):
209 """List the entries in an image
211 This decodes the supplied image and returns a list of entries from that
212 image, preceded by a header.
215 entry_paths: List of paths to match (each can have wildcards). Only
216 entries whose names match one of these paths will be printed
219 String error message if something went wrong, otherwise
221 List of EntryInfo objects
223 List of text columns, each a string
224 List of widths of each column
226 def _EntryToStrings(entry):
227 """Convert an entry to a list of strings, one for each column
230 entry: EntryInfo object containing information to output
233 List of strings, one for each field in entry
236 """Append a hex value, or an empty string if val is None
239 val: Integer value, or None if none
241 args.append('' if val is None else '>%x' % val)
243 args = [' ' * entry.indent + entry.name]
244 _AppendHex(entry.image_pos)
245 _AppendHex(entry.size)
246 args.append(entry.etype)
247 _AppendHex(entry.offset)
248 _AppendHex(entry.uncomp_size)
251 def _DoLine(lines, line):
252 """Add a line to the output list
254 This adds a line (a list of columns) to the output list. It also updates
255 the widths[] array with the maximum width of each column
258 lines: List of lines to add to
259 line: List of strings, one for each column
261 for i, item in enumerate(line):
262 widths[i] = max(widths[i], len(item))
265 def _NameInPaths(fname, entry_paths):
266 """Check if a filename is in a list of wildcarded paths
269 fname: Filename to check
270 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
274 True if any wildcard matches the filename (using Unix filename
275 pattern matching, not regular expressions)
278 for path in entry_paths:
279 if fnmatch.fnmatch(fname, path):
283 entries = self.BuildEntryList()
285 # This is our list of lines. Each item in the list is a list of strings, one
288 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
290 num_columns = len(HEADER)
292 # This records the width of each column, calculated as the maximum width of
293 # all the strings in that column
294 widths = [0] * num_columns
295 _DoLine(lines, HEADER)
297 # We won't print anything unless it has at least this indent. So at the
298 # start we will print nothing, unless a path matches (or there are no
301 min_indent = MAX_INDENT
305 selected_entries = []
306 for entry in entries:
307 if entry.indent > indent:
308 path_stack.append(path)
309 elif entry.indent < indent:
312 path = path_stack[-1] + '/' + entry.name
313 indent = entry.indent
315 # If there are entry paths to match and we are not looking at a
316 # sub-entry of a previously matched entry, we need to check the path
317 if entry_paths and indent <= min_indent:
318 if _NameInPaths(path[1:], entry_paths):
319 # Print this entry and all sub-entries (=higher indent)
322 # Don't print this entry, nor any following entries until we get
324 min_indent = MAX_INDENT
326 _DoLine(lines, _EntryToStrings(entry))
327 selected_entries.append(entry)
328 return selected_entries, lines, widths