binman: Allow help to work without libfdt
[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 collections import OrderedDict
9 import os
10 import re
11 import sys
12 import tools
13
14 import command
15 import elf
16 from image import Image
17 import tout
18
19 # List of images we plan to create
20 # Make this global so that it can be referenced from tests
21 images = OrderedDict()
22
23 # Records the device-tree files known to binman, keyed by filename (e.g.
24 # 'u-boot-spl.dtb')
25 fdt_files = {}
26
27 # Arguments passed to binman to provide arguments to entries
28 entry_args = {}
29
30
31 def _ReadImageDesc(binman_node):
32     """Read the image descriptions from the /binman node
33
34     This normally produces a single Image object called 'image'. But if
35     multiple images are present, they will all be returned.
36
37     Args:
38         binman_node: Node object of the /binman node
39     Returns:
40         OrderedDict of Image objects, each of which describes an image
41     """
42     images = OrderedDict()
43     if 'multiple-images' in binman_node.props:
44         for node in binman_node.subnodes:
45             images[node.name] = Image(node.name, node)
46     else:
47         images['image'] = Image('image', binman_node)
48     return images
49
50 def _FindBinmanNode(dtb):
51     """Find the 'binman' node in the device tree
52
53     Args:
54         dtb: Fdt object to scan
55     Returns:
56         Node object of /binman node, or None if not found
57     """
58     for node in dtb.GetRoot().subnodes:
59         if node.name == 'binman':
60             return node
61     return None
62
63 def GetFdt(fname):
64     """Get the Fdt object for a particular device-tree filename
65
66     Binman keeps track of at least one device-tree file called u-boot.dtb but
67     can also have others (e.g. for SPL). This function looks up the given
68     filename and returns the associated Fdt object.
69
70     Args:
71         fname: Filename to look up (e.g. 'u-boot.dtb').
72
73     Returns:
74         Fdt object associated with the filename
75     """
76     return fdt_files[fname]
77
78 def GetFdtPath(fname):
79     return fdt_files[fname]._fname
80
81 def SetEntryArgs(args):
82     global entry_args
83
84     entry_args = {}
85     if args:
86         for arg in args:
87             m = re.match('([^=]*)=(.*)', arg)
88             if not m:
89                 raise ValueError("Invalid entry arguemnt '%s'" % arg)
90             entry_args[m.group(1)] = m.group(2)
91
92 def GetEntryArg(name):
93     return entry_args.get(name)
94
95 def Binman(options, args):
96     """The main control code for binman
97
98     This assumes that help and test options have already been dealt with. It
99     deals with the core task of building images.
100
101     Args:
102         options: Command line options object
103         args: Command line arguments (list of strings)
104     """
105     global images
106
107     if options.full_help:
108         pager = os.getenv('PAGER')
109         if not pager:
110             pager = 'more'
111         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
112                             'README')
113         command.Run(pager, fname)
114         return 0
115
116     # Try to figure out which device tree contains our image description
117     if options.dt:
118         dtb_fname = options.dt
119     else:
120         board = options.board
121         if not board:
122             raise ValueError('Must provide a board to process (use -b <board>)')
123         board_pathname = os.path.join(options.build_dir, board)
124         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
125         if not options.indir:
126             options.indir = ['.']
127         options.indir.append(board_pathname)
128
129     try:
130         # Import these here in case libfdt.py is not available, in which case
131         # the above help option still works.
132         import fdt
133         import fdt_util
134
135         tout.Init(options.verbosity)
136         elf.debug = options.debug
137         try:
138             tools.SetInputDirs(options.indir)
139             tools.PrepareOutputDir(options.outdir, options.preserve)
140             SetEntryArgs(options.entry_arg)
141
142             # Get the device tree ready by compiling it and copying the compiled
143             # output into a file in our output directly. Then scan it for use
144             # in binman.
145             dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
146             fname = tools.GetOutputFilename('u-boot-out.dtb')
147             with open(dtb_fname) as infd:
148                 with open(fname, 'wb') as outfd:
149                     outfd.write(infd.read())
150             dtb = fdt.FdtScan(fname)
151
152             # Note the file so that GetFdt() can find it
153             fdt_files['u-boot.dtb'] = dtb
154             node = _FindBinmanNode(dtb)
155             if not node:
156                 raise ValueError("Device tree '%s' does not have a 'binman' "
157                                  "node" % dtb_fname)
158
159             images = _ReadImageDesc(node)
160
161             # Prepare the device tree by making sure that any missing
162             # properties are added (e.g. 'pos' and 'size'). The values of these
163             # may not be correct yet, but we add placeholders so that the
164             # size of the device tree is correct. Later, in
165             # SetCalculatedProperties() we will insert the correct values
166             # without changing the device-tree size, thus ensuring that our
167             # entry offsets remain the same.
168             for image in images.values():
169                 if options.update_fdt:
170                     image.AddMissingProperties()
171                 image.ProcessFdt(dtb)
172
173             dtb.Pack()
174             dtb.Flush()
175
176             for image in images.values():
177                 # Perform all steps for this image, including checking and
178                 # writing it. This means that errors found with a later
179                 # image will be reported after earlier images are already
180                 # completed and written, but that does not seem important.
181                 image.GetEntryContents()
182                 image.GetEntryOffsets()
183                 image.PackEntries()
184                 image.CheckSize()
185                 image.CheckEntries()
186                 image.SetImagePos()
187                 if options.update_fdt:
188                     image.SetCalculatedProperties()
189                 image.ProcessEntryContents()
190                 image.WriteSymbols()
191                 image.BuildImage()
192                 if options.map:
193                     image.WriteMap()
194             with open(fname, 'wb') as outfd:
195                 outfd.write(dtb.GetContents())
196         finally:
197             tools.FinaliseOutputDir()
198     finally:
199         tout.Uninit()
200
201     return 0