blob: 3c2fe5ea62025b524691f0a51090c2b6b4937922 [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 Glassbf7fd502016-11-25 20:15:51 -070050 """
Simon Glass939d1062021-01-06 21:35:16 -070051 def __init__(self, name, node, copy_to_orig=True, test=False,
52 ignore_missing=False):
Simon Glass34861d52020-07-09 18:39:35 -060053 super().__init__(None, 'section', node, test=test)
Simon Glass12bb1a92019-07-20 12:23:51 -060054 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060055 self.name = 'main-section'
56 self.image_name = name
57 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060058 self.fdtmap_dtb = None
59 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060060 self.allow_repack = False
Simon Glass939d1062021-01-06 21:35:16 -070061 self._ignore_missing = ignore_missing
Simon Glass8beb11e2019-07-08 14:25:47 -060062 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060063 self.ReadNode()
64
65 def ReadNode(self):
Simon Glass34861d52020-07-09 18:39:35 -060066 super().ReadNode()
Simon Glassc6bd6e22019-07-20 12:23:45 -060067 filename = fdt_util.GetString(self._node, 'filename')
68 if filename:
69 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060070 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070071
Simon Glassffded752019-07-08 14:25:46 -060072 @classmethod
73 def FromFile(cls, fname):
74 """Convert an image file into an Image for use in binman
75
76 Args:
77 fname: Filename of image file to read
78
79 Returns:
80 Image object on success
81
82 Raises:
83 ValueError if something goes wrong
84 """
85 data = tools.ReadFile(fname)
86 size = len(data)
87
88 # First look for an image header
89 pos = image_header.LocateHeaderOffset(data)
90 if pos is None:
91 # Look for the FDT map
92 pos = fdtmap.LocateFdtmap(data)
93 if pos is None:
94 raise ValueError('Cannot find FDT map in image')
95
96 # We don't know the FDT size, so check its header first
97 probe_dtb = fdt.Fdt.FromData(
98 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
99 dtb_size = probe_dtb.GetFdtObj().totalsize()
100 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -0600101 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
102 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
103 tools.WriteFile(out_fname, fdt_data)
Simon Glass51014aa2019-07-20 12:23:56 -0600104 dtb = fdt.Fdt(out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600105 dtb.Scan()
106
107 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600108 root = dtb.GetRoot()
Simon Glass939d1062021-01-06 21:35:16 -0700109 image = Image('image', root, copy_to_orig=False, ignore_missing=True)
Simon Glass51014aa2019-07-20 12:23:56 -0600110
Simon Glass589d8f92019-07-20 12:23:40 -0600111 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
112 image.fdtmap_dtb = dtb
113 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600114 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600115 image._filename = fname
116 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600117 return image
Simon Glassffded752019-07-08 14:25:46 -0600118
Simon Glassc52c9e72019-07-08 14:25:37 -0600119 def Raise(self, msg):
120 """Convenience function to raise an error referencing an image"""
121 raise ValueError("Image '%s': %s" % (self._node.path, msg))
122
Simon Glassbf7fd502016-11-25 20:15:51 -0700123 def PackEntries(self):
124 """Pack all entries into the image"""
Simon Glass34861d52020-07-09 18:39:35 -0600125 super().Pack(0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600126
Simon Glassdbf6be92018-08-01 15:22:42 -0600127 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600128 # This first section in the image so it starts at 0
Simon Glass34861d52020-07-09 18:39:35 -0600129 super().SetImagePos(0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600130
Simon Glassbf7fd502016-11-25 20:15:51 -0700131 def ProcessEntryContents(self):
132 """Call the ProcessContents() method for each entry
133
134 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600135
136 Returns:
137 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700138 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600139 sizes_ok = True
140 for entry in self._entries.values():
141 if not entry.ProcessContents():
142 sizes_ok = False
Simon Glasseea264e2019-07-08 14:25:49 -0600143 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass8beb11e2019-07-08 14:25:47 -0600144 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700145
Simon Glass19790632017-11-13 18:55:01 -0700146 def WriteSymbols(self):
147 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600148 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600149
Simon Glassbf7fd502016-11-25 20:15:51 -0700150 def BuildImage(self):
151 """Write the image to a file"""
152 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600153 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700154 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600155 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600156 fd.write(data)
157 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600158
159 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600160 """Write a map of the image to a .map file
161
162 Returns:
163 Filename of map file written
164 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600165 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600166 fname = tools.GetOutputFilename(filename)
167 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600168 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
169 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600170 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600171 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600172
173 def BuildEntryList(self):
174 """List the files in an image
175
176 Returns:
177 List of entry.EntryInfo objects describing all entries in the image
178 """
179 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600180 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600181 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600182
183 def FindEntryPath(self, entry_path):
184 """Find an entry at a given path in the image
185
186 Args:
187 entry_path: Path to entry (e.g. /ro-section/u-boot')
188
189 Returns:
190 Entry object corresponding to that past
191
192 Raises:
193 ValueError if no entry found
194 """
195 parts = entry_path.split('/')
196 entries = self.GetEntries()
197 parent = '/'
198 for part in parts:
199 entry = entries.get(part)
200 if not entry:
201 raise ValueError("Entry '%s' not found in '%s'" %
202 (part, parent))
203 parent = entry.GetPath()
204 entries = entry.GetEntries()
205 return entry
206
207 def ReadData(self, decomp=True):
Simon Glass2d553c02019-09-25 08:56:21 -0600208 tout.Debug("Image '%s' ReadData(), size=%#x" %
209 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600210 return self._data
211
212 def GetListEntries(self, entry_paths):
213 """List the entries in an image
214
215 This decodes the supplied image and returns a list of entries from that
216 image, preceded by a header.
217
218 Args:
219 entry_paths: List of paths to match (each can have wildcards). Only
220 entries whose names match one of these paths will be printed
221
222 Returns:
223 String error message if something went wrong, otherwise
224 3-Tuple:
225 List of EntryInfo objects
226 List of lines, each
227 List of text columns, each a string
228 List of widths of each column
229 """
230 def _EntryToStrings(entry):
231 """Convert an entry to a list of strings, one for each column
232
233 Args:
234 entry: EntryInfo object containing information to output
235
236 Returns:
237 List of strings, one for each field in entry
238 """
239 def _AppendHex(val):
240 """Append a hex value, or an empty string if val is None
241
242 Args:
243 val: Integer value, or None if none
244 """
245 args.append('' if val is None else '>%x' % val)
246
247 args = [' ' * entry.indent + entry.name]
248 _AppendHex(entry.image_pos)
249 _AppendHex(entry.size)
250 args.append(entry.etype)
251 _AppendHex(entry.offset)
252 _AppendHex(entry.uncomp_size)
253 return args
254
255 def _DoLine(lines, line):
256 """Add a line to the output list
257
258 This adds a line (a list of columns) to the output list. It also updates
259 the widths[] array with the maximum width of each column
260
261 Args:
262 lines: List of lines to add to
263 line: List of strings, one for each column
264 """
265 for i, item in enumerate(line):
266 widths[i] = max(widths[i], len(item))
267 lines.append(line)
268
269 def _NameInPaths(fname, entry_paths):
270 """Check if a filename is in a list of wildcarded paths
271
272 Args:
273 fname: Filename to check
274 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
275 'section/u-boot'])
276
277 Returns:
278 True if any wildcard matches the filename (using Unix filename
279 pattern matching, not regular expressions)
280 False if not
281 """
282 for path in entry_paths:
283 if fnmatch.fnmatch(fname, path):
284 return True
285 return False
286
287 entries = self.BuildEntryList()
288
289 # This is our list of lines. Each item in the list is a list of strings, one
290 # for each column
291 lines = []
292 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
293 'Uncomp-size']
294 num_columns = len(HEADER)
295
296 # This records the width of each column, calculated as the maximum width of
297 # all the strings in that column
298 widths = [0] * num_columns
299 _DoLine(lines, HEADER)
300
301 # We won't print anything unless it has at least this indent. So at the
302 # start we will print nothing, unless a path matches (or there are no
303 # entry paths)
304 MAX_INDENT = 100
305 min_indent = MAX_INDENT
306 path_stack = []
307 path = ''
308 indent = 0
309 selected_entries = []
310 for entry in entries:
311 if entry.indent > indent:
312 path_stack.append(path)
313 elif entry.indent < indent:
314 path_stack.pop()
315 if path_stack:
316 path = path_stack[-1] + '/' + entry.name
317 indent = entry.indent
318
319 # If there are entry paths to match and we are not looking at a
320 # sub-entry of a previously matched entry, we need to check the path
321 if entry_paths and indent <= min_indent:
322 if _NameInPaths(path[1:], entry_paths):
323 # Print this entry and all sub-entries (=higher indent)
324 min_indent = indent
325 else:
326 # Don't print this entry, nor any following entries until we get
327 # a path match
328 min_indent = MAX_INDENT
329 continue
330 _DoLine(lines, _EntryToStrings(entry))
331 selected_entries.append(entry)
332 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700333
334 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
335 """Look up a symbol in an ELF file
336
337 Looks up a symbol in an ELF file. Only entry types which come from an
338 ELF image can be used by this function.
339
340 This searches through this image including all of its subsections.
341
342 At present the only entry properties supported are:
343 offset
344 image_pos - 'base_addr' is added if this is not an end-at-4gb image
345 size
346
347 Args:
348 sym_name: Symbol name in the ELF file to look up in the format
349 _binman_<entry>_prop_<property> where <entry> is the name of
350 the entry and <property> is the property to find (e.g.
351 _binman_u_boot_prop_offset). As a special case, you can append
352 _any to <entry> to have it search for any matching entry. E.g.
353 _binman_u_boot_any_prop_offset will match entries called u-boot,
354 u-boot-img and u-boot-nodtb)
355 optional: True if the symbol is optional. If False this function
356 will raise if the symbol is not found
357 msg: Message to display if an error occurs
358 base_addr: Base address of image. This is added to the returned
359 image_pos in most cases so that the returned position indicates
360 where the targeted entry/binary has actually been loaded. But
361 if end-at-4gb is used, this is not done, since the binary is
362 already assumed to be linked to the ROM position and using
363 execute-in-place (XIP).
364
365 Returns:
366 Value that should be assigned to that symbol, or None if it was
367 optional and not found
368
369 Raises:
370 ValueError if the symbol is invalid or not found, or references a
371 property which is not supported
372 """
373 entries = OrderedDict()
374 entries_by_name = {}
375 self._CollectEntries(entries, entries_by_name, self)
376 return self.LookupSymbol(sym_name, optional, msg, base_addr,
377 entries_by_name)