blob: e94943524131d674d9b90328ef5f4ad645bee247 [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 Glass5af9ebc2021-01-06 21:35:17 -0700139 return super().ProcessContents()
Simon Glassbf7fd502016-11-25 20:15:51 -0700140
Simon Glass19790632017-11-13 18:55:01 -0700141 def WriteSymbols(self):
142 """Write symbol values into binary files for access at run time"""
Simon Glass34861d52020-07-09 18:39:35 -0600143 super().WriteSymbols(self)
Simon Glass8beb11e2019-07-08 14:25:47 -0600144
Simon Glassbf7fd502016-11-25 20:15:51 -0700145 def BuildImage(self):
146 """Write the image to a file"""
147 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600148 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700149 with open(fname, 'wb') as fd:
Simon Glass4a655c92020-10-26 17:40:12 -0600150 data = self.GetPaddedData()
Simon Glass74001072019-07-20 12:23:54 -0600151 fd.write(data)
152 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c3822018-06-01 09:38:20 -0600153
154 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600155 """Write a map of the image to a .map file
156
157 Returns:
158 Filename of map file written
159 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600160 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600161 fname = tools.GetOutputFilename(filename)
162 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600163 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
164 file=fd)
Simon Glass34861d52020-07-09 18:39:35 -0600165 super().WriteMap(fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600166 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600167
168 def BuildEntryList(self):
169 """List the files in an image
170
171 Returns:
172 List of entry.EntryInfo objects describing all entries in the image
173 """
174 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600175 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600176 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600177
178 def FindEntryPath(self, entry_path):
179 """Find an entry at a given path in the image
180
181 Args:
182 entry_path: Path to entry (e.g. /ro-section/u-boot')
183
184 Returns:
185 Entry object corresponding to that past
186
187 Raises:
188 ValueError if no entry found
189 """
190 parts = entry_path.split('/')
191 entries = self.GetEntries()
192 parent = '/'
193 for part in parts:
194 entry = entries.get(part)
195 if not entry:
196 raise ValueError("Entry '%s' not found in '%s'" %
197 (part, parent))
198 parent = entry.GetPath()
199 entries = entry.GetEntries()
200 return entry
201
202 def ReadData(self, decomp=True):
Simon Glass2d553c02019-09-25 08:56:21 -0600203 tout.Debug("Image '%s' ReadData(), size=%#x" %
204 (self.GetPath(), len(self._data)))
Simon Glass61f564d2019-07-08 14:25:48 -0600205 return self._data
206
207 def GetListEntries(self, entry_paths):
208 """List the entries in an image
209
210 This decodes the supplied image and returns a list of entries from that
211 image, preceded by a header.
212
213 Args:
214 entry_paths: List of paths to match (each can have wildcards). Only
215 entries whose names match one of these paths will be printed
216
217 Returns:
218 String error message if something went wrong, otherwise
219 3-Tuple:
220 List of EntryInfo objects
221 List of lines, each
222 List of text columns, each a string
223 List of widths of each column
224 """
225 def _EntryToStrings(entry):
226 """Convert an entry to a list of strings, one for each column
227
228 Args:
229 entry: EntryInfo object containing information to output
230
231 Returns:
232 List of strings, one for each field in entry
233 """
234 def _AppendHex(val):
235 """Append a hex value, or an empty string if val is None
236
237 Args:
238 val: Integer value, or None if none
239 """
240 args.append('' if val is None else '>%x' % val)
241
242 args = [' ' * entry.indent + entry.name]
243 _AppendHex(entry.image_pos)
244 _AppendHex(entry.size)
245 args.append(entry.etype)
246 _AppendHex(entry.offset)
247 _AppendHex(entry.uncomp_size)
248 return args
249
250 def _DoLine(lines, line):
251 """Add a line to the output list
252
253 This adds a line (a list of columns) to the output list. It also updates
254 the widths[] array with the maximum width of each column
255
256 Args:
257 lines: List of lines to add to
258 line: List of strings, one for each column
259 """
260 for i, item in enumerate(line):
261 widths[i] = max(widths[i], len(item))
262 lines.append(line)
263
264 def _NameInPaths(fname, entry_paths):
265 """Check if a filename is in a list of wildcarded paths
266
267 Args:
268 fname: Filename to check
269 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
270 'section/u-boot'])
271
272 Returns:
273 True if any wildcard matches the filename (using Unix filename
274 pattern matching, not regular expressions)
275 False if not
276 """
277 for path in entry_paths:
278 if fnmatch.fnmatch(fname, path):
279 return True
280 return False
281
282 entries = self.BuildEntryList()
283
284 # This is our list of lines. Each item in the list is a list of strings, one
285 # for each column
286 lines = []
287 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
288 'Uncomp-size']
289 num_columns = len(HEADER)
290
291 # This records the width of each column, calculated as the maximum width of
292 # all the strings in that column
293 widths = [0] * num_columns
294 _DoLine(lines, HEADER)
295
296 # We won't print anything unless it has at least this indent. So at the
297 # start we will print nothing, unless a path matches (or there are no
298 # entry paths)
299 MAX_INDENT = 100
300 min_indent = MAX_INDENT
301 path_stack = []
302 path = ''
303 indent = 0
304 selected_entries = []
305 for entry in entries:
306 if entry.indent > indent:
307 path_stack.append(path)
308 elif entry.indent < indent:
309 path_stack.pop()
310 if path_stack:
311 path = path_stack[-1] + '/' + entry.name
312 indent = entry.indent
313
314 # If there are entry paths to match and we are not looking at a
315 # sub-entry of a previously matched entry, we need to check the path
316 if entry_paths and indent <= min_indent:
317 if _NameInPaths(path[1:], entry_paths):
318 # Print this entry and all sub-entries (=higher indent)
319 min_indent = indent
320 else:
321 # Don't print this entry, nor any following entries until we get
322 # a path match
323 min_indent = MAX_INDENT
324 continue
325 _DoLine(lines, _EntryToStrings(entry))
326 selected_entries.append(entry)
327 return selected_entries, lines, widths
Simon Glass870a9ea2021-01-06 21:35:15 -0700328
329 def LookupImageSymbol(self, sym_name, optional, msg, base_addr):
330 """Look up a symbol in an ELF file
331
332 Looks up a symbol in an ELF file. Only entry types which come from an
333 ELF image can be used by this function.
334
335 This searches through this image including all of its subsections.
336
337 At present the only entry properties supported are:
338 offset
339 image_pos - 'base_addr' is added if this is not an end-at-4gb image
340 size
341
342 Args:
343 sym_name: Symbol name in the ELF file to look up in the format
344 _binman_<entry>_prop_<property> where <entry> is the name of
345 the entry and <property> is the property to find (e.g.
346 _binman_u_boot_prop_offset). As a special case, you can append
347 _any to <entry> to have it search for any matching entry. E.g.
348 _binman_u_boot_any_prop_offset will match entries called u-boot,
349 u-boot-img and u-boot-nodtb)
350 optional: True if the symbol is optional. If False this function
351 will raise if the symbol is not found
352 msg: Message to display if an error occurs
353 base_addr: Base address of image. This is added to the returned
354 image_pos in most cases so that the returned position indicates
355 where the targeted entry/binary has actually been loaded. But
356 if end-at-4gb is used, this is not done, since the binary is
357 already assumed to be linked to the ROM position and using
358 execute-in-place (XIP).
359
360 Returns:
361 Value that should be assigned to that symbol, or None if it was
362 optional and not found
363
364 Raises:
365 ValueError if the symbol is invalid or not found, or references a
366 property which is not supported
367 """
368 entries = OrderedDict()
369 entries_by_name = {}
370 self._CollectEntries(entries, entries_by_name, self)
371 return self.LookupSymbol(sym_name, optional, msg, base_addr,
372 entries_by_name)