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