blob: f0a7d65299e897b578d69ede6124734724ded326 [file] [log] [blame]
Tom Rini83d290c2018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glassbf7fd502016-11-25 20:15:51 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glassbf7fd502016-11-25 20:15:51 -07005# Class for an image, the output of binman
6#
7
8from collections import OrderedDict
Simon Glass61f564d2019-07-08 14:25:48 -06009import fnmatch
Simon Glassbf7fd502016-11-25 20:15:51 -070010from operator import attrgetter
Simon Glass10f9d002019-07-20 12:23:50 -060011import os
Simon Glass19790632017-11-13 18:55:01 -070012import re
13import sys
Simon Glassbf7fd502016-11-25 20:15:51 -070014
Simon Glass16287932020-04-17 18:09:03 -060015from binman.entry import Entry
16from binman.etype import fdtmap
17from binman.etype import image_header
18from binman.etype import section
19from dtoc import fdt
20from dtoc import fdt_util
Simon Glassbf776672020-04-17 18:09:04 -060021from patman import tools
22from patman import tout
Simon Glassbf7fd502016-11-25 20:15:51 -070023
Simon Glass8beb11e2019-07-08 14:25:47 -060024class Image(section.Entry_section):
Simon Glassbf7fd502016-11-25 20:15:51 -070025 """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
Simon Glass8beb11e2019-07-08 14:25:47 -060032 Attributes:
33 filename: Output filename for image
Simon Glass589d8f92019-07-20 12:23:40 -060034 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
Simon Glass12bb1a92019-07-20 12:23:51 -060037 allow_repack: True to add properties to allow the image to be safely
38 repacked later
Simon Glassc69d19c2021-07-06 10:36:37 -060039 test_section_timeout: Use a zero timeout for section multi-threading
40 (for testing)
Simon Glass7ae5f312018-06-01 09:38:19 -060041
42 Args:
Simon Glass12bb1a92019-07-20 12:23:51 -060043 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
44 from the device tree
Simon Glass7ae5f312018-06-01 09:38:19 -060045 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.
Simon Glass939d1062021-01-06 21:35:16 -070048 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.
Simon Glass0b6023e2021-03-18 20:25:06 +130052 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.
Simon Glass858436d2021-11-23 21:09:49 -070066 missing_etype: Use a default entry type ('blob') if the requested one
67 does not exist in binman. This is useful if an image was created by
68 binman a newer version of binman but we want to list it in an older
69 version which does not support all the entry types.
Simon Glassbf7fd502016-11-25 20:15:51 -070070 """
Simon Glass939d1062021-01-06 21:35:16 -070071 def __init__(self, name, node, copy_to_orig=True, test=False,
Simon Glass858436d2021-11-23 21:09:49 -070072 ignore_missing=False, use_expanded=False, missing_etype=False):
Simon Glass34861d52020-07-09 18:39:35 -060073 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060074 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060075 self.name = 'main-section'
76 self.image_name = name
77 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060078 self.fdtmap_dtb = None
79 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060080 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070081 self._ignore_missing = ignore_missing
Simon Glass858436d2021-11-23 21:09:49 -070082 self.missing_etype = missing_etype
Simon Glass0b6023e2021-03-18 20:25:06 +130083 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060084 self.test_section_timeout = False
Simon Glass8beb11e2019-07-08 14:25:47 -060085 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060086 self.ReadNode()
87
88 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060089 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060090 filename = fdt_util.GetString(self._node, 'filename')
91 if filename:
92 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060093 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070094
Simon Glassffded752019-07-08 14:25:46 -060095 @classmethod
96 def FromFile(cls, fname):
97 """Convert an image file into an Image for use in binman
98
99 Args:
100 fname: Filename of image file to read
101
102 Returns:
103 Image object on success
104
105 Raises:
106 ValueError if something goes wrong
107 """
108 data = tools.ReadFile(fname)
109 size = len(data)
110
111 # First look for an image header
112 pos = image_header.LocateHeaderOffset(data)
113 if pos is None:
114 # Look for the FDT map
115 pos = fdtmap.LocateFdtmap(data)
116 if pos is None:
117 raise ValueError('Cannot find FDT map in image')
118
119 # We don't know the FDT size, so check its header first
120 probe_dtb = fdt.Fdt.FromData(
121 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
122 dtb_size = probe_dtb.GetFdtObj().totalsize()
123 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600124 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
125 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
126 tools.WriteFile(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600127 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600128 dtb.Scan()
129
130 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600131 root = dtb.GetRoot()
Simon Glass858436d2021-11-23 21:09:49 -0700132 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
133 missing_etype=True)
Simon Glass51014aa2019-07-20 12:23:56 -0600134
Simon Glass589d8f92019-07-20 12:23:40 -0600135 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
136 image.fdtmap_dtb = dtb
137 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600138 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600139 image._filename = fname
140 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600141 return image
Simon Glassffded752019-07-08 14:25:46 -0600142
Simon Glassc52c9e72019-07-08 14:25:37 -0600143 def Raise(self, msg):
144 """Convenience function to raise an error referencing an image"""
145 raise ValueError("Image '%s': %s" % (self._node.path, msg))
146
Simon Glassbf7fd502016-11-25 20:15:51 -0700147 def PackEntries(self):
148 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600149 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600150
Simon Glassdbf6be92018-08-01 15:22:42 -0600151 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600152 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600153 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600154
Simon Glassbf7fd502016-11-25 20:15:51 -0700155 def ProcessEntryContents(self):
156 """Call the ProcessContents() method for each entry
157
158 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600159
160 Returns:
161 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700162 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700163 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700164
Simon Glass19790632017-11-13 18:55:01 -0700165 def WriteSymbols(self):
166 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600167 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600168
Simon Glassbf7fd502016-11-25 20:15:51 -0700169 def BuildImage(self):
170 """Write the image to a file"""
171 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600172 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700173 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600174 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600175 fd.write(data)
176 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600177
178 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600179 """Write a map of the image to a .map file
180
181 Returns:
182 Filename of map file written
183 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600184 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600185 fname = tools.GetOutputFilename(filename)
186 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600187 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
188 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600189 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600190 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600191
192 def BuildEntryList(self):
193 """List the files in an image
194
195 Returns:
196 List of entry.EntryInfo objects describing all entries in the image
197 """
198 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600199 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600200 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600201
202 def FindEntryPath(self, entry_path):
203 """Find an entry at a given path in the image
204
205 Args:
206 entry_path: Path to entry (e.g. /ro-section/u-boot')
207
208 Returns:
209 Entry object corresponding to that past
210
211 Raises:
212 ValueError if no entry found
213 """
214 parts = entry_path.split('/')
215 entries = self.GetEntries()
216 parent = '/'
217 for part in parts:
218 entry = entries.get(part)
219 if not entry:
220 raise ValueError("Entry '%s' not found in '%s'" %
221 (part, parent))
222 parent = entry.GetPath()
223 entries = entry.GetEntries()
224 return entry
225
Simon Glass943bf782021-11-23 21:09:50 -0700226 def ReadData(self, decomp=True, alt_format=None):
Simon Glass2d553c02019-09-25 08:56:21 -0600227 tout.Debug("Image '%s' ReadData(), size=%#x" %
228 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600229 return self._data
230
231 def GetListEntries(self, entry_paths):
232 """List the entries in an image
233
234 This decodes the supplied image and returns a list of entries from that
235 image, preceded by a header.
236
237 Args:
238 entry_paths: List of paths to match (each can have wildcards). Only
239 entries whose names match one of these paths will be printed
240
241 Returns:
242 String error message if something went wrong, otherwise
243 3-Tuple:
244 List of EntryInfo objects
245 List of lines, each
246 List of text columns, each a string
247 List of widths of each column
248 """
249 def _EntryToStrings(entry):
250 """Convert an entry to a list of strings, one for each column
251
252 Args:
253 entry: EntryInfo object containing information to output
254
255 Returns:
256 List of strings, one for each field in entry
257 """
258 def _AppendHex(val):
259 """Append a hex value, or an empty string if val is None
260
261 Args:
262 val: Integer value, or None if none
263 """
264 args.append('' if val is None else '>%x' % val)
265
266 args = [' ' * entry.indent + entry.name]
267 _AppendHex(entry.image_pos)
268 _AppendHex(entry.size)
269 args.append(entry.etype)
270 _AppendHex(entry.offset)
271 _AppendHex(entry.uncomp_size)
272 return args
273
274 def _DoLine(lines, line):
275 """Add a line to the output list
276
277 This adds a line (a list of columns) to the output list. It also updates
278 the widths[] array with the maximum width of each column
279
280 Args:
281 lines: List of lines to add to
282 line: List of strings, one for each column
283 """
284 for i, item in enumerate(line):
285 widths[i] = max(widths[i], len(item))
286 lines.append(line)
287
288 def _NameInPaths(fname, entry_paths):
289 """Check if a filename is in a list of wildcarded paths
290
291 Args:
292 fname: Filename to check
293 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
294 'section/u-boot'])
295
296 Returns:
297 True if any wildcard matches the filename (using Unix filename
298 pattern matching, not regular expressions)
299 False if not
300 """
301 for path in entry_paths:
302 if fnmatch.fnmatch(fname, path):
303 return True
304 return False
305
306 entries = self.BuildEntryList()
307
308 # This is our list of lines. Each item in the list is a list of strings, one
309 # for each column
310 lines = []
311 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
312 'Uncomp-size']
313 num_columns = len(HEADER)
314
315 # This records the width of each column, calculated as the maximum width of
316 # all the strings in that column
317 widths = [0] * num_columns
318 _DoLine(lines, HEADER)
319
320 # We won't print anything unless it has at least this indent. So at the
321 # start we will print nothing, unless a path matches (or there are no
322 # entry paths)
323 MAX_INDENT = 100
324 min_indent = MAX_INDENT
325 path_stack = []
326 path = ''
327 indent = 0
328 selected_entries = []
329 for entry in entries:
330 if entry.indent > indent:
331 path_stack.append(path)
332 elif entry.indent < indent:
333 path_stack.pop()
334 if path_stack:
335 path = path_stack[-1] + '/' + entry.name
336 indent = entry.indent
337
338 # If there are entry paths to match and we are not looking at a
339 # sub-entry of a previously matched entry, we need to check the path
340 if entry_paths and indent <= min_indent:
341 if _NameInPaths(path[1:], entry_paths):
342 # Print this entry and all sub-entries (=higher indent)
343 min_indent = indent
344 else:
345 # Don't print this entry, nor any following entries until we get
346 # a path match
347 min_indent = MAX_INDENT
348 continue
349 _DoLine(lines, _EntryToStrings(entry))
350 selected_entries.append(entry)
351 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700352
353 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
354 """Look up a symbol in an ELF file
355
356 Looks up a symbol in an ELF file. Only entry types which come from an
357 ELF image can be used by this function.
358
359 This searches through this image including all of its subsections.
360
361 At present the only entry properties supported are:
362 offset
363 image_pos - 'base_addr' is added if this is not an end-at-4gb image
364 size
365
366 Args:
367 sym_name: Symbol name in the ELF file to look up in the format
368 _binman_<entry>_prop_<property> where <entry> is the name of
369 the entry and <property> is the property to find (e.g.
370 _binman_u_boot_prop_offset). As a special case, you can append
371 _any to <entry> to have it search for any matching entry. E.g.
372 _binman_u_boot_any_prop_offset will match entries called u-boot,
373 u-boot-img and u-boot-nodtb)
374 optional: True if the symbol is optional. If False this function
375 will raise if the symbol is not found
376 msg: Message to display if an error occurs
377 base_addr: Base address of image. This is added to the returned
378 image_pos in most cases so that the returned position indicates
379 where the targeted entry/binary has actually been loaded. But
380 if end-at-4gb is used, this is not done, since the binary is
381 already assumed to be linked to the ROM position and using
382 execute-in-place (XIP).
383
384 Returns:
385 Value that should be assigned to that symbol, or None if it was
386 optional and not found
387
388 Raises:
389 ValueError if the symbol is invalid or not found, or references a
390 property which is not supported
391 """
392 entries = OrderedDict()
393 entries_by_name = {}
394 self._CollectEntries(entries, entries_by_name, self)
395 return self.LookupSymbol(sym_name, optional, msg, base_addr,
396 entries_by_name)