blob: 6d4bff584363d9b5d7c25af6f3c01aed3cf81079 [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)
Neha Malcom Francis3545e852022-10-17 16:36:25 +053041 symlink: Name of symlink to image
Simon Glass7ae5f312018-06-01 09:38:19 -060042
43 Args:
Simon Glass12bb1a92019-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 Glass7ae5f312018-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 Glass939d1062021-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 Glass0b6023e2021-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 Glass858436d2021-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 Kiszkafcc87ef2022-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 Glassbf7fd502016-11-25 20:15:51 -070074 """
Simon Glass939d1062021-01-06 21:35:16 -070075 def __init__(self, name, node, copy_to_orig=True, test=False,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010076 ignore_missing=False, use_expanded=False, missing_etype=False,
77 generate=True):
Simon Glass34861d52020-07-09 18:39:35 -060078 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060079 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060080 self.name = 'main-section'
81 self.image_name = name
82 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060083 self.fdtmap_dtb = None
84 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060085 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070086 self._ignore_missing = ignore_missing
Simon Glass858436d2021-11-23 21:09:49 -070087 self.missing_etype = missing_etype
Simon Glass0b6023e2021-03-18 20:25:06 +130088 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060089 self.test_section_timeout = False
Simon Glass386c63c2022-01-09 20:13:50 -070090 self.bintools = {}
Jan Kiszkafcc87ef2022-01-28 20:37:53 +010091 self.generate = generate
Simon Glass8beb11e2019-07-08 14:25:47 -060092 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060093 self.ReadNode()
94
95 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060096 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060097 filename = fdt_util.GetString(self._node, 'filename')
98 if filename:
99 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -0600100 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Neha Malcom Francis3545e852022-10-17 16:36:25 +0530101 self._symlink = fdt_util.GetString(self._node, 'symlink')
Simon Glassbf7fd502016-11-25 20:15:51 -0700102
Simon Glassffded752019-07-08 14:25:46 -0600103 @classmethod
104 def FromFile(cls, fname):
105 """Convert an image file into an Image for use in binman
106
107 Args:
108 fname: Filename of image file to read
109
110 Returns:
111 Image object on success
112
113 Raises:
114 ValueError if something goes wrong
115 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700116 data = tools.read_file(fname)
Simon Glassffded752019-07-08 14:25:46 -0600117 size = len(data)
118
119 # First look for an image header
120 pos = image_header.LocateHeaderOffset(data)
121 if pos is None:
122 # Look for the FDT map
123 pos = fdtmap.LocateFdtmap(data)
124 if pos is None:
125 raise ValueError('Cannot find FDT map in image')
126
127 # We don't know the FDT size, so check its header first
128 probe_dtb = fdt.Fdt.FromData(
129 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
130 dtb_size = probe_dtb.GetFdtObj().totalsize()
131 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600132 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
Simon Glassc1aa66e2022-01-29 14:14:04 -0700133 out_fname = tools.get_output_filename('fdtmap.in.dtb')
134 tools.write_file(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600135 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600136 dtb.Scan()
137
138 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600139 root = dtb.GetRoot()
Simon Glass858436d2021-11-23 21:09:49 -0700140 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
Jan Kiszkafcc87ef2022-01-28 20:37:53 +0100141 missing_etype=True, generate=False)
Simon Glass51014aa2019-07-20 12:23:56 -0600142
Simon Glass589d8f92019-07-20 12:23:40 -0600143 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
144 image.fdtmap_dtb = dtb
145 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600146 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600147 image._filename = fname
148 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600149 return image
Simon Glassffded752019-07-08 14:25:46 -0600150
Simon Glassc52c9e72019-07-08 14:25:37 -0600151 def Raise(self, msg):
152 """Convenience function to raise an error referencing an image"""
153 raise ValueError("Image '%s': %s" % (self._node.path, msg))
154
Simon Glassbf7fd502016-11-25 20:15:51 -0700155 def PackEntries(self):
156 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600157 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600158
Simon Glassdbf6be92018-08-01 15:22:42 -0600159 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600160 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600161 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600162
Simon Glassbf7fd502016-11-25 20:15:51 -0700163 def ProcessEntryContents(self):
164 """Call the ProcessContents() method for each entry
165
166 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600167
168 Returns:
169 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700170 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700171 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700172
Simon Glass19790632017-11-13 18:55:01 -0700173 def WriteSymbols(self):
174 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600175 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600176
Simon Glassbf7fd502016-11-25 20:15:51 -0700177 def BuildImage(self):
178 """Write the image to a file"""
Simon Glassc1aa66e2022-01-29 14:14:04 -0700179 fname = tools.get_output_filename(self._filename)
Simon Glassf3385a52022-01-29 14:14:15 -0700180 tout.info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700181 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600182 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600183 fd.write(data)
Simon Glassf3385a52022-01-29 14:14:15 -0700184 tout.info("Wrote %#x bytes" % len(data))
Neha Malcom Francis3545e852022-10-17 16:36:25 +0530185 # Create symlink to file if symlink given
186 if self._symlink is not None:
187 sname = tools.get_output_filename(self._symlink)
188 os.symlink(fname, sname)
Simon Glass3b0c3822018-06-01 09:38:20 -0600189
190 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600191 """Write a map of the image to a .map file
192
193 Returns:
194 Filename of map file written
195 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600196 filename = '%s.map' % self.image_name
Simon Glassc1aa66e2022-01-29 14:14:04 -0700197 fname = tools.get_output_filename(filename)
Simon Glass3b0c3822018-06-01 09:38:20 -0600198 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600199 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
200 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600201 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600202 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600203
204 def BuildEntryList(self):
205 """List the files in an image
206
207 Returns:
208 List of entry.EntryInfo objects describing all entries in the image
209 """
210 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600211 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600212 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600213
214 def FindEntryPath(self, entry_path):
215 """Find an entry at a given path in the image
216
217 Args:
218 entry_path: Path to entry (e.g. /ro-section/u-boot')
219
220 Returns:
221 Entry object corresponding to that past
222
223 Raises:
224 ValueError if no entry found
225 """
226 parts = entry_path.split('/')
227 entries = self.GetEntries()
228 parent = '/'
229 for part in parts:
230 entry = entries.get(part)
231 if not entry:
232 raise ValueError("Entry '%s' not found in '%s'" %
233 (part, parent))
234 parent = entry.GetPath()
235 entries = entry.GetEntries()
236 return entry
237
Simon Glass943bf782021-11-23 21:09:50 -0700238 def ReadData(self, decomp=True, alt_format=None):
Simon Glassf3385a52022-01-29 14:14:15 -0700239 tout.debug("Image '%s' ReadData(), size=%#x" %
Simon Glass2d553c02019-09-25 08:56:21 -0600240 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600241 return self._data
242
243 def GetListEntries(self, entry_paths):
244 """List the entries in an image
245
246 This decodes the supplied image and returns a list of entries from that
247 image, preceded by a header.
248
249 Args:
250 entry_paths: List of paths to match (each can have wildcards). Only
251 entries whose names match one of these paths will be printed
252
253 Returns:
254 String error message if something went wrong, otherwise
255 3-Tuple:
256 List of EntryInfo objects
257 List of lines, each
258 List of text columns, each a string
259 List of widths of each column
260 """
261 def _EntryToStrings(entry):
262 """Convert an entry to a list of strings, one for each column
263
264 Args:
265 entry: EntryInfo object containing information to output
266
267 Returns:
268 List of strings, one for each field in entry
269 """
270 def _AppendHex(val):
271 """Append a hex value, or an empty string if val is None
272
273 Args:
274 val: Integer value, or None if none
275 """
276 args.append('' if val is None else '>%x' % val)
277
278 args = [' ' * entry.indent + entry.name]
279 _AppendHex(entry.image_pos)
280 _AppendHex(entry.size)
281 args.append(entry.etype)
282 _AppendHex(entry.offset)
283 _AppendHex(entry.uncomp_size)
284 return args
285
286 def _DoLine(lines, line):
287 """Add a line to the output list
288
289 This adds a line (a list of columns) to the output list. It also updates
290 the widths[] array with the maximum width of each column
291
292 Args:
293 lines: List of lines to add to
294 line: List of strings, one for each column
295 """
296 for i, item in enumerate(line):
297 widths[i] = max(widths[i], len(item))
298 lines.append(line)
299
300 def _NameInPaths(fname, entry_paths):
301 """Check if a filename is in a list of wildcarded paths
302
303 Args:
304 fname: Filename to check
305 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
306 'section/u-boot'])
307
308 Returns:
309 True if any wildcard matches the filename (using Unix filename
310 pattern matching, not regular expressions)
311 False if not
312 """
313 for path in entry_paths:
314 if fnmatch.fnmatch(fname, path):
315 return True
316 return False
317
318 entries = self.BuildEntryList()
319
320 # This is our list of lines. Each item in the list is a list of strings, one
321 # for each column
322 lines = []
323 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
324 'Uncomp-size']
325 num_columns = len(HEADER)
326
327 # This records the width of each column, calculated as the maximum width of
328 # all the strings in that column
329 widths = [0] * num_columns
330 _DoLine(lines, HEADER)
331
332 # We won't print anything unless it has at least this indent. So at the
333 # start we will print nothing, unless a path matches (or there are no
334 # entry paths)
335 MAX_INDENT = 100
336 min_indent = MAX_INDENT
337 path_stack = []
338 path = ''
339 indent = 0
340 selected_entries = []
341 for entry in entries:
342 if entry.indent > indent:
343 path_stack.append(path)
344 elif entry.indent < indent:
345 path_stack.pop()
346 if path_stack:
347 path = path_stack[-1] + '/' + entry.name
348 indent = entry.indent
349
350 # If there are entry paths to match and we are not looking at a
351 # sub-entry of a previously matched entry, we need to check the path
352 if entry_paths and indent <= min_indent:
353 if _NameInPaths(path[1:], entry_paths):
354 # Print this entry and all sub-entries (=higher indent)
355 min_indent = indent
356 else:
357 # Don't print this entry, nor any following entries until we get
358 # a path match
359 min_indent = MAX_INDENT
360 continue
361 _DoLine(lines, _EntryToStrings(entry))
362 selected_entries.append(entry)
363 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700364
365 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
366 """Look up a symbol in an ELF file
367
368 Looks up a symbol in an ELF file. Only entry types which come from an
369 ELF image can be used by this function.
370
371 This searches through this image including all of its subsections.
372
373 At present the only entry properties supported are:
374 offset
375 image_pos - 'base_addr' is added if this is not an end-at-4gb image
376 size
377
378 Args:
379 sym_name: Symbol name in the ELF file to look up in the format
380 _binman_<entry>_prop_<property> where <entry> is the name of
381 the entry and <property> is the property to find (e.g.
382 _binman_u_boot_prop_offset). As a special case, you can append
383 _any to <entry> to have it search for any matching entry. E.g.
384 _binman_u_boot_any_prop_offset will match entries called u-boot,
385 u-boot-img and u-boot-nodtb)
386 optional: True if the symbol is optional. If False this function
387 will raise if the symbol is not found
388 msg: Message to display if an error occurs
389 base_addr: Base address of image. This is added to the returned
390 image_pos in most cases so that the returned position indicates
391 where the targeted entry/binary has actually been loaded. But
392 if end-at-4gb is used, this is not done, since the binary is
393 already assumed to be linked to the ROM position and using
394 execute-in-place (XIP).
395
396 Returns:
397 Value that should be assigned to that symbol, or None if it was
398 optional and not found
399
400 Raises:
401 ValueError if the symbol is invalid or not found, or references a
402 property which is not supported
403 """
404 entries = OrderedDict()
405 entries_by_name = {}
406 self._CollectEntries(entries, entries_by_name, self)
407 return self.LookupSymbol(sym_name, optional, msg, base_addr,
408 entries_by_name)
Simon Glass386c63c2022-01-09 20:13:50 -0700409
410 def CollectBintools(self):
411 """Collect all the bintools used by this image
412
413 Returns:
414 Dict of bintools:
415 key: name of tool
416 value: Bintool object
417 """
418 bintools = {}
419 super().AddBintools(bintools)
420 self.bintools = bintools
421 return bintools