blob: 24ce0af7c72b5256a9a71963a6d94c080ed8bdd4 [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 Glass4583c002023-02-23 18:18:04 -070021from u_boot_pylib import tools
22from u_boot_pylib import tout
Simon Glassbf7fd502016-11-25 20:15:51 -070023
Simon Glass2e3697b2024-07-20 11:49:43 +010024# This is imported if needed
25state = None
26
Simon Glass8beb11e2019-07-08 14:25:47 -060027class Image(section.Entry_section):
Simon Glassbf7fd502016-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 Glass8beb11e2019-07-08 14:25:47 -060035 Attributes:
36 filename: Output filename for image
Simon Glass589d8f92019-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 Glass12bb1a92019-07-20 12:23:51 -060040 allow_repack: True to add properties to allow the image to be safely
41 repacked later
Simon Glassc69d19c2021-07-06 10:36:37 -060042 test_section_timeout: Use a zero timeout for section multi-threading
43 (for testing)
Neha Malcom Francis3545e852022-10-17 16:36:25 +053044 symlink: Name of symlink to image
Simon Glass7ae5f312018-06-01 09:38:19 -060045
46 Args:
Simon Glass12bb1a92019-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 Glass7ae5f312018-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 Glass939d1062021-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 Glass0b6023e2021-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 Glass858436d2021-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 Kiszkafcc87ef2022-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 Glassbf7fd502016-11-25 20:15:51 -070077 """
Simon Glass939d1062021-01-06 21:35:16 -070078 def __init__(self, name, node, copy_to_orig=True, test=False,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010079 ignore_missing=False, use_expanded=False, missing_etype=False,
80 generate=True):
Simon Glass2e3697b2024-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 Glass34861d52020-07-09 18:39:35 -060085 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060086 self.copy_to_orig = copy_to_orig
Simon Glass193d3db2023-02-07 14:34:18 -070087 self.name = name
Simon Glass8beb11e2019-07-08 14:25:47 -060088 self.image_name = name
89 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060090 self.fdtmap_dtb = None
91 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060092 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070093 self._ignore_missing = ignore_missing
Simon Glass858436d2021-11-23 21:09:49 -070094 self.missing_etype = missing_etype
Simon Glass0b6023e2021-03-18 20:25:06 +130095 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060096 self.test_section_timeout = False
Simon Glass386c63c2022-01-09 20:13:50 -070097 self.bintools = {}
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010098 self.generate = generate
Simon Glass8beb11e2019-07-08 14:25:47 -060099 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -0600100 self.ReadNode()
101
102 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -0600103 super().ReadNode()
Simon Glass12bb1a92019-07-20 12:23:51 -0600104 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Neha Malcom Francis3545e852022-10-17 16:36:25 +0530105 self._symlink = fdt_util.GetString(self._node, 'symlink')
Simon Glassbf7fd502016-11-25 20:15:51 -0700106
Simon Glassffded752019-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 Glassc1aa66e2022-01-29 14:14:04 -0700120 data = tools.read_file(fname)
Simon Glassffded752019-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 Glass96b6c502019-07-20 12:23:53 -0600136 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700137 out_fname = tools.get_output_filename('fdtmap.in.dtb')
138 tools.write_file(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600139 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600140 dtb.Scan()
141
142 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600143 root = dtb.GetRoot()
Simon Glass858436d2021-11-23 21:09:49 -0700144 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +0100145 missing_etype=True, generate=False)
Simon Glass51014aa2019-07-20 12:23:56 -0600146
Simon Glass589d8f92019-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 Glassf667e452019-07-08 14:25:50 -0600150 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600151 image._filename = fname
152 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600153 return image
Simon Glassffded752019-07-08 14:25:46 -0600154
Simon Glassc52c9e72019-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 Glassbf7fd502016-11-25 20:15:51 -0700159 def PackEntries(self):
160 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600161 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600162
Simon Glassdbf6be92018-08-01 15:22:42 -0600163 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600164 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600165 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600166
Simon Glassbf7fd502016-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 Glassa0dcaf22019-07-08 14:25:35 -0600171
172 Returns:
173 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700174 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700175 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700176
Simon Glass19790632017-11-13 18:55:01 -0700177 def WriteSymbols(self):
178 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600179 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600180
Simon Glassbf7fd502016-11-25 20:15:51 -0700181 def BuildImage(self):
182 """Write the image to a file"""
Simon Glassc1aa66e2022-01-29 14:14:04 -0700183 fname = tools.get_output_filename(self._filename)
Simon Glassf3385a52022-01-29 14:14:15 -0700184 tout.info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700185 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600186 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600187 fd.write(data)
Simon Glassf3385a52022-01-29 14:14:15 -0700188 tout.info("Wrote %#x bytes" % len(data))
Neha Malcom Francis3545e852022-10-17 16:36:25 +0530189 # Create symlink to file if symlink given
190 if self._symlink is not None:
191 sname = tools.get_output_filename(self._symlink)
Andrew Davis15432ea2023-07-22 00:14:44 +0530192 if os.path.islink(sname):
193 os.remove(sname)
Neha Malcom Francis3545e852022-10-17 16:36:25 +0530194 os.symlink(fname, sname)
Simon Glass3b0c3822018-06-01 09:38:20 -0600195
Simon Glass7081a942024-07-20 11:49:45 +0100196 def WriteAlternates(self):
197 """Write out alternative devicetree blobs, each in its own file"""
198 alt_entry = self.FindEntryType('alternates-fdt')
199 if not alt_entry:
200 return
201
202 for alt in alt_entry.alternates:
203 fname, data = alt_entry.ProcessWithFdt(alt)
204 pathname = tools.get_output_filename(fname)
205 tout.info(f"Writing alternate '{alt}' to '{pathname}'")
206 tools.write_file(pathname, data)
207 tout.info("Wrote %#x bytes" % len(data))
208
Simon Glass3b0c3822018-06-01 09:38:20 -0600209 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600210 """Write a map of the image to a .map file
211
212 Returns:
213 Filename of map file written
214 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600215 filename = '%s.map' % self.image_name
Simon Glassc1aa66e2022-01-29 14:14:04 -0700216 fname = tools.get_output_filename(filename)
Simon Glass3b0c3822018-06-01 09:38:20 -0600217 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600218 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
219 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600220 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600221 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600222
223 def BuildEntryList(self):
224 """List the files in an image
225
226 Returns:
227 List of entry.EntryInfo objects describing all entries in the image
228 """
229 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600230 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600231 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600232
233 def FindEntryPath(self, entry_path):
234 """Find an entry at a given path in the image
235
236 Args:
237 entry_path: Path to entry (e.g. /ro-section/u-boot')
238
239 Returns:
240 Entry object corresponding to that past
241
242 Raises:
243 ValueError if no entry found
244 """
245 parts = entry_path.split('/')
246 entries = self.GetEntries()
247 parent = '/'
248 for part in parts:
249 entry = entries.get(part)
250 if not entry:
251 raise ValueError("Entry '%s' not found in '%s'" %
252 (part, parent))
253 parent = entry.GetPath()
254 entries = entry.GetEntries()
255 return entry
256
Simon Glass943bf782021-11-23 21:09:50 -0700257 def ReadData(self, decomp=True, alt_format=None):
Simon Glassf3385a52022-01-29 14:14:15 -0700258 tout.debug("Image '%s' ReadData(), size=%#x" %
Simon Glass2d553c02019-09-25 08:56:21 -0600259 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600260 return self._data
261
262 def GetListEntries(self, entry_paths):
263 """List the entries in an image
264
265 This decodes the supplied image and returns a list of entries from that
266 image, preceded by a header.
267
268 Args:
269 entry_paths: List of paths to match (each can have wildcards). Only
270 entries whose names match one of these paths will be printed
271
272 Returns:
273 String error message if something went wrong, otherwise
274 3-Tuple:
275 List of EntryInfo objects
276 List of lines, each
277 List of text columns, each a string
278 List of widths of each column
279 """
280 def _EntryToStrings(entry):
281 """Convert an entry to a list of strings, one for each column
282
283 Args:
284 entry: EntryInfo object containing information to output
285
286 Returns:
287 List of strings, one for each field in entry
288 """
289 def _AppendHex(val):
290 """Append a hex value, or an empty string if val is None
291
292 Args:
293 val: Integer value, or None if none
294 """
295 args.append('' if val is None else '>%x' % val)
296
297 args = [' ' * entry.indent + entry.name]
298 _AppendHex(entry.image_pos)
299 _AppendHex(entry.size)
300 args.append(entry.etype)
301 _AppendHex(entry.offset)
302 _AppendHex(entry.uncomp_size)
303 return args
304
305 def _DoLine(lines, line):
306 """Add a line to the output list
307
308 This adds a line (a list of columns) to the output list. It also updates
309 the widths[] array with the maximum width of each column
310
311 Args:
312 lines: List of lines to add to
313 line: List of strings, one for each column
314 """
315 for i, item in enumerate(line):
316 widths[i] = max(widths[i], len(item))
317 lines.append(line)
318
319 def _NameInPaths(fname, entry_paths):
320 """Check if a filename is in a list of wildcarded paths
321
322 Args:
323 fname: Filename to check
324 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
325 'section/u-boot'])
326
327 Returns:
328 True if any wildcard matches the filename (using Unix filename
329 pattern matching, not regular expressions)
330 False if not
331 """
332 for path in entry_paths:
333 if fnmatch.fnmatch(fname, path):
334 return True
335 return False
336
337 entries = self.BuildEntryList()
338
339 # This is our list of lines. Each item in the list is a list of strings, one
340 # for each column
341 lines = []
342 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
343 'Uncomp-size']
344 num_columns = len(HEADER)
345
346 # This records the width of each column, calculated as the maximum width of
347 # all the strings in that column
348 widths = [0] * num_columns
349 _DoLine(lines, HEADER)
350
351 # We won't print anything unless it has at least this indent. So at the
352 # start we will print nothing, unless a path matches (or there are no
353 # entry paths)
354 MAX_INDENT = 100
355 min_indent = MAX_INDENT
356 path_stack = []
357 path = ''
358 indent = 0
359 selected_entries = []
360 for entry in entries:
361 if entry.indent > indent:
362 path_stack.append(path)
363 elif entry.indent < indent:
364 path_stack.pop()
365 if path_stack:
366 path = path_stack[-1] + '/' + entry.name
367 indent = entry.indent
368
369 # If there are entry paths to match and we are not looking at a
370 # sub-entry of a previously matched entry, we need to check the path
371 if entry_paths and indent <= min_indent:
372 if _NameInPaths(path[1:], entry_paths):
373 # Print this entry and all sub-entries (=higher indent)
374 min_indent = indent
375 else:
376 # Don't print this entry, nor any following entries until we get
377 # a path match
378 min_indent = MAX_INDENT
379 continue
380 _DoLine(lines, _EntryToStrings(entry))
381 selected_entries.append(entry)
382 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700383
Simon Glassf2154c32024-08-26 13:11:38 -0600384 def GetImageSymbolValue(self, sym_name, optional, msg, base_addr):
385 """Get the value of a Binman symbol
Simon Glass870a9ea2021-01-06 21:35:15 -0700386
Simon Glassf2154c32024-08-26 13:11:38 -0600387 Look up a Binman symbol and obtain its value.
Simon Glass870a9ea2021-01-06 21:35:15 -0700388
389 This searches through this image including all of its subsections.
390
391 At present the only entry properties supported are:
392 offset
393 image_pos - 'base_addr' is added if this is not an end-at-4gb image
394 size
395
396 Args:
397 sym_name: Symbol name in the ELF file to look up in the format
398 _binman_<entry>_prop_<property> where <entry> is the name of
399 the entry and <property> is the property to find (e.g.
400 _binman_u_boot_prop_offset). As a special case, you can append
401 _any to <entry> to have it search for any matching entry. E.g.
402 _binman_u_boot_any_prop_offset will match entries called u-boot,
403 u-boot-img and u-boot-nodtb)
404 optional: True if the symbol is optional. If False this function
405 will raise if the symbol is not found
406 msg: Message to display if an error occurs
Simon Glassb73d0bb2024-08-26 13:11:41 -0600407 base_addr (int): Base address of image. This is added to the
408 returned value of image-pos so that the returned position
409 indicates where the targeted entry/binary has actually been
410 loaded
Simon Glass870a9ea2021-01-06 21:35:15 -0700411
412 Returns:
413 Value that should be assigned to that symbol, or None if it was
414 optional and not found
415
416 Raises:
417 ValueError if the symbol is invalid or not found, or references a
418 property which is not supported
419 """
420 entries = OrderedDict()
421 entries_by_name = {}
422 self._CollectEntries(entries, entries_by_name, self)
Simon Glassf2154c32024-08-26 13:11:38 -0600423 return self.GetSymbolValue(sym_name, optional, msg, base_addr,
424 entries_by_name)
Simon Glass386c63c2022-01-09 20:13:50 -0700425
426 def CollectBintools(self):
427 """Collect all the bintools used by this image
428
429 Returns:
430 Dict of bintools:
431 key: name of tool
432 value: Bintool object
433 """
434 bintools = {}
435 super().AddBintools(bintools)
436 self.bintools = bintools
437 return bintools
Simon Glassdaed9b42024-07-20 11:49:44 +0100438
439 def FdtContents(self, fdt_etype):
440 """This base-class implementation simply calls the state function"""
441 return state.GetFdtContents(fdt_etype)