mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-18 10:48:51 +00:00
6ad2452bc6
Add support for U-Boot's Verifying Program Loader phase. Signed-off-by: Simon Glass <sjg@chromium.org>
536 lines
16 KiB
Python
536 lines
16 KiB
Python
# 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 patman import tools
|
|
from patman 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 notes, 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.detail("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
|
|
if algo == 'sha256':
|
|
m = hashlib.sha256()
|
|
m.update(get_data_func())
|
|
data = m.digest()
|
|
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
|