binman: Store image fdtmap when loading from a file
[oweals/u-boot.git] / tools / binman / image.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Class for an image, the output of binman
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import fnmatch
12 from operator import attrgetter
13 import re
14 import sys
15
16 from entry import Entry
17 from etype import fdtmap
18 from etype import image_header
19 from etype import section
20 import fdt
21 import fdt_util
22 import tools
23 import tout
24
25 class Image(section.Entry_section):
26     """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
33     Attributes:
34         filename: Output filename for image
35         image_node: Name of node containing the description for this image
36         fdtmap_dtb: Fdt object for the fdtmap when loading from a file
37         fdtmap_data: Contents of the fdtmap when loading from a file
38
39     Args:
40         test: True if this is being called from a test of Images. This this case
41             there is no device tree defining the structure of the section, so
42             we create a section manually.
43     """
44     def __init__(self, name, node, test=False):
45         self.image = self
46         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
50         self.fdtmap_dtb = None
51         self.fdtmap_data = None
52         if not test:
53             filename = fdt_util.GetString(self._node, 'filename')
54             if filename:
55                 self._filename = filename
56
57     @classmethod
58     def FromFile(cls, fname):
59         """Convert an image file into an Image for use in binman
60
61         Args:
62             fname: Filename of image file to read
63
64         Returns:
65             Image object on success
66
67         Raises:
68             ValueError if something goes wrong
69         """
70         data = tools.ReadFile(fname)
71         size = len(data)
72
73         # First look for an image header
74         pos = image_header.LocateHeaderOffset(data)
75         if pos is None:
76             # Look for the FDT map
77             pos = fdtmap.LocateFdtmap(data)
78         if pos is None:
79             raise ValueError('Cannot find FDT map in image')
80
81         # We don't know the FDT size, so check its header first
82         probe_dtb = fdt.Fdt.FromData(
83             data[pos + fdtmap.FDTMAP_HDR_LEN:pos + 256])
84         dtb_size = probe_dtb.GetFdtObj().totalsize()
85         fdtmap_data = data[pos:pos + dtb_size + fdtmap.FDTMAP_HDR_LEN]
86         dtb = fdt.Fdt.FromData(fdtmap_data[fdtmap.FDTMAP_HDR_LEN:])
87         dtb.Scan()
88
89         # Return an Image with the associated nodes
90         root = dtb.GetRoot()
91         image = Image('image', root)
92         image.image_node = fdt_util.GetString(root, 'image-node', 'image')
93         image.fdtmap_dtb = dtb
94         image.fdtmap_data = fdtmap_data
95         image._data = data
96         return image
97
98     def Raise(self, msg):
99         """Convenience function to raise an error referencing an image"""
100         raise ValueError("Image '%s': %s" % (self._node.path, msg))
101
102     def PackEntries(self):
103         """Pack all entries into the image"""
104         section.Entry_section.Pack(self, 0)
105
106     def SetImagePos(self):
107         # This first section in the image so it starts at 0
108         section.Entry_section.SetImagePos(self, 0)
109
110     def ProcessEntryContents(self):
111         """Call the ProcessContents() method for each entry
112
113         This is intended to adjust the contents as needed by the entry type.
114
115         Returns:
116             True if the new data size is OK, False if expansion is needed
117         """
118         sizes_ok = True
119         for entry in self._entries.values():
120             if not entry.ProcessContents():
121                 sizes_ok = False
122                 tout.Debug("Entry '%s' size change" % self._node.path)
123         return sizes_ok
124
125     def WriteSymbols(self):
126         """Write symbol values into binary files for access at run time"""
127         section.Entry_section.WriteSymbols(self, self)
128
129     def BuildSection(self, fd, base_offset):
130         """Write the section to a file"""
131         fd.seek(base_offset)
132         fd.write(self.GetData())
133
134     def BuildImage(self):
135         """Write the image to a file"""
136         fname = tools.GetOutputFilename(self._filename)
137         with open(fname, 'wb') as fd:
138             self.BuildSection(fd, 0)
139
140     def WriteMap(self):
141         """Write a map of the image to a .map file
142
143         Returns:
144             Filename of map file written
145         """
146         filename = '%s.map' % self.image_name
147         fname = tools.GetOutputFilename(filename)
148         with open(fname, 'w') as fd:
149             print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
150                   file=fd)
151             section.Entry_section.WriteMap(self, fd, 0)
152         return fname
153
154     def BuildEntryList(self):
155         """List the files in an image
156
157         Returns:
158             List of entry.EntryInfo objects describing all entries in the image
159         """
160         entries = []
161         self.ListEntries(entries, 0)
162         return entries
163
164     def FindEntryPath(self, entry_path):
165         """Find an entry at a given path in the image
166
167         Args:
168             entry_path: Path to entry (e.g. /ro-section/u-boot')
169
170         Returns:
171             Entry object corresponding to that past
172
173         Raises:
174             ValueError if no entry found
175         """
176         parts = entry_path.split('/')
177         entries = self.GetEntries()
178         parent = '/'
179         for part in parts:
180             entry = entries.get(part)
181             if not entry:
182                 raise ValueError("Entry '%s' not found in '%s'" %
183                                  (part, parent))
184             parent = entry.GetPath()
185             entries = entry.GetEntries()
186         return entry
187
188     def ReadData(self, decomp=True):
189         return self._data
190
191     def GetListEntries(self, entry_paths):
192         """List the entries in an image
193
194         This decodes the supplied image and returns a list of entries from that
195         image, preceded by a header.
196
197         Args:
198             entry_paths: List of paths to match (each can have wildcards). Only
199                 entries whose names match one of these paths will be printed
200
201         Returns:
202             String error message if something went wrong, otherwise
203             3-Tuple:
204                 List of EntryInfo objects
205                 List of lines, each
206                     List of text columns, each a string
207                 List of widths of each column
208         """
209         def _EntryToStrings(entry):
210             """Convert an entry to a list of strings, one for each column
211
212             Args:
213                 entry: EntryInfo object containing information to output
214
215             Returns:
216                 List of strings, one for each field in entry
217             """
218             def _AppendHex(val):
219                 """Append a hex value, or an empty string if val is None
220
221                 Args:
222                     val: Integer value, or None if none
223                 """
224                 args.append('' if val is None else '>%x' % val)
225
226             args = ['  ' * entry.indent + entry.name]
227             _AppendHex(entry.image_pos)
228             _AppendHex(entry.size)
229             args.append(entry.etype)
230             _AppendHex(entry.offset)
231             _AppendHex(entry.uncomp_size)
232             return args
233
234         def _DoLine(lines, line):
235             """Add a line to the output list
236
237             This adds a line (a list of columns) to the output list. It also updates
238             the widths[] array with the maximum width of each column
239
240             Args:
241                 lines: List of lines to add to
242                 line: List of strings, one for each column
243             """
244             for i, item in enumerate(line):
245                 widths[i] = max(widths[i], len(item))
246             lines.append(line)
247
248         def _NameInPaths(fname, entry_paths):
249             """Check if a filename is in a list of wildcarded paths
250
251             Args:
252                 fname: Filename to check
253                 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
254                                                              'section/u-boot'])
255
256             Returns:
257                 True if any wildcard matches the filename (using Unix filename
258                     pattern matching, not regular expressions)
259                 False if not
260             """
261             for path in entry_paths:
262                 if fnmatch.fnmatch(fname, path):
263                     return True
264             return False
265
266         entries = self.BuildEntryList()
267
268         # This is our list of lines. Each item in the list is a list of strings, one
269         # for each column
270         lines = []
271         HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
272                   'Uncomp-size']
273         num_columns = len(HEADER)
274
275         # This records the width of each column, calculated as the maximum width of
276         # all the strings in that column
277         widths = [0] * num_columns
278         _DoLine(lines, HEADER)
279
280         # We won't print anything unless it has at least this indent. So at the
281         # start we will print nothing, unless a path matches (or there are no
282         # entry paths)
283         MAX_INDENT = 100
284         min_indent = MAX_INDENT
285         path_stack = []
286         path = ''
287         indent = 0
288         selected_entries = []
289         for entry in entries:
290             if entry.indent > indent:
291                 path_stack.append(path)
292             elif entry.indent < indent:
293                 path_stack.pop()
294             if path_stack:
295                 path = path_stack[-1] + '/' + entry.name
296             indent = entry.indent
297
298             # If there are entry paths to match and we are not looking at a
299             # sub-entry of a previously matched entry, we need to check the path
300             if entry_paths and indent <= min_indent:
301                 if _NameInPaths(path[1:], entry_paths):
302                     # Print this entry and all sub-entries (=higher indent)
303                     min_indent = indent
304                 else:
305                     # Don't print this entry, nor any following entries until we get
306                     # a path match
307                     min_indent = MAX_INDENT
308                     continue
309             _DoLine(lines, _EntryToStrings(entry))
310             selected_entries.append(entry)
311         return selected_entries, lines, widths