Merge tag 'xilinx-for-v2021.07' of https://source.denx.de/u-boot/custodians/u-boot...
[platform/kernel/u-boot.git] / tools / binman / image.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Class for an image, the output of binman
6 #
7
8 from collections import OrderedDict
9 import fnmatch
10 from operator import attrgetter
11 import os
12 import re
13 import sys
14
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
19 from dtoc import fdt
20 from dtoc import fdt_util
21 from patman import tools
22 from patman import tout
23
24 class Image(section.Entry_section):
25     """A Image, representing an output from binman
26
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.
29
30     This class implements the various operations needed for images.
31
32     Attributes:
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
38             repacked later
39
40     Args:
41         copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
42             from the device tree
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.
50         use_expanded: True if we are updating the FDT wth entry offsets, etc.
51             and should use the expanded versions of the U-Boot entries.
52             Any entry type that includes a devicetree must put it in a
53             separate entry so that it will be updated. For example. 'u-boot'
54             normally just picks up 'u-boot.bin' which includes the
55             devicetree, but this is not updateable, since it comes into
56             binman as one piece and binman doesn't know that it is actually
57             an executable followed by a devicetree. Of course it could be
58             taught this, but then when reading an image (e.g. 'binman ls')
59             it may need to be able to split the devicetree out of the image
60             in order to determine the location of things. Instead we choose
61             to ignore 'u-boot-bin' in this case, and build it ourselves in
62             binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
63             Entry_u_boot_expanded and Entry_blob_phase for details.
64     """
65     def __init__(self, name, node, copy_to_orig=True, test=False,
66                  ignore_missing=False, use_expanded=False):
67         super().__init__(None, 'section', node, test=test)
68         self.copy_to_orig = copy_to_orig
69         self.name = 'main-section'
70         self.image_name = name
71         self._filename = '%s.bin' % self.image_name
72         self.fdtmap_dtb = None
73         self.fdtmap_data = None
74         self.allow_repack = False
75         self._ignore_missing = ignore_missing
76         self.use_expanded = use_expanded
77         if not test:
78             self.ReadNode()
79
80     def ReadNode(self):
81         super().ReadNode()
82         filename = fdt_util.GetString(self._node, 'filename')
83         if filename:
84             self._filename = filename
85         self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
86
87     @classmethod
88     def FromFile(cls, fname):
89         """Convert an image file into an Image for use in binman
90
91         Args:
92             fname: Filename of image file to read
93
94         Returns:
95             Image object on success
96
97         Raises:
98             ValueError if something goes wrong
99         """
100         data = tools.ReadFile(fname)
101         size = len(data)
102
103         # First look for an image header
104         pos = image_header.LocateHeaderOffset(data)
105         if pos is None:
106             # Look for the FDT map
107             pos = fdtmap.LocateFdtmap(data)
108         if pos is None:
109             raise ValueError('Cannot find FDT map in image')
110
111         # We don't know the FDT size, so check its header first
112         probe_dtb = fdt.Fdt.FromData(
113             data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
114         dtb_size = probe_dtb.GetFdtObj().totalsize()
115         fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
116         fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
117         out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
118         tools.WriteFile(out_fname, fdt_data)
119         dtb = fdt.Fdt(out_fname)
120         dtb.Scan()
121
122         # Return an Image with the associated nodes
123         root = dtb.GetRoot()
124         image = Image('image', root, copy_to_orig=False, ignore_missing=True)
125
126         image.image_node = fdt_util.GetString(root, 'image-node', 'image')
127         image.fdtmap_dtb = dtb
128         image.fdtmap_data = fdtmap_data
129         image._data = data
130         image._filename = fname
131         image.image_name, _ = os.path.splitext(fname)
132         return image
133
134     def Raise(self, msg):
135         """Convenience function to raise an error referencing an image"""
136         raise ValueError("Image '%s': %s" % (self._node.path, msg))
137
138     def PackEntries(self):
139         """Pack all entries into the image"""
140         super().Pack(0)
141
142     def SetImagePos(self):
143         # This first section in the image so it starts at 0
144         super().SetImagePos(0)
145
146     def ProcessEntryContents(self):
147         """Call the ProcessContents() method for each entry
148
149         This is intended to adjust the contents as needed by the entry type.
150
151         Returns:
152             True if the new data size is OK, False if expansion is needed
153         """
154         return super().ProcessContents()
155
156     def WriteSymbols(self):
157         """Write symbol values into binary files for access at run time"""
158         super().WriteSymbols(self)
159
160     def BuildImage(self):
161         """Write the image to a file"""
162         fname = tools.GetOutputFilename(self._filename)
163         tout.Info("Writing image to '%s'" % fname)
164         with open(fname, 'wb') as fd:
165             data = self.GetPaddedData()
166             fd.write(data)
167         tout.Info("Wrote %#x bytes" % len(data))
168
169     def WriteMap(self):
170         """Write a map of the image to a .map file
171
172         Returns:
173             Filename of map file written
174         """
175         filename = '%s.map' % self.image_name
176         fname = tools.GetOutputFilename(filename)
177         with open(fname, 'w') as fd:
178             print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
179                   file=fd)
180             super().WriteMap(fd, 0)
181         return fname
182
183     def BuildEntryList(self):
184         """List the files in an image
185
186         Returns:
187             List of entry.EntryInfo objects describing all entries in the image
188         """
189         entries = []
190         self.ListEntries(entries, 0)
191         return entries
192
193     def FindEntryPath(self, entry_path):
194         """Find an entry at a given path in the image
195
196         Args:
197             entry_path: Path to entry (e.g. /ro-section/u-boot')
198
199         Returns:
200             Entry object corresponding to that past
201
202         Raises:
203             ValueError if no entry found
204         """
205         parts = entry_path.split('/')
206         entries = self.GetEntries()
207         parent = '/'
208         for part in parts:
209             entry = entries.get(part)
210             if not entry:
211                 raise ValueError("Entry '%s' not found in '%s'" %
212                                  (part, parent))
213             parent = entry.GetPath()
214             entries = entry.GetEntries()
215         return entry
216
217     def ReadData(self, decomp=True):
218         tout.Debug("Image '%s' ReadData(), size=%#x" %
219                    (self.GetPath(), len(self._data)))
220         return self._data
221
222     def GetListEntries(self, entry_paths):
223         """List the entries in an image
224
225         This decodes the supplied image and returns a list of entries from that
226         image, preceded by a header.
227
228         Args:
229             entry_paths: List of paths to match (each can have wildcards). Only
230                 entries whose names match one of these paths will be printed
231
232         Returns:
233             String error message if something went wrong, otherwise
234             3-Tuple:
235                 List of EntryInfo objects
236                 List of lines, each
237                     List of text columns, each a string
238                 List of widths of each column
239         """
240         def _EntryToStrings(entry):
241             """Convert an entry to a list of strings, one for each column
242
243             Args:
244                 entry: EntryInfo object containing information to output
245
246             Returns:
247                 List of strings, one for each field in entry
248             """
249             def _AppendHex(val):
250                 """Append a hex value, or an empty string if val is None
251
252                 Args:
253                     val: Integer value, or None if none
254                 """
255                 args.append('' if val is None else '>%x' % val)
256
257             args = ['  ' * entry.indent + entry.name]
258             _AppendHex(entry.image_pos)
259             _AppendHex(entry.size)
260             args.append(entry.etype)
261             _AppendHex(entry.offset)
262             _AppendHex(entry.uncomp_size)
263             return args
264
265         def _DoLine(lines, line):
266             """Add a line to the output list
267
268             This adds a line (a list of columns) to the output list. It also updates
269             the widths[] array with the maximum width of each column
270
271             Args:
272                 lines: List of lines to add to
273                 line: List of strings, one for each column
274             """
275             for i, item in enumerate(line):
276                 widths[i] = max(widths[i], len(item))
277             lines.append(line)
278
279         def _NameInPaths(fname, entry_paths):
280             """Check if a filename is in a list of wildcarded paths
281
282             Args:
283                 fname: Filename to check
284                 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
285                                                              'section/u-boot'])
286
287             Returns:
288                 True if any wildcard matches the filename (using Unix filename
289                     pattern matching, not regular expressions)
290                 False if not
291             """
292             for path in entry_paths:
293                 if fnmatch.fnmatch(fname, path):
294                     return True
295             return False
296
297         entries = self.BuildEntryList()
298
299         # This is our list of lines. Each item in the list is a list of strings, one
300         # for each column
301         lines = []
302         HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
303                   'Uncomp-size']
304         num_columns = len(HEADER)
305
306         # This records the width of each column, calculated as the maximum width of
307         # all the strings in that column
308         widths = [0] * num_columns
309         _DoLine(lines, HEADER)
310
311         # We won't print anything unless it has at least this indent. So at the
312         # start we will print nothing, unless a path matches (or there are no
313         # entry paths)
314         MAX_INDENT = 100
315         min_indent = MAX_INDENT
316         path_stack = []
317         path = ''
318         indent = 0
319         selected_entries = []
320         for entry in entries:
321             if entry.indent > indent:
322                 path_stack.append(path)
323             elif entry.indent < indent:
324                 path_stack.pop()
325             if path_stack:
326                 path = path_stack[-1] + '/' + entry.name
327             indent = entry.indent
328
329             # If there are entry paths to match and we are not looking at a
330             # sub-entry of a previously matched entry, we need to check the path
331             if entry_paths and indent <= min_indent:
332                 if _NameInPaths(path[1:], entry_paths):
333                     # Print this entry and all sub-entries (=higher indent)
334                     min_indent = indent
335                 else:
336                     # Don't print this entry, nor any following entries until we get
337                     # a path match
338                     min_indent = MAX_INDENT
339                     continue
340             _DoLine(lines, _EntryToStrings(entry))
341             selected_entries.append(entry)
342         return selected_entries, lines, widths
343
344     def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
345         """Look up a symbol in an ELF file
346
347         Looks up a symbol in an ELF file. Only entry types which come from an
348         ELF image can be used by this function.
349
350         This searches through this image including all of its subsections.
351
352         At present the only entry properties supported are:
353             offset
354             image_pos - 'base_addr' is added if this is not an end-at-4gb image
355             size
356
357         Args:
358             sym_name: Symbol name in the ELF file to look up in the format
359                 _binman_<entry>_prop_<property> where <entry> is the name of
360                 the entry and <property> is the property to find (e.g.
361                 _binman_u_boot_prop_offset). As a special case, you can append
362                 _any to <entry> to have it search for any matching entry. E.g.
363                 _binman_u_boot_any_prop_offset will match entries called u-boot,
364                 u-boot-img and u-boot-nodtb)
365             optional: True if the symbol is optional. If False this function
366                 will raise if the symbol is not found
367             msg: Message to display if an error occurs
368             base_addr: Base address of image. This is added to the returned
369                 image_pos in most cases so that the returned position indicates
370                 where the targeted entry/binary has actually been loaded. But
371                 if end-at-4gb is used, this is not done, since the binary is
372                 already assumed to be linked to the ROM position and using
373                 execute-in-place (XIP).
374
375         Returns:
376             Value that should be assigned to that symbol, or None if it was
377                 optional and not found
378
379         Raises:
380             ValueError if the symbol is invalid or not found, or references a
381                 property which is not supported
382         """
383         entries = OrderedDict()
384         entries_by_name = {}
385         self._CollectEntries(entries, entries_by_name, self)
386         return self.LookupSymbol(sym_name, optional, msg, base_addr,
387                                  entries_by_name)