dtoc: Allow syncing of the device tree back 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 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 WriteEntryDocs(modules, test_missing=None):
96     from entry import Entry
97     Entry.WriteDocs(modules, test_missing)
98
99 def Binman(options, args):
100     """The main control code for binman
101
102     This assumes that help and test options have already been dealt with. It
103     deals with the core task of building images.
104
105     Args:
106         options: Command line options object
107         args: Command line arguments (list of strings)
108     """
109     global images
110
111     if options.full_help:
112         pager = os.getenv('PAGER')
113         if not pager:
114             pager = 'more'
115         fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
116                             'README')
117         command.Run(pager, fname)
118         return 0
119
120     # Try to figure out which device tree contains our image description
121     if options.dt:
122         dtb_fname = options.dt
123     else:
124         board = options.board
125         if not board:
126             raise ValueError('Must provide a board to process (use -b <board>)')
127         board_pathname = os.path.join(options.build_dir, board)
128         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
129         if not options.indir:
130             options.indir = ['.']
131         options.indir.append(board_pathname)
132
133     try:
134         # Import these here in case libfdt.py is not available, in which case
135         # the above help option still works.
136         import fdt
137         import fdt_util
138
139         tout.Init(options.verbosity)
140         elf.debug = options.debug
141         try:
142             tools.SetInputDirs(options.indir)
143             tools.PrepareOutputDir(options.outdir, options.preserve)
144             SetEntryArgs(options.entry_arg)
145
146             # Get the device tree ready by compiling it and copying the compiled
147             # output into a file in our output directly. Then scan it for use
148             # in binman.
149             dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
150             fname = tools.GetOutputFilename('u-boot-out.dtb')
151             with open(dtb_fname) as infd:
152                 with open(fname, 'wb') as outfd:
153                     outfd.write(infd.read())
154             dtb = fdt.FdtScan(fname)
155
156             # Note the file so that GetFdt() can find it
157             fdt_files['u-boot.dtb'] = dtb
158             node = _FindBinmanNode(dtb)
159             if not node:
160                 raise ValueError("Device tree '%s' does not have a 'binman' "
161                                  "node" % dtb_fname)
162
163             images = _ReadImageDesc(node)
164
165             if options.image:
166                 skip = []
167                 for name, image in images.iteritems():
168                     if name not in options.image:
169                         del images[name]
170                         skip.append(name)
171                 if skip:
172                     print 'Skipping images: %s\n' % ', '.join(skip)
173
174             # Prepare the device tree by making sure that any missing
175             # properties are added (e.g. 'pos' and 'size'). The values of these
176             # may not be correct yet, but we add placeholders so that the
177             # size of the device tree is correct. Later, in
178             # SetCalculatedProperties() we will insert the correct values
179             # without changing the device-tree size, thus ensuring that our
180             # entry offsets remain the same.
181             for image in images.values():
182                 if options.update_fdt:
183                     image.AddMissingProperties()
184                 image.ProcessFdt(dtb)
185
186             dtb.Sync(auto_resize=True)
187             dtb.Pack()
188             dtb.Flush()
189
190             for image in images.values():
191                 # Perform all steps for this image, including checking and
192                 # writing it. This means that errors found with a later
193                 # image will be reported after earlier images are already
194                 # completed and written, but that does not seem important.
195                 image.GetEntryContents()
196                 image.GetEntryOffsets()
197                 image.PackEntries()
198                 image.CheckSize()
199                 image.CheckEntries()
200                 image.SetImagePos()
201                 if options.update_fdt:
202                     image.SetCalculatedProperties()
203                     dtb.Sync()
204                 image.ProcessEntryContents()
205                 image.WriteSymbols()
206                 image.BuildImage()
207                 if options.map:
208                     image.WriteMap()
209             with open(fname, 'wb') as outfd:
210                 outfd.write(dtb.GetContents())
211         finally:
212             tools.FinaliseOutputDir()
213     finally:
214         tout.Uninit()
215
216     return 0