binman: Allow reading an entry from an image
[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 Binman(args):
122     """The main control code for binman
123
124     This assumes that help and test options have already been dealt with. It
125     deals with the core task of building images.
126
127     Args:
128         args: Command line arguments Namespace object
129     """
130     global images
131
132     if args.full_help:
133         pager = os.getenv('PAGER')
134         if not pager:
135             pager = 'more'
136         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
137                             'README')
138         command.Run(pager, fname)
139         return 0
140
141     if args.cmd == 'ls':
142         ListEntries(args.image, args.paths)
143         return 0
144
145     # Try to figure out which device tree contains our image description
146     if args.dt:
147         dtb_fname = args.dt
148     else:
149         board = args.board
150         if not board:
151             raise ValueError('Must provide a board to process (use -b <board>)')
152         board_pathname = os.path.join(args.build_dir, board)
153         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
154         if not args.indir:
155             args.indir = ['.']
156         args.indir.append(board_pathname)
157
158     try:
159         # Import these here in case libfdt.py is not available, in which case
160         # the above help option still works.
161         import fdt
162         import fdt_util
163
164         tout.Init(args.verbosity)
165         elf.debug = args.debug
166         cbfs_util.VERBOSE = args.verbosity > 2
167         state.use_fake_dtb = args.fake_dtb
168         try:
169             tools.SetInputDirs(args.indir)
170             tools.PrepareOutputDir(args.outdir, args.preserve)
171             tools.SetToolPaths(args.toolpath)
172             state.SetEntryArgs(args.entry_arg)
173
174             # Get the device tree ready by compiling it and copying the compiled
175             # output into a file in our output directly. Then scan it for use
176             # in binman.
177             dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
178             fname = tools.GetOutputFilename('u-boot.dtb.out')
179             tools.WriteFile(fname, tools.ReadFile(dtb_fname))
180             dtb = fdt.FdtScan(fname)
181
182             node = _FindBinmanNode(dtb)
183             if not node:
184                 raise ValueError("Device tree '%s' does not have a 'binman' "
185                                  "node" % dtb_fname)
186
187             images = _ReadImageDesc(node)
188
189             if args.image:
190                 skip = []
191                 new_images = OrderedDict()
192                 for name, image in images.items():
193                     if name in args.image:
194                         new_images[name] = image
195                     else:
196                         skip.append(name)
197                 images = new_images
198                 if skip and args.verbosity >= 2:
199                     print('Skipping images: %s' % ', '.join(skip))
200
201             state.Prepare(images, dtb)
202
203             # Prepare the device tree by making sure that any missing
204             # properties are added (e.g. 'pos' and 'size'). The values of these
205             # may not be correct yet, but we add placeholders so that the
206             # size of the device tree is correct. Later, in
207             # SetCalculatedProperties() we will insert the correct values
208             # without changing the device-tree size, thus ensuring that our
209             # entry offsets remain the same.
210             for image in images.values():
211                 image.ExpandEntries()
212                 if args.update_fdt:
213                     image.AddMissingProperties()
214                 image.ProcessFdt(dtb)
215
216             for dtb_item in state.GetFdts():
217                 dtb_item.Sync(auto_resize=True)
218                 dtb_item.Pack()
219                 dtb_item.Flush()
220
221             for image in images.values():
222                 # Perform all steps for this image, including checking and
223                 # writing it. This means that errors found with a later
224                 # image will be reported after earlier images are already
225                 # completed and written, but that does not seem important.
226                 image.GetEntryContents()
227                 image.GetEntryOffsets()
228
229                 # We need to pack the entries to figure out where everything
230                 # should be placed. This sets the offset/size of each entry.
231                 # However, after packing we call ProcessEntryContents() which
232                 # may result in an entry changing size. In that case we need to
233                 # do another pass. Since the device tree often contains the
234                 # final offset/size information we try to make space for this in
235                 # AddMissingProperties() above. However, if the device is
236                 # compressed we cannot know this compressed size in advance,
237                 # since changing an offset from 0x100 to 0x104 (for example) can
238                 # alter the compressed size of the device tree. So we need a
239                 # third pass for this.
240                 passes = 3
241                 for pack_pass in range(passes):
242                     try:
243                         image.PackEntries()
244                         image.CheckSize()
245                         image.CheckEntries()
246                     except Exception as e:
247                         if args.map:
248                             fname = image.WriteMap()
249                             print("Wrote map file '%s' to show errors"  % fname)
250                         raise
251                     image.SetImagePos()
252                     if args.update_fdt:
253                         image.SetCalculatedProperties()
254                         for dtb_item in state.GetFdts():
255                             dtb_item.Sync()
256                     sizes_ok = image.ProcessEntryContents()
257                     if sizes_ok:
258                         break
259                     image.ResetForPack()
260                 if not sizes_ok:
261                     image.Raise('Entries expanded after packing (tried %s passes)' %
262                                 passes)
263
264                 image.WriteSymbols()
265                 image.BuildImage()
266                 if args.map:
267                     image.WriteMap()
268
269             # Write the updated FDTs to our output files
270             for dtb_item in state.GetFdts():
271                 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
272
273         finally:
274             tools.FinaliseOutputDir()
275     finally:
276         tout.Uninit()
277
278     return 0