blob: 0f0c1d29e8079ea9c5d9db273e10b9bf85940f1d [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 Glassa997ea52020-04-17 18:09:04 -060021from patman import tools
22from patman 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)
Simon Glass1e324002018-06-01 09:38:19 -060041
42 Args:
Simon Glassfb30e292019-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 Glass1e324002018-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 Glass3fb25402021-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 Glass55ab0b62021-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 Glassb9028bc2021-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.
Simon Glass2574ef62016-11-25 20:15:51 -070070 """
Simon Glass3fb25402021-01-06 21:35:16 -070071 def __init__(self, name, node, copy_to_orig=True, test=False,
Simon Glassb9028bc2021-11-23 21:09:49 -070072 ignore_missing=False, use_expanded=False, missing_etype=False):
Simon Glass67c59802020-07-09 18:39:35 -060073 super().__init__(None, 'section', node, test=test)
Simon Glassfb30e292019-07-20 12:23:51 -060074 self.copy_to_orig = copy_to_orig
Simon Glass39dd2152019-07-08 14:25:47 -060075 self.name = 'main-section'
76 self.image_name = name
77 self._filename = '%s.bin' % self.image_name
Simon Glass76546572019-07-20 12:23:40 -060078 self.fdtmap_dtb = None
79 self.fdtmap_data = None
Simon Glassfb30e292019-07-20 12:23:51 -060080 self.allow_repack = False
Simon Glass3fb25402021-01-06 21:35:16 -070081 self._ignore_missing = ignore_missing
Simon Glassb9028bc2021-11-23 21:09:49 -070082 self.missing_etype = missing_etype
Simon Glass55ab0b62021-03-18 20:25:06 +130083 self.use_expanded = use_expanded
Simon Glass76f496d2021-07-06 10:36:37 -060084 self.test_section_timeout = False
Simon Glass4eae9252022-01-09 20:13:50 -070085 self.bintools = {}
Simon Glass39dd2152019-07-08 14:25:47 -060086 if not test:
Simon Glass2c360cf2019-07-20 12:23:45 -060087 self.ReadNode()
88
89 def ReadNode(self):
Simon Glass67c59802020-07-09 18:39:35 -060090 super().ReadNode()
Simon Glass2c360cf2019-07-20 12:23:45 -060091 filename = fdt_util.GetString(self._node, 'filename')
92 if filename:
93 self._filename = filename
Simon Glassfb30e292019-07-20 12:23:51 -060094 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glass2574ef62016-11-25 20:15:51 -070095
Simon Glassb8424fa2019-07-08 14:25:46 -060096 @classmethod
97 def FromFile(cls, fname):
98 """Convert an image file into an Image for use in binman
99
100 Args:
101 fname: Filename of image file to read
102
103 Returns:
104 Image object on success
105
106 Raises:
107 ValueError if something goes wrong
108 """
109 data = tools.ReadFile(fname)
110 size = len(data)
111
112 # First look for an image header
113 pos = image_header.LocateHeaderOffset(data)
114 if pos is None:
115 # Look for the FDT map
116 pos = fdtmap.LocateFdtmap(data)
117 if pos is None:
118 raise ValueError('Cannot find FDT map in image')
119
120 # We don't know the FDT size, so check its header first
121 probe_dtb = fdt.Fdt.FromData(
122 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
123 dtb_size = probe_dtb.GetFdtObj().totalsize()
124 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass9b7f5002019-07-20 12:23:53 -0600125 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
126 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
127 tools.WriteFile(out_fname, fdt_data)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600128 dtb = fdt.Fdt(out_fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600129 dtb.Scan()
130
131 # Return an Image with the associated nodes
Simon Glass76546572019-07-20 12:23:40 -0600132 root = dtb.GetRoot()
Simon Glassb9028bc2021-11-23 21:09:49 -0700133 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
134 missing_etype=True)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600135
Simon Glass76546572019-07-20 12:23:40 -0600136 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
137 image.fdtmap_dtb = dtb
138 image.fdtmap_data = fdtmap_data
Simon Glass4c613bf2019-07-08 14:25:50 -0600139 image._data = data
Simon Glass072959a2019-07-20 12:23:50 -0600140 image._filename = fname
141 image.image_name, _ = os.path.splitext(fname)
Simon Glass4c613bf2019-07-08 14:25:50 -0600142 return image
Simon Glassb8424fa2019-07-08 14:25:46 -0600143
Simon Glasse61b6f62019-07-08 14:25:37 -0600144 def Raise(self, msg):
145 """Convenience function to raise an error referencing an image"""
146 raise ValueError("Image '%s': %s" % (self._node.path, msg))
147
Simon Glass2574ef62016-11-25 20:15:51 -0700148 def PackEntries(self):
149 """Pack all entries into the image"""
Simon Glass67c59802020-07-09 18:39:35 -0600150 super().Pack(0)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600151
Simon Glass9dcc8612018-08-01 15:22:42 -0600152 def SetImagePos(self):
Simon Glass39dd2152019-07-08 14:25:47 -0600153 # This first section in the image so it starts at 0
Simon Glass67c59802020-07-09 18:39:35 -0600154 super().SetImagePos(0)
Simon Glass9dcc8612018-08-01 15:22:42 -0600155
Simon Glass2574ef62016-11-25 20:15:51 -0700156 def ProcessEntryContents(self):
157 """Call the ProcessContents() method for each entry
158
159 This is intended to adjust the contents as needed by the entry type.
Simon Glassec849852019-07-08 14:25:35 -0600160
161 Returns:
162 True if the new data size is OK, False if expansion is needed
Simon Glass2574ef62016-11-25 20:15:51 -0700163 """
Simon Glass220c6222021-01-06 21:35:17 -0700164 return super().ProcessContents()
Simon Glass2574ef62016-11-25 20:15:51 -0700165
Simon Glass4ca8e042017-11-13 18:55:01 -0700166 def WriteSymbols(self):
167 """Write symbol values into binary files for access at run time"""
Simon Glass67c59802020-07-09 18:39:35 -0600168 super().WriteSymbols(self)
Simon Glass39dd2152019-07-08 14:25:47 -0600169
Simon Glass2574ef62016-11-25 20:15:51 -0700170 def BuildImage(self):
171 """Write the image to a file"""
172 fname = tools.GetOutputFilename(self._filename)
Simon Glass74b31572019-07-20 12:23:54 -0600173 tout.Info("Writing image to '%s'" % fname)
Simon Glass2574ef62016-11-25 20:15:51 -0700174 with open(fname, 'wb') as fd:
Simon Glassff1903a2020-10-26 17:40:12 -0600175 data = self.GetPaddedData()
Simon Glass74b31572019-07-20 12:23:54 -0600176 fd.write(data)
177 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass30732662018-06-01 09:38:20 -0600178
179 def WriteMap(self):
Simon Glasscd817d52018-09-14 04:57:36 -0600180 """Write a map of the image to a .map file
181
182 Returns:
183 Filename of map file written
184 """
Simon Glass39dd2152019-07-08 14:25:47 -0600185 filename = '%s.map' % self.image_name
Simon Glass30732662018-06-01 09:38:20 -0600186 fname = tools.GetOutputFilename(filename)
187 with open(fname, 'w') as fd:
Simon Glass7eca7922018-07-17 13:25:49 -0600188 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
189 file=fd)
Simon Glass67c59802020-07-09 18:39:35 -0600190 super().WriteMap(fd, 0)
Simon Glasscd817d52018-09-14 04:57:36 -0600191 return fname
Simon Glass6b156f82019-07-08 14:25:43 -0600192
193 def BuildEntryList(self):
194 """List the files in an image
195
196 Returns:
197 List of entry.EntryInfo objects describing all entries in the image
198 """
199 entries = []
Simon Glass39dd2152019-07-08 14:25:47 -0600200 self.ListEntries(entries, 0)
Simon Glass6b156f82019-07-08 14:25:43 -0600201 return entries
Simon Glassb2fd11d2019-07-08 14:25:48 -0600202
203 def FindEntryPath(self, entry_path):
204 """Find an entry at a given path in the image
205
206 Args:
207 entry_path: Path to entry (e.g. /ro-section/u-boot')
208
209 Returns:
210 Entry object corresponding to that past
211
212 Raises:
213 ValueError if no entry found
214 """
215 parts = entry_path.split('/')
216 entries = self.GetEntries()
217 parent = '/'
218 for part in parts:
219 entry = entries.get(part)
220 if not entry:
221 raise ValueError("Entry '%s' not found in '%s'" %
222 (part, parent))
223 parent = entry.GetPath()
224 entries = entry.GetEntries()
225 return entry
226
Simon Glass637958f2021-11-23 21:09:50 -0700227 def ReadData(self, decomp=True, alt_format=None):
Simon Glass4d8151f2019-09-25 08:56:21 -0600228 tout.Debug("Image '%s' ReadData(), size=%#x" %
229 (self.GetPath(), len(self._data)))
Simon Glassb2fd11d2019-07-08 14:25:48 -0600230 return self._data
231
232 def GetListEntries(self, entry_paths):
233 """List the entries in an image
234
235 This decodes the supplied image and returns a list of entries from that
236 image, preceded by a header.
237
238 Args:
239 entry_paths: List of paths to match (each can have wildcards). Only
240 entries whose names match one of these paths will be printed
241
242 Returns:
243 String error message if something went wrong, otherwise
244 3-Tuple:
245 List of EntryInfo objects
246 List of lines, each
247 List of text columns, each a string
248 List of widths of each column
249 """
250 def _EntryToStrings(entry):
251 """Convert an entry to a list of strings, one for each column
252
253 Args:
254 entry: EntryInfo object containing information to output
255
256 Returns:
257 List of strings, one for each field in entry
258 """
259 def _AppendHex(val):
260 """Append a hex value, or an empty string if val is None
261
262 Args:
263 val: Integer value, or None if none
264 """
265 args.append('' if val is None else '>%x' % val)
266
267 args = [' ' * entry.indent + entry.name]
268 _AppendHex(entry.image_pos)
269 _AppendHex(entry.size)
270 args.append(entry.etype)
271 _AppendHex(entry.offset)
272 _AppendHex(entry.uncomp_size)
273 return args
274
275 def _DoLine(lines, line):
276 """Add a line to the output list
277
278 This adds a line (a list of columns) to the output list. It also updates
279 the widths[] array with the maximum width of each column
280
281 Args:
282 lines: List of lines to add to
283 line: List of strings, one for each column
284 """
285 for i, item in enumerate(line):
286 widths[i] = max(widths[i], len(item))
287 lines.append(line)
288
289 def _NameInPaths(fname, entry_paths):
290 """Check if a filename is in a list of wildcarded paths
291
292 Args:
293 fname: Filename to check
294 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
295 'section/u-boot'])
296
297 Returns:
298 True if any wildcard matches the filename (using Unix filename
299 pattern matching, not regular expressions)
300 False if not
301 """
302 for path in entry_paths:
303 if fnmatch.fnmatch(fname, path):
304 return True
305 return False
306
307 entries = self.BuildEntryList()
308
309 # This is our list of lines. Each item in the list is a list of strings, one
310 # for each column
311 lines = []
312 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
313 'Uncomp-size']
314 num_columns = len(HEADER)
315
316 # This records the width of each column, calculated as the maximum width of
317 # all the strings in that column
318 widths = [0] * num_columns
319 _DoLine(lines, HEADER)
320
321 # We won't print anything unless it has at least this indent. So at the
322 # start we will print nothing, unless a path matches (or there are no
323 # entry paths)
324 MAX_INDENT = 100
325 min_indent = MAX_INDENT
326 path_stack = []
327 path = ''
328 indent = 0
329 selected_entries = []
330 for entry in entries:
331 if entry.indent > indent:
332 path_stack.append(path)
333 elif entry.indent < indent:
334 path_stack.pop()
335 if path_stack:
336 path = path_stack[-1] + '/' + entry.name
337 indent = entry.indent
338
339 # If there are entry paths to match and we are not looking at a
340 # sub-entry of a previously matched entry, we need to check the path
341 if entry_paths and indent <= min_indent:
342 if _NameInPaths(path[1:], entry_paths):
343 # Print this entry and all sub-entries (=higher indent)
344 min_indent = indent
345 else:
346 # Don't print this entry, nor any following entries until we get
347 # a path match
348 min_indent = MAX_INDENT
349 continue
350 _DoLine(lines, _EntryToStrings(entry))
351 selected_entries.append(entry)
352 return selected_entries, lines, widths
Simon Glassecbe4732021-01-06 21:35:15 -0700353
354 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
355 """Look up a symbol in an ELF file
356
357 Looks up a symbol in an ELF file. Only entry types which come from an
358 ELF image can be used by this function.
359
360 This searches through this image including all of its subsections.
361
362 At present the only entry properties supported are:
363 offset
364 image_pos - 'base_addr' is added if this is not an end-at-4gb image
365 size
366
367 Args:
368 sym_name: Symbol name in the ELF file to look up in the format
369 _binman_<entry>_prop_<property> where <entry> is the name of
370 the entry and <property> is the property to find (e.g.
371 _binman_u_boot_prop_offset). As a special case, you can append
372 _any to <entry> to have it search for any matching entry. E.g.
373 _binman_u_boot_any_prop_offset will match entries called u-boot,
374 u-boot-img and u-boot-nodtb)
375 optional: True if the symbol is optional. If False this function
376 will raise if the symbol is not found
377 msg: Message to display if an error occurs
378 base_addr: Base address of image. This is added to the returned
379 image_pos in most cases so that the returned position indicates
380 where the targeted entry/binary has actually been loaded. But
381 if end-at-4gb is used, this is not done, since the binary is
382 already assumed to be linked to the ROM position and using
383 execute-in-place (XIP).
384
385 Returns:
386 Value that should be assigned to that symbol, or None if it was
387 optional and not found
388
389 Raises:
390 ValueError if the symbol is invalid or not found, or references a
391 property which is not supported
392 """
393 entries = OrderedDict()
394 entries_by_name = {}
395 self._CollectEntries(entries, entries_by_name, self)
396 return self.LookupSymbol(sym_name, optional, msg, base_addr,
397 entries_by_name)
Simon Glass4eae9252022-01-09 20:13:50 -0700398
399 def CollectBintools(self):
400 """Collect all the bintools used by this image
401
402 Returns:
403 Dict of bintools:
404 key: name of tool
405 value: Bintool object
406 """
407 bintools = {}
408 super().AddBintools(bintools)
409 self.bintools = bintools
410 return bintools