blob: 5185b68990a49cf0c86e9537cdd1691b25edef24 [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 Glass10f9d002019-07-20 12:23:50 -060013import os
Simon Glass19790632017-11-13 18:55:01 -070014import re
15import sys
Simon Glassbf7fd502016-11-25 20:15:51 -070016
Simon Glass8beb11e2019-07-08 14:25:47 -060017from entry import Entry
Simon Glassffded752019-07-08 14:25:46 -060018from etype import fdtmap
19from etype import image_header
Simon Glass8beb11e2019-07-08 14:25:47 -060020from etype import section
Simon Glassffded752019-07-08 14:25:46 -060021import fdt
Simon Glassbf7fd502016-11-25 20:15:51 -070022import fdt_util
23import tools
Simon Glasseea264e2019-07-08 14:25:49 -060024import tout
Simon Glassbf7fd502016-11-25 20:15:51 -070025
Simon Glass8beb11e2019-07-08 14:25:47 -060026class Image(section.Entry_section):
Simon Glassbf7fd502016-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 Glass8beb11e2019-07-08 14:25:47 -060034 Attributes:
35 filename: Output filename for image
Simon Glass589d8f92019-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 Glass7ae5f312018-06-01 09:38:19 -060039
40 Args:
41 test: True if this is being called from a test of Images. This this case
42 there is no device tree defining the structure of the section, so
43 we create a section manually.
Simon Glassbf7fd502016-11-25 20:15:51 -070044 """
Simon Glass19790632017-11-13 18:55:01 -070045 def __init__(self, name, node, test=False):
Simon Glass8beb11e2019-07-08 14:25:47 -060046 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:
Simon Glassc6bd6e22019-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 Glassbf7fd502016-11-25 20:15:51 -070060
Simon Glassffded752019-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 Glass589d8f92019-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 Glassf667e452019-07-08 14:25:50 -060099 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600100 image._filename = fname
101 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600102 return image
Simon Glassffded752019-07-08 14:25:46 -0600103
Simon Glassc52c9e72019-07-08 14:25:37 -0600104 def Raise(self, msg):
105 """Convenience function to raise an error referencing an image"""
106 raise ValueError("Image '%s': %s" % (self._node.path, msg))
107
Simon Glassbf7fd502016-11-25 20:15:51 -0700108 def PackEntries(self):
109 """Pack all entries into the image"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600110 section.Entry_section.Pack(self, 0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600111
Simon Glassdbf6be92018-08-01 15:22:42 -0600112 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600113 # This first section in the image so it starts at 0
114 section.Entry_section.SetImagePos(self, 0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600115
Simon Glassbf7fd502016-11-25 20:15:51 -0700116 def ProcessEntryContents(self):
117 """Call the ProcessContents() method for each entry
118
119 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600120
121 Returns:
122 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700123 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600124 sizes_ok = True
125 for entry in self._entries.values():
126 if not entry.ProcessContents():
127 sizes_ok = False
Simon Glasseea264e2019-07-08 14:25:49 -0600128 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass8beb11e2019-07-08 14:25:47 -0600129 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700130
Simon Glass19790632017-11-13 18:55:01 -0700131 def WriteSymbols(self):
132 """Write symbol values into binary files for access at run time"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600133 section.Entry_section.WriteSymbols(self, self)
134
135 def BuildSection(self, fd, base_offset):
136 """Write the section to a file"""
137 fd.seek(base_offset)
138 fd.write(self.GetData())
Simon Glass19790632017-11-13 18:55:01 -0700139
Simon Glassbf7fd502016-11-25 20:15:51 -0700140 def BuildImage(self):
141 """Write the image to a file"""
142 fname = tools.GetOutputFilename(self._filename)
143 with open(fname, 'wb') as fd:
Simon Glass8beb11e2019-07-08 14:25:47 -0600144 self.BuildSection(fd, 0)
Simon Glass3b0c3822018-06-01 09:38:20 -0600145
146 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600147 """Write a map of the image to a .map file
148
149 Returns:
150 Filename of map file written
151 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600152 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600153 fname = tools.GetOutputFilename(filename)
154 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600155 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
156 file=fd)
Simon Glass8beb11e2019-07-08 14:25:47 -0600157 section.Entry_section.WriteMap(self, fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600158 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600159
160 def BuildEntryList(self):
161 """List the files in an image
162
163 Returns:
164 List of entry.EntryInfo objects describing all entries in the image
165 """
166 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600167 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600168 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600169
170 def FindEntryPath(self, entry_path):
171 """Find an entry at a given path in the image
172
173 Args:
174 entry_path: Path to entry (e.g. /ro-section/u-boot')
175
176 Returns:
177 Entry object corresponding to that past
178
179 Raises:
180 ValueError if no entry found
181 """
182 parts = entry_path.split('/')
183 entries = self.GetEntries()
184 parent = '/'
185 for part in parts:
186 entry = entries.get(part)
187 if not entry:
188 raise ValueError("Entry '%s' not found in '%s'" %
189 (part, parent))
190 parent = entry.GetPath()
191 entries = entry.GetEntries()
192 return entry
193
194 def ReadData(self, decomp=True):
195 return self._data
196
197 def GetListEntries(self, entry_paths):
198 """List the entries in an image
199
200 This decodes the supplied image and returns a list of entries from that
201 image, preceded by a header.
202
203 Args:
204 entry_paths: List of paths to match (each can have wildcards). Only
205 entries whose names match one of these paths will be printed
206
207 Returns:
208 String error message if something went wrong, otherwise
209 3-Tuple:
210 List of EntryInfo objects
211 List of lines, each
212 List of text columns, each a string
213 List of widths of each column
214 """
215 def _EntryToStrings(entry):
216 """Convert an entry to a list of strings, one for each column
217
218 Args:
219 entry: EntryInfo object containing information to output
220
221 Returns:
222 List of strings, one for each field in entry
223 """
224 def _AppendHex(val):
225 """Append a hex value, or an empty string if val is None
226
227 Args:
228 val: Integer value, or None if none
229 """
230 args.append('' if val is None else '>%x' % val)
231
232 args = [' ' * entry.indent + entry.name]
233 _AppendHex(entry.image_pos)
234 _AppendHex(entry.size)
235 args.append(entry.etype)
236 _AppendHex(entry.offset)
237 _AppendHex(entry.uncomp_size)
238 return args
239
240 def _DoLine(lines, line):
241 """Add a line to the output list
242
243 This adds a line (a list of columns) to the output list. It also updates
244 the widths[] array with the maximum width of each column
245
246 Args:
247 lines: List of lines to add to
248 line: List of strings, one for each column
249 """
250 for i, item in enumerate(line):
251 widths[i] = max(widths[i], len(item))
252 lines.append(line)
253
254 def _NameInPaths(fname, entry_paths):
255 """Check if a filename is in a list of wildcarded paths
256
257 Args:
258 fname: Filename to check
259 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
260 'section/u-boot'])
261
262 Returns:
263 True if any wildcard matches the filename (using Unix filename
264 pattern matching, not regular expressions)
265 False if not
266 """
267 for path in entry_paths:
268 if fnmatch.fnmatch(fname, path):
269 return True
270 return False
271
272 entries = self.BuildEntryList()
273
274 # This is our list of lines. Each item in the list is a list of strings, one
275 # for each column
276 lines = []
277 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
278 'Uncomp-size']
279 num_columns = len(HEADER)
280
281 # This records the width of each column, calculated as the maximum width of
282 # all the strings in that column
283 widths = [0] * num_columns
284 _DoLine(lines, HEADER)
285
286 # We won't print anything unless it has at least this indent. So at the
287 # start we will print nothing, unless a path matches (or there are no
288 # entry paths)
289 MAX_INDENT = 100
290 min_indent = MAX_INDENT
291 path_stack = []
292 path = ''
293 indent = 0
294 selected_entries = []
295 for entry in entries:
296 if entry.indent > indent:
297 path_stack.append(path)
298 elif entry.indent < indent:
299 path_stack.pop()
300 if path_stack:
301 path = path_stack[-1] + '/' + entry.name
302 indent = entry.indent
303
304 # If there are entry paths to match and we are not looking at a
305 # sub-entry of a previously matched entry, we need to check the path
306 if entry_paths and indent <= min_indent:
307 if _NameInPaths(path[1:], entry_paths):
308 # Print this entry and all sub-entries (=higher indent)
309 min_indent = indent
310 else:
311 # Don't print this entry, nor any following entries until we get
312 # a path match
313 min_indent = MAX_INDENT
314 continue
315 _DoLine(lines, _EntryToStrings(entry))
316 selected_entries.append(entry)
317 return selected_entries, lines, widths