blob: 698cfa4148e1a0f762d90d99ee3bc581cff5105a [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 Glass482dd312024-07-20 11:49:43 +010024# This is imported if needed
25state = None
26
Simon Glass39dd2152019-07-08 14:25:47 -060027class Image(section.Entry_section):
Simon Glass2574ef62016-11-25 20:15:51 -070028 """A Image, representing an output from binman
29
30 An image is comprised of a collection of entries each containing binary
31 data. The image size must be large enough to hold all of this data.
32
33 This class implements the various operations needed for images.
34
Simon Glass39dd2152019-07-08 14:25:47 -060035 Attributes:
36 filename: Output filename for image
Simon Glass76546572019-07-20 12:23:40 -060037 image_node: Name of node containing the description for this image
38 fdtmap_dtb: Fdt object for the fdtmap when loading from a file
39 fdtmap_data: Contents of the fdtmap when loading from a file
Simon Glassfb30e292019-07-20 12:23:51 -060040 allow_repack: True to add properties to allow the image to be safely
41 repacked later
Simon Glass76f496d2021-07-06 10:36:37 -060042 test_section_timeout: Use a zero timeout for section multi-threading
43 (for testing)
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +053044 symlink: Name of symlink to image
Simon Glass1e324002018-06-01 09:38:19 -060045
46 Args:
Simon Glassfb30e292019-07-20 12:23:51 -060047 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
48 from the device tree
Simon Glass1e324002018-06-01 09:38:19 -060049 test: True if this is being called from a test of Images. This this case
50 there is no device tree defining the structure of the section, so
51 we create a section manually.
Simon Glass3fb25402021-01-06 21:35:16 -070052 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
53 exception). This should be used if the Image is being loaded from
54 a file rather than generated. In that case we obviously don't need
55 the entry arguments since the contents already exists.
Simon Glass55ab0b62021-03-18 20:25:06 +130056 use_expanded: True if we are updating the FDT wth entry offsets, etc.
57 and should use the expanded versions of the U-Boot entries.
58 Any entry type that includes a devicetree must put it in a
59 separate entry so that it will be updated. For example. 'u-boot'
60 normally just picks up 'u-boot.bin' which includes the
61 devicetree, but this is not updateable, since it comes into
62 binman as one piece and binman doesn't know that it is actually
63 an executable followed by a devicetree. Of course it could be
64 taught this, but then when reading an image (e.g. 'binman ls')
65 it may need to be able to split the devicetree out of the image
66 in order to determine the location of things. Instead we choose
67 to ignore 'u-boot-bin' in this case, and build it ourselves in
68 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
69 Entry_u_boot_expanded and Entry_blob_phase for details.
Simon Glassb9028bc2021-11-23 21:09:49 -070070 missing_etype: Use a default entry type ('blob') if the requested one
71 does not exist in binman. This is useful if an image was created by
72 binman a newer version of binman but we want to list it in an older
73 version which does not support all the entry types.
Jan Kiszka58c407f2022-01-28 20:37:53 +010074 generate: If true, generator nodes are processed. If false they are
75 ignored which is useful when an existing image is read back from a
76 file.
Simon Glass2574ef62016-11-25 20:15:51 -070077 """
Simon Glass3fb25402021-01-06 21:35:16 -070078 def __init__(self, name, node, copy_to_orig=True, test=False,
Jan Kiszka58c407f2022-01-28 20:37:53 +010079 ignore_missing=False, use_expanded=False, missing_etype=False,
80 generate=True):
Simon Glass482dd312024-07-20 11:49:43 +010081 # Put this here to allow entry-docs and help to work without libfdt
82 global state
83 from binman import state
84
Simon Glass67c59802020-07-09 18:39:35 -060085 super().__init__(None, 'section', node, test=test)
Simon Glassfb30e292019-07-20 12:23:51 -060086 self.copy_to_orig = copy_to_orig
Simon Glass49cd2b32023-02-07 14:34:18 -070087 self.name = name
Simon Glass39dd2152019-07-08 14:25:47 -060088 self.image_name = name
89 self._filename = '%s.bin' % self.image_name
Simon Glass76546572019-07-20 12:23:40 -060090 self.fdtmap_dtb = None
91 self.fdtmap_data = None
Simon Glassfb30e292019-07-20 12:23:51 -060092 self.allow_repack = False
Simon Glass3fb25402021-01-06 21:35:16 -070093 self._ignore_missing = ignore_missing
Simon Glassb9028bc2021-11-23 21:09:49 -070094 self.missing_etype = missing_etype
Simon Glass55ab0b62021-03-18 20:25:06 +130095 self.use_expanded = use_expanded
Simon Glass76f496d2021-07-06 10:36:37 -060096 self.test_section_timeout = False
Simon Glass4eae9252022-01-09 20:13:50 -070097 self.bintools = {}
Jan Kiszka58c407f2022-01-28 20:37:53 +010098 self.generate = generate
Simon Glass39dd2152019-07-08 14:25:47 -060099 if not test:
Simon Glass2c360cf2019-07-20 12:23:45 -0600100 self.ReadNode()
101
102 def ReadNode(self):
Simon Glass67c59802020-07-09 18:39:35 -0600103 super().ReadNode()
Simon Glassfb30e292019-07-20 12:23:51 -0600104 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +0530105 self._symlink = fdt_util.GetString(self._node, 'symlink')
Simon Glass2574ef62016-11-25 20:15:51 -0700106
Simon Glassb8424fa2019-07-08 14:25:46 -0600107 @classmethod
108 def FromFile(cls, fname):
109 """Convert an image file into an Image for use in binman
110
111 Args:
112 fname: Filename of image file to read
113
114 Returns:
115 Image object on success
116
117 Raises:
118 ValueError if something goes wrong
119 """
Simon Glass80025522022-01-29 14:14:04 -0700120 data = tools.read_file(fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600121 size = len(data)
122
123 # First look for an image header
124 pos = image_header.LocateHeaderOffset(data)
125 if pos is None:
126 # Look for the FDT map
127 pos = fdtmap.LocateFdtmap(data)
128 if pos is None:
129 raise ValueError('Cannot find FDT map in image')
130
131 # We don't know the FDT size, so check its header first
132 probe_dtb = fdt.Fdt.FromData(
133 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
134 dtb_size = probe_dtb.GetFdtObj().totalsize()
135 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass9b7f5002019-07-20 12:23:53 -0600136 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
Simon Glass80025522022-01-29 14:14:04 -0700137 out_fname = tools.get_output_filename('fdtmap.in.dtb')
138 tools.write_file(out_fname, fdt_data)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600139 dtb = fdt.Fdt(out_fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600140 dtb.Scan()
141
142 # Return an Image with the associated nodes
Simon Glass76546572019-07-20 12:23:40 -0600143 root = dtb.GetRoot()
Simon Glassb9028bc2021-11-23 21:09:49 -0700144 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
Jan Kiszka58c407f2022-01-28 20:37:53 +0100145 missing_etype=True, generate=False)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600146
Simon Glass76546572019-07-20 12:23:40 -0600147 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
148 image.fdtmap_dtb = dtb
149 image.fdtmap_data = fdtmap_data
Simon Glass4c613bf2019-07-08 14:25:50 -0600150 image._data = data
Simon Glass072959a2019-07-20 12:23:50 -0600151 image._filename = fname
152 image.image_name, _ = os.path.splitext(fname)
Simon Glass4c613bf2019-07-08 14:25:50 -0600153 return image
Simon Glassb8424fa2019-07-08 14:25:46 -0600154
Simon Glasse61b6f62019-07-08 14:25:37 -0600155 def Raise(self, msg):
156 """Convenience function to raise an error referencing an image"""
157 raise ValueError("Image '%s': %s" % (self._node.path, msg))
158
Simon Glass2574ef62016-11-25 20:15:51 -0700159 def PackEntries(self):
160 """Pack all entries into the image"""
Simon Glass67c59802020-07-09 18:39:35 -0600161 super().Pack(0)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600162
Simon Glass9dcc8612018-08-01 15:22:42 -0600163 def SetImagePos(self):
Simon Glass39dd2152019-07-08 14:25:47 -0600164 # This first section in the image so it starts at 0
Simon Glass67c59802020-07-09 18:39:35 -0600165 super().SetImagePos(0)
Simon Glass9dcc8612018-08-01 15:22:42 -0600166
Simon Glass2574ef62016-11-25 20:15:51 -0700167 def ProcessEntryContents(self):
168 """Call the ProcessContents() method for each entry
169
170 This is intended to adjust the contents as needed by the entry type.
Simon Glassec849852019-07-08 14:25:35 -0600171
172 Returns:
173 True if the new data size is OK, False if expansion is needed
Simon Glass2574ef62016-11-25 20:15:51 -0700174 """
Simon Glass220c6222021-01-06 21:35:17 -0700175 return super().ProcessContents()
Simon Glass2574ef62016-11-25 20:15:51 -0700176
Simon Glass4ca8e042017-11-13 18:55:01 -0700177 def WriteSymbols(self):
178 """Write symbol values into binary files for access at run time"""
Simon Glass67c59802020-07-09 18:39:35 -0600179 super().WriteSymbols(self)
Simon Glass39dd2152019-07-08 14:25:47 -0600180
Simon Glass2574ef62016-11-25 20:15:51 -0700181 def BuildImage(self):
182 """Write the image to a file"""
Simon Glass80025522022-01-29 14:14:04 -0700183 fname = tools.get_output_filename(self._filename)
Simon Glass011f1b32022-01-29 14:14:15 -0700184 tout.info("Writing image to '%s'" % fname)
Simon Glass2574ef62016-11-25 20:15:51 -0700185 with open(fname, 'wb') as fd:
Yannic Moog911ba9a2025-06-13 14:02:42 +0200186 # For final image, don't write absent blobs to file
187 self.drop_absent_optional()
Simon Glassff1903a2020-10-26 17:40:12 -0600188 data = self.GetPaddedData()
Simon Glass74b31572019-07-20 12:23:54 -0600189 fd.write(data)
Simon Glass011f1b32022-01-29 14:14:15 -0700190 tout.info("Wrote %#x bytes" % len(data))
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +0530191 # Create symlink to file if symlink given
192 if self._symlink is not None:
193 sname = tools.get_output_filename(self._symlink)
Andrew Davis6b463da2023-07-22 00:14:44 +0530194 if os.path.islink(sname):
195 os.remove(sname)
Neha Malcom Francis3eb4be32022-10-17 16:36:25 +0530196 os.symlink(fname, sname)
Simon Glass30732662018-06-01 09:38:20 -0600197
Simon Glassf3598922024-07-20 11:49:45 +0100198 def WriteAlternates(self):
199 """Write out alternative devicetree blobs, each in its own file"""
200 alt_entry = self.FindEntryType('alternates-fdt')
201 if not alt_entry:
202 return
203
204 for alt in alt_entry.alternates:
205 fname, data = alt_entry.ProcessWithFdt(alt)
206 pathname = tools.get_output_filename(fname)
207 tout.info(f"Writing alternate '{alt}' to '{pathname}'")
208 tools.write_file(pathname, data)
209 tout.info("Wrote %#x bytes" % len(data))
210
Simon Glass30732662018-06-01 09:38:20 -0600211 def WriteMap(self):
Simon Glasscd817d52018-09-14 04:57:36 -0600212 """Write a map of the image to a .map file
213
214 Returns:
215 Filename of map file written
216 """
Simon Glass39dd2152019-07-08 14:25:47 -0600217 filename = '%s.map' % self.image_name
Simon Glass80025522022-01-29 14:14:04 -0700218 fname = tools.get_output_filename(filename)
Simon Glass30732662018-06-01 09:38:20 -0600219 with open(fname, 'w') as fd:
Simon Glass7eca7922018-07-17 13:25:49 -0600220 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
221 file=fd)
Simon Glass67c59802020-07-09 18:39:35 -0600222 super().WriteMap(fd, 0)
Simon Glasscd817d52018-09-14 04:57:36 -0600223 return fname
Simon Glass6b156f82019-07-08 14:25:43 -0600224
225 def BuildEntryList(self):
226 """List the files in an image
227
228 Returns:
229 List of entry.EntryInfo objects describing all entries in the image
230 """
231 entries = []
Simon Glass39dd2152019-07-08 14:25:47 -0600232 self.ListEntries(entries, 0)
Simon Glass6b156f82019-07-08 14:25:43 -0600233 return entries
Simon Glassb2fd11d2019-07-08 14:25:48 -0600234
235 def FindEntryPath(self, entry_path):
236 """Find an entry at a given path in the image
237
238 Args:
239 entry_path: Path to entry (e.g. /ro-section/u-boot')
240
241 Returns:
242 Entry object corresponding to that past
243
244 Raises:
245 ValueError if no entry found
246 """
247 parts = entry_path.split('/')
248 entries = self.GetEntries()
249 parent = '/'
250 for part in parts:
251 entry = entries.get(part)
252 if not entry:
253 raise ValueError("Entry '%s' not found in '%s'" %
254 (part, parent))
255 parent = entry.GetPath()
256 entries = entry.GetEntries()
257 return entry
258
Simon Glass637958f2021-11-23 21:09:50 -0700259 def ReadData(self, decomp=True, alt_format=None):
Simon Glass011f1b32022-01-29 14:14:15 -0700260 tout.debug("Image '%s' ReadData(), size=%#x" %
Simon Glass4d8151f2019-09-25 08:56:21 -0600261 (self.GetPath(), len(self._data)))
Simon Glassb2fd11d2019-07-08 14:25:48 -0600262 return self._data
263
264 def GetListEntries(self, entry_paths):
265 """List the entries in an image
266
267 This decodes the supplied image and returns a list of entries from that
268 image, preceded by a header.
269
270 Args:
271 entry_paths: List of paths to match (each can have wildcards). Only
272 entries whose names match one of these paths will be printed
273
274 Returns:
275 String error message if something went wrong, otherwise
276 3-Tuple:
277 List of EntryInfo objects
278 List of lines, each
279 List of text columns, each a string
280 List of widths of each column
281 """
282 def _EntryToStrings(entry):
283 """Convert an entry to a list of strings, one for each column
284
285 Args:
286 entry: EntryInfo object containing information to output
287
288 Returns:
289 List of strings, one for each field in entry
290 """
291 def _AppendHex(val):
292 """Append a hex value, or an empty string if val is None
293
294 Args:
295 val: Integer value, or None if none
296 """
297 args.append('' if val is None else '>%x' % val)
298
299 args = [' ' * entry.indent + entry.name]
300 _AppendHex(entry.image_pos)
301 _AppendHex(entry.size)
302 args.append(entry.etype)
303 _AppendHex(entry.offset)
304 _AppendHex(entry.uncomp_size)
305 return args
306
307 def _DoLine(lines, line):
308 """Add a line to the output list
309
310 This adds a line (a list of columns) to the output list. It also updates
311 the widths[] array with the maximum width of each column
312
313 Args:
314 lines: List of lines to add to
315 line: List of strings, one for each column
316 """
317 for i, item in enumerate(line):
318 widths[i] = max(widths[i], len(item))
319 lines.append(line)
320
321 def _NameInPaths(fname, entry_paths):
322 """Check if a filename is in a list of wildcarded paths
323
324 Args:
325 fname: Filename to check
326 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
327 'section/u-boot'])
328
329 Returns:
330 True if any wildcard matches the filename (using Unix filename
331 pattern matching, not regular expressions)
332 False if not
333 """
334 for path in entry_paths:
335 if fnmatch.fnmatch(fname, path):
336 return True
337 return False
338
339 entries = self.BuildEntryList()
340
341 # This is our list of lines. Each item in the list is a list of strings, one
342 # for each column
343 lines = []
344 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
345 'Uncomp-size']
346 num_columns = len(HEADER)
347
348 # This records the width of each column, calculated as the maximum width of
349 # all the strings in that column
350 widths = [0] * num_columns
351 _DoLine(lines, HEADER)
352
353 # We won't print anything unless it has at least this indent. So at the
354 # start we will print nothing, unless a path matches (or there are no
355 # entry paths)
356 MAX_INDENT = 100
357 min_indent = MAX_INDENT
358 path_stack = []
359 path = ''
360 indent = 0
361 selected_entries = []
362 for entry in entries:
363 if entry.indent > indent:
364 path_stack.append(path)
365 elif entry.indent < indent:
366 path_stack.pop()
367 if path_stack:
368 path = path_stack[-1] + '/' + entry.name
369 indent = entry.indent
370
371 # If there are entry paths to match and we are not looking at a
372 # sub-entry of a previously matched entry, we need to check the path
373 if entry_paths and indent <= min_indent:
374 if _NameInPaths(path[1:], entry_paths):
375 # Print this entry and all sub-entries (=higher indent)
376 min_indent = indent
377 else:
378 # Don't print this entry, nor any following entries until we get
379 # a path match
380 min_indent = MAX_INDENT
381 continue
382 _DoLine(lines, _EntryToStrings(entry))
383 selected_entries.append(entry)
384 return selected_entries, lines, widths
Simon Glassecbe4732021-01-06 21:35:15 -0700385
Simon Glass65cf1ca2024-08-26 13:11:38 -0600386 def GetImageSymbolValue(self, sym_name, optional, msg, base_addr):
387 """Get the value of a Binman symbol
Simon Glassecbe4732021-01-06 21:35:15 -0700388
Simon Glass65cf1ca2024-08-26 13:11:38 -0600389 Look up a Binman symbol and obtain its value.
Simon Glassecbe4732021-01-06 21:35:15 -0700390
391 This searches through this image including all of its subsections.
392
393 At present the only entry properties supported are:
394 offset
395 image_pos - 'base_addr' is added if this is not an end-at-4gb image
396 size
397
398 Args:
399 sym_name: Symbol name in the ELF file to look up in the format
400 _binman_<entry>_prop_<property> where <entry> is the name of
401 the entry and <property> is the property to find (e.g.
402 _binman_u_boot_prop_offset). As a special case, you can append
403 _any to <entry> to have it search for any matching entry. E.g.
404 _binman_u_boot_any_prop_offset will match entries called u-boot,
405 u-boot-img and u-boot-nodtb)
406 optional: True if the symbol is optional. If False this function
407 will raise if the symbol is not found
408 msg: Message to display if an error occurs
Simon Glass56112842024-08-26 13:11:41 -0600409 base_addr (int): Base address of image. This is added to the
410 returned value of image-pos so that the returned position
411 indicates where the targeted entry/binary has actually been
412 loaded
Simon Glassecbe4732021-01-06 21:35:15 -0700413
414 Returns:
415 Value that should be assigned to that symbol, or None if it was
416 optional and not found
417
418 Raises:
419 ValueError if the symbol is invalid or not found, or references a
420 property which is not supported
421 """
422 entries = OrderedDict()
423 entries_by_name = {}
424 self._CollectEntries(entries, entries_by_name, self)
Simon Glass65cf1ca2024-08-26 13:11:38 -0600425 return self.GetSymbolValue(sym_name, optional, msg, base_addr,
426 entries_by_name)
Simon Glass4eae9252022-01-09 20:13:50 -0700427
428 def CollectBintools(self):
429 """Collect all the bintools used by this image
430
431 Returns:
432 Dict of bintools:
433 key: name of tool
434 value: Bintool object
435 """
436 bintools = {}
437 super().AddBintools(bintools)
438 self.bintools = bintools
439 return bintools
Simon Glass7b72c912024-07-20 11:49:44 +0100440
441 def FdtContents(self, fdt_etype):
442 """This base-class implementation simply calls the state function"""
443 return state.GetFdtContents(fdt_etype)