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