blob: 970d33f71101c5e3fe6fc84ed08ac3a9d16d57a2 [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 Glass4ca8e042017-11-13 18:55:01 -070013import re
14import sys
Simon Glass2574ef62016-11-25 20:15:51 -070015
Simon Glass39dd2152019-07-08 14:25:47 -060016from entry import Entry
Simon Glassb8424fa2019-07-08 14:25:46 -060017from etype import fdtmap
18from etype import image_header
Simon Glass39dd2152019-07-08 14:25:47 -060019from etype import section
Simon Glassb8424fa2019-07-08 14:25:46 -060020import fdt
Simon Glass2574ef62016-11-25 20:15:51 -070021import fdt_util
22import tools
Simon Glass233a26a92019-07-08 14:25:49 -060023import tout
Simon Glass2574ef62016-11-25 20:15:51 -070024
Simon Glass39dd2152019-07-08 14:25:47 -060025class Image(section.Entry_section):
Simon Glass2574ef62016-11-25 20:15:51 -070026 """A Image, representing an output from binman
27
28 An image is comprised of a collection of entries each containing binary
29 data. The image size must be large enough to hold all of this data.
30
31 This class implements the various operations needed for images.
32
Simon Glass39dd2152019-07-08 14:25:47 -060033 Attributes:
34 filename: Output filename for image
Simon Glass76546572019-07-20 12:23:40 -060035 image_node: Name of node containing the description for this image
36 fdtmap_dtb: Fdt object for the fdtmap when loading from a file
37 fdtmap_data: Contents of the fdtmap when loading from a file
Simon Glass1e324002018-06-01 09:38:19 -060038
39 Args:
40 test: True if this is being called from a test of Images. This this case
41 there is no device tree defining the structure of the section, so
42 we create a section manually.
Simon Glass2574ef62016-11-25 20:15:51 -070043 """
Simon Glass4ca8e042017-11-13 18:55:01 -070044 def __init__(self, name, node, test=False):
Simon Glass39dd2152019-07-08 14:25:47 -060045 self.image = self
46 section.Entry_section.__init__(self, None, 'section', node, test)
47 self.name = 'main-section'
48 self.image_name = name
49 self._filename = '%s.bin' % self.image_name
Simon Glass76546572019-07-20 12:23:40 -060050 self.fdtmap_dtb = None
51 self.fdtmap_data = None
Simon Glass39dd2152019-07-08 14:25:47 -060052 if not test:
Simon Glass2c360cf2019-07-20 12:23:45 -060053 self.ReadNode()
54
55 def ReadNode(self):
56 section.Entry_section.ReadNode(self)
57 filename = fdt_util.GetString(self._node, 'filename')
58 if filename:
59 self._filename = filename
Simon Glass2574ef62016-11-25 20:15:51 -070060
Simon Glassb8424fa2019-07-08 14:25:46 -060061 @classmethod
62 def FromFile(cls, fname):
63 """Convert an image file into an Image for use in binman
64
65 Args:
66 fname: Filename of image file to read
67
68 Returns:
69 Image object on success
70
71 Raises:
72 ValueError if something goes wrong
73 """
74 data = tools.ReadFile(fname)
75 size = len(data)
76
77 # First look for an image header
78 pos = image_header.LocateHeaderOffset(data)
79 if pos is None:
80 # Look for the FDT map
81 pos = fdtmap.LocateFdtmap(data)
82 if pos is None:
83 raise ValueError('Cannot find FDT map in image')
84
85 # We don't know the FDT size, so check its header first
86 probe_dtb = fdt.Fdt.FromData(
87 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
88 dtb_size = probe_dtb.GetFdtObj().totalsize()
89 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
90 dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
91 dtb.Scan()
92
93 # Return an Image with the associated nodes
Simon Glass76546572019-07-20 12:23:40 -060094 root = dtb.GetRoot()
95 image = Image('image', root)
96 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
97 image.fdtmap_dtb = dtb
98 image.fdtmap_data = fdtmap_data
Simon Glass4c613bf2019-07-08 14:25:50 -060099 image._data = data
100 return image
Simon Glassb8424fa2019-07-08 14:25:46 -0600101
Simon Glasse61b6f62019-07-08 14:25:37 -0600102 def Raise(self, msg):
103 """Convenience function to raise an error referencing an image"""
104 raise ValueError("Image '%s': %s" % (self._node.path, msg))
105
Simon Glass2574ef62016-11-25 20:15:51 -0700106 def PackEntries(self):
107 """Pack all entries into the image"""
Simon Glass39dd2152019-07-08 14:25:47 -0600108 section.Entry_section.Pack(self, 0)
Simon Glasse22f8fa2018-07-06 10:27:41 -0600109
Simon Glass9dcc8612018-08-01 15:22:42 -0600110 def SetImagePos(self):
Simon Glass39dd2152019-07-08 14:25:47 -0600111 # This first section in the image so it starts at 0
112 section.Entry_section.SetImagePos(self, 0)
Simon Glass9dcc8612018-08-01 15:22:42 -0600113
Simon Glass2574ef62016-11-25 20:15:51 -0700114 def ProcessEntryContents(self):
115 """Call the ProcessContents() method for each entry
116
117 This is intended to adjust the contents as needed by the entry type.
Simon Glassec849852019-07-08 14:25:35 -0600118
119 Returns:
120 True if the new data size is OK, False if expansion is needed
Simon Glass2574ef62016-11-25 20:15:51 -0700121 """
Simon Glass39dd2152019-07-08 14:25:47 -0600122 sizes_ok = True
123 for entry in self._entries.values():
124 if not entry.ProcessContents():
125 sizes_ok = False
Simon Glass233a26a92019-07-08 14:25:49 -0600126 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass39dd2152019-07-08 14:25:47 -0600127 return sizes_ok
Simon Glass2574ef62016-11-25 20:15:51 -0700128
Simon Glass4ca8e042017-11-13 18:55:01 -0700129 def WriteSymbols(self):
130 """Write symbol values into binary files for access at run time"""
Simon Glass39dd2152019-07-08 14:25:47 -0600131 section.Entry_section.WriteSymbols(self, self)
132
133 def BuildSection(self, fd, base_offset):
134 """Write the section to a file"""
135 fd.seek(base_offset)
136 fd.write(self.GetData())
Simon Glass4ca8e042017-11-13 18:55:01 -0700137
Simon Glass2574ef62016-11-25 20:15:51 -0700138 def BuildImage(self):
139 """Write the image to a file"""
140 fname = tools.GetOutputFilename(self._filename)
141 with open(fname, 'wb') as fd:
Simon Glass39dd2152019-07-08 14:25:47 -0600142 self.BuildSection(fd, 0)
Simon Glass30732662018-06-01 09:38:20 -0600143
144 def WriteMap(self):
Simon Glasscd817d52018-09-14 04:57:36 -0600145 """Write a map of the image to a .map file
146
147 Returns:
148 Filename of map file written
149 """
Simon Glass39dd2152019-07-08 14:25:47 -0600150 filename = '%s.map' % self.image_name
Simon Glass30732662018-06-01 09:38:20 -0600151 fname = tools.GetOutputFilename(filename)
152 with open(fname, 'w') as fd:
Simon Glass7eca7922018-07-17 13:25:49 -0600153 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
154 file=fd)
Simon Glass39dd2152019-07-08 14:25:47 -0600155 section.Entry_section.WriteMap(self, fd, 0)
Simon Glasscd817d52018-09-14 04:57:36 -0600156 return fname
Simon Glass6b156f82019-07-08 14:25:43 -0600157
158 def BuildEntryList(self):
159 """List the files in an image
160
161 Returns:
162 List of entry.EntryInfo objects describing all entries in the image
163 """
164 entries = []
Simon Glass39dd2152019-07-08 14:25:47 -0600165 self.ListEntries(entries, 0)
Simon Glass6b156f82019-07-08 14:25:43 -0600166 return entries
Simon Glassb2fd11d2019-07-08 14:25:48 -0600167
168 def FindEntryPath(self, entry_path):
169 """Find an entry at a given path in the image
170
171 Args:
172 entry_path: Path to entry (e.g. /ro-section/u-boot')
173
174 Returns:
175 Entry object corresponding to that past
176
177 Raises:
178 ValueError if no entry found
179 """
180 parts = entry_path.split('/')
181 entries = self.GetEntries()
182 parent = '/'
183 for part in parts:
184 entry = entries.get(part)
185 if not entry:
186 raise ValueError("Entry '%s' not found in '%s'" %
187 (part, parent))
188 parent = entry.GetPath()
189 entries = entry.GetEntries()
190 return entry
191
192 def ReadData(self, decomp=True):
193 return self._data
194
195 def GetListEntries(self, entry_paths):
196 """List the entries in an image
197
198 This decodes the supplied image and returns a list of entries from that
199 image, preceded by a header.
200
201 Args:
202 entry_paths: List of paths to match (each can have wildcards). Only
203 entries whose names match one of these paths will be printed
204
205 Returns:
206 String error message if something went wrong, otherwise
207 3-Tuple:
208 List of EntryInfo objects
209 List of lines, each
210 List of text columns, each a string
211 List of widths of each column
212 """
213 def _EntryToStrings(entry):
214 """Convert an entry to a list of strings, one for each column
215
216 Args:
217 entry: EntryInfo object containing information to output
218
219 Returns:
220 List of strings, one for each field in entry
221 """
222 def _AppendHex(val):
223 """Append a hex value, or an empty string if val is None
224
225 Args:
226 val: Integer value, or None if none
227 """
228 args.append('' if val is None else '>%x' % val)
229
230 args = [' ' * entry.indent + entry.name]
231 _AppendHex(entry.image_pos)
232 _AppendHex(entry.size)
233 args.append(entry.etype)
234 _AppendHex(entry.offset)
235 _AppendHex(entry.uncomp_size)
236 return args
237
238 def _DoLine(lines, line):
239 """Add a line to the output list
240
241 This adds a line (a list of columns) to the output list. It also updates
242 the widths[] array with the maximum width of each column
243
244 Args:
245 lines: List of lines to add to
246 line: List of strings, one for each column
247 """
248 for i, item in enumerate(line):
249 widths[i] = max(widths[i], len(item))
250 lines.append(line)
251
252 def _NameInPaths(fname, entry_paths):
253 """Check if a filename is in a list of wildcarded paths
254
255 Args:
256 fname: Filename to check
257 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
258 'section/u-boot'])
259
260 Returns:
261 True if any wildcard matches the filename (using Unix filename
262 pattern matching, not regular expressions)
263 False if not
264 """
265 for path in entry_paths:
266 if fnmatch.fnmatch(fname, path):
267 return True
268 return False
269
270 entries = self.BuildEntryList()
271
272 # This is our list of lines. Each item in the list is a list of strings, one
273 # for each column
274 lines = []
275 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
276 'Uncomp-size']
277 num_columns = len(HEADER)
278
279 # This records the width of each column, calculated as the maximum width of
280 # all the strings in that column
281 widths = [0] * num_columns
282 _DoLine(lines, HEADER)
283
284 # We won't print anything unless it has at least this indent. So at the
285 # start we will print nothing, unless a path matches (or there are no
286 # entry paths)
287 MAX_INDENT = 100
288 min_indent = MAX_INDENT
289 path_stack = []
290 path = ''
291 indent = 0
292 selected_entries = []
293 for entry in entries:
294 if entry.indent > indent:
295 path_stack.append(path)
296 elif entry.indent < indent:
297 path_stack.pop()
298 if path_stack:
299 path = path_stack[-1] + '/' + entry.name
300 indent = entry.indent
301
302 # If there are entry paths to match and we are not looking at a
303 # sub-entry of a previously matched entry, we need to check the path
304 if entry_paths and indent <= min_indent:
305 if _NameInPaths(path[1:], entry_paths):
306 # Print this entry and all sub-entries (=higher indent)
307 min_indent = indent
308 else:
309 # Don't print this entry, nor any following entries until we get
310 # a path match
311 min_indent = MAX_INDENT
312 continue
313 _DoLine(lines, _EntryToStrings(entry))
314 selected_entries.append(entry)
315 return selected_entries, lines, widths