binman: Write the original input fdtmap to a file
[platform/kernel/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 os
14 import re
15 import sys
16
17 from entry import Entry
18 from etype import fdtmap
19 from etype import image_header
20 from etype import section
21 import fdt
22 import fdt_util
23 import tools
24 import tout
25
26 class Image(section.Entry_section):
27     """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
34     Attributes:
35         filename: Output filename for image
36         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
39         allow_repack: True to add properties to allow the image to be safely
40             repacked later
41
42     Args:
43         copy_to_orig: Copy offset/size to orig_offset/orig_size after reading
44             from the device tree
45         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.
48     """
49     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
52         self.name = 'main-section'
53         self.image_name = name
54         self._filename = '%s.bin' % self.image_name
55         self.fdtmap_dtb = None
56         self.fdtmap_data = None
57         self.allow_repack = False
58         if not test:
59             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
66         self.allow_repack = fdt_util.GetBool(self._node, 'allow-repack')
67
68     @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]
97         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)
101         dtb.Scan()
102
103         # Return an Image with the associated nodes
104         root = dtb.GetRoot()
105         image = Image('image', root, copy_to_orig=False)
106         image.image_node = fdt_util.GetString(root, 'image-node', 'image')
107         image.fdtmap_dtb = dtb
108         image.fdtmap_data = fdtmap_data
109         image._data = data
110         image._filename = fname
111         image.image_name, _ = os.path.splitext(fname)
112         return image
113
114     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
118     def PackEntries(self):
119         """Pack all entries into the image"""
120         section.Entry_section.Pack(self, 0)
121
122     def SetImagePos(self):
123         # This first section in the image so it starts at 0
124         section.Entry_section.SetImagePos(self, 0)
125
126     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.
130
131         Returns:
132             True if the new data size is OK, False if expansion is needed
133         """
134         sizes_ok = True
135         for entry in self._entries.values():
136             if not entry.ProcessContents():
137                 sizes_ok = False
138                 tout.Debug("Entry '%s' size change" % self._node.path)
139         return sizes_ok
140
141     def WriteSymbols(self):
142         """Write symbol values into binary files for access at run time"""
143         section.Entry_section.WriteSymbols(self, self)
144
145     def BuildSection(self, fd, base_offset):
146         """Write the section to a file"""
147         fd.seek(base_offset)
148         fd.write(self.GetData())
149
150     def BuildImage(self):
151         """Write the image to a file"""
152         fname = tools.GetOutputFilename(self._filename)
153         with open(fname, 'wb') as fd:
154             self.BuildSection(fd, 0)
155
156     def WriteMap(self):
157         """Write a map of the image to a .map file
158
159         Returns:
160             Filename of map file written
161         """
162         filename = '%s.map' % self.image_name
163         fname = tools.GetOutputFilename(filename)
164         with open(fname, 'w') as fd:
165             print('%8s  %8s  %8s  %s' % ('ImagePos', 'Offset', 'Size', 'Name'),
166                   file=fd)
167             section.Entry_section.WriteMap(self, fd, 0)
168         return fname
169
170     def BuildEntryList(self):
171         """List the files in an image
172
173         Returns:
174             List of entry.EntryInfo objects describing all entries in the image
175         """
176         entries = []
177         self.ListEntries(entries, 0)
178         return entries
179
180     def FindEntryPath(self, entry_path):
181         """Find an entry at a given path in the image
182
183         Args:
184             entry_path: Path to entry (e.g. /ro-section/u-boot')
185
186         Returns:
187             Entry object corresponding to that past
188
189         Raises:
190             ValueError if no entry found
191         """
192         parts = entry_path.split('/')
193         entries = self.GetEntries()
194         parent = '/'
195         for part in parts:
196             entry = entries.get(part)
197             if not entry:
198                 raise ValueError("Entry '%s' not found in '%s'" %
199                                  (part, parent))
200             parent = entry.GetPath()
201             entries = entry.GetEntries()
202         return entry
203
204     def ReadData(self, decomp=True):
205         return self._data
206
207     def GetListEntries(self, entry_paths):
208         """List the entries in an image
209
210         This decodes the supplied image and returns a list of entries from that
211         image, preceded by a header.
212
213         Args:
214             entry_paths: List of paths to match (each can have wildcards). Only
215                 entries whose names match one of these paths will be printed
216
217         Returns:
218             String error message if something went wrong, otherwise
219             3-Tuple:
220                 List of EntryInfo objects
221                 List of lines, each
222                     List of text columns, each a string
223                 List of widths of each column
224         """
225         def _EntryToStrings(entry):
226             """Convert an entry to a list of strings, one for each column
227
228             Args:
229                 entry: EntryInfo object containing information to output
230
231             Returns:
232                 List of strings, one for each field in entry
233             """
234             def _AppendHex(val):
235                 """Append a hex value, or an empty string if val is None
236
237                 Args:
238                     val: Integer value, or None if none
239                 """
240                 args.append('' if val is None else '>%x' % val)
241
242             args = ['  ' * entry.indent + entry.name]
243             _AppendHex(entry.image_pos)
244             _AppendHex(entry.size)
245             args.append(entry.etype)
246             _AppendHex(entry.offset)
247             _AppendHex(entry.uncomp_size)
248             return args
249
250         def _DoLine(lines, line):
251             """Add a line to the output list
252
253             This adds a line (a list of columns) to the output list. It also updates
254             the widths[] array with the maximum width of each column
255
256             Args:
257                 lines: List of lines to add to
258                 line: List of strings, one for each column
259             """
260             for i, item in enumerate(line):
261                 widths[i] = max(widths[i], len(item))
262             lines.append(line)
263
264         def _NameInPaths(fname, entry_paths):
265             """Check if a filename is in a list of wildcarded paths
266
267             Args:
268                 fname: Filename to check
269                 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
270                                                              'section/u-boot'])
271
272             Returns:
273                 True if any wildcard matches the filename (using Unix filename
274                     pattern matching, not regular expressions)
275                 False if not
276             """
277             for path in entry_paths:
278                 if fnmatch.fnmatch(fname, path):
279                     return True
280             return False
281
282         entries = self.BuildEntryList()
283
284         # This is our list of lines. Each item in the list is a list of strings, one
285         # for each column
286         lines = []
287         HEADER = ['Name', 'Image-pos', 'Size', 'Entry-type', 'Offset',
288                   'Uncomp-size']
289         num_columns = len(HEADER)
290
291         # This records the width of each column, calculated as the maximum width of
292         # all the strings in that column
293         widths = [0] * num_columns
294         _DoLine(lines, HEADER)
295
296         # We won't print anything unless it has at least this indent. So at the
297         # start we will print nothing, unless a path matches (or there are no
298         # entry paths)
299         MAX_INDENT = 100
300         min_indent = MAX_INDENT
301         path_stack = []
302         path = ''
303         indent = 0
304         selected_entries = []
305         for entry in entries:
306             if entry.indent > indent:
307                 path_stack.append(path)
308             elif entry.indent < indent:
309                 path_stack.pop()
310             if path_stack:
311                 path = path_stack[-1] + '/' + entry.name
312             indent = entry.indent
313
314             # If there are entry paths to match and we are not looking at a
315             # sub-entry of a previously matched entry, we need to check the path
316             if entry_paths and indent <= min_indent:
317                 if _NameInPaths(path[1:], entry_paths):
318                     # Print this entry and all sub-entries (=higher indent)
319                     min_indent = indent
320                 else:
321                     # Don't print this entry, nor any following entries until we get
322                     # a path match
323                     min_indent = MAX_INDENT
324                     continue
325             _DoLine(lines, _EntryToStrings(entry))
326             selected_entries.append(entry)
327         return selected_entries, lines, widths