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