123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- # SPDX-License-Identifier: GPL-2.0+
- # Copyright (c) 2016 Google, Inc
- # Written by Simon Glass <sjg@chromium.org>
- #
- # Creates binary images from input files controlled by a description
- #
- from collections import OrderedDict
- import glob
- import os
- import pkg_resources
- import sys
- from patman import tools
- from binman import cbfs_util
- from binman import elf
- from patman import command
- from patman import tout
- # List of images we plan to create
- # Make this global so that it can be referenced from tests
- images = OrderedDict()
- def _ReadImageDesc(binman_node):
- """Read the image descriptions from the /binman node
- This normally produces a single Image object called 'image'. But if
- multiple images are present, they will all be returned.
- Args:
- binman_node: Node object of the /binman node
- Returns:
- OrderedDict of Image objects, each of which describes an image
- """
- images = OrderedDict()
- if 'multiple-images' in binman_node.props:
- for node in binman_node.subnodes:
- images[node.name] = Image(node.name, node)
- else:
- images['image'] = Image('image', binman_node)
- return images
- def _FindBinmanNode(dtb):
- """Find the 'binman' node in the device tree
- Args:
- dtb: Fdt object to scan
- Returns:
- Node object of /binman node, or None if not found
- """
- for node in dtb.GetRoot().subnodes:
- if node.name == 'binman':
- return node
- return None
- def GetEntryModules(include_testing=True):
- """Get a set of entry class implementations
- Returns:
- Set of paths to entry class filenames
- """
- glob_list = pkg_resources.resource_listdir(__name__, 'etype')
- glob_list = [fname for fname in glob_list if fname.endswith('.py')]
- return set([os.path.splitext(os.path.basename(item))[0]
- for item in glob_list
- if include_testing or '_testing' not in item])
- def WriteEntryDocs(modules, test_missing=None):
- """Write out documentation for all entries
- Args:
- modules: List of Module objects to get docs for
- test_missing: Used for testing only, to force an entry's documeentation
- to show as missing even if it is present. Should be set to None in
- normal use.
- """
- from binman.entry import Entry
- Entry.WriteDocs(modules, test_missing)
- def ListEntries(image_fname, entry_paths):
- """List the entries in an image
- This decodes the supplied image and displays a table of entries from that
- image, preceded by a header.
- Args:
- image_fname: Image filename to process
- entry_paths: List of wildcarded paths (e.g. ['*dtb*', 'u-boot*',
- 'section/u-boot'])
- """
- image = Image.FromFile(image_fname)
- entries, lines, widths = image.GetListEntries(entry_paths)
- num_columns = len(widths)
- for linenum, line in enumerate(lines):
- if linenum == 1:
- # Print header line
- print('-' * (sum(widths) + num_columns * 2))
- out = ''
- for i, item in enumerate(line):
- width = -widths[i]
- if item.startswith('>'):
- width = -width
- item = item[1:]
- txt = '%*s ' % (width, item)
- out += txt
- print(out.rstrip())
- def ReadEntry(image_fname, entry_path, decomp=True):
- """Extract an entry from an image
- This extracts the data from a particular entry in an image
- Args:
- image_fname: Image filename to process
- entry_path: Path to entry to extract
- decomp: True to return uncompressed data, if the data is compress
- False to return the raw data
- Returns:
- data extracted from the entry
- """
- global Image
- from binman.image import Image
- image = Image.FromFile(image_fname)
- entry = image.FindEntryPath(entry_path)
- return entry.ReadData(decomp)
- def ExtractEntries(image_fname, output_fname, outdir, entry_paths,
- decomp=True):
- """Extract the data from one or more entries and write it to files
- Args:
- image_fname: Image filename to process
- output_fname: Single output filename to use if extracting one file, None
- otherwise
- outdir: Output directory to use (for any number of files), else None
- entry_paths: List of entry paths to extract
- decomp: True to decompress the entry data
- Returns:
- List of EntryInfo records that were written
- """
- image = Image.FromFile(image_fname)
- # Output an entry to a single file, as a special case
- if output_fname:
- if not entry_paths:
- raise ValueError('Must specify an entry path to write with -f')
- if len(entry_paths) != 1:
- raise ValueError('Must specify exactly one entry path to write with -f')
- entry = image.FindEntryPath(entry_paths[0])
- data = entry.ReadData(decomp)
- tools.WriteFile(output_fname, data)
- tout.Notice("Wrote %#x bytes to file '%s'" % (len(data), output_fname))
- return
- # Otherwise we will output to a path given by the entry path of each entry.
- # This means that entries will appear in subdirectories if they are part of
- # a sub-section.
- einfos = image.GetListEntries(entry_paths)[0]
- tout.Notice('%d entries match and will be written' % len(einfos))
- for einfo in einfos:
- entry = einfo.entry
- data = entry.ReadData(decomp)
- path = entry.GetPath()[1:]
- fname = os.path.join(outdir, path)
- # If this entry has children, create a directory for it and put its
- # data in a file called 'root' in that directory
- if entry.GetEntries():
- if not os.path.exists(fname):
- os.makedirs(fname)
- fname = os.path.join(fname, 'root')
- tout.Notice("Write entry '%s' to '%s'" % (entry.GetPath(), fname))
- tools.WriteFile(fname, data)
- return einfos
- def BeforeReplace(image, allow_resize):
- """Handle getting an image ready for replacing entries in it
- Args:
- image: Image to prepare
- """
- state.PrepareFromLoadedData(image)
- image.LoadData()
- # If repacking, drop the old offset/size values except for the original
- # ones, so we are only left with the constraints.
- if allow_resize:
- image.ResetForPack()
- def ReplaceOneEntry(image, entry, data, do_compress, allow_resize):
- """Handle replacing a single entry an an image
- Args:
- image: Image to update
- entry: Entry to write
- data: Data to replace with
- do_compress: True to compress the data if needed, False if data is
- already compressed so should be used as is
- allow_resize: True to allow entries to change size (this does a re-pack
- of the entries), False to raise an exception
- """
- if not entry.WriteData(data, do_compress):
- if not image.allow_repack:
- entry.Raise('Entry data size does not match, but allow-repack is not present for this image')
- if not allow_resize:
- entry.Raise('Entry data size does not match, but resize is disabled')
- def AfterReplace(image, allow_resize, write_map):
- """Handle write out an image after replacing entries in it
- Args:
- image: Image to write
- allow_resize: True to allow entries to change size (this does a re-pack
- of the entries), False to raise an exception
- write_map: True to write a map file
- """
- tout.Info('Processing image')
- ProcessImage(image, update_fdt=True, write_map=write_map,
- get_contents=False, allow_resize=allow_resize)
- def WriteEntryToImage(image, entry, data, do_compress=True, allow_resize=True,
- write_map=False):
- BeforeReplace(image, allow_resize)
- tout.Info('Writing data to %s' % entry.GetPath())
- ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
- AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
- def WriteEntry(image_fname, entry_path, data, do_compress=True,
- allow_resize=True, write_map=False):
- """Replace an entry in an image
- This replaces the data in a particular entry in an image. This size of the
- new data must match the size of the old data unless allow_resize is True.
- Args:
- image_fname: Image filename to process
- entry_path: Path to entry to extract
- data: Data to replace with
- do_compress: True to compress the data if needed, False if data is
- already compressed so should be used as is
- allow_resize: True to allow entries to change size (this does a re-pack
- of the entries), False to raise an exception
- write_map: True to write a map file
- Returns:
- Image object that was updated
- """
- tout.Info("Write entry '%s', file '%s'" % (entry_path, image_fname))
- image = Image.FromFile(image_fname)
- entry = image.FindEntryPath(entry_path)
- WriteEntryToImage(image, entry, data, do_compress=do_compress,
- allow_resize=allow_resize, write_map=write_map)
- return image
- def ReplaceEntries(image_fname, input_fname, indir, entry_paths,
- do_compress=True, allow_resize=True, write_map=False):
- """Replace the data from one or more entries from input files
- Args:
- image_fname: Image filename to process
- input_fname: Single input ilename to use if replacing one file, None
- otherwise
- indir: Input directory to use (for any number of files), else None
- entry_paths: List of entry paths to extract
- do_compress: True if the input data is uncompressed and may need to be
- compressed if the entry requires it, False if the data is already
- compressed.
- write_map: True to write a map file
- Returns:
- List of EntryInfo records that were written
- """
- image = Image.FromFile(image_fname)
- # Replace an entry from a single file, as a special case
- if input_fname:
- if not entry_paths:
- raise ValueError('Must specify an entry path to read with -f')
- if len(entry_paths) != 1:
- raise ValueError('Must specify exactly one entry path to write with -f')
- entry = image.FindEntryPath(entry_paths[0])
- data = tools.ReadFile(input_fname)
- tout.Notice("Read %#x bytes from file '%s'" % (len(data), input_fname))
- WriteEntryToImage(image, entry, data, do_compress=do_compress,
- allow_resize=allow_resize, write_map=write_map)
- return
- # Otherwise we will input from a path given by the entry path of each entry.
- # This means that files must appear in subdirectories if they are part of
- # a sub-section.
- einfos = image.GetListEntries(entry_paths)[0]
- tout.Notice("Replacing %d matching entries in image '%s'" %
- (len(einfos), image_fname))
- BeforeReplace(image, allow_resize)
- for einfo in einfos:
- entry = einfo.entry
- if entry.GetEntries():
- tout.Info("Skipping section entry '%s'" % entry.GetPath())
- continue
- path = entry.GetPath()[1:]
- fname = os.path.join(indir, path)
- if os.path.exists(fname):
- tout.Notice("Write entry '%s' from file '%s'" %
- (entry.GetPath(), fname))
- data = tools.ReadFile(fname)
- ReplaceOneEntry(image, entry, data, do_compress, allow_resize)
- else:
- tout.Warning("Skipping entry '%s' from missing file '%s'" %
- (entry.GetPath(), fname))
- AfterReplace(image, allow_resize=allow_resize, write_map=write_map)
- return image
- def PrepareImagesAndDtbs(dtb_fname, select_images, update_fdt):
- """Prepare the images to be processed and select the device tree
- This function:
- - reads in the device tree
- - finds and scans the binman node to create all entries
- - selects which images to build
- - Updates the device tress with placeholder properties for offset,
- image-pos, etc.
- Args:
- dtb_fname: Filename of the device tree file to use (.dts or .dtb)
- selected_images: List of images to output, or None for all
- update_fdt: True to update the FDT wth entry offsets, etc.
- Returns:
- OrderedDict of images:
- key: Image name (str)
- value: Image object
- """
- # Import these here in case libfdt.py is not available, in which case
- # the above help option still works.
- from dtoc import fdt
- from dtoc import fdt_util
- global images
- # Get the device tree ready by compiling it and copying the compiled
- # output into a file in our output directly. Then scan it for use
- # in binman.
- dtb_fname = fdt_util.EnsureCompiled(dtb_fname)
- fname = tools.GetOutputFilename('u-boot.dtb.out')
- tools.WriteFile(fname, tools.ReadFile(dtb_fname))
- dtb = fdt.FdtScan(fname)
- node = _FindBinmanNode(dtb)
- if not node:
- raise ValueError("Device tree '%s' does not have a 'binman' "
- "node" % dtb_fname)
- images = _ReadImageDesc(node)
- if select_images:
- skip = []
- new_images = OrderedDict()
- for name, image in images.items():
- if name in select_images:
- new_images[name] = image
- else:
- skip.append(name)
- images = new_images
- tout.Notice('Skipping images: %s' % ', '.join(skip))
- state.Prepare(images, dtb)
- # Prepare the device tree by making sure that any missing
- # properties are added (e.g. 'pos' and 'size'). The values of these
- # may not be correct yet, but we add placeholders so that the
- # size of the device tree is correct. Later, in
- # SetCalculatedProperties() we will insert the correct values
- # without changing the device-tree size, thus ensuring that our
- # entry offsets remain the same.
- for image in images.values():
- image.ExpandEntries()
- if update_fdt:
- image.AddMissingProperties()
- image.ProcessFdt(dtb)
- for dtb_item in state.GetAllFdts():
- dtb_item.Sync(auto_resize=True)
- dtb_item.Pack()
- dtb_item.Flush()
- return images
- def ProcessImage(image, update_fdt, write_map, get_contents=True,
- allow_resize=True, allow_missing=False):
- """Perform all steps for this image, including checking and # writing it.
- This means that errors found with a later image will be reported after
- earlier images are already completed and written, but that does not seem
- important.
- Args:
- image: Image to process
- update_fdt: True to update the FDT wth entry offsets, etc.
- write_map: True to write a map file
- get_contents: True to get the image contents from files, etc., False if
- the contents is already present
- allow_resize: True to allow entries to change size (this does a re-pack
- of the entries), False to raise an exception
- allow_missing: Allow blob_ext objects to be missing
- Returns:
- True if one or more external blobs are missing, False if all are present
- """
- if get_contents:
- image.SetAllowMissing(allow_missing)
- image.GetEntryContents()
- image.GetEntryOffsets()
- # We need to pack the entries to figure out where everything
- # should be placed. This sets the offset/size of each entry.
- # However, after packing we call ProcessEntryContents() which
- # may result in an entry changing size. In that case we need to
- # do another pass. Since the device tree often contains the
- # final offset/size information we try to make space for this in
- # AddMissingProperties() above. However, if the device is
- # compressed we cannot know this compressed size in advance,
- # since changing an offset from 0x100 to 0x104 (for example) can
- # alter the compressed size of the device tree. So we need a
- # third pass for this.
- passes = 5
- for pack_pass in range(passes):
- try:
- image.PackEntries()
- image.CheckSize()
- image.CheckEntries()
- except Exception as e:
- if write_map:
- fname = image.WriteMap()
- print("Wrote map file '%s' to show errors" % fname)
- raise
- image.SetImagePos()
- if update_fdt:
- image.SetCalculatedProperties()
- for dtb_item in state.GetAllFdts():
- dtb_item.Sync()
- dtb_item.Flush()
- image.WriteSymbols()
- sizes_ok = image.ProcessEntryContents()
- if sizes_ok:
- break
- image.ResetForPack()
- tout.Info('Pack completed after %d pass(es)' % (pack_pass + 1))
- if not sizes_ok:
- image.Raise('Entries changed size after packing (tried %s passes)' %
- passes)
- image.BuildImage()
- if write_map:
- image.WriteMap()
- missing_list = []
- image.CheckMissing(missing_list)
- if missing_list:
- tout.Warning("Image '%s' is missing external blobs and is non-functional: %s" %
- (image.name, ' '.join([e.name for e in missing_list])))
- return bool(missing_list)
- def Binman(args):
- """The main control code for binman
- This assumes that help and test options have already been dealt with. It
- deals with the core task of building images.
- Args:
- args: Command line arguments Namespace object
- """
- global Image
- global state
- if args.full_help:
- pager = os.getenv('PAGER')
- if not pager:
- pager = 'more'
- fname = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])),
- 'README')
- command.Run(pager, fname)
- return 0
- # Put these here so that we can import this module without libfdt
- from binman.image import Image
- from binman import state
- if args.cmd in ['ls', 'extract', 'replace']:
- try:
- tout.Init(args.verbosity)
- tools.PrepareOutputDir(None)
- if args.cmd == 'ls':
- ListEntries(args.image, args.paths)
- if args.cmd == 'extract':
- ExtractEntries(args.image, args.filename, args.outdir, args.paths,
- not args.uncompressed)
- if args.cmd == 'replace':
- ReplaceEntries(args.image, args.filename, args.indir, args.paths,
- do_compress=not args.compressed,
- allow_resize=not args.fix_size, write_map=args.map)
- except:
- raise
- finally:
- tools.FinaliseOutputDir()
- return 0
- # Try to figure out which device tree contains our image description
- if args.dt:
- dtb_fname = args.dt
- else:
- board = args.board
- if not board:
- raise ValueError('Must provide a board to process (use -b <board>)')
- board_pathname = os.path.join(args.build_dir, board)
- dtb_fname = os.path.join(board_pathname, 'u-boot.dtb')
- if not args.indir:
- args.indir = ['.']
- args.indir.append(board_pathname)
- try:
- tout.Init(args.verbosity)
- elf.debug = args.debug
- cbfs_util.VERBOSE = args.verbosity > 2
- state.use_fake_dtb = args.fake_dtb
- try:
- tools.SetInputDirs(args.indir)
- tools.PrepareOutputDir(args.outdir, args.preserve)
- tools.SetToolPaths(args.toolpath)
- state.SetEntryArgs(args.entry_arg)
- images = PrepareImagesAndDtbs(dtb_fname, args.image,
- args.update_fdt)
- missing = False
- for image in images.values():
- missing |= ProcessImage(image, args.update_fdt, args.map,
- allow_missing=args.allow_missing)
- # Write the updated FDTs to our output files
- for dtb_item in state.GetAllFdts():
- tools.WriteFile(dtb_item._fname, dtb_item.GetContents())
- if missing:
- tout.Warning("Some images are invalid")
- finally:
- tools.FinaliseOutputDir()
- finally:
- tout.Uninit()
- return 0
|