blob: 232e752258c13868c149d44006e95d7f07d368b4 [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
Simon Glass19790632017-11-13 18:55:01 -07008from __future__ import print_function
9
Simon Glassbf7fd502016-11-25 20:15:51 -070010from collections import OrderedDict
Simon Glass61f564d2019-07-08 14:25:48 -060011import fnmatch
Simon Glassbf7fd502016-11-25 20:15:51 -070012from operator import attrgetter
Simon Glass19790632017-11-13 18:55:01 -070013import re
14import sys
Simon Glassbf7fd502016-11-25 20:15:51 -070015
Simon Glass8beb11e2019-07-08 14:25:47 -060016from entry import Entry
Simon Glassffded752019-07-08 14:25:46 -060017from etype import fdtmap
18from etype import image_header
Simon Glass8beb11e2019-07-08 14:25:47 -060019from etype import section
Simon Glassffded752019-07-08 14:25:46 -060020import fdt
Simon Glassbf7fd502016-11-25 20:15:51 -070021import fdt_util
22import tools
Simon Glasseea264e2019-07-08 14:25:49 -060023import tout
Simon Glassbf7fd502016-11-25 20:15:51 -070024
Simon Glass8beb11e2019-07-08 14:25:47 -060025class Image(section.Entry_section):
Simon Glassbf7fd502016-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 Glass8beb11e2019-07-08 14:25:47 -060033 Attributes:
34 filename: Output filename for image
Simon Glass589d8f92019-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 Glass7ae5f312018-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 Glassbf7fd502016-11-25 20:15:51 -070043 """
Simon Glass19790632017-11-13 18:55:01 -070044 def __init__(self, name, node, test=False):
Simon Glass8beb11e2019-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 Glass589d8f92019-07-20 12:23:40 -060050 self.fdtmap_dtb = None
51 self.fdtmap_data = None
Simon Glass8beb11e2019-07-08 14:25:47 -060052 if not test:
53 filename = fdt_util.GetString(self._node, 'filename')
54 if filename:
55 self._filename = filename
Simon Glassbf7fd502016-11-25 20:15:51 -070056
Simon Glassffded752019-07-08 14:25:46 -060057 @classmethod
58 def FromFile(cls, fname):
59 """Convert an image file into an Image for use in binman
60
61 Args:
62 fname: Filename of image file to read
63
64 Returns:
65 Image object on success
66
67 Raises:
68 ValueError if something goes wrong
69 """
70 data = tools.ReadFile(fname)
71 size = len(data)
72
73 # First look for an image header
74 pos = image_header.LocateHeaderOffset(data)
75 if pos is None:
76 # Look for the FDT map
77 pos = fdtmap.LocateFdtmap(data)
78 if pos is None:
79 raise ValueError('Cannot find FDT map in image')
80
81 # We don't know the FDT size, so check its header first
82 probe_dtb = fdt.Fdt.FromData(
83 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
84 dtb_size = probe_dtb.GetFdtObj().totalsize()
85 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
86 dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
87 dtb.Scan()
88
89 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -060090 root = dtb.GetRoot()
91 image = Image('image', root)
92 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
93 image.fdtmap_dtb = dtb
94 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -060095 image._data = data
96 return image
Simon Glassffded752019-07-08 14:25:46 -060097
Simon Glassc52c9e72019-07-08 14:25:37 -060098 def Raise(self, msg):
99 """Convenience function to raise an error referencing an image"""
100 raise ValueError("Image '%s': %s" % (self._node.path, msg))
101
Simon Glassbf7fd502016-11-25 20:15:51 -0700102 def PackEntries(self):
103 """Pack all entries into the image"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600104 section.Entry_section.Pack(self, 0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600105
Simon Glassdbf6be92018-08-01 15:22:42 -0600106 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600107 # This first section in the image so it starts at 0
108 section.Entry_section.SetImagePos(self, 0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600109
Simon Glassbf7fd502016-11-25 20:15:51 -0700110 def ProcessEntryContents(self):
111 """Call the ProcessContents() method for each entry
112
113 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600114
115 Returns:
116 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700117 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600118 sizes_ok = True
119 for entry in self._entries.values():
120 if not entry.ProcessContents():
121 sizes_ok = False
Simon Glasseea264e2019-07-08 14:25:49 -0600122 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass8beb11e2019-07-08 14:25:47 -0600123 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700124
Simon Glass19790632017-11-13 18:55:01 -0700125 def WriteSymbols(self):
126 """Write symbol values into binary files for access at run time"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600127 section.Entry_section.WriteSymbols(self, self)
128
129 def BuildSection(self, fd, base_offset):
130 """Write the section to a file"""
131 fd.seek(base_offset)
132 fd.write(self.GetData())
Simon Glass19790632017-11-13 18:55:01 -0700133
Simon Glassbf7fd502016-11-25 20:15:51 -0700134 def BuildImage(self):
135 """Write the image to a file"""
136 fname = tools.GetOutputFilename(self._filename)
137 with open(fname, 'wb') as fd:
Simon Glass8beb11e2019-07-08 14:25:47 -0600138 self.BuildSection(fd, 0)
Simon Glass3b0c3822018-06-01 09:38:20 -0600139
140 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600141 """Write a map of the image to a .map file
142
143 Returns:
144 Filename of map file written
145 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600146 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600147 fname = tools.GetOutputFilename(filename)
148 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600149 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
150 file=fd)
Simon Glass8beb11e2019-07-08 14:25:47 -0600151 section.Entry_section.WriteMap(self, fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600152 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600153
154 def BuildEntryList(self):
155 """List the files in an image
156
157 Returns:
158 List of entry.EntryInfo objects describing all entries in the image
159 """
160 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600161 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600162 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600163
164 def FindEntryPath(self, entry_path):
165 """Find an entry at a given path in the image
166
167 Args:
168 entry_path: Path to entry (e.g. /ro-section/u-boot')
169
170 Returns:
171 Entry object corresponding to that past
172
173 Raises:
174 ValueError if no entry found
175 """
176 parts = entry_path.split('/')
177 entries = self.GetEntries()
178 parent = '/'
179 for part in parts:
180 entry = entries.get(part)
181 if not entry:
182 raise ValueError("Entry '%s' not found in '%s'" %
183 (part, parent))
184 parent = entry.GetPath()
185 entries = entry.GetEntries()
186 return entry
187
188 def ReadData(self, decomp=True):
189 return self._data
190
191 def GetListEntries(self, entry_paths):
192 """List the entries in an image
193
194 This decodes the supplied image and returns a list of entries from that
195 image, preceded by a header.
196
197 Args:
198 entry_paths: List of paths to match (each can have wildcards). Only
199 entries whose names match one of these paths will be printed
200
201 Returns:
202 String error message if something went wrong, otherwise
203 3-Tuple:
204 List of EntryInfo objects
205 List of lines, each
206 List of text columns, each a string
207 List of widths of each column
208 """
209 def _EntryToStrings(entry):
210 """Convert an entry to a list of strings, one for each column
211
212 Args:
213 entry: EntryInfo object containing information to output
214
215 Returns:
216 List of strings, one for each field in entry
217 """
218 def _AppendHex(val):
219 """Append a hex value, or an empty string if val is None
220
221 Args:
222 val: Integer value, or None if none
223 """
224 args.append('' if val is None else '>%x' % val)
225
226 args = [' ' * entry.indent + entry.name]
227 _AppendHex(entry.image_pos)
228 _AppendHex(entry.size)
229 args.append(entry.etype)
230 _AppendHex(entry.offset)
231 _AppendHex(entry.uncomp_size)
232 return args
233
234 def _DoLine(lines, line):
235 """Add a line to the output list
236
237 This adds a line (a list of columns) to the output list. It also updates
238 the widths[] array with the maximum width of each column
239
240 Args:
241 lines: List of lines to add to
242 line: List of strings, one for each column
243 """
244 for i, item in enumerate(line):
245 widths[i] = max(widths[i], len(item))
246 lines.append(line)
247
248 def _NameInPaths(fname, entry_paths):
249 """Check if a filename is in a list of wildcarded paths
250
251 Args:
252 fname: Filename to check
253 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
254 'section/u-boot'])
255
256 Returns:
257 True if any wildcard matches the filename (using Unix filename
258 pattern matching, not regular expressions)
259 False if not
260 """
261 for path in entry_paths:
262 if fnmatch.fnmatch(fname, path):
263 return True
264 return False
265
266 entries = self.BuildEntryList()
267
268 # This is our list of lines. Each item in the list is a list of strings, one
269 # for each column
270 lines = []
271 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
272 'Uncomp-size']
273 num_columns = len(HEADER)
274
275 # This records the width of each column, calculated as the maximum width of
276 # all the strings in that column
277 widths = [0] * num_columns
278 _DoLine(lines, HEADER)
279
280 # We won't print anything unless it has at least this indent. So at the
281 # start we will print nothing, unless a path matches (or there are no
282 # entry paths)
283 MAX_INDENT = 100
284 min_indent = MAX_INDENT
285 path_stack = []
286 path = ''
287 indent = 0
288 selected_entries = []
289 for entry in entries:
290 if entry.indent > indent:
291 path_stack.append(path)
292 elif entry.indent < indent:
293 path_stack.pop()
294 if path_stack:
295 path = path_stack[-1] + '/' + entry.name
296 indent = entry.indent
297
298 # If there are entry paths to match and we are not looking at a
299 # sub-entry of a previously matched entry, we need to check the path
300 if entry_paths and indent <= min_indent:
301 if _NameInPaths(path[1:], entry_paths):
302 # Print this entry and all sub-entries (=higher indent)
303 min_indent = indent
304 else:
305 # Don't print this entry, nor any following entries until we get
306 # a path match
307 min_indent = MAX_INDENT
308 continue
309 _DoLine(lines, _EntryToStrings(entry))
310 selected_entries.append(entry)
311 return selected_entries, lines, widths