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