blob: 2beab7fd4d2d50ebdf9b2627005a1944cea78631 [file] [log] [blame]
Tom Rini10e47792018-05-06 17:58:06 -04001# SPDX-License-Identifier: GPL-2.0+
Simon Glass2574ef62016-11-25 20:15:51 -07002# Copyright (c) 2016 Google, Inc
3# Written by Simon Glass <sjg@chromium.org>
4#
Simon Glass2574ef62016-11-25 20:15:51 -07005# Class for an image, the output of binman
6#
7
Simon Glass4ca8e042017-11-13 18:55:01 -07008from __future__ import print_function
9
Simon Glass2574ef62016-11-25 20:15:51 -070010from collections import OrderedDict
Simon Glassb2fd11d2019-07-08 14:25:48 -060011import fnmatch
Simon Glass2574ef62016-11-25 20:15:51 -070012from operator import attrgetter
Simon Glass072959a2019-07-20 12:23:50 -060013import os
Simon Glass4ca8e042017-11-13 18:55:01 -070014import re
15import sys
Simon Glass2574ef62016-11-25 20:15:51 -070016
Simon Glass39dd2152019-07-08 14:25:47 -060017from entry import Entry
Simon Glassb8424fa2019-07-08 14:25:46 -060018from etype import fdtmap
19from etype import image_header
Simon Glass39dd2152019-07-08 14:25:47 -060020from etype import section
Simon Glassb8424fa2019-07-08 14:25:46 -060021import fdt
Simon Glass2574ef62016-11-25 20:15:51 -070022import fdt_util
23import tools
Simon Glass233a26a92019-07-08 14:25:49 -060024import tout
Simon Glass2574ef62016-11-25 20:15:51 -070025
Simon Glass39dd2152019-07-08 14:25:47 -060026class Image(section.Entry_section):
Simon Glass2574ef62016-11-25 20:15:51 -070027 """A Image, representing an output from binman
28
29 An image is comprised of a collection of entries each containing binary
30 data. The image size must be large enough to hold all of this data.
31
32 This class implements the various operations needed for images.
33
Simon Glass39dd2152019-07-08 14:25:47 -060034 Attributes:
35 filename: Output filename for image
Simon Glass76546572019-07-20 12:23:40 -060036 image_node: Name of node containing the description for this image
37 fdtmap_dtb: Fdt object for the fdtmap when loading from a file
38 fdtmap_data: Contents of the fdtmap when loading from a file
Simon Glassfb30e292019-07-20 12:23:51 -060039 allow_repack: True to add properties to allow the image to be safely
40 repacked later
Simon Glass1e324002018-06-01 09:38:19 -060041
42 Args:
Simon Glassfb30e292019-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 Glass1e324002018-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 Glass2574ef62016-11-25 20:15:51 -070048 """
Simon Glassfb30e292019-07-20 12:23:51 -060049 def __init__(self, name, node, copy_to_orig=True, test=False):
50 section.Entry_section.__init__(self, None, 'section', node, test=test)
51 self.copy_to_orig = copy_to_orig
Simon Glass39dd2152019-07-08 14:25:47 -060052 self.name = 'main-section'
53 self.image_name = name
54 self._filename = '%s.bin' % self.image_name
Simon Glass76546572019-07-20 12:23:40 -060055 self.fdtmap_dtb = None
56 self.fdtmap_data = None
Simon Glassfb30e292019-07-20 12:23:51 -060057 self.allow_repack = False
Simon Glass39dd2152019-07-08 14:25:47 -060058 if not test:
Simon Glass2c360cf2019-07-20 12:23:45 -060059 self.ReadNode()
60
61 def ReadNode(self):
62 section.Entry_section.ReadNode(self)
63 filename = fdt_util.GetString(self._node, 'filename')
64 if filename:
65 self._filename = filename
Simon Glassfb30e292019-07-20 12:23:51 -060066 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glass2574ef62016-11-25 20:15:51 -070067
Simon Glassb8424fa2019-07-08 14:25:46 -060068 @classmethod
69 def FromFile(cls, fname):
70 """Convert an image file into an Image for use in binman
71
72 Args:
73 fname: Filename of image file to read
74
75 Returns:
76 Image object on success
77
78 Raises:
79 ValueError if something goes wrong
80 """
81 data = tools.ReadFile(fname)
82 size = len(data)
83
84 # First look for an image header
85 pos = image_header.LocateHeaderOffset(data)
86 if pos is None:
87 # Look for the FDT map
88 pos = fdtmap.LocateFdtmap(data)
89 if pos is None:
90 raise ValueError('Cannot find FDT map in image')
91
92 # We don't know the FDT size, so check its header first
93 probe_dtb = fdt.Fdt.FromData(
94 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
95 dtb_size = probe_dtb.GetFdtObj().totalsize()
96 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass9b7f5002019-07-20 12:23:53 -060097 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
98 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
99 tools.WriteFile(out_fname, fdt_data)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600100 dtb = fdt.Fdt(out_fname)
Simon Glassb8424fa2019-07-08 14:25:46 -0600101 dtb.Scan()
102
103 # Return an Image with the associated nodes
Simon Glass76546572019-07-20 12:23:40 -0600104 root = dtb.GetRoot()
Simon Glassfb30e292019-07-20 12:23:51 -0600105 image = Image('image', root, copy_to_orig=False)
Simon Glassf8a54bc2019-07-20 12:23:56 -0600106
Simon Glass76546572019-07-20 12:23:40 -0600107 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
108 image.fdtmap_dtb = dtb
109 image.fdtmap_data = fdtmap_data
Simon Glass4c613bf2019-07-08 14:25:50 -0600110 image._data = data
Simon Glass072959a2019-07-20 12:23:50 -0600111 image._filename = fname
112 image.image_name, _ = os.path.splitext(fname)
Simon Glass4c613bf2019-07-08 14:25:50 -0600113 return image
Simon Glassb8424fa2019-07-08 14:25:46 -0600114
Simon Glasse61b6f62019-07-08 14:25:37 -0600115 def Raise(self, msg):
116 """Convenience function to raise an error referencing an image"""
117 raise ValueError("Image '%s': %s" % (self._node.path, msg))
118
Simon Glass2574ef62016-11-25 20:15:51 -0700119 def PackEntries(self):
120 """Pack all entries into the image"""
Simon Glass39dd2152019-07-08 14:25:47 -0600121 section.Entry_section.Pack(self, 0)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600122
Simon Glass9dcc8612018-08-01 15:22:42 -0600123 def SetImagePos(self):
Simon Glass39dd2152019-07-08 14:25:47 -0600124 # This first section in the image so it starts at 0
125 section.Entry_section.SetImagePos(self, 0)
Simon Glass9dcc8612018-08-01 15:22:42 -0600126
Simon Glass2574ef62016-11-25 20:15:51 -0700127 def ProcessEntryContents(self):
128 """Call the ProcessContents() method for each entry
129
130 This is intended to adjust the contents as needed by the entry type.
Simon Glassec849852019-07-08 14:25:35 -0600131
132 Returns:
133 True if the new data size is OK, False if expansion is needed
Simon Glass2574ef62016-11-25 20:15:51 -0700134 """
Simon Glass39dd2152019-07-08 14:25:47 -0600135 sizes_ok = True
136 for entry in self._entries.values():
137 if not entry.ProcessContents():
138 sizes_ok = False
Simon Glass233a26a92019-07-08 14:25:49 -0600139 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass39dd2152019-07-08 14:25:47 -0600140 return sizes_ok
Simon Glass2574ef62016-11-25 20:15:51 -0700141
Simon Glass4ca8e042017-11-13 18:55:01 -0700142 def WriteSymbols(self):
143 """Write symbol values into binary files for access at run time"""
Simon Glass39dd2152019-07-08 14:25:47 -0600144 section.Entry_section.WriteSymbols(self, self)
145
Simon Glass2574ef62016-11-25 20:15:51 -0700146 def BuildImage(self):
147 """Write the image to a file"""
148 fname = tools.GetOutputFilename(self._filename)
Simon Glass74b31572019-07-20 12:23:54 -0600149 tout.Info("Writing image to '%s'" % fname)
Simon Glass2574ef62016-11-25 20:15:51 -0700150 with open(fname, 'wb') as fd:
Simon Glass74b31572019-07-20 12:23:54 -0600151 data = self.GetData()
152 fd.write(data)
153 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass30732662018-06-01 09:38:20 -0600154
155 def WriteMap(self):
Simon Glasscd817d52018-09-14 04:57:36 -0600156 """Write a map of the image to a .map file
157
158 Returns:
159 Filename of map file written
160 """
Simon Glass39dd2152019-07-08 14:25:47 -0600161 filename = '%s.map' % self.image_name
Simon Glass30732662018-06-01 09:38:20 -0600162 fname = tools.GetOutputFilename(filename)
163 with open(fname, 'w') as fd:
Simon Glass7eca7922018-07-17 13:25:49 -0600164 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
165 file=fd)
Simon Glass39dd2152019-07-08 14:25:47 -0600166 section.Entry_section.WriteMap(self, fd, 0)
Simon Glasscd817d52018-09-14 04:57:36 -0600167 return fname
Simon Glass6b156f82019-07-08 14:25:43 -0600168
169 def BuildEntryList(self):
170 """List the files in an image
171
172 Returns:
173 List of entry.EntryInfo objects describing all entries in the image
174 """
175 entries = []
Simon Glass39dd2152019-07-08 14:25:47 -0600176 self.ListEntries(entries, 0)
Simon Glass6b156f82019-07-08 14:25:43 -0600177 return entries
Simon Glassb2fd11d2019-07-08 14:25:48 -0600178
179 def FindEntryPath(self, entry_path):
180 """Find an entry at a given path in the image
181
182 Args:
183 entry_path: Path to entry (e.g. /ro-section/u-boot')
184
185 Returns:
186 Entry object corresponding to that past
187
188 Raises:
189 ValueError if no entry found
190 """
191 parts = entry_path.split('/')
192 entries = self.GetEntries()
193 parent = '/'
194 for part in parts:
195 entry = entries.get(part)
196 if not entry:
197 raise ValueError("Entry '%s' not found in '%s'" %
198 (part, parent))
199 parent = entry.GetPath()
200 entries = entry.GetEntries()
201 return entry
202
203 def ReadData(self, decomp=True):
Simon Glass4d8151f2019-09-25 08:56:21 -0600204 tout.Debug("Image '%s' ReadData(), size=%#x" %
205 (self.GetPath(), len(self._data)))
Simon Glassb2fd11d2019-07-08 14:25:48 -0600206 return self._data
207
208 def GetListEntries(self, entry_paths):
209 """List the entries in an image
210
211 This decodes the supplied image and returns a list of entries from that
212 image, preceded by a header.
213
214 Args:
215 entry_paths: List of paths to match (each can have wildcards). Only
216 entries whose names match one of these paths will be printed
217
218 Returns:
219 String error message if something went wrong, otherwise
220 3-Tuple:
221 List of EntryInfo objects
222 List of lines, each
223 List of text columns, each a string
224 List of widths of each column
225 """
226 def _EntryToStrings(entry):
227 """Convert an entry to a list of strings, one for each column
228
229 Args:
230 entry: EntryInfo object containing information to output
231
232 Returns:
233 List of strings, one for each field in entry
234 """
235 def _AppendHex(val):
236 """Append a hex value, or an empty string if val is None
237
238 Args:
239 val: Integer value, or None if none
240 """
241 args.append('' if val is None else '>%x' % val)
242
243 args = [' ' * entry.indent + entry.name]
244 _AppendHex(entry.image_pos)
245 _AppendHex(entry.size)
246 args.append(entry.etype)
247 _AppendHex(entry.offset)
248 _AppendHex(entry.uncomp_size)
249 return args
250
251 def _DoLine(lines, line):
252 """Add a line to the output list
253
254 This adds a line (a list of columns) to the output list. It also updates
255 the widths[] array with the maximum width of each column
256
257 Args:
258 lines: List of lines to add to
259 line: List of strings, one for each column
260 """
261 for i, item in enumerate(line):
262 widths[i] = max(widths[i], len(item))
263 lines.append(line)
264
265 def _NameInPaths(fname, entry_paths):
266 """Check if a filename is in a list of wildcarded paths
267
268 Args:
269 fname: Filename to check
270 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
271 'section/u-boot'])
272
273 Returns:
274 True if any wildcard matches the filename (using Unix filename
275 pattern matching, not regular expressions)
276 False if not
277 """
278 for path in entry_paths:
279 if fnmatch.fnmatch(fname, path):
280 return True
281 return False
282
283 entries = self.BuildEntryList()
284
285 # This is our list of lines. Each item in the list is a list of strings, one
286 # for each column
287 lines = []
288 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
289 'Uncomp-size']
290 num_columns = len(HEADER)
291
292 # This records the width of each column, calculated as the maximum width of
293 # all the strings in that column
294 widths = [0] * num_columns
295 _DoLine(lines, HEADER)
296
297 # We won't print anything unless it has at least this indent. So at the
298 # start we will print nothing, unless a path matches (or there are no
299 # entry paths)
300 MAX_INDENT = 100
301 min_indent = MAX_INDENT
302 path_stack = []
303 path = ''
304 indent = 0
305 selected_entries = []
306 for entry in entries:
307 if entry.indent > indent:
308 path_stack.append(path)
309 elif entry.indent < indent:
310 path_stack.pop()
311 if path_stack:
312 path = path_stack[-1] + '/' + entry.name
313 indent = entry.indent
314
315 # If there are entry paths to match and we are not looking at a
316 # sub-entry of a previously matched entry, we need to check the path
317 if entry_paths and indent <= min_indent:
318 if _NameInPaths(path[1:], entry_paths):
319 # Print this entry and all sub-entries (=higher indent)
320 min_indent = indent
321 else:
322 # Don't print this entry, nor any following entries until we get
323 # a path match
324 min_indent = MAX_INDENT
325 continue
326 _DoLine(lines, _EntryToStrings(entry))
327 selected_entries.append(entry)
328 return selected_entries, lines, widths