diff options
Diffstat (limited to 'tools/binman/state.py')
-rw-r--r-- | tools/binman/state.py | 538 |
1 files changed, 538 insertions, 0 deletions
diff --git a/tools/binman/state.py b/tools/binman/state.py new file mode 100644 index 00000000000..f4d885c772a --- /dev/null +++ b/tools/binman/state.py @@ -0,0 +1,538 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright 2018 Google, Inc +# Written by Simon Glass <sjg@chromium.org> +# +# Holds and modifies the state information held by binman +# + +from collections import defaultdict +import hashlib +import re +import time +import threading + +from dtoc import fdt +import os +from u_boot_pylib import tools +from u_boot_pylib import tout + +OUR_PATH = os.path.dirname(os.path.realpath(__file__)) + +# Map an dtb etype to its expected filename +DTB_TYPE_FNAME = { + 'u-boot-spl-dtb': 'spl/u-boot-spl.dtb', + 'u-boot-tpl-dtb': 'tpl/u-boot-tpl.dtb', + 'u-boot-vpl-dtb': 'vpl/u-boot-vpl.dtb', + } + +# Records the device-tree files known to binman, keyed by entry type (e.g. +# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by +# binman. They have been copied to <xxx>.out files. +# +# key: entry type (e.g. 'u-boot-dtb) +# value: tuple: +# Fdt object +# Filename +output_fdt_info = {} + +# Prefix to add to an fdtmap path to turn it into a path to the /binman node +fdt_path_prefix = '' + +# Arguments passed to binman to provide arguments to entries +entry_args = {} + +# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in +# ftest.py) +use_fake_dtb = False + +# The DTB which contains the full image information +main_dtb = None + +# Allow entries to expand after they have been packed. This is detected and +# forces a re-pack. If not allowed, any attempted expansion causes an error in +# Entry.ProcessContentsUpdate() +allow_entry_expansion = True + +# Don't allow entries to contract after they have been packed. Instead just +# leave some wasted space. If allowed, this is detected and forces a re-pack, +# but may result in entries that oscillate in size, thus causing a pack error. +# An example is a compressed device tree where the original offset values +# result in a larger compressed size than the new ones, but then after updating +# to the new ones, the compressed size increases, etc. +allow_entry_contraction = False + +# Number of threads to use for binman (None means machine-dependent) +num_threads = None + + +class Timing: + """Holds information about an operation that is being timed + + Properties: + name: Operation name (only one of each name is stored) + start: Start time of operation in seconds (None if not start) + accum:: Amount of time spent on this operation so far, in seconds + """ + def __init__(self, name): + self.name = name + self.start = None # cause an error if TimingStart() is not called + self.accum = 0.0 + + +# Holds timing info for each name: +# key: name of Timing info (Timing.name) +# value: Timing object +timing_info = {} + + +def GetFdtForEtype(etype): + """Get the Fdt object for a particular device-tree entry + + Binman keeps track of at least one device-tree file called u-boot.dtb but + can also have others (e.g. for SPL). This function looks up the given + entry and returns the associated Fdt object. + + Args: + etype: Entry type of device tree (e.g. 'u-boot-dtb') + + Returns: + Fdt object associated with the entry type + """ + value = output_fdt_info.get(etype); + if not value: + return None + return value[0] + +def GetFdtPath(etype): + """Get the full pathname of a particular Fdt object + + Similar to GetFdtForEtype() but returns the pathname associated with the + Fdt. + + Args: + etype: Entry type of device tree (e.g. 'u-boot-dtb') + + Returns: + Full path name to the associated Fdt + """ + return output_fdt_info[etype][0]._fname + +def GetFdtContents(etype='u-boot-dtb'): + """Looks up the FDT pathname and contents + + This is used to obtain the Fdt pathname and contents when needed by an + entry. It supports a 'fake' dtb, allowing tests to substitute test data for + the real dtb. + + Args: + etype: Entry type to look up (e.g. 'u-boot.dtb'). + + Returns: + tuple: + pathname to Fdt + Fdt data (as bytes) + """ + if etype not in output_fdt_info: + return None, None + if not use_fake_dtb: + pathname = GetFdtPath(etype) + data = GetFdtForEtype(etype).GetContents() + else: + fname = output_fdt_info[etype][1] + pathname = tools.get_input_filename(fname) + data = tools.read_file(pathname) + return pathname, data + +def UpdateFdtContents(etype, data): + """Update the contents of a particular device tree + + The device tree is updated and written back to its file. This affects what + is returned from future called to GetFdtContents(), etc. + + Args: + etype: Entry type (e.g. 'u-boot-dtb') + data: Data to replace the DTB with + """ + dtb, fname = output_fdt_info[etype] + dtb_fname = dtb.GetFilename() + tools.write_file(dtb_fname, data) + dtb = fdt.FdtScan(dtb_fname) + output_fdt_info[etype] = [dtb, fname] + +def SetEntryArgs(args): + """Set the value of the entry args + + This sets up the entry_args dict which is used to supply entry arguments to + entries. + + Args: + args: List of entry arguments, each in the format "name=value" + """ + global entry_args + + entry_args = {} + tout.debug('Processing entry args:') + if args: + for arg in args: + m = re.match('([^=]*)=(.*)', arg) + if not m: + raise ValueError("Invalid entry arguemnt '%s'" % arg) + name, value = m.groups() + tout.debug(' %20s = %s' % (name, value)) + entry_args[name] = value + tout.debug('Processing entry args done') + +def GetEntryArg(name): + """Get the value of an entry argument + + Args: + name: Name of argument to retrieve + + Returns: + String value of argument + """ + return entry_args.get(name) + +def GetEntryArgBool(name): + """Get the value of an entry argument as a boolean + + Args: + name: Name of argument to retrieve + + Returns: + False if the entry argument is consider False (empty, '0' or 'n'), else + True + """ + val = GetEntryArg(name) + return val and val not in ['n', '0'] + +def Prepare(images, dtb): + """Get device tree files ready for use + + This sets up a set of device tree files that can be retrieved by + GetAllFdts(). This includes U-Boot proper and any SPL device trees. + + Args: + images: List of images being used + dtb: Main dtb + """ + global output_fdt_info, main_dtb, fdt_path_prefix + # 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 + + # If we are updating the DTBs we need to put these updated versions + # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb' + # since it is assumed to be the one passed in with options.dt, and + # was handled just above. + main_dtb = dtb + output_fdt_info.clear() + fdt_path_prefix = '' + output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb'] + if use_fake_dtb: + for etype, fname in DTB_TYPE_FNAME.items(): + output_fdt_info[etype] = [dtb, fname] + else: + fdt_set = {} + for etype, fname in DTB_TYPE_FNAME.items(): + infile = tools.get_input_filename(fname, allow_missing=True) + if infile and os.path.exists(infile): + fname_dtb = fdt_util.EnsureCompiled(infile) + out_fname = tools.get_output_filename('%s.out' % + os.path.split(fname)[1]) + tools.write_file(out_fname, tools.read_file(fname_dtb)) + other_dtb = fdt.FdtScan(out_fname) + output_fdt_info[etype] = [other_dtb, out_fname] + + +def PrepareFromLoadedData(image): + """Get device tree files ready for use with a loaded image + + Loaded images are different from images that are being created by binman, + since there is generally already an fdtmap and we read the description from + that. This provides the position and size of every entry in the image with + no calculation required. + + This function uses the same output_fdt_info[] as Prepare(). It finds the + device tree files, adds a reference to the fdtmap and sets the FDT path + prefix to translate from the fdtmap (where the root node is the image node) + to the normal device tree (where the image node is under a /binman node). + + Args: + images: List of images being used + """ + global output_fdt_info, main_dtb, fdt_path_prefix + + tout.info('Preparing device trees') + output_fdt_info.clear() + fdt_path_prefix = '' + output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb'] + main_dtb = None + tout.info(" Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name) + for etype, value in image.GetFdts().items(): + entry, fname = value + out_fname = tools.get_output_filename('%s.dtb' % entry.etype) + tout.info(" Found device tree type '%s' at '%s' path '%s'" % + (etype, out_fname, entry.GetPath())) + entry._filename = entry.GetDefaultFilename() + data = entry.ReadData() + + tools.write_file(out_fname, data) + dtb = fdt.Fdt(out_fname) + dtb.Scan() + image_node = dtb.GetNode('/binman') + if 'multiple-images' in image_node.props: + image_node = dtb.GetNode('/binman/%s' % image.image_node) + fdt_path_prefix = image_node.path + output_fdt_info[etype] = [dtb, None] + tout.info(" FDT path prefix '%s'" % fdt_path_prefix) + + +def GetAllFdts(): + """Yield all device tree files being used by binman + + Yields: + Device trees being used (U-Boot proper, SPL, TPL, VPL) + """ + if main_dtb: + yield main_dtb + for etype in output_fdt_info: + dtb = output_fdt_info[etype][0] + if dtb != main_dtb: + yield dtb + +def GetUpdateNodes(node, for_repack=False): + """Yield all the nodes that need to be updated in all device trees + + The property referenced by this node is added to any device trees which + have the given node. Due to removable of unwanted nodes, SPL and TPL may + not have this node. + + Args: + node: Node object in the main device tree to look up + for_repack: True if we want only nodes which need 'repack' properties + added to them (e.g. 'orig-offset'), False to return all nodes. We + don't add repack properties to SPL/TPL device trees. + + Yields: + Node objects in each device tree that is in use (U-Boot proper, which + is node, SPL and TPL) + """ + yield node + for entry_type, (dtb, fname) in output_fdt_info.items(): + if dtb != node.GetFdt(): + if for_repack and entry_type != 'u-boot-dtb': + continue + other_node = dtb.GetNode(fdt_path_prefix + node.path) + if other_node: + yield other_node + +def AddZeroProp(node, prop, for_repack=False): + """Add a new property to affected device trees with an integer value of 0. + + Args: + prop_name: Name of property + for_repack: True is this property is only needed for repacking + """ + for n in GetUpdateNodes(node, for_repack): + n.AddZeroProp(prop) + +def AddSubnode(node, name): + """Add a new subnode to a node in affected device trees + + Args: + node: Node to add to + name: name of node to add + + Returns: + New subnode that was created in main tree + """ + first = None + for n in GetUpdateNodes(node): + subnode = n.AddSubnode(name) + if not first: + first = subnode + return first + +def AddString(node, prop, value): + """Add a new string property to affected device trees + + Args: + prop_name: Name of property + value: String value (which will be \0-terminated in the DT) + """ + for n in GetUpdateNodes(node): + n.AddString(prop, value) + +def AddInt(node, prop, value): + """Add a new string property to affected device trees + + Args: + prop_name: Name of property + val: Integer value of property + """ + for n in GetUpdateNodes(node): + n.AddInt(prop, value) + +def SetInt(node, prop, value, for_repack=False): + """Update an integer property in affected device trees with an integer value + + This is not allowed to change the size of the FDT. + + Args: + prop_name: Name of property + for_repack: True is this property is only needed for repacking + """ + for n in GetUpdateNodes(node, for_repack): + tout.debug("File %s: Update node '%s' prop '%s' to %#x" % + (n.GetFdt().name, n.path, prop, value)) + n.SetInt(prop, value) + +def CheckAddHashProp(node): + hash_node = node.FindNode('hash') + if hash_node: + algo = hash_node.props.get('algo') + if not algo: + return "Missing 'algo' property for hash node" + if algo.value == 'sha256': + size = 32 + else: + return "Unknown hash algorithm '%s'" % algo.value + for n in GetUpdateNodes(hash_node): + n.AddEmptyProp('value', size) + +def CheckSetHashValue(node, get_data_func): + hash_node = node.FindNode('hash') + if hash_node: + algo = hash_node.props.get('algo').value + data = None + if algo == 'sha256': + m = hashlib.sha256() + m.update(get_data_func()) + data = m.digest() + assert data + for n in GetUpdateNodes(hash_node): + n.SetData('value', data) + +def SetAllowEntryExpansion(allow): + """Set whether post-pack expansion of entries is allowed + + Args: + allow: True to allow expansion, False to raise an exception + """ + global allow_entry_expansion + + allow_entry_expansion = allow + +def AllowEntryExpansion(): + """Check whether post-pack expansion of entries is allowed + + Returns: + True if expansion should be allowed, False if an exception should be + raised + """ + return allow_entry_expansion + +def SetAllowEntryContraction(allow): + """Set whether post-pack contraction of entries is allowed + + Args: + allow: True to allow contraction, False to raise an exception + """ + global allow_entry_contraction + + allow_entry_contraction = allow + +def AllowEntryContraction(): + """Check whether post-pack contraction of entries is allowed + + Returns: + True if contraction should be allowed, False if an exception should be + raised + """ + return allow_entry_contraction + +def SetThreads(threads): + """Set the number of threads to use when building sections + + Args: + threads: Number of threads to use (None for default, 0 for + single-threaded) + """ + global num_threads + + num_threads = threads + +def GetThreads(): + """Get the number of threads to use when building sections + + Returns: + Number of threads to use (None for default, 0 for single-threaded) + """ + return num_threads + +def GetTiming(name): + """Get the timing info for a particular operation + + The object is created if it does not already exist. + + Args: + name: Operation name to get + + Returns: + Timing object for the current thread + """ + threaded_name = '%s:%d' % (name, threading.get_ident()) + timing = timing_info.get(threaded_name) + if not timing: + timing = Timing(threaded_name) + timing_info[threaded_name] = timing + return timing + +def TimingStart(name): + """Start the timer for an operation + + Args: + name: Operation name to start + """ + timing = GetTiming(name) + timing.start = time.monotonic() + +def TimingAccum(name): + """Stop and accumlate the time for an operation + + This measures the time since the last TimingStart() and adds that to the + accumulated time. + + Args: + name: Operation name to start + """ + timing = GetTiming(name) + timing.accum += time.monotonic() - timing.start + +def TimingShow(): + """Show all timing information""" + duration = defaultdict(float) + for threaded_name, timing in timing_info.items(): + name = threaded_name.split(':')[0] + duration[name] += timing.accum + + for name, seconds in duration.items(): + print('%10s: %10.1fms' % (name, seconds * 1000)) + +def GetVersion(path=OUR_PATH): + """Get the version string for binman + + Args: + path: Path to 'version' file + + Returns: + str: String version, e.g. 'v2021.10' + """ + version_fname = os.path.join(path, 'version') + if os.path.exists(version_fname): + version = tools.read_file(version_fname, binary=False) + else: + version = '(unreleased)' + return version |