blob: e77b5d0d97cd02b88d67f9335568e4879b493646 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass2574ef62016-11-25 20:15:51 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glass2574ef62016-11-25 20:15:51 -07005# Class for an image, the output of binman
6#
7
8from collections import OrderedDict
Simon Glassb2fd11d2019-07-08 14:25:48 -06009import fnmatch
Simon Glass2574ef62016-11-25 20:15:51 -070010from operator import attrgetter
Simon Glass072959a2019-07-20 12:23:50 -060011import os
Simon Glass4ca8e042017-11-13 18:55:01 -070012import re
13import sys
Simon Glass2574ef62016-11-25 20:15:51 -070014
Simon Glassc585dd42020-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 Glass131444f2023-02-23 18:18:04 -070021from u_boot_pylib import tools
22from u_boot_pylib import tout
Simon Glass2574ef62016-11-25 20:15:51 -070023
Simon Glass39dd2152019-07-08 14:25:47 -060024class Image(section.Entry_section):
Simon Glass2574ef62016-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 Glass39dd2152019-07-08 14:25:47 -060032 Attributes:
33 filename: Output filename for image
Simon Glass76546572019-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 Glassfb30e292019-07-20 12:23:51 -060037 allow_repack: True to add properties to allow the image to be safely
38 repacked later
Simon Glass76f496d2021-07-06 10:36:37 -060039 test_section_timeout: Use a zero timeout for section multi-threading
40 (for testing)
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +053041 symlink: Name of symlink to image
Simon Glass1e324002018-06-01 09:38:19 -060042
43 Args:
Simon Glassfb30e292019-07-20 12:23:51 -060044 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
45 from the device tree
Simon Glass1e324002018-06-01 09:38:19 -060046 test: True if this is being called from a test of Images. This this case
47 there is no device tree defining the structure of the section, so
48 we create a section manually.
Simon Glass3fb25402021-01-06 21:35:16 -070049 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
50 exception). This should be used if the Image is being loaded from
51 a file rather than generated. In that case we obviously don't need
52 the entry arguments since the contents already exists.
Simon Glass55ab0b62021-03-18 20:25:06 +130053 use_expanded: True if we are updating the FDT wth entry offsets, etc.
54 and should use the expanded versions of the U-Boot entries.
55 Any entry type that includes a devicetree must put it in a
56 separate entry so that it will be updated. For example. 'u-boot'
57 normally just picks up 'u-boot.bin' which includes the
58 devicetree, but this is not updateable, since it comes into
59 binman as one piece and binman doesn't know that it is actually
60 an executable followed by a devicetree. Of course it could be
61 taught this, but then when reading an image (e.g. 'binman ls')
62 it may need to be able to split the devicetree out of the image
63 in order to determine the location of things. Instead we choose
64 to ignore 'u-boot-bin' in this case, and build it ourselves in
65 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
66 Entry_u_boot_expanded and Entry_blob_phase for details.
Simon Glassb9028bc2021-11-23 21:09:49 -070067 missing_etype: Use a default entry type ('blob') if the requested one
68 does not exist in binman. This is useful if an image was created by
69 binman a newer version of binman but we want to list it in an older
70 version which does not support all the entry types.
Jan Kiszka58c407f2022-01-28 20:37:53 +010071 generate: If true, generator nodes are processed. If false they are
72 ignored which is useful when an existing image is read back from a
73 file.
Simon Glass2574ef62016-11-25 20:15:51 -070074 """
Simon Glass3fb25402021-01-06 21:35:16 -070075 def __init__(self, name, node, copy_to_orig=True, test=False,
Jan Kiszka58c407f2022-01-28 20:37:53 +010076 ignore_missing=False, use_expanded=False, missing_etype=False,
77 generate=True):
Simon Glass67c59802020-07-09 18:39:35 -060078 super().__init__(None, 'section', node, test=test)
Simon Glassfb30e292019-07-20 12:23:51 -060079 self.copy_to_orig = copy_to_orig
Simon Glass49cd2b32023-02-07 14:34:18 -070080 self.name = name
Simon Glass39dd2152019-07-08 14:25:47 -060081 self.image_name = name
82 self._filename = '%s.bin' % self.image_name
Simon Glass76546572019-07-20 12:23:40 -060083 self.fdtmap_dtb = None
84 self.fdtmap_data = None
Simon Glassfb30e292019-07-20 12:23:51 -060085 self.allow_repack = False
Simon Glass3fb25402021-01-06 21:35:16 -070086 self._ignore_missing = ignore_missing
Simon Glassb9028bc2021-11-23 21:09:49 -070087 self.missing_etype = missing_etype
Simon Glass55ab0b62021-03-18 20:25:06 +130088 self.use_expanded = use_expanded
Simon Glass76f496d2021-07-06 10:36:37 -060089 self.test_section_timeout = False
Simon Glass4eae9252022-01-09 20:13:50 -070090 self.bintools = {}
Jan Kiszka58c407f2022-01-28 20:37:53 +010091 self.generate = generate
Simon Glass39dd2152019-07-08 14:25:47 -060092 if not test:
Simon Glass2c360cf2019-07-20 12:23:45 -060093 self.ReadNode()
94
95 def ReadNode(self):
Simon Glass67c59802020-07-09 18:39:35 -060096 super().ReadNode()
Simon Glassfb30e292019-07-20 12:23:51 -060097 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +053098 self._symlink = fdt_util.GetString(self._node, 'symlink')
Simon Glass2574ef62016-11-25 20:15:51 -070099
Simon Glassb8424fa2019-07-08 14:25:46 -0600100 @classmethod
101 def FromFile(cls, fname):
102 """Convert an image file into an Image for use in binman
103
104 Args:
105 fname: Filename of image file to read
106
107 Returns:
108 Image object on success
109
110 Raises:
111 ValueError if something goes wrong
112 """
Simon Glass80025522022-01-29 14:14:04 -0700113 data = tools.read_file(fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600114 size = len(data)
115
116 # First look for an image header
117 pos = image_header.LocateHeaderOffset(data)
118 if pos is None:
119 # Look for the FDT map
120 pos = fdtmap.LocateFdtmap(data)
121 if pos is None:
122 raise ValueError('Cannot find FDT map in image')
123
124 # We don't know the FDT size, so check its header first
125 probe_dtb = fdt.Fdt.FromData(
126 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
127 dtb_size = probe_dtb.GetFdtObj().totalsize()
128 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass9b7f5002019-07-20 12:23:53 -0600129 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
Simon Glass80025522022-01-29 14:14:04 -0700130 out_fname = tools.get_output_filename('fdtmap.in.dtb')
131 tools.write_file(out_fname, fdt_data)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600132 dtb = fdt.Fdt(out_fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600133 dtb.Scan()
134
135 # Return an Image with the associated nodes
Simon Glass76546572019-07-20 12:23:40 -0600136 root = dtb.GetRoot()
Simon Glassb9028bc2021-11-23 21:09:49 -0700137 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
Jan Kiszka58c407f2022-01-28 20:37:53 +0100138 missing_etype=True, generate=False)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600139
Simon Glass76546572019-07-20 12:23:40 -0600140 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
141 image.fdtmap_dtb = dtb
142 image.fdtmap_data = fdtmap_data
Simon Glass4c613bf2019-07-08 14:25:50 -0600143 image._data = data
Simon Glass072959a2019-07-20 12:23:50 -0600144 image._filename = fname
145 image.image_name, _ = os.path.splitext(fname)
Simon Glass4c613bf2019-07-08 14:25:50 -0600146 return image
Simon Glassb8424fa2019-07-08 14:25:46 -0600147
Simon Glasse61b6f62019-07-08 14:25:37 -0600148 def Raise(self, msg):
149 """Convenience function to raise an error referencing an image"""
150 raise ValueError("Image '%s': %s" % (self._node.path, msg))
151
Simon Glass2574ef62016-11-25 20:15:51 -0700152 def PackEntries(self):
153 """Pack all entries into the image"""
Simon Glass67c59802020-07-09 18:39:35 -0600154 super().Pack(0)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600155
Simon Glass9dcc8612018-08-01 15:22:42 -0600156 def SetImagePos(self):
Simon Glass39dd2152019-07-08 14:25:47 -0600157 # This first section in the image so it starts at 0
Simon Glass67c59802020-07-09 18:39:35 -0600158 super().SetImagePos(0)
Simon Glass9dcc8612018-08-01 15:22:42 -0600159
Simon Glass2574ef62016-11-25 20:15:51 -0700160 def ProcessEntryContents(self):
161 """Call the ProcessContents() method for each entry
162
163 This is intended to adjust the contents as needed by the entry type.
Simon Glassec849852019-07-08 14:25:35 -0600164
165 Returns:
166 True if the new data size is OK, False if expansion is needed
Simon Glass2574ef62016-11-25 20:15:51 -0700167 """
Simon Glass220c6222021-01-06 21:35:17 -0700168 return super().ProcessContents()
Simon Glass2574ef62016-11-25 20:15:51 -0700169
Simon Glass4ca8e042017-11-13 18:55:01 -0700170 def WriteSymbols(self):
171 """Write symbol values into binary files for access at run time"""
Simon Glass67c59802020-07-09 18:39:35 -0600172 super().WriteSymbols(self)
Simon Glass39dd2152019-07-08 14:25:47 -0600173
Simon Glass2574ef62016-11-25 20:15:51 -0700174 def BuildImage(self):
175 """Write the image to a file"""
Simon Glass80025522022-01-29 14:14:04 -0700176 fname = tools.get_output_filename(self._filename)
Simon Glass011f1b32022-01-29 14:14:15 -0700177 tout.info("Writing image to '%s'" % fname)
Simon Glass2574ef62016-11-25 20:15:51 -0700178 with open(fname, 'wb') as fd:
Simon Glassff1903a2020-10-26 17:40:12 -0600179 data = self.GetPaddedData()
Simon Glass74b31572019-07-20 12:23:54 -0600180 fd.write(data)
Simon Glass011f1b32022-01-29 14:14:15 -0700181 tout.info("Wrote %#x bytes" % len(data))
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +0530182 # Create symlink to file if symlink given
183 if self._symlink is not None:
184 sname = tools.get_output_filename(self._symlink)
Andrew Davis6b463da2023-07-22 00:14:44 +0530185 if os.path.islink(sname):
186 os.remove(sname)
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +0530187 os.symlink(fname, sname)
Simon Glass30732662018-06-01 09:38:20 -0600188
189 def WriteMap(self):
Simon Glasscd817d52018-09-14 04:57:36 -0600190 """Write a map of the image to a .map file
191
192 Returns:
193 Filename of map file written
194 """
Simon Glass39dd2152019-07-08 14:25:47 -0600195 filename = '%s.map' % self.image_name
Simon Glass80025522022-01-29 14:14:04 -0700196 fname = tools.get_output_filename(filename)
Simon Glass30732662018-06-01 09:38:20 -0600197 with open(fname, 'w') as fd:
Simon Glass7eca7922018-07-17 13:25:49 -0600198 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
199 file=fd)
Simon Glass67c59802020-07-09 18:39:35 -0600200 super().WriteMap(fd, 0)
Simon Glasscd817d52018-09-14 04:57:36 -0600201 return fname
Simon Glass6b156f82019-07-08 14:25:43 -0600202
203 def BuildEntryList(self):
204 """List the files in an image
205
206 Returns:
207 List of entry.EntryInfo objects describing all entries in the image
208 """
209 entries = []
Simon Glass39dd2152019-07-08 14:25:47 -0600210 self.ListEntries(entries, 0)
Simon Glass6b156f82019-07-08 14:25:43 -0600211 return entries
Simon Glassb2fd11d2019-07-08 14:25:48 -0600212
213 def FindEntryPath(self, entry_path):
214 """Find an entry at a given path in the image
215
216 Args:
217 entry_path: Path to entry (e.g. /ro-section/u-boot')
218
219 Returns:
220 Entry object corresponding to that past
221
222 Raises:
223 ValueError if no entry found
224 """
225 parts = entry_path.split('/')
226 entries = self.GetEntries()
227 parent = '/'
228 for part in parts:
229 entry = entries.get(part)
230 if not entry:
231 raise ValueError("Entry '%s' not found in '%s'" %
232 (part, parent))
233 parent = entry.GetPath()
234 entries = entry.GetEntries()
235 return entry
236
Simon Glass637958f2021-11-23 21:09:50 -0700237 def ReadData(self, decomp=True, alt_format=None):
Simon Glass011f1b32022-01-29 14:14:15 -0700238 tout.debug("Image '%s' ReadData(), size=%#x" %
Simon Glass4d8151f2019-09-25 08:56:21 -0600239 (self.GetPath(), len(self._data)))
Simon Glassb2fd11d2019-07-08 14:25:48 -0600240 return self._data
241
242 def GetListEntries(self, entry_paths):
243 """List the entries in an image
244
245 This decodes the supplied image and returns a list of entries from that
246 image, preceded by a header.
247
248 Args:
249 entry_paths: List of paths to match (each can have wildcards). Only
250 entries whose names match one of these paths will be printed
251
252 Returns:
253 String error message if something went wrong, otherwise
254 3-Tuple:
255 List of EntryInfo objects
256 List of lines, each
257 List of text columns, each a string
258 List of widths of each column
259 """
260 def _EntryToStrings(entry):
261 """Convert an entry to a list of strings, one for each column
262
263 Args:
264 entry: EntryInfo object containing information to output
265
266 Returns:
267 List of strings, one for each field in entry
268 """
269 def _AppendHex(val):
270 """Append a hex value, or an empty string if val is None
271
272 Args:
273 val: Integer value, or None if none
274 """
275 args.append('' if val is None else '>%x' % val)
276
277 args = [' ' * entry.indent + entry.name]
278 _AppendHex(entry.image_pos)
279 _AppendHex(entry.size)
280 args.append(entry.etype)
281 _AppendHex(entry.offset)
282 _AppendHex(entry.uncomp_size)
283 return args
284
285 def _DoLine(lines, line):
286 """Add a line to the output list
287
288 This adds a line (a list of columns) to the output list. It also updates
289 the widths[] array with the maximum width of each column
290
291 Args:
292 lines: List of lines to add to
293 line: List of strings, one for each column
294 """
295 for i, item in enumerate(line):
296 widths[i] = max(widths[i], len(item))
297 lines.append(line)
298
299 def _NameInPaths(fname, entry_paths):
300 """Check if a filename is in a list of wildcarded paths
301
302 Args:
303 fname: Filename to check
304 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
305 'section/u-boot'])
306
307 Returns:
308 True if any wildcard matches the filename (using Unix filename
309 pattern matching, not regular expressions)
310 False if not
311 """
312 for path in entry_paths:
313 if fnmatch.fnmatch(fname, path):
314 return True
315 return False
316
317 entries = self.BuildEntryList()
318
319 # This is our list of lines. Each item in the list is a list of strings, one
320 # for each column
321 lines = []
322 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
323 'Uncomp-size']
324 num_columns = len(HEADER)
325
326 # This records the width of each column, calculated as the maximum width of
327 # all the strings in that column
328 widths = [0] * num_columns
329 _DoLine(lines, HEADER)
330
331 # We won't print anything unless it has at least this indent. So at the
332 # start we will print nothing, unless a path matches (or there are no
333 # entry paths)
334 MAX_INDENT = 100
335 min_indent = MAX_INDENT
336 path_stack = []
337 path = ''
338 indent = 0
339 selected_entries = []
340 for entry in entries:
341 if entry.indent > indent:
342 path_stack.append(path)
343 elif entry.indent < indent:
344 path_stack.pop()
345 if path_stack:
346 path = path_stack[-1] + '/' + entry.name
347 indent = entry.indent
348
349 # If there are entry paths to match and we are not looking at a
350 # sub-entry of a previously matched entry, we need to check the path
351 if entry_paths and indent <= min_indent:
352 if _NameInPaths(path[1:], entry_paths):
353 # Print this entry and all sub-entries (=higher indent)
354 min_indent = indent
355 else:
356 # Don't print this entry, nor any following entries until we get
357 # a path match
358 min_indent = MAX_INDENT
359 continue
360 _DoLine(lines, _EntryToStrings(entry))
361 selected_entries.append(entry)
362 return selected_entries, lines, widths
Simon Glassecbe4732021-01-06 21:35:15 -0700363
364 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
365 """Look up a symbol in an ELF file
366
367 Looks up a symbol in an ELF file. Only entry types which come from an
368 ELF image can be used by this function.
369
370 This searches through this image including all of its subsections.
371
372 At present the only entry properties supported are:
373 offset
374 image_pos - 'base_addr' is added if this is not an end-at-4gb image
375 size
376
377 Args:
378 sym_name: Symbol name in the ELF file to look up in the format
379 _binman_<entry>_prop_<property> where <entry> is the name of
380 the entry and <property> is the property to find (e.g.
381 _binman_u_boot_prop_offset). As a special case, you can append
382 _any to <entry> to have it search for any matching entry. E.g.
383 _binman_u_boot_any_prop_offset will match entries called u-boot,
384 u-boot-img and u-boot-nodtb)
385 optional: True if the symbol is optional. If False this function
386 will raise if the symbol is not found
387 msg: Message to display if an error occurs
388 base_addr: Base address of image. This is added to the returned
389 image_pos in most cases so that the returned position indicates
390 where the targeted entry/binary has actually been loaded. But
391 if end-at-4gb is used, this is not done, since the binary is
392 already assumed to be linked to the ROM position and using
393 execute-in-place (XIP).
394
395 Returns:
396 Value that should be assigned to that symbol, or None if it was
397 optional and not found
398
399 Raises:
400 ValueError if the symbol is invalid or not found, or references a
401 property which is not supported
402 """
403 entries = OrderedDict()
404 entries_by_name = {}
405 self._CollectEntries(entries, entries_by_name, self)
406 return self.LookupSymbol(sym_name, optional, msg, base_addr,
407 entries_by_name)
Simon Glass4eae9252022-01-09 20:13:50 -0700408
409 def CollectBintools(self):
410 """Collect all the bintools used by this image
411
412 Returns:
413 Dict of bintools:
414 key: name of tool
415 value: Bintool object
416 """
417 bintools = {}
418 super().AddBintools(bintools)
419 self.bintools = bintools
420 return bintools