1 # SPDX-License-Identifier: GPL-2.0+
2 # Copyright (c) 2016 Google, Inc
3 # Written by Simon Glass <sjg@chromium.org>
5 # Creates binary images from input files controlled by a description
8 from __future__ import print_function
10 from collections import OrderedDict
20 # List of images we plan to create
21 # Make this global so that it can be referenced from tests
22 images = OrderedDict()
24 def _ReadImageDesc(binman_node):
25 """Read the image descriptions from the /binman node
27 This normally produces a single Image object called 'image'. But if
28 multiple images are present, they will all be returned.
31 binman_node: Node object of the /binman node
33 OrderedDict of Image objects, each of which describes an image
35 images = OrderedDict()
36 if 'multiple-images' in binman_node.props:
37 for node in binman_node.subnodes:
38 images[node.name] = Image(node.name, node)
40 images['image'] = Image('image', binman_node)
43 def _FindBinmanNode(dtb):
44 """Find the 'binman' node in the device tree
47 dtb: Fdt object to scan
49 Node object of /binman node, or None if not found
51 for node in dtb.GetRoot().subnodes:
52 if node.name == 'binman':
56 def WriteEntryDocs(modules, test_missing=None):
57 """Write out documentation for all entries
60 modules: List of Module objects to get docs for
61 test_missing: Used for testing only, to force an entry's documeentation
62 to show as missing even if it is present. Should be set to None in
65 from entry import Entry
66 Entry.WriteDocs(modules, test_missing)
69 def ListEntries(image_fname, entry_paths):
70 """List the entries in an image
72 This decodes the supplied image and displays a table of entries from that
73 image, preceded by a header.
76 image_fname: Image filename to process
77 entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
80 image = Image.FromFile(image_fname)
82 entries, lines, widths = image.GetListEntries(entry_paths)
84 num_columns = len(widths)
85 for linenum, line in enumerate(lines):
88 print('-' * (sum(widths) + num_columns * 2))
90 for i, item in enumerate(line):
92 if item.startswith('>'):
95 txt = '%*s ' % (width, item)
100 def ReadEntry(image_fname, entry_path, decomp=True):
101 """Extract an entry from an image
103 This extracts the data from a particular entry in an image
106 image_fname: Image filename to process
107 entry_path: Path to entry to extract
108 decomp: True to return uncompressed data, if the data is compress
109 False to return the raw data
112 data extracted from the entry
115 from image import Image
117 image = Image.FromFile(image_fname)
118 entry = image.FindEntryPath(entry_path)
119 return entry.ReadData(decomp)
122 def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
124 """Extract the data from one or more entries and write it to files
127 image_fname: Image filename to process
128 output_fname: Single output filename to use if extracting one file, None
130 outdir: Output directory to use (for any number of files), else None
131 entry_paths: List of entry paths to extract
132 decomp: True to decompress the entry data
135 List of EntryInfo records that were written
137 image = Image.FromFile(image_fname)
139 # Output an entry to a single file, as a special case
142 raise ValueError('Must specify an entry path to write with -f')
143 if len(entry_paths) != 1:
144 raise ValueError('Must specify exactly one entry path to write with -f')
145 entry = image.FindEntryPath(entry_paths[0])
146 data = entry.ReadData(decomp)
147 tools.WriteFile(output_fname, data)
148 tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
151 # Otherwise we will output to a path given by the entry path of each entry.
152 # This means that entries will appear in subdirectories if they are part of
154 einfos = image.GetListEntries(entry_paths)[0]
155 tout.Notice('%d entries match and will be written' % len(einfos))
158 data = entry.ReadData(decomp)
159 path = entry.GetPath()[1:]
160 fname = os.path.join(outdir, path)
162 # If this entry has children, create a directory for it and put its
163 # data in a file called 'root' in that directory
164 if entry.GetEntries():
165 if not os.path.exists(fname):
167 fname = os.path.join(fname, 'root')
168 tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
169 tools.WriteFile(fname, data)
173 def BeforeReplace(image, allow_resize):
174 """Handle getting an image ready for replacing entries in it
177 image: Image to prepare
179 state.PrepareFromLoadedData(image)
182 # If repacking, drop the old offset/size values except for the original
183 # ones, so we are only left with the constraints.
188 def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
189 """Handle replacing a single entry an an image
192 image: Image to update
193 entry: Entry to write
194 data: Data to replace with
195 do_compress: True to compress the data if needed, False if data is
196 already compressed so should be used as is
197 allow_resize: True to allow entries to change size (this does a re-pack
198 of the entries), False to raise an exception
200 if not entry.WriteData(data, do_compress):
201 if not image.allow_repack:
202 entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
204 entry.Raise('Entry data size does not match, but resize is disabled')
207 def AfterReplace(image, allow_resize, write_map):
208 """Handle write out an image after replacing entries in it
211 image: Image to write
212 allow_resize: True to allow entries to change size (this does a re-pack
213 of the entries), False to raise an exception
214 write_map: True to write a map file
216 tout.Info('Processing image')
217 ProcessImage(image, update_fdt=True, write_map=write_map,
218 get_contents=False, allow_resize=allow_resize)
221 def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
223 BeforeReplace(image, allow_resize)
224 tout.Info('Writing data to %s' % entry.GetPath())
225 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
226 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
229 def WriteEntry(image_fname, entry_path, data, do_compress=True,
230 allow_resize=True, write_map=False):
231 """Replace an entry in an image
233 This replaces the data in a particular entry in an image. This size of the
234 new data must match the size of the old data unless allow_resize is True.
237 image_fname: Image filename to process
238 entry_path: Path to entry to extract
239 data: Data to replace with
240 do_compress: True to compress the data if needed, False if data is
241 already compressed so should be used as is
242 allow_resize: True to allow entries to change size (this does a re-pack
243 of the entries), False to raise an exception
244 write_map: True to write a map file
247 Image object that was updated
249 tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
250 image = Image.FromFile(image_fname)
251 entry = image.FindEntryPath(entry_path)
252 WriteEntryToImage(image, entry, data, do_compress=do_compress,
253 allow_resize=allow_resize, write_map=write_map)
258 def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
259 do_compress=True, allow_resize=True, write_map=False):
260 """Replace the data from one or more entries from input files
263 image_fname: Image filename to process
264 input_fname: Single input ilename to use if replacing one file, None
266 indir: Input directory to use (for any number of files), else None
267 entry_paths: List of entry paths to extract
268 do_compress: True if the input data is uncompressed and may need to be
269 compressed if the entry requires it, False if the data is already
271 write_map: True to write a map file
274 List of EntryInfo records that were written
276 image = Image.FromFile(image_fname)
278 # Replace an entry from a single file, as a special case
281 raise ValueError('Must specify an entry path to read with -f')
282 if len(entry_paths) != 1:
283 raise ValueError('Must specify exactly one entry path to write with -f')
284 entry = image.FindEntryPath(entry_paths[0])
285 data = tools.ReadFile(input_fname)
286 tout.Notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
287 WriteEntryToImage(image, entry, data, do_compress=do_compress,
288 allow_resize=allow_resize, write_map=write_map)
291 # Otherwise we will input from a path given by the entry path of each entry.
292 # This means that files must appear in subdirectories if they are part of
294 einfos = image.GetListEntries(entry_paths)[0]
295 tout.Notice("Replacing %d matching entries in image '%s'" %
296 (len(einfos), image_fname))
298 BeforeReplace(image, allow_resize)
302 if entry.GetEntries():
303 tout.Info("Skipping section entry '%s'" % entry.GetPath())
306 path = entry.GetPath()[1:]
307 fname = os.path.join(indir, path)
309 if os.path.exists(fname):
310 tout.Notice("Write entry '%s' from file '%s'" %
311 (entry.GetPath(), fname))
312 data = tools.ReadFile(fname)
313 ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
315 tout.Warning("Skipping entry '%s' from missing file '%s'" %
316 (entry.GetPath(), fname))
318 AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
322 def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt):
323 """Prepare the images to be processed and select the device tree
326 - reads in the device tree
327 - finds and scans the binman node to create all entries
328 - selects which images to build
329 - Updates the device tress with placeholder properties for offset,
333 dtb_fname: Filename of the device tree file to use (.dts or .dtb)
334 selected_images: List of images to output, or None for all
335 update_fdt: True to update the FDT wth entry offsets, etc.
337 # Import these here in case libfdt.py is not available, in which case
338 # the above help option still works.
343 # Get the device tree ready by compiling it and copying the compiled
344 # output into a file in our output directly. Then scan it for use
346 dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
347 fname = tools.GetOutputFilename('u-boot.dtb.out')
348 tools.WriteFile(fname, tools.ReadFile(dtb_fname))
349 dtb = fdt.FdtScan(fname)
351 node = _FindBinmanNode(dtb)
353 raise ValueError("Device tree '%s' does not have a 'binman' "
356 images = _ReadImageDesc(node)
360 new_images = OrderedDict()
361 for name, image in images.items():
362 if name in select_images:
363 new_images[name] = image
367 tout.Notice('Skipping images: %s' % ', '.join(skip))
369 state.Prepare(images, dtb)
371 # Prepare the device tree by making sure that any missing
372 # properties are added (e.g. 'pos' and 'size'). The values of these
373 # may not be correct yet, but we add placeholders so that the
374 # size of the device tree is correct. Later, in
375 # SetCalculatedProperties() we will insert the correct values
376 # without changing the device-tree size, thus ensuring that our
377 # entry offsets remain the same.
378 for image in images.values():
379 image.ExpandEntries()
381 image.AddMissingProperties()
382 image.ProcessFdt(dtb)
384 for dtb_item in state.GetAllFdts():
385 dtb_item.Sync(auto_resize=True)
391 def ProcessImage(image, update_fdt, write_map, get_contents=True,
393 """Perform all steps for this image, including checking and # writing it.
395 This means that errors found with a later image will be reported after
396 earlier images are already completed and written, but that does not seem
400 image: Image to process
401 update_fdt: True to update the FDT wth entry offsets, etc.
402 write_map: True to write a map file
403 get_contents: True to get the image contents from files, etc., False if
404 the contents is already present
405 allow_resize: True to allow entries to change size (this does a re-pack
406 of the entries), False to raise an exception
409 image.GetEntryContents()
410 image.GetEntryOffsets()
412 # We need to pack the entries to figure out where everything
413 # should be placed. This sets the offset/size of each entry.
414 # However, after packing we call ProcessEntryContents() which
415 # may result in an entry changing size. In that case we need to
416 # do another pass. Since the device tree often contains the
417 # final offset/size information we try to make space for this in
418 # AddMissingProperties() above. However, if the device is
419 # compressed we cannot know this compressed size in advance,
420 # since changing an offset from 0x100 to 0x104 (for example) can
421 # alter the compressed size of the device tree. So we need a
422 # third pass for this.
424 for pack_pass in range(passes):
429 except Exception as e:
431 fname = image.WriteMap()
432 print("Wrote map file '%s' to show errors" % fname)
436 image.SetCalculatedProperties()
437 for dtb_item in state.GetAllFdts():
441 sizes_ok = image.ProcessEntryContents()
445 tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
447 image.Raise('Entries changed size after packing (tried %s passes)' %
456 """The main control code for binman
458 This assumes that help and test options have already been dealt with. It
459 deals with the core task of building images.
462 args: Command line arguments Namespace object
468 pager = os.getenv('PAGER')
471 fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
473 command.Run(pager, fname)
476 # Put these here so that we can import this module without libfdt
477 from image import Image
480 if args.cmd in ['ls', 'extract', 'replace']:
482 tout.Init(args.verbosity)
483 tools.PrepareOutputDir(None)
485 ListEntries(args.image, args.paths)
487 if args.cmd == 'extract':
488 ExtractEntries(args.image, args.filename, args.outdir, args.paths,
489 not args.uncompressed)
491 if args.cmd == 'replace':
492 ReplaceEntries(args.image, args.filename, args.indir, args.paths,
493 do_compress=not args.compressed,
494 allow_resize=not args.fix_size, write_map=args.map)
498 tools.FinaliseOutputDir()
501 # Try to figure out which device tree contains our image description
507 raise ValueError('Must provide a board to process (use -b <board>)')
508 board_pathname = os.path.join(args.build_dir, board)
509 dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
512 args.indir.append(board_pathname)
515 tout.Init(args.verbosity)
516 elf.debug = args.debug
517 cbfs_util.VERBOSE = args.verbosity > 2
518 state.use_fake_dtb = args.fake_dtb
520 tools.SetInputDirs(args.indir)
521 tools.PrepareOutputDir(args.outdir, args.preserve)
522 tools.SetToolPaths(args.toolpath)
523 state.SetEntryArgs(args.entry_arg)
525 images = PrepareImagesAndDtbs(dtb_fname, args.image,
527 for image in images.values():
528 ProcessImage(image, args.update_fdt, args.map)
530 # Write the updated FDTs to our output files
531 for dtb_item in state.GetAllFdts():
532 tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
535 tools.FinaliseOutputDir()