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.
47 def __init__(self, name, node, copy_to_orig=True, test=False):
48 section.Entry_section.__init__(self, None, 'section', node, test=test)
49 self.copy_to_orig = copy_to_orig
50 self.name = 'main-section'
51 self.image_name = name
52 self._filename = '%s.bin' % self.image_name
53 self.fdtmap_dtb = None
54 self.fdtmap_data = None
55 self.allow_repack = False
60 section.Entry_section.ReadNode(self)
61 filename = fdt_util.GetString(self._node, 'filename')
63 self._filename = filename
64 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
67 def FromFile(cls, fname):
68 """Convert an image file into an Image for use in binman
71 fname: Filename of image file to read
74 Image object on success
77 ValueError if something goes wrong
79 data = tools.ReadFile(fname)
82 # First look for an image header
83 pos = image_header.LocateHeaderOffset(data)
85 # Look for the FDT map
86 pos = fdtmap.LocateFdtmap(data)
88 raise ValueError('Cannot find FDT map in image')
90 # We don't know the FDT size, so check its header first
91 probe_dtb = fdt.Fdt.FromData(
92 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
93 dtb_size = probe_dtb.GetFdtObj().totalsize()
94 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
95 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
96 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
97 tools.WriteFile(out_fname, fdt_data)
98 dtb = fdt.Fdt(out_fname)
101 # Return an Image with the associated nodes
103 image = Image('image', root, copy_to_orig=False)
105 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
106 image.fdtmap_dtb = dtb
107 image.fdtmap_data = fdtmap_data
109 image._filename = fname
110 image.image_name, _ = os.path.splitext(fname)
113 def Raise(self, msg):
114 """Convenience function to raise an error referencing an image"""
115 raise ValueError("Image '%s': %s" % (self._node.path, msg))
117 def PackEntries(self):
118 """Pack all entries into the image"""
119 section.Entry_section.Pack(self, 0)
121 def SetImagePos(self):
122 # This first section in the image so it starts at 0
123 section.Entry_section.SetImagePos(self, 0)
125 def ProcessEntryContents(self):
126 """Call the ProcessContents() method for each entry
128 This is intended to adjust the contents as needed by the entry type.
131 True if the new data size is OK, False if expansion is needed
134 for entry in self._entries.values():
135 if not entry.ProcessContents():
137 tout.Debug("Entry '%s' size change" % self._node.path)
140 def WriteSymbols(self):
141 """Write symbol values into binary files for access at run time"""
142 section.Entry_section.WriteSymbols(self, self)
144 def BuildImage(self):
145 """Write the image to a file"""
146 fname = tools.GetOutputFilename(self._filename)
147 tout.Info("Writing image to '%s'" % fname)
148 with open(fname, 'wb') as fd:
149 data = self.GetData()
151 tout.Info("Wrote %#x bytes" % len(data))
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):
202 tout.Debug("Image '%s' ReadData(), size=%#x" %
203 (self.GetPath(), len(self._data)))
206 def GetListEntries(self, entry_paths):
207 """List the entries in an image
209 This decodes the supplied image and returns a list of entries from that
210 image, preceded by a header.
213 entry_paths: List of paths to match (each can have wildcards). Only
214 entries whose names match one of these paths will be printed
217 String error message if something went wrong, otherwise
219 List of EntryInfo objects
221 List of text columns, each a string
222 List of widths of each column
224 def _EntryToStrings(entry):
225 """Convert an entry to a list of strings, one for each column
228 entry: EntryInfo object containing information to output
231 List of strings, one for each field in entry
234 """Append a hex value, or an empty string if val is None
237 val: Integer value, or None if none
239 args.append('' if val is None else '>%x' % val)
241 args = [' ' * entry.indent + entry.name]
242 _AppendHex(entry.image_pos)
243 _AppendHex(entry.size)
244 args.append(entry.etype)
245 _AppendHex(entry.offset)
246 _AppendHex(entry.uncomp_size)
249 def _DoLine(lines, line):
250 """Add a line to the output list
252 This adds a line (a list of columns) to the output list. It also updates
253 the widths[] array with the maximum width of each column
256 lines: List of lines to add to
257 line: List of strings, one for each column
259 for i, item in enumerate(line):
260 widths[i] = max(widths[i], len(item))
263 def _NameInPaths(fname, entry_paths):
264 """Check if a filename is in a list of wildcarded paths
267 fname: Filename to check
268 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
272 True if any wildcard matches the filename (using Unix filename
273 pattern matching, not regular expressions)
276 for path in entry_paths:
277 if fnmatch.fnmatch(fname, path):
281 entries = self.BuildEntryList()
283 # This is our list of lines. Each item in the list is a list of strings, one
286 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
288 num_columns = len(HEADER)
290 # This records the width of each column, calculated as the maximum width of
291 # all the strings in that column
292 widths = [0] * num_columns
293 _DoLine(lines, HEADER)
295 # We won't print anything unless it has at least this indent. So at the
296 # start we will print nothing, unless a path matches (or there are no
299 min_indent = MAX_INDENT
303 selected_entries = []
304 for entry in entries:
305 if entry.indent > indent:
306 path_stack.append(path)
307 elif entry.indent < indent:
310 path = path_stack[-1] + '/' + entry.name
311 indent = entry.indent
313 # If there are entry paths to match and we are not looking at a
314 # sub-entry of a previously matched entry, we need to check the path
315 if entry_paths and indent <= min_indent:
316 if _NameInPaths(path[1:], entry_paths):
317 # Print this entry and all sub-entries (=higher indent)
320 # Don't print this entry, nor any following entries until we get
322 min_indent = MAX_INDENT
324 _DoLine(lines, _EntryToStrings(entry))
325 selected_entries.append(entry)
326 return selected_entries, lines, widths