Merge tag 'xilinx-for-v2023.07-rc1' of https://source.denx.de/u-boot/custodians/u...
[platform/kernel/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 glob
10 import importlib.resources
11 import os
12 import pkg_resources
13 import re
14
15 import sys
16
17 from binman import bintool
18 from binman import cbfs_util
19 from binman import elf
20 from binman import entry
21 from u_boot_pylib import command
22 from u_boot_pylib import tools
23 from u_boot_pylib import tout
24
25 # These are imported if needed since they import libfdt
26 state = None
27 Image = None
28
29 # List of images we plan to create
30 # Make this global so that it can be referenced from tests
31 images = OrderedDict()
32
33 # Help text for each type of missing blob, dict:
34 #    key: Value of the entry's 'missing-msg' or entry name
35 #    value: Text for the help
36 missing_blob_help = {}
37
38 def _ReadImageDesc(binman_node, use_expanded):
39     """Read the image descriptions from the /binman node
40
41     This normally produces a single Image object called 'image'. But if
42     multiple images are present, they will all be returned.
43
44     Args:
45         binman_node: Node object of the /binman node
46         use_expanded: True if the FDT will be updated with the entry information
47     Returns:
48         OrderedDict of Image objects, each of which describes an image
49     """
50     # For Image()
51     # pylint: disable=E1102
52     images = OrderedDict()
53     if 'multiple-images' in binman_node.props:
54         for node in binman_node.subnodes:
55             images[node.name] = Image(node.name, node,
56                                       use_expanded=use_expanded)
57     else:
58         images['image'] = Image('image', binman_node, use_expanded=use_expanded)
59     return images
60
61 def _FindBinmanNode(dtb):
62     """Find the 'binman' node in the device tree
63
64     Args:
65         dtb: Fdt object to scan
66     Returns:
67         Node object of /binman node, or None if not found
68     """
69     for node in dtb.GetRoot().subnodes:
70         if node.name == 'binman':
71             return node
72     return None
73
74 def _ReadMissingBlobHelp():
75     """Read the missing-blob-help file
76
77     This file containins help messages explaining what to do when external blobs
78     are missing.
79
80     Returns:
81         Dict:
82             key: Message tag (str)
83             value: Message text (str)
84     """
85
86     def _FinishTag(tag, msg, result):
87         if tag:
88             result[tag] = msg.rstrip()
89             tag = None
90             msg = ''
91         return tag, msg
92
93     my_data = pkg_resources.resource_string(__name__, 'missing-blob-help')
94     re_tag = re.compile('^([-a-z0-9]+):$')
95     result = {}
96     tag = None
97     msg = ''
98     for line in my_data.decode('utf-8').splitlines():
99         if not line.startswith('#'):
100             m_tag = re_tag.match(line)
101             if m_tag:
102                 _, msg = _FinishTag(tag, msg, result)
103                 tag = m_tag.group(1)
104             elif tag:
105                 msg += line + '\n'
106     _FinishTag(tag, msg, result)
107     return result
108
109 def _ShowBlobHelp(path, text):
110     tout.warning('\n%s:' % path)
111     for line in text.splitlines():
112         tout.warning('   %s' % line)
113
114 def _ShowHelpForMissingBlobs(missing_list):
115     """Show help for each missing blob to help the user take action
116
117     Args:
118         missing_list: List of Entry objects to show help for
119     """
120     global missing_blob_help
121
122     if not missing_blob_help:
123         missing_blob_help = _ReadMissingBlobHelp()
124
125     for entry in missing_list:
126         tags = entry.GetHelpTags()
127
128         # Show the first match help message
129         for tag in tags:
130             if tag in missing_blob_help:
131                 _ShowBlobHelp(entry._node.path, missing_blob_help[tag])
132                 break
133
134 def GetEntryModules(include_testing=True):
135     """Get a set of entry class implementations
136
137     Returns:
138         Set of paths to entry class filenames
139     """
140     glob_list = pkg_resources.resource_listdir(__name__, 'etype')
141     glob_list = [fname for fname in glob_list if fname.endswith('.py')]
142     return set([os.path.splitext(os.path.basename(item))[0]
143                 for item in glob_list
144                 if include_testing or '_testing' not in item])
145
146 def WriteEntryDocs(modules, test_missing=None):
147     """Write out documentation for all entries
148
149     Args:
150         modules: List of Module objects to get docs for
151         test_missing: Used for testing only, to force an entry's documentation
152             to show as missing even if it is present. Should be set to None in
153             normal use.
154     """
155     from binman.entry import Entry
156     Entry.WriteDocs(modules, test_missing)
157
158
159 def write_bintool_docs(modules, test_missing=None):
160     """Write out documentation for all bintools
161
162     Args:
163         modules: List of Module objects to get docs for
164         test_missing: Used for testing only, to force an entry's documentation
165             to show as missing even if it is present. Should be set to None in
166             normal use.
167     """
168     bintool.Bintool.WriteDocs(modules, test_missing)
169
170
171 def ListEntries(image_fname, entry_paths):
172     """List the entries in an image
173
174     This decodes the supplied image and displays a table of entries from that
175     image, preceded by a header.
176
177     Args:
178         image_fname: Image filename to process
179         entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
180                                                      'section/u-boot'])
181     """
182     image = Image.FromFile(image_fname)
183
184     entries, lines, widths = image.GetListEntries(entry_paths)
185
186     num_columns = len(widths)
187     for linenum, line in enumerate(lines):
188         if linenum == 1:
189             # Print header line
190             print('-' * (sum(widths) + num_columns * 2))
191         out = ''
192         for i, item in enumerate(line):
193             width = -widths[i]
194             if item.startswith('>'):
195                 width = -width
196                 item = item[1:]
197             txt = '%*s  ' % (width, item)
198             out += txt
199         print(out.rstrip())
200
201
202 def ReadEntry(image_fname, entry_path, decomp=True):
203     """Extract an entry from an image
204
205     This extracts the data from a particular entry in an image
206
207     Args:
208         image_fname: Image filename to process
209         entry_path: Path to entry to extract
210         decomp: True to return uncompressed data, if the data is compress
211             False to return the raw data
212
213     Returns:
214         data extracted from the entry
215     """
216     global Image
217     from binman.image import Image
218
219     image = Image.FromFile(image_fname)
220     image.CollectBintools()
221     entry = image.FindEntryPath(entry_path)
222     return entry.ReadData(decomp)
223
224
225 def ShowAltFormats(image):
226     """Show alternative formats available for entries in the image
227
228     This shows a list of formats available.
229
230     Args:
231         image (Image): Image to check
232     """
233     alt_formats = {}
234     image.CheckAltFormats(alt_formats)
235     print('%-10s  %-20s  %s' % ('Flag (-F)', 'Entry type', 'Description'))
236     for name, val in alt_formats.items():
237         entry, helptext = val
238         print('%-10s  %-20s  %s' % (name, entry.etype, helptext))
239
240
241 def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
242                    decomp=True, alt_format=None):
243     """Extract the data from one or more entries and write it to files
244
245     Args:
246         image_fname: Image filename to process
247         output_fname: Single output filename to use if extracting one file, None
248             otherwise
249         outdir: Output directory to use (for any number of files), else None
250         entry_paths: List of entry paths to extract
251         decomp: True to decompress the entry data
252
253     Returns:
254         List of EntryInfo records that were written
255     """
256     image = Image.FromFile(image_fname)
257     image.CollectBintools()
258
259     if alt_format == 'list':
260         ShowAltFormats(image)
261         return
262
263     # Output an entry to a single file, as a special case
264     if output_fname:
265         if not entry_paths:
266             raise ValueError('Must specify an entry path to write with -f')
267         if len(entry_paths) != 1:
268             raise ValueError('Must specify exactly one entry path to write with -f')
269         entry = image.FindEntryPath(entry_paths[0])
270         data = entry.ReadData(decomp, alt_format)
271         tools.write_file(output_fname, data)
272         tout.notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
273         return
274
275     # Otherwise we will output to a path given by the entry path of each entry.
276     # This means that entries will appear in subdirectories if they are part of
277     # a sub-section.
278     einfos = image.GetListEntries(entry_paths)[0]
279     tout.notice('%d entries match and will be written' % len(einfos))
280     for einfo in einfos:
281         entry = einfo.entry
282         data = entry.ReadData(decomp, alt_format)
283         path = entry.GetPath()[1:]
284         fname = os.path.join(outdir, path)
285
286         # If this entry has children, create a directory for it and put its
287         # data in a file called 'root' in that directory
288         if entry.GetEntries():
289             if fname and not os.path.exists(fname):
290                 os.makedirs(fname)
291             fname = os.path.join(fname, 'root')
292         tout.notice("Write entry '%s' size %x to '%s'" %
293                     (entry.GetPath(), len(data), fname))
294         tools.write_file(fname, data)
295     return einfos
296
297
298 def BeforeReplace(image, allow_resize):
299     """Handle getting an image ready for replacing entries in it
300
301     Args:
302         image: Image to prepare
303     """
304     state.PrepareFromLoadedData(image)
305     image.LoadData()
306     image.CollectBintools()
307
308     # If repacking, drop the old offset/size values except for the original
309     # ones, so we are only left with the constraints.
310     if image.allow_repack and allow_resize:
311         image.ResetForPack()
312
313
314 def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
315     """Handle replacing a single entry an an image
316
317     Args:
318         image: Image to update
319         entry: Entry to write
320         data: Data to replace with
321         do_compress: True to compress the data if needed, False if data is
322             already compressed so should be used as is
323         allow_resize: True to allow entries to change size (this does a re-pack
324             of the entries), False to raise an exception
325     """
326     if not entry.WriteData(data, do_compress):
327         if not image.allow_repack:
328             entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
329         if not allow_resize:
330             entry.Raise('Entry data size does not match, but resize is disabled')
331
332
333 def AfterReplace(image, allow_resize, write_map):
334     """Handle write out an image after replacing entries in it
335
336     Args:
337         image: Image to write
338         allow_resize: True to allow entries to change size (this does a re-pack
339             of the entries), False to raise an exception
340         write_map: True to write a map file
341     """
342     tout.info('Processing image')
343     ProcessImage(image, update_fdt=True, write_map=write_map,
344                  get_contents=False, allow_resize=allow_resize)
345
346
347 def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
348                       write_map=False):
349     BeforeReplace(image, allow_resize)
350     tout.info('Writing data to %s' % entry.GetPath())
351     ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
352     AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
353
354
355 def WriteEntry(image_fname, entry_path, data, do_compress=True,
356                allow_resize=True, write_map=False):
357     """Replace an entry in an image
358
359     This replaces the data in a particular entry in an image. This size of the
360     new data must match the size of the old data unless allow_resize is True.
361
362     Args:
363         image_fname: Image filename to process
364         entry_path: Path to entry to extract
365         data: Data to replace with
366         do_compress: True to compress the data if needed, False if data is
367             already compressed so should be used as is
368         allow_resize: True to allow entries to change size (this does a re-pack
369             of the entries), False to raise an exception
370         write_map: True to write a map file
371
372     Returns:
373         Image object that was updated
374     """
375     tout.info("Write entry '%s', file '%s'" % (entry_path, image_fname))
376     image = Image.FromFile(image_fname)
377     image.CollectBintools()
378     entry = image.FindEntryPath(entry_path)
379     WriteEntryToImage(image, entry, data, do_compress=do_compress,
380                       allow_resize=allow_resize, write_map=write_map)
381
382     return image
383
384
385 def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
386                    do_compress=True, allow_resize=True, write_map=False):
387     """Replace the data from one or more entries from input files
388
389     Args:
390         image_fname: Image filename to process
391         input_fname: Single input filename to use if replacing one file, None
392             otherwise
393         indir: Input directory to use (for any number of files), else None
394         entry_paths: List of entry paths to replace
395         do_compress: True if the input data is uncompressed and may need to be
396             compressed if the entry requires it, False if the data is already
397             compressed.
398         write_map: True to write a map file
399
400     Returns:
401         List of EntryInfo records that were written
402     """
403     image_fname = os.path.abspath(image_fname)
404     image = Image.FromFile(image_fname)
405
406     image.mark_build_done()
407
408     # Replace an entry from a single file, as a special case
409     if input_fname:
410         if not entry_paths:
411             raise ValueError('Must specify an entry path to read with -f')
412         if len(entry_paths) != 1:
413             raise ValueError('Must specify exactly one entry path to write with -f')
414         entry = image.FindEntryPath(entry_paths[0])
415         data = tools.read_file(input_fname)
416         tout.notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
417         WriteEntryToImage(image, entry, data, do_compress=do_compress,
418                           allow_resize=allow_resize, write_map=write_map)
419         return
420
421     # Otherwise we will input from a path given by the entry path of each entry.
422     # This means that files must appear in subdirectories if they are part of
423     # a sub-section.
424     einfos = image.GetListEntries(entry_paths)[0]
425     tout.notice("Replacing %d matching entries in image '%s'" %
426                 (len(einfos), image_fname))
427
428     BeforeReplace(image, allow_resize)
429
430     for einfo in einfos:
431         entry = einfo.entry
432         if entry.GetEntries():
433             tout.info("Skipping section entry '%s'" % entry.GetPath())
434             continue
435
436         path = entry.GetPath()[1:]
437         fname = os.path.join(indir, path)
438
439         if os.path.exists(fname):
440             tout.notice("Write entry '%s' from file '%s'" %
441                         (entry.GetPath(), fname))
442             data = tools.read_file(fname)
443             ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
444         else:
445             tout.warning("Skipping entry '%s' from missing file '%s'" %
446                          (entry.GetPath(), fname))
447
448     AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
449     return image
450
451 def SignEntries(image_fname, input_fname, privatekey_fname, algo, entry_paths,
452                 write_map=False):
453     """Sign and replace the data from one or more entries from input files
454
455     Args:
456         image_fname: Image filename to process
457         input_fname: Single input filename to use if replacing one file, None
458             otherwise
459         algo: Hashing algorithm
460         entry_paths: List of entry paths to sign
461         privatekey_fname: Private key filename
462         write_map (bool): True to write the map file
463     """
464     image_fname = os.path.abspath(image_fname)
465     image = Image.FromFile(image_fname)
466
467     image.mark_build_done()
468
469     BeforeReplace(image, allow_resize=True)
470
471     for entry_path in entry_paths:
472         entry = image.FindEntryPath(entry_path)
473         entry.UpdateSignatures(privatekey_fname, algo, input_fname)
474
475     AfterReplace(image, allow_resize=True, write_map=write_map)
476
477 def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt, use_expanded):
478     """Prepare the images to be processed and select the device tree
479
480     This function:
481     - reads in the device tree
482     - finds and scans the binman node to create all entries
483     - selects which images to build
484     - Updates the device tress with placeholder properties for offset,
485         image-pos, etc.
486
487     Args:
488         dtb_fname: Filename of the device tree file to use (.dts or .dtb)
489         selected_images: List of images to output, or None for all
490         update_fdt: True to update the FDT wth entry offsets, etc.
491         use_expanded: True to use expanded versions of entries, if available.
492             So if 'u-boot' is called for, we use 'u-boot-expanded' instead. This
493             is needed if update_fdt is True (although tests may disable it)
494
495     Returns:
496         OrderedDict of images:
497             key: Image name (str)
498             value: Image object
499     """
500     # Import these here in case libfdt.py is not available, in which case
501     # the above help option still works.
502     from dtoc import fdt
503     from dtoc import fdt_util
504     global images
505
506     # Get the device tree ready by compiling it and copying the compiled
507     # output into a file in our output directly. Then scan it for use
508     # in binman.
509     dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
510     fname = tools.get_output_filename('u-boot.dtb.out')
511     tools.write_file(fname, tools.read_file(dtb_fname))
512     dtb = fdt.FdtScan(fname)
513
514     node = _FindBinmanNode(dtb)
515     if not node:
516         raise ValueError("Device tree '%s' does not have a 'binman' "
517                             "node" % dtb_fname)
518
519     images = _ReadImageDesc(node, use_expanded)
520
521     if select_images:
522         skip = []
523         new_images = OrderedDict()
524         for name, image in images.items():
525             if name in select_images:
526                 new_images[name] = image
527             else:
528                 skip.append(name)
529         images = new_images
530         tout.notice('Skipping images: %s' % ', '.join(skip))
531
532     state.Prepare(images, dtb)
533
534     # Prepare the device tree by making sure that any missing
535     # properties are added (e.g. 'pos' and 'size'). The values of these
536     # may not be correct yet, but we add placeholders so that the
537     # size of the device tree is correct. Later, in
538     # SetCalculatedProperties() we will insert the correct values
539     # without changing the device-tree size, thus ensuring that our
540     # entry offsets remain the same.
541     for image in images.values():
542         image.gen_entries()
543         image.CollectBintools()
544         if update_fdt:
545             image.AddMissingProperties(True)
546         image.ProcessFdt(dtb)
547
548     for dtb_item in state.GetAllFdts():
549         dtb_item.Sync(auto_resize=True)
550         dtb_item.Pack()
551         dtb_item.Flush()
552     return images
553
554
555 def ProcessImage(image, update_fdt, write_map, get_contents=True,
556                  allow_resize=True, allow_missing=False,
557                  allow_fake_blobs=False):
558     """Perform all steps for this image, including checking and # writing it.
559
560     This means that errors found with a later image will be reported after
561     earlier images are already completed and written, but that does not seem
562     important.
563
564     Args:
565         image: Image to process
566         update_fdt: True to update the FDT wth entry offsets, etc.
567         write_map: True to write a map file
568         get_contents: True to get the image contents from files, etc., False if
569             the contents is already present
570         allow_resize: True to allow entries to change size (this does a re-pack
571             of the entries), False to raise an exception
572         allow_missing: Allow blob_ext objects to be missing
573         allow_fake_blobs: Allow blob_ext objects to be faked with dummy files
574
575     Returns:
576         True if one or more external blobs are missing or faked,
577         False if all are present
578     """
579     if get_contents:
580         image.SetAllowMissing(allow_missing)
581         image.SetAllowFakeBlob(allow_fake_blobs)
582         image.GetEntryContents()
583         image.drop_absent()
584     image.GetEntryOffsets()
585
586     # We need to pack the entries to figure out where everything
587     # should be placed. This sets the offset/size of each entry.
588     # However, after packing we call ProcessEntryContents() which
589     # may result in an entry changing size. In that case we need to
590     # do another pass. Since the device tree often contains the
591     # final offset/size information we try to make space for this in
592     # AddMissingProperties() above. However, if the device is
593     # compressed we cannot know this compressed size in advance,
594     # since changing an offset from 0x100 to 0x104 (for example) can
595     # alter the compressed size of the device tree. So we need a
596     # third pass for this.
597     passes = 5
598     for pack_pass in range(passes):
599         try:
600             image.PackEntries()
601         except Exception as e:
602             if write_map:
603                 fname = image.WriteMap()
604                 print("Wrote map file '%s' to show errors"  % fname)
605             raise
606         image.SetImagePos()
607         if update_fdt:
608             image.SetCalculatedProperties()
609             for dtb_item in state.GetAllFdts():
610                 dtb_item.Sync()
611                 dtb_item.Flush()
612         image.WriteSymbols()
613         sizes_ok = image.ProcessEntryContents()
614         if sizes_ok:
615             break
616         image.ResetForPack()
617     tout.info('Pack completed after %d pass(es)' % (pack_pass + 1))
618     if not sizes_ok:
619         image.Raise('Entries changed size after packing (tried %s passes)' %
620                     passes)
621
622     image.BuildImage()
623     if write_map:
624         image.WriteMap()
625
626     missing_list = []
627     image.CheckMissing(missing_list)
628     if missing_list:
629         tout.warning("Image '%s' is missing external blobs and is non-functional: %s" %
630                      (image.name, ' '.join([e.name for e in missing_list])))
631         _ShowHelpForMissingBlobs(missing_list)
632
633     faked_list = []
634     image.CheckFakedBlobs(faked_list)
635     if faked_list:
636         tout.warning(
637             "Image '%s' has faked external blobs and is non-functional: %s" %
638             (image.name, ' '.join([os.path.basename(e.GetDefaultFilename())
639                                    for e in faked_list])))
640
641     optional_list = []
642     image.CheckOptional(optional_list)
643     if optional_list:
644         tout.warning(
645             "Image '%s' is missing external blobs but is still functional: %s" %
646             (image.name, ' '.join([e.name for e in optional_list])))
647         _ShowHelpForMissingBlobs(optional_list)
648
649     missing_bintool_list = []
650     image.check_missing_bintools(missing_bintool_list)
651     if missing_bintool_list:
652         tout.warning(
653             "Image '%s' has missing bintools and is non-functional: %s" %
654             (image.name, ' '.join([os.path.basename(bintool.name)
655                                    for bintool in missing_bintool_list])))
656     return any([missing_list, faked_list, missing_bintool_list])
657
658
659 def Binman(args):
660     """The main control code for binman
661
662     This assumes that help and test options have already been dealt with. It
663     deals with the core task of building images.
664
665     Args:
666         args: Command line arguments Namespace object
667     """
668     global Image
669     global state
670
671     if args.full_help:
672         with importlib.resources.path('binman', 'README.rst') as readme:
673             tools.print_full_help(str(readme))
674         return 0
675
676     # Put these here so that we can import this module without libfdt
677     from binman.image import Image
678     from binman import state
679
680     tool_paths = []
681     if args.toolpath:
682         tool_paths += args.toolpath
683     if args.tooldir:
684         tool_paths.append(args.tooldir)
685     tools.set_tool_paths(tool_paths or None)
686     bintool.Bintool.set_tool_dir(args.tooldir)
687
688     if args.cmd in ['ls', 'extract', 'replace', 'tool', 'sign']:
689         try:
690             tout.init(args.verbosity)
691             if args.cmd == 'replace':
692                 tools.prepare_output_dir(args.outdir, args.preserve)
693             else:
694                 tools.prepare_output_dir(None)
695             if args.cmd == 'ls':
696                 ListEntries(args.image, args.paths)
697
698             if args.cmd == 'extract':
699                 ExtractEntries(args.image, args.filename, args.outdir, args.paths,
700                                not args.uncompressed, args.format)
701
702             if args.cmd == 'replace':
703                 ReplaceEntries(args.image, args.filename, args.indir, args.paths,
704                                do_compress=not args.compressed,
705                                allow_resize=not args.fix_size, write_map=args.map)
706
707             if args.cmd == 'sign':
708                 SignEntries(args.image, args.file, args.key, args.algo, args.paths)
709
710             if args.cmd == 'tool':
711                 if args.list:
712                     bintool.Bintool.list_all()
713                 elif args.fetch:
714                     if not args.bintools:
715                         raise ValueError(
716                             "Please specify bintools to fetch or 'all' or 'missing'")
717                     bintool.Bintool.fetch_tools(bintool.FETCH_ANY,
718                                                 args.bintools)
719                 else:
720                     raise ValueError("Invalid arguments to 'tool' subcommand")
721         except:
722             raise
723         finally:
724             tools.finalise_output_dir()
725         return 0
726
727     elf_params = None
728     if args.update_fdt_in_elf:
729         elf_params = args.update_fdt_in_elf.split(',')
730         if len(elf_params) != 4:
731             raise ValueError('Invalid args %s to --update-fdt-in-elf: expected infile,outfile,begin_sym,end_sym' %
732                              elf_params)
733
734     # Try to figure out which device tree contains our image description
735     if args.dt:
736         dtb_fname = args.dt
737     else:
738         board = args.board
739         if not board:
740             raise ValueError('Must provide a board to process (use -b <board>)')
741         board_pathname = os.path.join(args.build_dir, board)
742         dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
743         if not args.indir:
744             args.indir = ['.']
745         args.indir.append(board_pathname)
746
747     try:
748         tout.init(args.verbosity)
749         elf.debug = args.debug
750         cbfs_util.VERBOSE = args.verbosity > 2
751         state.use_fake_dtb = args.fake_dtb
752
753         # Normally we replace the 'u-boot' etype with 'u-boot-expanded', etc.
754         # When running tests this can be disabled using this flag. When not
755         # updating the FDT in image, it is not needed by binman, but we use it
756         # for consistency, so that the images look the same to U-Boot at
757         # runtime.
758         use_expanded = not args.no_expanded
759         try:
760             tools.set_input_dirs(args.indir)
761             tools.prepare_output_dir(args.outdir, args.preserve)
762             state.SetEntryArgs(args.entry_arg)
763             state.SetThreads(args.threads)
764
765             images = PrepareImagesAndDtbs(dtb_fname, args.image,
766                                           args.update_fdt, use_expanded)
767
768             if args.test_section_timeout:
769                 # Set the first image to timeout, used in testThreadTimeout()
770                 images[list(images.keys())[0]].test_section_timeout = True
771             invalid = False
772             bintool.Bintool.set_missing_list(
773                 args.force_missing_bintools.split(',') if
774                 args.force_missing_bintools else None)
775
776             # Create the directory here instead of Entry.check_fake_fname()
777             # since that is called from a threaded context so different threads
778             # may race to create the directory
779             if args.fake_ext_blobs:
780                 entry.Entry.create_fake_dir()
781
782             for image in images.values():
783                 invalid |= ProcessImage(image, args.update_fdt, args.map,
784                                        allow_missing=args.allow_missing,
785                                        allow_fake_blobs=args.fake_ext_blobs)
786
787             # Write the updated FDTs to our output files
788             for dtb_item in state.GetAllFdts():
789                 tools.write_file(dtb_item._fname, dtb_item.GetContents())
790
791             if elf_params:
792                 data = state.GetFdtForEtype('u-boot-dtb').GetContents()
793                 elf.UpdateFile(*elf_params, data)
794
795             # This can only be True if -M is provided, since otherwise binman
796             # would have raised an error already
797             if invalid:
798                 msg = '\nSome images are invalid'
799                 if args.ignore_missing:
800                     tout.warning(msg)
801                 else:
802                     tout.error(msg)
803                     return 103
804
805             # Use this to debug the time take to pack the image
806             #state.TimingShow()
807         finally:
808             tools.finalise_output_dir()
809     finally:
810         tout.uninit()
811
812     return 0