blob: afc4b4d64304c6aab99d7cc1a92a4a731d6249bc [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.
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010070 generate: If true, generator nodes are processed. If false they are
71 ignored which is useful when an existing image is read back from a
72 file.
Simon Glassbf7fd502016-11-25 20:15:51 -070073 """
Simon Glass939d1062021-01-06 21:35:16 -070074 def __init__(self, name, node, copy_to_orig=True, test=False,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010075 ignore_missing=False, use_expanded=False, missing_etype=False,
76 generate=True):
Simon Glass34861d52020-07-09 18:39:35 -060077 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060078 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060079 self.name = 'main-section'
80 self.image_name = name
81 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060082 self.fdtmap_dtb = None
83 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060084 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070085 self._ignore_missing = ignore_missing
Simon Glass858436d2021-11-23 21:09:49 -070086 self.missing_etype = missing_etype
Simon Glass0b6023e2021-03-18 20:25:06 +130087 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060088 self.test_section_timeout = False
Simon Glass386c63c2022-01-09 20:13:50 -070089 self.bintools = {}
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010090 self.generate = generate
Simon Glass8beb11e2019-07-08 14:25:47 -060091 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060092 self.ReadNode()
93
94 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060095 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060096 filename = fdt_util.GetString(self._node, 'filename')
97 if filename:
98 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060099 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -0700100
Simon Glassffded752019-07-08 14:25:46 -0600101 @classmethod
102 def FromFile(cls, fname):
103 """Convert an image file into an Image for use in binman
104
105 Args:
106 fname: Filename of image file to read
107
108 Returns:
109 Image object on success
110
111 Raises:
112 ValueError if something goes wrong
113 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700114 data = tools.read_file(fname)
Simon Glassffded752019-07-08 14:25:46 -0600115 size = len(data)
116
117 # First look for an image header
118 pos = image_header.LocateHeaderOffset(data)
119 if pos is None:
120 # Look for the FDT map
121 pos = fdtmap.LocateFdtmap(data)
122 if pos is None:
123 raise ValueError('Cannot find FDT map in image')
124
125 # We don't know the FDT size, so check its header first
126 probe_dtb = fdt.Fdt.FromData(
127 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
128 dtb_size = probe_dtb.GetFdtObj().totalsize()
129 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600130 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700131 out_fname = tools.get_output_filename('fdtmap.in.dtb')
132 tools.write_file(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600133 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600134 dtb.Scan()
135
136 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600137 root = dtb.GetRoot()
Simon Glass858436d2021-11-23 21:09:49 -0700138 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +0100139 missing_etype=True, generate=False)
Simon Glass51014aa2019-07-20 12:23:56 -0600140
Simon Glass589d8f92019-07-20 12:23:40 -0600141 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
142 image.fdtmap_dtb = dtb
143 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600144 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600145 image._filename = fname
146 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600147 return image
Simon Glassffded752019-07-08 14:25:46 -0600148
Simon Glassc52c9e72019-07-08 14:25:37 -0600149 def Raise(self, msg):
150 """Convenience function to raise an error referencing an image"""
151 raise ValueError("Image '%s': %s" % (self._node.path, msg))
152
Simon Glassbf7fd502016-11-25 20:15:51 -0700153 def PackEntries(self):
154 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600155 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600156
Simon Glassdbf6be92018-08-01 15:22:42 -0600157 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600158 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600159 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600160
Simon Glassbf7fd502016-11-25 20:15:51 -0700161 def ProcessEntryContents(self):
162 """Call the ProcessContents() method for each entry
163
164 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600165
166 Returns:
167 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700168 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700169 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700170
Simon Glass19790632017-11-13 18:55:01 -0700171 def WriteSymbols(self):
172 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600173 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600174
Simon Glassbf7fd502016-11-25 20:15:51 -0700175 def BuildImage(self):
176 """Write the image to a file"""
Simon Glassc1aa66e2022-01-29 14:14:04 -0700177 fname = tools.get_output_filename(self._filename)
Simon Glassf3385a52022-01-29 14:14:15 -0700178 tout.info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700179 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600180 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600181 fd.write(data)
Simon Glassf3385a52022-01-29 14:14:15 -0700182 tout.info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600183
184 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600185 """Write a map of the image to a .map file
186
187 Returns:
188 Filename of map file written
189 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600190 filename = '%s.map' % self.image_name
Simon Glassc1aa66e2022-01-29 14:14:04 -0700191 fname = tools.get_output_filename(filename)
Simon Glass3b0c3822018-06-01 09:38:20 -0600192 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600193 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
194 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600195 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600196 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600197
198 def BuildEntryList(self):
199 """List the files in an image
200
201 Returns:
202 List of entry.EntryInfo objects describing all entries in the image
203 """
204 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600205 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600206 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600207
208 def FindEntryPath(self, entry_path):
209 """Find an entry at a given path in the image
210
211 Args:
212 entry_path: Path to entry (e.g. /ro-section/u-boot')
213
214 Returns:
215 Entry object corresponding to that past
216
217 Raises:
218 ValueError if no entry found
219 """
220 parts = entry_path.split('/')
221 entries = self.GetEntries()
222 parent = '/'
223 for part in parts:
224 entry = entries.get(part)
225 if not entry:
226 raise ValueError("Entry '%s' not found in '%s'" %
227 (part, parent))
228 parent = entry.GetPath()
229 entries = entry.GetEntries()
230 return entry
231
Simon Glass943bf782021-11-23 21:09:50 -0700232 def ReadData(self, decomp=True, alt_format=None):
Simon Glassf3385a52022-01-29 14:14:15 -0700233 tout.debug("Image '%s' ReadData(), size=%#x" %
Simon Glass2d553c02019-09-25 08:56:21 -0600234 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600235 return self._data
236
237 def GetListEntries(self, entry_paths):
238 """List the entries in an image
239
240 This decodes the supplied image and returns a list of entries from that
241 image, preceded by a header.
242
243 Args:
244 entry_paths: List of paths to match (each can have wildcards). Only
245 entries whose names match one of these paths will be printed
246
247 Returns:
248 String error message if something went wrong, otherwise
249 3-Tuple:
250 List of EntryInfo objects
251 List of lines, each
252 List of text columns, each a string
253 List of widths of each column
254 """
255 def _EntryToStrings(entry):
256 """Convert an entry to a list of strings, one for each column
257
258 Args:
259 entry: EntryInfo object containing information to output
260
261 Returns:
262 List of strings, one for each field in entry
263 """
264 def _AppendHex(val):
265 """Append a hex value, or an empty string if val is None
266
267 Args:
268 val: Integer value, or None if none
269 """
270 args.append('' if val is None else '>%x' % val)
271
272 args = [' ' * entry.indent + entry.name]
273 _AppendHex(entry.image_pos)
274 _AppendHex(entry.size)
275 args.append(entry.etype)
276 _AppendHex(entry.offset)
277 _AppendHex(entry.uncomp_size)
278 return args
279
280 def _DoLine(lines, line):
281 """Add a line to the output list
282
283 This adds a line (a list of columns) to the output list. It also updates
284 the widths[] array with the maximum width of each column
285
286 Args:
287 lines: List of lines to add to
288 line: List of strings, one for each column
289 """
290 for i, item in enumerate(line):
291 widths[i] = max(widths[i], len(item))
292 lines.append(line)
293
294 def _NameInPaths(fname, entry_paths):
295 """Check if a filename is in a list of wildcarded paths
296
297 Args:
298 fname: Filename to check
299 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
300 'section/u-boot'])
301
302 Returns:
303 True if any wildcard matches the filename (using Unix filename
304 pattern matching, not regular expressions)
305 False if not
306 """
307 for path in entry_paths:
308 if fnmatch.fnmatch(fname, path):
309 return True
310 return False
311
312 entries = self.BuildEntryList()
313
314 # This is our list of lines. Each item in the list is a list of strings, one
315 # for each column
316 lines = []
317 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
318 'Uncomp-size']
319 num_columns = len(HEADER)
320
321 # This records the width of each column, calculated as the maximum width of
322 # all the strings in that column
323 widths = [0] * num_columns
324 _DoLine(lines, HEADER)
325
326 # We won't print anything unless it has at least this indent. So at the
327 # start we will print nothing, unless a path matches (or there are no
328 # entry paths)
329 MAX_INDENT = 100
330 min_indent = MAX_INDENT
331 path_stack = []
332 path = ''
333 indent = 0
334 selected_entries = []
335 for entry in entries:
336 if entry.indent > indent:
337 path_stack.append(path)
338 elif entry.indent < indent:
339 path_stack.pop()
340 if path_stack:
341 path = path_stack[-1] + '/' + entry.name
342 indent = entry.indent
343
344 # If there are entry paths to match and we are not looking at a
345 # sub-entry of a previously matched entry, we need to check the path
346 if entry_paths and indent <= min_indent:
347 if _NameInPaths(path[1:], entry_paths):
348 # Print this entry and all sub-entries (=higher indent)
349 min_indent = indent
350 else:
351 # Don't print this entry, nor any following entries until we get
352 # a path match
353 min_indent = MAX_INDENT
354 continue
355 _DoLine(lines, _EntryToStrings(entry))
356 selected_entries.append(entry)
357 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700358
359 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
360 """Look up a symbol in an ELF file
361
362 Looks up a symbol in an ELF file. Only entry types which come from an
363 ELF image can be used by this function.
364
365 This searches through this image including all of its subsections.
366
367 At present the only entry properties supported are:
368 offset
369 image_pos - 'base_addr' is added if this is not an end-at-4gb image
370 size
371
372 Args:
373 sym_name: Symbol name in the ELF file to look up in the format
374 _binman_<entry>_prop_<property> where <entry> is the name of
375 the entry and <property> is the property to find (e.g.
376 _binman_u_boot_prop_offset). As a special case, you can append
377 _any to <entry> to have it search for any matching entry. E.g.
378 _binman_u_boot_any_prop_offset will match entries called u-boot,
379 u-boot-img and u-boot-nodtb)
380 optional: True if the symbol is optional. If False this function
381 will raise if the symbol is not found
382 msg: Message to display if an error occurs
383 base_addr: Base address of image. This is added to the returned
384 image_pos in most cases so that the returned position indicates
385 where the targeted entry/binary has actually been loaded. But
386 if end-at-4gb is used, this is not done, since the binary is
387 already assumed to be linked to the ROM position and using
388 execute-in-place (XIP).
389
390 Returns:
391 Value that should be assigned to that symbol, or None if it was
392 optional and not found
393
394 Raises:
395 ValueError if the symbol is invalid or not found, or references a
396 property which is not supported
397 """
398 entries = OrderedDict()
399 entries_by_name = {}
400 self._CollectEntries(entries, entries_by_name, self)
401 return self.LookupSymbol(sym_name, optional, msg, base_addr,
402 entries_by_name)
Simon Glass386c63c2022-01-09 20:13:50 -0700403
404 def CollectBintools(self):
405 """Collect all the bintools used by this image
406
407 Returns:
408 Dict of bintools:
409 key: name of tool
410 value: Bintool object
411 """
412 bintools = {}
413 super().AddBintools(bintools)
414 self.bintools = bintools
415 return bintools