blob: fd4f50449297dd8cb1e264ace66fbc4aeccc3eb7 [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 Glass12bb1a92019-07-20 12:23:51 -060039 allow_repack: True to add properties to allow the image to be safely
40 repacked later
Simon Glass7ae5f312018-06-01 09:38:19 -060041
42 Args:
Simon Glass12bb1a92019-07-20 12:23:51 -060043 copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
44 from the device tree
Simon Glass7ae5f312018-06-01 09:38:19 -060045 test: True if this is being called from a test of Images. This this case
46 there is no device tree defining the structure of the section, so
47 we create a section manually.
Simon Glassbf7fd502016-11-25 20:15:51 -070048 """
Simon Glass12bb1a92019-07-20 12:23:51 -060049 def __init__(self, name, node, copy_to_orig=True, test=False):
50 section.Entry_section.__init__(self, None, 'section', node, test=test)
51 self.copy_to_orig = copy_to_orig
Simon Glass8beb11e2019-07-08 14:25:47 -060052 self.name = 'main-section'
53 self.image_name = name
54 self._filename = '%s.bin' % self.image_name
Simon Glass589d8f92019-07-20 12:23:40 -060055 self.fdtmap_dtb = None
56 self.fdtmap_data = None
Simon Glass12bb1a92019-07-20 12:23:51 -060057 self.allow_repack = False
Simon Glass8beb11e2019-07-08 14:25:47 -060058 if not test:
Simon Glassc6bd6e22019-07-20 12:23:45 -060059 self.ReadNode()
60
61 def ReadNode(self):
62 section.Entry_section.ReadNode(self)
63 filename = fdt_util.GetString(self._node, 'filename')
64 if filename:
65 self._filename = filename
Simon Glass12bb1a92019-07-20 12:23:51 -060066 self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
Simon Glassbf7fd502016-11-25 20:15:51 -070067
Simon Glassffded752019-07-08 14:25:46 -060068 @classmethod
69 def FromFile(cls, fname):
70 """Convert an image file into an Image for use in binman
71
72 Args:
73 fname: Filename of image file to read
74
75 Returns:
76 Image object on success
77
78 Raises:
79 ValueError if something goes wrong
80 """
81 data = tools.ReadFile(fname)
82 size = len(data)
83
84 # First look for an image header
85 pos = image_header.LocateHeaderOffset(data)
86 if pos is None:
87 # Look for the FDT map
88 pos = fdtmap.LocateFdtmap(data)
89 if pos is None:
90 raise ValueError('Cannot find FDT map in image')
91
92 # We don't know the FDT size, so check its header first
93 probe_dtb = fdt.Fdt.FromData(
94 data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
95 dtb_size = probe_dtb.GetFdtObj().totalsize()
96 fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
Simon Glass96b6c502019-07-20 12:23:53 -060097 fdt_data = fdtmap_data[fdtmap.FDTMAP_HDR_LEN:]
98 out_fname = tools.GetOutputFilename('fdtmap.in.dtb')
99 tools.WriteFile(out_fname, fdt_data)
100 dtb = fdt.Fdt.FromData(fdt_data, out_fname)
Simon Glassffded752019-07-08 14:25:46 -0600101 dtb.Scan()
102
103 # Return an Image with the associated nodes
Simon Glass589d8f92019-07-20 12:23:40 -0600104 root = dtb.GetRoot()
Simon Glass12bb1a92019-07-20 12:23:51 -0600105 image = Image('image', root, copy_to_orig=False)
Simon Glass589d8f92019-07-20 12:23:40 -0600106 image.image_node = fdt_util.GetString(root, 'image-node', 'image')
107 image.fdtmap_dtb = dtb
108 image.fdtmap_data = fdtmap_data
Simon Glassf667e452019-07-08 14:25:50 -0600109 image._data = data
Simon Glass10f9d002019-07-20 12:23:50 -0600110 image._filename = fname
111 image.image_name, _ = os.path.splitext(fname)
Simon Glassf667e452019-07-08 14:25:50 -0600112 return image
Simon Glassffded752019-07-08 14:25:46 -0600113
Simon Glassc52c9e72019-07-08 14:25:37 -0600114 def Raise(self, msg):
115 """Convenience function to raise an error referencing an image"""
116 raise ValueError("Image '%s': %s" % (self._node.path, msg))
117
Simon Glassbf7fd502016-11-25 20:15:51 -0700118 def PackEntries(self):
119 """Pack all entries into the image"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600120 section.Entry_section.Pack(self, 0)
Simon Glass078ab1a2018-07-06 10:27:41 -0600121
Simon Glassdbf6be92018-08-01 15:22:42 -0600122 def SetImagePos(self):
Simon Glass8beb11e2019-07-08 14:25:47 -0600123 # This first section in the image so it starts at 0
124 section.Entry_section.SetImagePos(self, 0)
Simon Glassdbf6be92018-08-01 15:22:42 -0600125
Simon Glassbf7fd502016-11-25 20:15:51 -0700126 def ProcessEntryContents(self):
127 """Call the ProcessContents() method for each entry
128
129 This is intended to adjust the contents as needed by the entry type.
Simon Glassa0dcaf22019-07-08 14:25:35 -0600130
131 Returns:
132 True if the new data size is OK, False if expansion is needed
Simon Glassbf7fd502016-11-25 20:15:51 -0700133 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600134 sizes_ok = True
135 for entry in self._entries.values():
136 if not entry.ProcessContents():
137 sizes_ok = False
Simon Glasseea264e2019-07-08 14:25:49 -0600138 tout.Debug("Entry '%s' size change" % self._node.path)
Simon Glass8beb11e2019-07-08 14:25:47 -0600139 return sizes_ok
Simon Glassbf7fd502016-11-25 20:15:51 -0700140
Simon Glass19790632017-11-13 18:55:01 -0700141 def WriteSymbols(self):
142 """Write symbol values into binary files for access at run time"""
Simon Glass8beb11e2019-07-08 14:25:47 -0600143 section.Entry_section.WriteSymbols(self, self)
144
Simon Glassbf7fd502016-11-25 20:15:51 -0700145 def BuildImage(self):
146 """Write the image to a file"""
147 fname = tools.GetOutputFilename(self._filename)
Simon Glass74001072019-07-20 12:23:54 -0600148 tout.Info("Writing image to '%s'" % fname)
Simon Glassbf7fd502016-11-25 20:15:51 -0700149 with open(fname, 'wb') as fd:
Simon Glass74001072019-07-20 12:23:54 -0600150 data = self.GetData()
151 fd.write(data)
152 tout.Info("Wrote %#x bytes" % len(data))
Simon Glass3b0c38212018-06-01 09:38:20 -0600153
154 def WriteMap(self):
Simon Glass163ed6c2018-09-14 04:57:36 -0600155 """Write a map of the image to a .map file
156
157 Returns:
158 Filename of map file written
159 """
Simon Glass8beb11e2019-07-08 14:25:47 -0600160 filename = '%s.map' % self.image_name
Simon Glass3b0c38212018-06-01 09:38:20 -0600161 fname = tools.GetOutputFilename(filename)
162 with open(fname, 'w') as fd:
Simon Glass1be70d22018-07-17 13:25:49 -0600163 print('%8s %8s %8s %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
164 file=fd)
Simon Glass8beb11e2019-07-08 14:25:47 -0600165 section.Entry_section.WriteMap(self, fd, 0)
Simon Glass163ed6c2018-09-14 04:57:36 -0600166 return fname
Simon Glass41b8ba02019-07-08 14:25:43 -0600167
168 def BuildEntryList(self):
169 """List the files in an image
170
171 Returns:
172 List of entry.EntryInfo objects describing all entries in the image
173 """
174 entries = []
Simon Glass8beb11e2019-07-08 14:25:47 -0600175 self.ListEntries(entries, 0)
Simon Glass41b8ba02019-07-08 14:25:43 -0600176 return entries
Simon Glass61f564d2019-07-08 14:25:48 -0600177
178 def FindEntryPath(self, entry_path):
179 """Find an entry at a given path in the image
180
181 Args:
182 entry_path: Path to entry (e.g. /ro-section/u-boot')
183
184 Returns:
185 Entry object corresponding to that past
186
187 Raises:
188 ValueError if no entry found
189 """
190 parts = entry_path.split('/')
191 entries = self.GetEntries()
192 parent = '/'
193 for part in parts:
194 entry = entries.get(part)
195 if not entry:
196 raise ValueError("Entry '%s' not found in '%s'" %
197 (part, parent))
198 parent = entry.GetPath()
199 entries = entry.GetEntries()
200 return entry
201
202 def ReadData(self, decomp=True):
203 return self._data
204
205 def GetListEntries(self, entry_paths):
206 """List the entries in an image
207
208 This decodes the supplied image and returns a list of entries from that
209 image, preceded by a header.
210
211 Args:
212 entry_paths: List of paths to match (each can have wildcards). Only
213 entries whose names match one of these paths will be printed
214
215 Returns:
216 String error message if something went wrong, otherwise
217 3-Tuple:
218 List of EntryInfo objects
219 List of lines, each
220 List of text columns, each a string
221 List of widths of each column
222 """
223 def _EntryToStrings(entry):
224 """Convert an entry to a list of strings, one for each column
225
226 Args:
227 entry: EntryInfo object containing information to output
228
229 Returns:
230 List of strings, one for each field in entry
231 """
232 def _AppendHex(val):
233 """Append a hex value, or an empty string if val is None
234
235 Args:
236 val: Integer value, or None if none
237 """
238 args.append('' if val is None else '>%x' % val)
239
240 args = [' ' * entry.indent + entry.name]
241 _AppendHex(entry.image_pos)
242 _AppendHex(entry.size)
243 args.append(entry.etype)
244 _AppendHex(entry.offset)
245 _AppendHex(entry.uncomp_size)
246 return args
247
248 def _DoLine(lines, line):
249 """Add a line to the output list
250
251 This adds a line (a list of columns) to the output list. It also updates
252 the widths[] array with the maximum width of each column
253
254 Args:
255 lines: List of lines to add to
256 line: List of strings, one for each column
257 """
258 for i, item in enumerate(line):
259 widths[i] = max(widths[i], len(item))
260 lines.append(line)
261
262 def _NameInPaths(fname, entry_paths):
263 """Check if a filename is in a list of wildcarded paths
264
265 Args:
266 fname: Filename to check
267 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
268 'section/u-boot'])
269
270 Returns:
271 True if any wildcard matches the filename (using Unix filename
272 pattern matching, not regular expressions)
273 False if not
274 """
275 for path in entry_paths:
276 if fnmatch.fnmatch(fname, path):
277 return True
278 return False
279
280 entries = self.BuildEntryList()
281
282 # This is our list of lines. Each item in the list is a list of strings, one
283 # for each column
284 lines = []
285 HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
286 'Uncomp-size']
287 num_columns = len(HEADER)
288
289 # This records the width of each column, calculated as the maximum width of
290 # all the strings in that column
291 widths = [0] * num_columns
292 _DoLine(lines, HEADER)
293
294 # We won't print anything unless it has at least this indent. So at the
295 # start we will print nothing, unless a path matches (or there are no
296 # entry paths)
297 MAX_INDENT = 100
298 min_indent = MAX_INDENT
299 path_stack = []
300 path = ''
301 indent = 0
302 selected_entries = []
303 for entry in entries:
304 if entry.indent > indent:
305 path_stack.append(path)
306 elif entry.indent < indent:
307 path_stack.pop()
308 if path_stack:
309 path = path_stack[-1] + '/' + entry.name
310 indent = entry.indent
311
312 # If there are entry paths to match and we are not looking at a
313 # sub-entry of a previously matched entry, we need to check the path
314 if entry_paths and indent <= min_indent:
315 if _NameInPaths(path[1:], entry_paths):
316 # Print this entry and all sub-entries (=higher indent)
317 min_indent = indent
318 else:
319 # Don't print this entry, nor any following entries until we get
320 # a path match
321 min_indent = MAX_INDENT
322 continue
323 _DoLine(lines, _EntryToStrings(entry))
324 selected_entries.append(entry)
325 return selected_entries, lines, widths