blob: 10778f47fe92250384f43e1c77a87d26b4135707 [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 Glass7ae5f312018-06-01 09:38:19 -060039
40 Args:
Simon Glass12bb1a92019-07-20 12:23:51 -060041 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
42 from the device tree
Simon Glass7ae5f312018-06-01 09:38:19 -060043 test: True if this is being called from a test of Images. This this case
44 there is no device tree defining the structure of the section, so
45 we create a section manually.
Simon Glass939d1062021-01-06 21:35:16 -070046 ignore_missing: Ignore any missing entry arguments (i.e. don't raise an
47 exception). This should be used if the Image is being loaded from
48 a file rather than generated. In that case we obviously don't need
49 the entry arguments since the contents already exists.
Simon Glass0b6023e2021-03-18 20:25:06 +130050 use_expanded: True if we are updating the FDT wth entry offsets, etc.
51 and should use the expanded versions of the U-Boot entries.
52 Any entry type that includes a devicetree must put it in a
53 separate entry so that it will be updated. For example. 'u-boot'
54 normally just picks up 'u-boot.bin' which includes the
55 devicetree, but this is not updateable, since it comes into
56 binman as one piece and binman doesn't know that it is actually
57 an executable followed by a devicetree. Of course it could be
58 taught this, but then when reading an image (e.g. 'binman ls')
59 it may need to be able to split the devicetree out of the image
60 in order to determine the location of things. Instead we choose
61 to ignore 'u-boot-bin' in this case, and build it ourselves in
62 binman with 'u-boot-dtb.bin' and 'u-boot.dtb'. See
63 Entry_u_boot_expanded and Entry_blob_phase for details.
Simon Glassbf7fd502016-11-25 20:15:51 -070064 """
Simon Glass939d1062021-01-06 21:35:16 -070065 def __init__(self, name, node, copy_to_orig=True, test=False,
Simon Glass0b6023e2021-03-18 20:25:06 +130066 ignore_missing=False, use_expanded=False):
Simon Glass34861d52020-07-09 18:39:35 -060067 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060068 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060069 self.name = 'main-section'
70 self.image_name = name
71 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060072 self.fdtmap_dtb = None
73 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060074 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070075 self._ignore_missing = ignore_missing
Simon Glass0b6023e2021-03-18 20:25:06 +130076 self.use_expanded = use_expanded
Simon Glass8beb11e2019-07-08 14:25:47 -060077 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060078 self.ReadNode()
79
80 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060081 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060082 filename = fdt_util.GetString(self._node, 'filename')
83 if filename:
84 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060085 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070086
Simon Glassffded752019-07-08 14:25:46 -060087 @classmethod
88 def FromFile(cls, fname):
89 """Convert an image file into an Image for use in binman
90
91 Args:
92 fname: Filename of image file to read
93
94 Returns:
95 Image object on success
96
97 Raises:
98 ValueError if something goes wrong
99 """
100 data = tools.ReadFile(fname)
101 size = len(data)
102
103 # First look for an image header
104 pos = image_header.LocateHeaderOffset(data)
105 if pos is None:
106 # Look for the FDT map
107 pos = fdtmap.LocateFdtmap(data)
108 if pos is None:
109 raise ValueError('Cannot find FDT map in image')
110
111 # We don't know the FDT size, so check its header first
112 probe_dtb = fdt.Fdt.FromData(
113 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
114 dtb_size = probe_dtb.GetFdtObj().totalsize()
115 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600116 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
117 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
118 tools.WriteFile(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600119 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600120 dtb.Scan()
121
122 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600123 root = dtb.GetRoot()
Simon Glass939d1062021-01-06 21:35:16 -0700124 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
Simon Glass51014aa2019-07-20 12:23:56 -0600125
Simon Glass589d8f92019-07-20 12:23:40 -0600126 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
127 image.fdtmap_dtb = dtb
128 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600129 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600130 image._filename = fname
131 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600132 return image
Simon Glassffded752019-07-08 14:25:46 -0600133
Simon Glassc52c9e72019-07-08 14:25:37 -0600134 def Raise(self, msg):
135 """Convenience function to raise an error referencing an image"""
136 raise ValueError("Image '%s': %s" % (self._node.path, msg))
137
Simon Glassbf7fd502016-11-25 20:15:51 -0700138 def PackEntries(self):
139 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600140 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600141
Simon Glassdbf6be92018-08-01 15:22:42 -0600142 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600143 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600144 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600145
Simon Glassbf7fd502016-11-25 20:15:51 -0700146 def ProcessEntryContents(self):
147 """Call the ProcessContents() method for each entry
148
149 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600150
151 Returns:
152 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700153 """
Simon Glass5af9ebc2021-01-06 21:35:17 -0700154 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700155
Simon Glass19790632017-11-13 18:55:01 -0700156 def WriteSymbols(self):
157 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600158 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600159
Simon Glassbf7fd502016-11-25 20:15:51 -0700160 def BuildImage(self):
161 """Write the image to a file"""
162 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600163 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700164 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600165 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600166 fd.write(data)
167 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600168
169 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600170 """Write a map of the image to a .map file
171
172 Returns:
173 Filename of map file written
174 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600175 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600176 fname = tools.GetOutputFilename(filename)
177 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600178 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
179 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600180 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600181 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600182
183 def BuildEntryList(self):
184 """List the files in an image
185
186 Returns:
187 List of entry.EntryInfo objects describing all entries in the image
188 """
189 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600190 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600191 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600192
193 def FindEntryPath(self, entry_path):
194 """Find an entry at a given path in the image
195
196 Args:
197 entry_path: Path to entry (e.g. /ro-section/u-boot')
198
199 Returns:
200 Entry object corresponding to that past
201
202 Raises:
203 ValueError if no entry found
204 """
205 parts = entry_path.split('/')
206 entries = self.GetEntries()
207 parent = '/'
208 for part in parts:
209 entry = entries.get(part)
210 if not entry:
211 raise ValueError("Entry '%s' not found in '%s'" %
212 (part, parent))
213 parent = entry.GetPath()
214 entries = entry.GetEntries()
215 return entry
216
217 def ReadData(self, decomp=True):
Simon Glass2d553c02019-09-25 08:56:21 -0600218 tout.Debug("Image '%s' ReadData(), size=%#x" %
219 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600220 return self._data
221
222 def GetListEntries(self, entry_paths):
223 """List the entries in an image
224
225 This decodes the supplied image and returns a list of entries from that
226 image, preceded by a header.
227
228 Args:
229 entry_paths: List of paths to match (each can have wildcards). Only
230 entries whose names match one of these paths will be printed
231
232 Returns:
233 String error message if something went wrong, otherwise
234 3-Tuple:
235 List of EntryInfo objects
236 List of lines, each
237 List of text columns, each a string
238 List of widths of each column
239 """
240 def _EntryToStrings(entry):
241 """Convert an entry to a list of strings, one for each column
242
243 Args:
244 entry: EntryInfo object containing information to output
245
246 Returns:
247 List of strings, one for each field in entry
248 """
249 def _AppendHex(val):
250 """Append a hex value, or an empty string if val is None
251
252 Args:
253 val: Integer value, or None if none
254 """
255 args.append('' if val is None else '>%x' % val)
256
257 args = [' ' * entry.indent + entry.name]
258 _AppendHex(entry.image_pos)
259 _AppendHex(entry.size)
260 args.append(entry.etype)
261 _AppendHex(entry.offset)
262 _AppendHex(entry.uncomp_size)
263 return args
264
265 def _DoLine(lines, line):
266 """Add a line to the output list
267
268 This adds a line (a list of columns) to the output list. It also updates
269 the widths[] array with the maximum width of each column
270
271 Args:
272 lines: List of lines to add to
273 line: List of strings, one for each column
274 """
275 for i, item in enumerate(line):
276 widths[i] = max(widths[i], len(item))
277 lines.append(line)
278
279 def _NameInPaths(fname, entry_paths):
280 """Check if a filename is in a list of wildcarded paths
281
282 Args:
283 fname: Filename to check
284 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
285 'section/u-boot'])
286
287 Returns:
288 True if any wildcard matches the filename (using Unix filename
289 pattern matching, not regular expressions)
290 False if not
291 """
292 for path in entry_paths:
293 if fnmatch.fnmatch(fname, path):
294 return True
295 return False
296
297 entries = self.BuildEntryList()
298
299 # This is our list of lines. Each item in the list is a list of strings, one
300 # for each column
301 lines = []
302 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
303 'Uncomp-size']
304 num_columns = len(HEADER)
305
306 # This records the width of each column, calculated as the maximum width of
307 # all the strings in that column
308 widths = [0] * num_columns
309 _DoLine(lines, HEADER)
310
311 # We won't print anything unless it has at least this indent. So at the
312 # start we will print nothing, unless a path matches (or there are no
313 # entry paths)
314 MAX_INDENT = 100
315 min_indent = MAX_INDENT
316 path_stack = []
317 path = ''
318 indent = 0
319 selected_entries = []
320 for entry in entries:
321 if entry.indent > indent:
322 path_stack.append(path)
323 elif entry.indent < indent:
324 path_stack.pop()
325 if path_stack:
326 path = path_stack[-1] + '/' + entry.name
327 indent = entry.indent
328
329 # If there are entry paths to match and we are not looking at a
330 # sub-entry of a previously matched entry, we need to check the path
331 if entry_paths and indent <= min_indent:
332 if _NameInPaths(path[1:], entry_paths):
333 # Print this entry and all sub-entries (=higher indent)
334 min_indent = indent
335 else:
336 # Don't print this entry, nor any following entries until we get
337 # a path match
338 min_indent = MAX_INDENT
339 continue
340 _DoLine(lines, _EntryToStrings(entry))
341 selected_entries.append(entry)
342 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700343
344 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
345 """Look up a symbol in an ELF file
346
347 Looks up a symbol in an ELF file. Only entry types which come from an
348 ELF image can be used by this function.
349
350 This searches through this image including all of its subsections.
351
352 At present the only entry properties supported are:
353 offset
354 image_pos - 'base_addr' is added if this is not an end-at-4gb image
355 size
356
357 Args:
358 sym_name: Symbol name in the ELF file to look up in the format
359 _binman_<entry>_prop_<property> where <entry> is the name of
360 the entry and <property> is the property to find (e.g.
361 _binman_u_boot_prop_offset). As a special case, you can append
362 _any to <entry> to have it search for any matching entry. E.g.
363 _binman_u_boot_any_prop_offset will match entries called u-boot,
364 u-boot-img and u-boot-nodtb)
365 optional: True if the symbol is optional. If False this function
366 will raise if the symbol is not found
367 msg: Message to display if an error occurs
368 base_addr: Base address of image. This is added to the returned
369 image_pos in most cases so that the returned position indicates
370 where the targeted entry/binary has actually been loaded. But
371 if end-at-4gb is used, this is not done, since the binary is
372 already assumed to be linked to the ROM position and using
373 execute-in-place (XIP).
374
375 Returns:
376 Value that should be assigned to that symbol, or None if it was
377 optional and not found
378
379 Raises:
380 ValueError if the symbol is invalid or not found, or references a
381 property which is not supported
382 """
383 entries = OrderedDict()
384 entries_by_name = {}
385 self._CollectEntries(entries, entries_by_name, self)
386 return self.LookupSymbol(sym_name, optional, msg, base_addr,
387 entries_by_name)