blob: bbb5e23c3b237b0b45b8976d18cb7e9b0ee7d16f [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
85 return Image('image', dtb.GetRoot())
86
Simon Glassc52c9e72019-07-08 14:25:37 -060087 def Raise(self, msg):
88 """Convenience function to raise an error referencing an image"""
89 raise ValueError("Image '%s': %s" % (self._node.path, msg))
90
Simon Glassbf7fd502016-11-25 20:15:51 -070091 def PackEntries(self):
92 """Pack all entries into the image"""
Simon Glass8beb11e2019-07-08 14:25:47 -060093 section.Entry_section.Pack(self, 0)
Simon Glass078ab1a2018-07-06 10:27:41 -060094
Simon Glassdbf6be92018-08-01 15:22:42 -060095 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -060096 # This first section in the image so it starts at 0
97 section.Entry_section.SetImagePos(self, 0)
Simon Glassdbf6be92018-08-01 15:22:42 -060098
Simon Glassbf7fd502016-11-25 20:15:51 -070099 def ProcessEntryContents(self):
100 """Call the ProcessContents() method for each entry
101
102 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600103
104 Returns:
105 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700106 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600107 sizes_ok = True
108 for entry in self._entries.values():
109 if not entry.ProcessContents():
110 sizes_ok = False
Simon Glasseea264e2019-07-08 14:25:49 -0600111 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass8beb11e2019-07-08 14:25:47 -0600112 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700113
Simon Glass19790632017-11-13 18:55:01 -0700114 def WriteSymbols(self):
115 """Write symbol values into binary files for access at run time"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600116 section.Entry_section.WriteSymbols(self, self)
117
118 def BuildSection(self, fd, base_offset):
119 """Write the section to a file"""
120 fd.seek(base_offset)
121 fd.write(self.GetData())
Simon Glass19790632017-11-13 18:55:01 -0700122
Simon Glassbf7fd502016-11-25 20:15:51 -0700123 def BuildImage(self):
124 """Write the image to a file"""
125 fname = tools.GetOutputFilename(self._filename)
126 with open(fname, 'wb') as fd:
Simon Glass8beb11e2019-07-08 14:25:47 -0600127 self.BuildSection(fd, 0)
Simon Glass3b0c38212018-06-01 09:38:20 -0600128
129 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600130 """Write a map of the image to a .map file
131
132 Returns:
133 Filename of map file written
134 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600135 filename = '%s.map' % self.image_name
Simon Glass3b0c38212018-06-01 09:38:20 -0600136 fname = tools.GetOutputFilename(filename)
137 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600138 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
139 file=fd)
Simon Glass8beb11e2019-07-08 14:25:47 -0600140 section.Entry_section.WriteMap(self, fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600141 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600142
143 def BuildEntryList(self):
144 """List the files in an image
145
146 Returns:
147 List of entry.EntryInfo objects describing all entries in the image
148 """
149 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600150 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600151 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600152
153 def FindEntryPath(self, entry_path):
154 """Find an entry at a given path in the image
155
156 Args:
157 entry_path: Path to entry (e.g. /ro-section/u-boot')
158
159 Returns:
160 Entry object corresponding to that past
161
162 Raises:
163 ValueError if no entry found
164 """
165 parts = entry_path.split('/')
166 entries = self.GetEntries()
167 parent = '/'
168 for part in parts:
169 entry = entries.get(part)
170 if not entry:
171 raise ValueError("Entry '%s' not found in '%s'" %
172 (part, parent))
173 parent = entry.GetPath()
174 entries = entry.GetEntries()
175 return entry
176
177 def ReadData(self, decomp=True):
178 return self._data
179
180 def GetListEntries(self, entry_paths):
181 """List the entries in an image
182
183 This decodes the supplied image and returns a list of entries from that
184 image, preceded by a header.
185
186 Args:
187 entry_paths: List of paths to match (each can have wildcards). Only
188 entries whose names match one of these paths will be printed
189
190 Returns:
191 String error message if something went wrong, otherwise
192 3-Tuple:
193 List of EntryInfo objects
194 List of lines, each
195 List of text columns, each a string
196 List of widths of each column
197 """
198 def _EntryToStrings(entry):
199 """Convert an entry to a list of strings, one for each column
200
201 Args:
202 entry: EntryInfo object containing information to output
203
204 Returns:
205 List of strings, one for each field in entry
206 """
207 def _AppendHex(val):
208 """Append a hex value, or an empty string if val is None
209
210 Args:
211 val: Integer value, or None if none
212 """
213 args.append('' if val is None else '>%x' % val)
214
215 args = [' ' * entry.indent + entry.name]
216 _AppendHex(entry.image_pos)
217 _AppendHex(entry.size)
218 args.append(entry.etype)
219 _AppendHex(entry.offset)
220 _AppendHex(entry.uncomp_size)
221 return args
222
223 def _DoLine(lines, line):
224 """Add a line to the output list
225
226 This adds a line (a list of columns) to the output list. It also updates
227 the widths[] array with the maximum width of each column
228
229 Args:
230 lines: List of lines to add to
231 line: List of strings, one for each column
232 """
233 for i, item in enumerate(line):
234 widths[i] = max(widths[i], len(item))
235 lines.append(line)
236
237 def _NameInPaths(fname, entry_paths):
238 """Check if a filename is in a list of wildcarded paths
239
240 Args:
241 fname: Filename to check
242 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
243 'section/u-boot'])
244
245 Returns:
246 True if any wildcard matches the filename (using Unix filename
247 pattern matching, not regular expressions)
248 False if not
249 """
250 for path in entry_paths:
251 if fnmatch.fnmatch(fname, path):
252 return True
253 return False
254
255 entries = self.BuildEntryList()
256
257 # This is our list of lines. Each item in the list is a list of strings, one
258 # for each column
259 lines = []
260 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
261 'Uncomp-size']
262 num_columns = len(HEADER)
263
264 # This records the width of each column, calculated as the maximum width of
265 # all the strings in that column
266 widths = [0] * num_columns
267 _DoLine(lines, HEADER)
268
269 # We won't print anything unless it has at least this indent. So at the
270 # start we will print nothing, unless a path matches (or there are no
271 # entry paths)
272 MAX_INDENT = 100
273 min_indent = MAX_INDENT
274 path_stack = []
275 path = ''
276 indent = 0
277 selected_entries = []
278 for entry in entries:
279 if entry.indent > indent:
280 path_stack.append(path)
281 elif entry.indent < indent:
282 path_stack.pop()
283 if path_stack:
284 path = path_stack[-1] + '/' + entry.name
285 indent = entry.indent
286
287 # If there are entry paths to match and we are not looking at a
288 # sub-entry of a previously matched entry, we need to check the path
289 if entry_paths and indent <= min_indent:
290 if _NameInPaths(path[1:], entry_paths):
291 # Print this entry and all sub-entries (=higher indent)
292 min_indent = indent
293 else:
294 # Don't print this entry, nor any following entries until we get
295 # a path match
296 min_indent = MAX_INDENT
297 continue
298 _DoLine(lines, _EntryToStrings(entry))
299 selected_entries.append(entry)
300 return selected_entries, lines, widths