binman: Write the original input fdtmap to a file
[oweals/u-boot.git] / tools / binman / control.py
1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
4 #
5 # Creates binary images from input files controlled by a description
6 #
7
8 from __future__ import print_function
9
10 from collections import OrderedDict
11 import os
12 import sys
13 import tools
14
15 import cbfs_util
16 import command
17 import elf
18 from image import Image
19 import state
20 import tout
21
22 # List of images we plan to create
23 # Make this global so that it can be referenced from tests
24 images = OrderedDict()
25
26 def _ReadImageDesc(binman_node):
27     """Read the image descriptions from the /binman node
28
29     This normally produces a single Image object called 'image'. But if
30     multiple images are present, they will all be returned.
31
32     Args:
33         binman_node: Node object of the /binman node
34     Returns:
35         OrderedDict of Image objects, each of which describes an image
36     """
37     images = OrderedDict()
38     if 'multiple-images' in binman_node.props:
39         for node in binman_node.subnodes:
40             images[node.name] = Image(node.name, node)
41     else:
42         images['image'] = Image('image', binman_node)
43     return images
44
45 def _FindBinmanNode(dtb):
46     """Find the 'binman' node in the device tree
47
48     Args:
49         dtb: Fdt object to scan
50     Returns:
51         Node object of /binman node, or None if not found
52     """
53     for node in dtb.GetRoot().subnodes:
54         if node.name == 'binman':
55             return node
56     return None
57
58 def WriteEntryDocs(modules, test_missing=None):
59     """Write out documentation for all entries
60
61     Args:
62         modules: List of Module objects to get docs for
63         test_missing: Used for testing only, to force an entry's documeentation
64             to show as missing even if it is present. Should be set to None in
65             normal use.
66     """
67     from entry import Entry
68     Entry.WriteDocs(modules, test_missing)
69
70
71 def ListEntries(image_fname, entry_paths):
72     """List the entries in an image
73
74     This decodes the supplied image and displays a table of entries from that
75     image, preceded by a header.
76
77     Args:
78         image_fname: Image filename to process
79         entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
80                                                      'section/u-boot'])
81     """
82     image = Image.FromFile(image_fname)
83
84     entries, lines, widths = image.GetListEntries(entry_paths)
85
86     num_columns = len(widths)
87     for linenum, line in enumerate(lines):
88         if linenum == 1:
89             # Print header line
90             print('-' * (sum(widths) + num_columns * 2))
91         out = ''
92         for i, item in enumerate(line):
93             width = -widths[i]
94             if item.startswith('>'):
95                 width = -width
96                 item = item[1:]
97             txt = '%*s  ' % (width, item)
98             out += txt
99         print(out.rstrip())
100
101
102 def ReadEntry(image_fname, entry_path, decomp=True):
103     """Extract an entry from an image
104
105     This extracts the data from a particular entry in an image
106
107     Args:
108         image_fname: Image filename to process
109         entry_path: Path to entry to extract
110         decomp: True to return uncompressed data, if the data is compress
111             False to return the raw data
112
113     Returns:
114         data extracted from the entry
115     """
116     image = Image.FromFile(image_fname)
117     entry = image.FindEntryPath(entry_path)
118     return entry.ReadData(decomp)
119
120
121 def WriteEntry(image_fname, entry_path, data, decomp=True):
122     """Replace an entry in an image
123
124     This replaces the data in a particular entry in an image. This size of the
125     new data must match the size of the old data
126
127     Args:
128         image_fname: Image filename to process
129         entry_path: Path to entry to extract
130         data: Data to replace with
131         decomp: True to compress the data if needed, False if data is
132             already compressed so should be used as is
133     """
134     tout.Info("WriteEntry '%s', file '%s'" % (entry_path, image_fname))
135     image = Image.FromFile(image_fname)
136     entry = image.FindEntryPath(entry_path)
137     state.PrepareFromLoadedData(image)
138     image.LoadData()
139     tout.Info('Writing data to %s' % entry.GetPath())
140     if not entry.WriteData(data, decomp):
141         entry.Raise('Entry data size does not match, but resize is disabled')
142     tout.Info('Processing image')
143     ProcessImage(image, update_fdt=True, write_map=False, get_contents=False)
144     tout.Info('WriteEntry done')
145
146
147 def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
148                    decomp=True):
149     """Extract the data from one or more entries and write it to files
150
151     Args:
152         image_fname: Image filename to process
153         output_fname: Single output filename to use if extracting one file, None
154             otherwise
155         outdir: Output directory to use (for any number of files), else None
156         entry_paths: List of entry paths to extract
157         decomp: True to compress the entry data
158
159     Returns:
160         List of EntryInfo records that were written
161     """
162     image = Image.FromFile(image_fname)
163
164     # Output an entry to a single file, as a special case
165     if output_fname:
166         if not entry_paths:
167             raise ValueError('Must specify an entry path to write with -o')
168         if len(entry_paths) != 1:
169             raise ValueError('Must specify exactly one entry path to write with -o')
170         entry = image.FindEntryPath(entry_paths[0])
171         data = entry.ReadData(decomp)
172         tools.WriteFile(output_fname, data)
173         tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
174         return
175
176     # Otherwise we will output to a path given by the entry path of each entry.
177     # This means that entries will appear in subdirectories if they are part of
178     # a sub-section.
179     einfos = image.GetListEntries(entry_paths)[0]
180     tout.Notice('%d entries match and will be written' % len(einfos))
181     for einfo in einfos:
182         entry = einfo.entry
183         data = entry.ReadData(decomp)
184         path = entry.GetPath()[1:]
185         fname = os.path.join(outdir, path)
186
187         # If this entry has children, create a directory for it and put its
188         # data in a file called 'root' in that directory
189         if entry.GetEntries():
190             if not os.path.exists(fname):
191                 os.makedirs(fname)
192             fname = os.path.join(fname, 'root')
193         tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
194         tools.WriteFile(fname, data)
195     return einfos
196
197
198 def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt):
199     """Prepare the images to be processed and select the device tree
200
201     This function:
202     - reads in the device tree
203     - finds and scans the binman node to create all entries
204     - selects which images to build
205     - Updates the device tress with placeholder properties for offset,
206         image-pos, etc.
207
208     Args:
209         dtb_fname: Filename of the device tree file to use (.dts or .dtb)
210         selected_images: List of images to output, or None for all
211         update_fdt: True to update the FDT wth entry offsets, etc.
212     """
213     # Import these here in case libfdt.py is not available, in which case
214     # the above help option still works.
215     import fdt
216     import fdt_util
217     global images
218
219     # Get the device tree ready by compiling it and copying the compiled
220     # output into a file in our output directly. Then scan it for use
221     # in binman.
222     dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
223     fname = tools.GetOutputFilename('u-boot.dtb.out')
224     tools.WriteFile(fname, tools.ReadFile(dtb_fname))
225     dtb = fdt.FdtScan(fname)
226
227     node = _FindBinmanNode(dtb)
228     if not node:
229         raise ValueError("Device tree '%s' does not have a 'binman' "
230                             "node" % dtb_fname)
231
232     images = _ReadImageDesc(node)
233
234     if select_images:
235         skip = []
236         new_images = OrderedDict()
237         for name, image in images.items():
238             if name in select_images:
239                 new_images[name] = image
240             else:
241                 skip.append(name)
242         images = new_images
243         tout.Notice('Skipping images: %s' % ', '.join(skip))
244
245     state.Prepare(images, dtb)
246
247     # Prepare the device tree by making sure that any missing
248     # properties are added (e.g. 'pos' and 'size'). The values of these
249     # may not be correct yet, but we add placeholders so that the
250     # size of the device tree is correct. Later, in
251     # SetCalculatedProperties() we will insert the correct values
252     # without changing the device-tree size, thus ensuring that our
253     # entry offsets remain the same.
254     for image in images.values():
255         image.ExpandEntries()
256         if update_fdt:
257             image.AddMissingProperties()
258         image.ProcessFdt(dtb)
259
260     for dtb_item in state.GetAllFdts():
261         dtb_item.Sync(auto_resize=True)
262         dtb_item.Pack()
263         dtb_item.Flush()
264     return images
265
266
267 def ProcessImage(image, update_fdt, write_map, get_contents=True):
268     """Perform all steps for this image, including checking and # writing it.
269
270     This means that errors found with a later image will be reported after
271     earlier images are already completed and written, but that does not seem
272     important.
273
274     Args:
275         image: Image to process
276         update_fdt: True to update the FDT wth entry offsets, etc.
277         write_map: True to write a map file
278         get_contents: True to get the image contents from files, etc., False if
279             the contents is already present
280     """
281     if get_contents:
282         image.GetEntryContents()
283     image.GetEntryOffsets()
284
285     # We need to pack the entries to figure out where everything
286     # should be placed. This sets the offset/size of each entry.
287     # However, after packing we call ProcessEntryContents() which
288     # may result in an entry changing size. In that case we need to
289     # do another pass. Since the device tree often contains the
290     # final offset/size information we try to make space for this in
291     # AddMissingProperties() above. However, if the device is
292     # compressed we cannot know this compressed size in advance,
293     # since changing an offset from 0x100 to 0x104 (for example) can
294     # alter the compressed size of the device tree. So we need a
295     # third pass for this.
296     passes = 3
297     for pack_pass in range(passes):
298         try:
299             image.PackEntries()
300             image.CheckSize()
301             image.CheckEntries()
302         except Exception as e:
303             if write_map:
304                 fname = image.WriteMap()
305                 print("Wrote map file '%s' to show errors"  % fname)
306             raise
307         image.SetImagePos()
308         if update_fdt:
309             image.SetCalculatedProperties()
310             for dtb_item in state.GetAllFdts():
311                 dtb_item.Sync()
312         sizes_ok = image.ProcessEntryContents()
313         if sizes_ok:
314             break
315         image.ResetForPack()
316     if not sizes_ok:
317         image.Raise('Entries expanded after packing (tried %s passes)' %
318                     passes)
319
320     image.WriteSymbols()
321     image.BuildImage()
322     if write_map:
323         image.WriteMap()
324
325
326 def Binman(args):
327     """The main control code for binman
328
329     This assumes that help and test options have already been dealt with. It
330     deals with the core task of building images.
331
332     Args:
333         args: Command line arguments Namespace object
334     """
335     if args.full_help:
336         pager = os.getenv('PAGER')
337         if not pager:
338             pager = 'more'
339         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
340                             'README')
341         command.Run(pager, fname)
342         return 0
343
344     if args.cmd == 'ls':
345         try:
346             tools.PrepareOutputDir(None)
347             ListEntries(args.image, args.paths)
348         finally:
349             tools.FinaliseOutputDir()
350         return 0
351
352     if args.cmd == 'extract':
353         try:
354             tools.PrepareOutputDir(None)
355             ExtractEntries(args.image, args.filename, args.outdir, args.paths,
356                            not args.uncompressed)
357         finally:
358             tools.FinaliseOutputDir()
359         return 0
360
361     # Try to figure out which device tree contains our image description
362     if args.dt:
363         dtb_fname = args.dt
364     else:
365         board = args.board
366         if not board:
367             raise ValueError('Must provide a board to process (use -b <board>)')
368         board_pathname = os.path.join(args.build_dir, board)
369         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
370         if not args.indir:
371             args.indir = ['.']
372         args.indir.append(board_pathname)
373
374     try:
375         tout.Init(args.verbosity)
376         elf.debug = args.debug
377         cbfs_util.VERBOSE = args.verbosity > 2
378         state.use_fake_dtb = args.fake_dtb
379         try:
380             tools.SetInputDirs(args.indir)
381             tools.PrepareOutputDir(args.outdir, args.preserve)
382             tools.SetToolPaths(args.toolpath)
383             state.SetEntryArgs(args.entry_arg)
384
385             images = PrepareImagesAndDtbs(dtb_fname, args.image,
386                                           args.update_fdt)
387             for image in images.values():
388                 ProcessImage(image, args.update_fdt, args.map)
389
390             # Write the updated FDTs to our output files
391             for dtb_item in state.GetAllFdts():
392                 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
393
394         finally:
395             tools.FinaliseOutputDir()
396     finally:
397         tout.Uninit()
398
399     return 0