blob: 0f0c1d29e8079ea9c5d9db273e10b9bf85940f1d [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)
Simon Glass7ae5f312018-06-01 09:38:19 -060041
42 Args:
Simon Glass12bb1a92019-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 Glass7ae5f312018-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 Glass939d1062021-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 Glass0b6023e2021-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 Glass858436d2021-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 Glassbf7fd502016-11-25 20:15:51 -070070 """
Simon Glass939d1062021-01-06 21:35:16 -070071 def __init__(self, name, node, copy_to_orig=True, test=False,
Simon Glass858436d2021-11-23 21:09:49 -070072 ignore_missing=False, use_expanded=False, missing_etype=False):
Simon Glass34861d52020-07-09 18:39:35 -060073 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060074 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060075 self.name = 'main-section'
76 self.image_name = name
77 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060078 self.fdtmap_dtb = None
79 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060080 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070081 self._ignore_missing = ignore_missing
Simon Glass858436d2021-11-23 21:09:49 -070082 self.missing_etype = missing_etype
Simon Glass0b6023e2021-03-18 20:25:06 +130083 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060084 self.test_section_timeout = False
Simon Glass386c63c2022-01-09 20:13:50 -070085 self.bintools = {}
Simon Glass8beb11e2019-07-08 14:25:47 -060086 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060087 self.ReadNode()
88
89 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060090 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060091 filename = fdt_util.GetString(self._node, 'filename')
92 if filename:
93 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060094 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070095
Simon Glassffded752019-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 Glass96b6c502019-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 Glass51014aa2019-07-20 12:23:56 -0600128 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600129 dtb.Scan()
130
131 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600132 root = dtb.GetRoot()
Simon Glass858436d2021-11-23 21:09:49 -0700133 image = Image('image', root, copy_to_orig=False, ignore_missing=True,
134 missing_etype=True)
Simon Glass51014aa2019-07-20 12:23:56 -0600135
Simon Glass589d8f92019-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 Glassf667e452019-07-08 14:25:50 -0600139 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600140 image._filename = fname
141 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600142 return image
Simon Glassffded752019-07-08 14:25:46 -0600143
Simon Glassc52c9e72019-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 Glassbf7fd502016-11-25 20:15:51 -0700148 def PackEntries(self):
149 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600150 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600151
Simon Glassdbf6be92018-08-01 15:22:42 -0600152 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600153 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600154 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600155
Simon Glassbf7fd502016-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 Glassa0dcaf22019-07-08 14:25:35 -0600160
161 Returns:
162 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700163 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700164 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700165
Simon Glass19790632017-11-13 18:55:01 -0700166 def WriteSymbols(self):
167 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600168 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600169
Simon Glassbf7fd502016-11-25 20:15:51 -0700170 def BuildImage(self):
171 """Write the image to a file"""
172 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600173 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700174 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600175 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600176 fd.write(data)
177 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600178
179 def WriteMap(self):
Simon Glass163ed6c2018-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 Glass8beb11e2019-07-08 14:25:47 -0600185 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600186 fname = tools.GetOutputFilename(filename)
187 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600188 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
189 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600190 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600191 return fname
Simon Glass41b8ba02019-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 Glass8beb11e2019-07-08 14:25:47 -0600200 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600201 return entries
Simon Glass61f564d2019-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 Glass943bf782021-11-23 21:09:50 -0700227 def ReadData(self, decomp=True, alt_format=None):
Simon Glass2d553c02019-09-25 08:56:21 -0600228 tout.Debug("Image '%s' ReadData(), size=%#x" %
229 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-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 Glass870a9ea2021-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 Glass386c63c2022-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