blob: 2c5668e2a96c1cfe8c5f98afce6063d31755f85e [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
23
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 Glass7ae5f312018-06-01 09:38:19 -060034
35 Args:
36 test: True if this is being called from a test of Images. This this case
37 there is no device tree defining the structure of the section, so
38 we create a section manually.
Simon Glassbf7fd502016-11-25 20:15:51 -070039 """
Simon Glass19790632017-11-13 18:55:01 -070040 def __init__(self, name, node, test=False):
Simon Glass8beb11e2019-07-08 14:25:47 -060041 self.image = self
42 section.Entry_section.__init__(self, None, 'section', node, test)
43 self.name = 'main-section'
44 self.image_name = name
45 self._filename = '%s.bin' % self.image_name
46 if not test:
47 filename = fdt_util.GetString(self._node, 'filename')
48 if filename:
49 self._filename = filename
Simon Glassbf7fd502016-11-25 20:15:51 -070050
Simon Glassffded752019-07-08 14:25:46 -060051 @classmethod
52 def FromFile(cls, fname):
53 """Convert an image file into an Image for use in binman
54
55 Args:
56 fname: Filename of image file to read
57
58 Returns:
59 Image object on success
60
61 Raises:
62 ValueError if something goes wrong
63 """
64 data = tools.ReadFile(fname)
65 size = len(data)
66
67 # First look for an image header
68 pos = image_header.LocateHeaderOffset(data)
69 if pos is None:
70 # Look for the FDT map
71 pos = fdtmap.LocateFdtmap(data)
72 if pos is None:
73 raise ValueError('Cannot find FDT map in image')
74
75 # We don't know the FDT size, so check its header first
76 probe_dtb = fdt.Fdt.FromData(
77 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
78 dtb_size = probe_dtb.GetFdtObj().totalsize()
79 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
80 dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
81 dtb.Scan()
82
83 # Return an Image with the associated nodes
84 return Image('image', dtb.GetRoot())
85
Simon Glassc52c9e72019-07-08 14:25:37 -060086 def Raise(self, msg):
87 """Convenience function to raise an error referencing an image"""
88 raise ValueError("Image '%s': %s" % (self._node.path, msg))
89
Simon Glassbf7fd502016-11-25 20:15:51 -070090 def PackEntries(self):
91 """Pack all entries into the image"""
Simon Glass8beb11e2019-07-08 14:25:47 -060092 section.Entry_section.Pack(self, 0)
Simon Glass078ab1a2018-07-06 10:27:41 -060093
Simon Glassdbf6be92018-08-01 15:22:42 -060094 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -060095 # This first section in the image so it starts at 0
96 section.Entry_section.SetImagePos(self, 0)
Simon Glassdbf6be92018-08-01 15:22:42 -060097
Simon Glassbf7fd502016-11-25 20:15:51 -070098 def ProcessEntryContents(self):
99 """Call the ProcessContents() method for each entry
100
101 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600102
103 Returns:
104 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700105 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600106 sizes_ok = True
107 for entry in self._entries.values():
108 if not entry.ProcessContents():
109 sizes_ok = False
110 print("Entry '%s' size change" % self._node.path)
111 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700112
Simon Glass19790632017-11-13 18:55:01 -0700113 def WriteSymbols(self):
114 """Write symbol values into binary files for access at run time"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600115 section.Entry_section.WriteSymbols(self, self)
116
117 def BuildSection(self, fd, base_offset):
118 """Write the section to a file"""
119 fd.seek(base_offset)
120 fd.write(self.GetData())
Simon Glass19790632017-11-13 18:55:01 -0700121
Simon Glassbf7fd502016-11-25 20:15:51 -0700122 def BuildImage(self):
123 """Write the image to a file"""
124 fname = tools.GetOutputFilename(self._filename)
125 with open(fname, 'wb') as fd:
Simon Glass8beb11e2019-07-08 14:25:47 -0600126 self.BuildSection(fd, 0)
Simon Glass3b0c3822018-06-01 09:38:20 -0600127
128 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600129 """Write a map of the image to a .map file
130
131 Returns:
132 Filename of map file written
133 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600134 filename = '%s.map' % self.image_name
Simon Glass3b0c3822018-06-01 09:38:20 -0600135 fname = tools.GetOutputFilename(filename)
136 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600137 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
138 file=fd)
Simon Glass8beb11e2019-07-08 14:25:47 -0600139 section.Entry_section.WriteMap(self, fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600140 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600141
142 def BuildEntryList(self):
143 """List the files in an image
144
145 Returns:
146 List of entry.EntryInfo objects describing all entries in the image
147 """
148 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600149 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600150 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600151
152 def FindEntryPath(self, entry_path):
153 """Find an entry at a given path in the image
154
155 Args:
156 entry_path: Path to entry (e.g. /ro-section/u-boot')
157
158 Returns:
159 Entry object corresponding to that past
160
161 Raises:
162 ValueError if no entry found
163 """
164 parts = entry_path.split('/')
165 entries = self.GetEntries()
166 parent = '/'
167 for part in parts:
168 entry = entries.get(part)
169 if not entry:
170 raise ValueError("Entry '%s' not found in '%s'" %
171 (part, parent))
172 parent = entry.GetPath()
173 entries = entry.GetEntries()
174 return entry
175
176 def ReadData(self, decomp=True):
177 return self._data
178
179 def GetListEntries(self, entry_paths):
180 """List the entries in an image
181
182 This decodes the supplied image and returns a list of entries from that
183 image, preceded by a header.
184
185 Args:
186 entry_paths: List of paths to match (each can have wildcards). Only
187 entries whose names match one of these paths will be printed
188
189 Returns:
190 String error message if something went wrong, otherwise
191 3-Tuple:
192 List of EntryInfo objects
193 List of lines, each
194 List of text columns, each a string
195 List of widths of each column
196 """
197 def _EntryToStrings(entry):
198 """Convert an entry to a list of strings, one for each column
199
200 Args:
201 entry: EntryInfo object containing information to output
202
203 Returns:
204 List of strings, one for each field in entry
205 """
206 def _AppendHex(val):
207 """Append a hex value, or an empty string if val is None
208
209 Args:
210 val: Integer value, or None if none
211 """
212 args.append('' if val is None else '>%x' % val)
213
214 args = [' ' * entry.indent + entry.name]
215 _AppendHex(entry.image_pos)
216 _AppendHex(entry.size)
217 args.append(entry.etype)
218 _AppendHex(entry.offset)
219 _AppendHex(entry.uncomp_size)
220 return args
221
222 def _DoLine(lines, line):
223 """Add a line to the output list
224
225 This adds a line (a list of columns) to the output list. It also updates
226 the widths[] array with the maximum width of each column
227
228 Args:
229 lines: List of lines to add to
230 line: List of strings, one for each column
231 """
232 for i, item in enumerate(line):
233 widths[i] = max(widths[i], len(item))
234 lines.append(line)
235
236 def _NameInPaths(fname, entry_paths):
237 """Check if a filename is in a list of wildcarded paths
238
239 Args:
240 fname: Filename to check
241 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
242 'section/u-boot'])
243
244 Returns:
245 True if any wildcard matches the filename (using Unix filename
246 pattern matching, not regular expressions)
247 False if not
248 """
249 for path in entry_paths:
250 if fnmatch.fnmatch(fname, path):
251 return True
252 return False
253
254 entries = self.BuildEntryList()
255
256 # This is our list of lines. Each item in the list is a list of strings, one
257 # for each column
258 lines = []
259 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
260 'Uncomp-size']
261 num_columns = len(HEADER)
262
263 # This records the width of each column, calculated as the maximum width of
264 # all the strings in that column
265 widths = [0] * num_columns
266 _DoLine(lines, HEADER)
267
268 # We won't print anything unless it has at least this indent. So at the
269 # start we will print nothing, unless a path matches (or there are no
270 # entry paths)
271 MAX_INDENT = 100
272 min_indent = MAX_INDENT
273 path_stack = []
274 path = ''
275 indent = 0
276 selected_entries = []
277 for entry in entries:
278 if entry.indent > indent:
279 path_stack.append(path)
280 elif entry.indent < indent:
281 path_stack.pop()
282 if path_stack:
283 path = path_stack[-1] + '/' + entry.name
284 indent = entry.indent
285
286 # If there are entry paths to match and we are not looking at a
287 # sub-entry of a previously matched entry, we need to check the path
288 if entry_paths and indent <= min_indent:
289 if _NameInPaths(path[1:], entry_paths):
290 # Print this entry and all sub-entries (=higher indent)
291 min_indent = indent
292 else:
293 # Don't print this entry, nor any following entries until we get
294 # a path match
295 min_indent = MAX_INDENT
296 continue
297 _DoLine(lines, _EntryToStrings(entry))
298 selected_entries.append(entry)
299 return selected_entries, lines, widths