blob: cdc58b39a402f4d7cb5f130c432bed08570b1731 [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 Glassbf7fd502016-11-25 20:15:51 -070066 """
Simon Glass939d1062021-01-06 21:35:16 -070067 def __init__(self, name, node, copy_to_orig=True, test=False,
Simon Glass0b6023e2021-03-18 20:25:06 +130068 ignore_missing=False, use_expanded=False):
Simon Glass34861d52020-07-09 18:39:35 -060069 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060070 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060071 self.name = 'main-section'
72 self.image_name = name
73 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060074 self.fdtmap_dtb = None
75 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060076 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070077 self._ignore_missing = ignore_missing
Simon Glass0b6023e2021-03-18 20:25:06 +130078 self.use_expanded = use_expanded
Simon Glassc69d19c2021-07-06 10:36:37 -060079 self.test_section_timeout = False
Simon Glass8beb11e2019-07-08 14:25:47 -060080 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060081 self.ReadNode()
82
83 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060084 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060085 filename = fdt_util.GetString(self._node, 'filename')
86 if filename:
87 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060088 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070089
Simon Glassffded752019-07-08 14:25:46 -060090 @classmethod
91 def FromFile(cls, fname):
92 """Convert an image file into an Image for use in binman
93
94 Args:
95 fname: Filename of image file to read
96
97 Returns:
98 Image object on success
99
100 Raises:
101 ValueError if something goes wrong
102 """
103 data = tools.ReadFile(fname)
104 size = len(data)
105
106 # First look for an image header
107 pos = image_header.LocateHeaderOffset(data)
108 if pos is None:
109 # Look for the FDT map
110 pos = fdtmap.LocateFdtmap(data)
111 if pos is None:
112 raise ValueError('Cannot find FDT map in image')
113
114 # We don't know the FDT size, so check its header first
115 probe_dtb = fdt.Fdt.FromData(
116 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
117 dtb_size = probe_dtb.GetFdtObj().totalsize()
118 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600119 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
120 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
121 tools.WriteFile(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600122 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600123 dtb.Scan()
124
125 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600126 root = dtb.GetRoot()
Simon Glass939d1062021-01-06 21:35:16 -0700127 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
Simon Glass51014aa2019-07-20 12:23:56 -0600128
Simon Glass589d8f92019-07-20 12:23:40 -0600129 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
130 image.fdtmap_dtb = dtb
131 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600132 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600133 image._filename = fname
134 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600135 return image
Simon Glassffded752019-07-08 14:25:46 -0600136
Simon Glassc52c9e72019-07-08 14:25:37 -0600137 def Raise(self, msg):
138 """Convenience function to raise an error referencing an image"""
139 raise ValueError("Image '%s': %s" % (self._node.path, msg))
140
Simon Glassbf7fd502016-11-25 20:15:51 -0700141 def PackEntries(self):
142 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600143 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600144
Simon Glassdbf6be92018-08-01 15:22:42 -0600145 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600146 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600147 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600148
Simon Glassbf7fd502016-11-25 20:15:51 -0700149 def ProcessEntryContents(self):
150 """Call the ProcessContents() method for each entry
151
152 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600153
154 Returns:
155 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700156 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700157 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700158
Simon Glass19790632017-11-13 18:55:01 -0700159 def WriteSymbols(self):
160 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600161 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600162
Simon Glassbf7fd502016-11-25 20:15:51 -0700163 def BuildImage(self):
164 """Write the image to a file"""
165 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600166 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700167 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600168 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600169 fd.write(data)
170 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600171
172 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600173 """Write a map of the image to a .map file
174
175 Returns:
176 Filename of map file written
177 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600178 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600179 fname = tools.GetOutputFilename(filename)
180 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600181 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
182 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600183 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600184 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600185
186 def BuildEntryList(self):
187 """List the files in an image
188
189 Returns:
190 List of entry.EntryInfo objects describing all entries in the image
191 """
192 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600193 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600194 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600195
196 def FindEntryPath(self, entry_path):
197 """Find an entry at a given path in the image
198
199 Args:
200 entry_path: Path to entry (e.g. /ro-section/u-boot')
201
202 Returns:
203 Entry object corresponding to that past
204
205 Raises:
206 ValueError if no entry found
207 """
208 parts = entry_path.split('/')
209 entries = self.GetEntries()
210 parent = '/'
211 for part in parts:
212 entry = entries.get(part)
213 if not entry:
214 raise ValueError("Entry '%s' not found in '%s'" %
215 (part, parent))
216 parent = entry.GetPath()
217 entries = entry.GetEntries()
218 return entry
219
220 def ReadData(self, decomp=True):
Simon Glass2d553c02019-09-25 08:56:21 -0600221 tout.Debug("Image '%s' ReadData(), size=%#x" %
222 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600223 return self._data
224
225 def GetListEntries(self, entry_paths):
226 """List the entries in an image
227
228 This decodes the supplied image and returns a list of entries from that
229 image, preceded by a header.
230
231 Args:
232 entry_paths: List of paths to match (each can have wildcards). Only
233 entries whose names match one of these paths will be printed
234
235 Returns:
236 String error message if something went wrong, otherwise
237 3-Tuple:
238 List of EntryInfo objects
239 List of lines, each
240 List of text columns, each a string
241 List of widths of each column
242 """
243 def _EntryToStrings(entry):
244 """Convert an entry to a list of strings, one for each column
245
246 Args:
247 entry: EntryInfo object containing information to output
248
249 Returns:
250 List of strings, one for each field in entry
251 """
252 def _AppendHex(val):
253 """Append a hex value, or an empty string if val is None
254
255 Args:
256 val: Integer value, or None if none
257 """
258 args.append('' if val is None else '>%x' % val)
259
260 args = [' ' * entry.indent + entry.name]
261 _AppendHex(entry.image_pos)
262 _AppendHex(entry.size)
263 args.append(entry.etype)
264 _AppendHex(entry.offset)
265 _AppendHex(entry.uncomp_size)
266 return args
267
268 def _DoLine(lines, line):
269 """Add a line to the output list
270
271 This adds a line (a list of columns) to the output list. It also updates
272 the widths[] array with the maximum width of each column
273
274 Args:
275 lines: List of lines to add to
276 line: List of strings, one for each column
277 """
278 for i, item in enumerate(line):
279 widths[i] = max(widths[i], len(item))
280 lines.append(line)
281
282 def _NameInPaths(fname, entry_paths):
283 """Check if a filename is in a list of wildcarded paths
284
285 Args:
286 fname: Filename to check
287 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
288 'section/u-boot'])
289
290 Returns:
291 True if any wildcard matches the filename (using Unix filename
292 pattern matching, not regular expressions)
293 False if not
294 """
295 for path in entry_paths:
296 if fnmatch.fnmatch(fname, path):
297 return True
298 return False
299
300 entries = self.BuildEntryList()
301
302 # This is our list of lines. Each item in the list is a list of strings, one
303 # for each column
304 lines = []
305 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
306 'Uncomp-size']
307 num_columns = len(HEADER)
308
309 # This records the width of each column, calculated as the maximum width of
310 # all the strings in that column
311 widths = [0] * num_columns
312 _DoLine(lines, HEADER)
313
314 # We won't print anything unless it has at least this indent. So at the
315 # start we will print nothing, unless a path matches (or there are no
316 # entry paths)
317 MAX_INDENT = 100
318 min_indent = MAX_INDENT
319 path_stack = []
320 path = ''
321 indent = 0
322 selected_entries = []
323 for entry in entries:
324 if entry.indent > indent:
325 path_stack.append(path)
326 elif entry.indent < indent:
327 path_stack.pop()
328 if path_stack:
329 path = path_stack[-1] + '/' + entry.name
330 indent = entry.indent
331
332 # If there are entry paths to match and we are not looking at a
333 # sub-entry of a previously matched entry, we need to check the path
334 if entry_paths and indent <= min_indent:
335 if _NameInPaths(path[1:], entry_paths):
336 # Print this entry and all sub-entries (=higher indent)
337 min_indent = indent
338 else:
339 # Don't print this entry, nor any following entries until we get
340 # a path match
341 min_indent = MAX_INDENT
342 continue
343 _DoLine(lines, _EntryToStrings(entry))
344 selected_entries.append(entry)
345 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700346
347 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
348 """Look up a symbol in an ELF file
349
350 Looks up a symbol in an ELF file. Only entry types which come from an
351 ELF image can be used by this function.
352
353 This searches through this image including all of its subsections.
354
355 At present the only entry properties supported are:
356 offset
357 image_pos - 'base_addr' is added if this is not an end-at-4gb image
358 size
359
360 Args:
361 sym_name: Symbol name in the ELF file to look up in the format
362 _binman_<entry>_prop_<property> where <entry> is the name of
363 the entry and <property> is the property to find (e.g.
364 _binman_u_boot_prop_offset). As a special case, you can append
365 _any to <entry> to have it search for any matching entry. E.g.
366 _binman_u_boot_any_prop_offset will match entries called u-boot,
367 u-boot-img and u-boot-nodtb)
368 optional: True if the symbol is optional. If False this function
369 will raise if the symbol is not found
370 msg: Message to display if an error occurs
371 base_addr: Base address of image. This is added to the returned
372 image_pos in most cases so that the returned position indicates
373 where the targeted entry/binary has actually been loaded. But
374 if end-at-4gb is used, this is not done, since the binary is
375 already assumed to be linked to the ROM position and using
376 execute-in-place (XIP).
377
378 Returns:
379 Value that should be assigned to that symbol, or None if it was
380 optional and not found
381
382 Raises:
383 ValueError if the symbol is invalid or not found, or references a
384 property which is not supported
385 """
386 entries = OrderedDict()
387 entries_by_name = {}
388 self._CollectEntries(entries, entries_by_name, self)
389 return self.LookupSymbol(sym_name, optional, msg, base_addr,
390 entries_by_name)