uboot/tools/binman/state.py
<<
>>
Prefs
   1# SPDX-License-Identifier: GPL-2.0+
   2# Copyright 2018 Google, Inc
   3# Written by Simon Glass <sjg@chromium.org>
   4#
   5# Holds and modifies the state information held by binman
   6#
   7
   8from collections import defaultdict
   9import hashlib
  10import re
  11import time
  12import threading
  13
  14from dtoc import fdt
  15import os
  16from patman import tools
  17from patman import tout
  18
  19# Map an dtb etype to its expected filename
  20DTB_TYPE_FNAME = {
  21    'u-boot-spl-dtb': 'spl/u-boot-spl.dtb',
  22    'u-boot-tpl-dtb': 'tpl/u-boot-tpl.dtb',
  23    }
  24
  25# Records the device-tree files known to binman, keyed by entry type (e.g.
  26# 'u-boot-spl-dtb'). These are the output FDT files, which can be updated by
  27# binman. They have been copied to <xxx>.out files.
  28#
  29#   key: entry type (e.g. 'u-boot-dtb)
  30#   value: tuple:
  31#       Fdt object
  32#       Filename
  33output_fdt_info = {}
  34
  35# Prefix to add to an fdtmap path to turn it into a path to the /binman node
  36fdt_path_prefix = ''
  37
  38# Arguments passed to binman to provide arguments to entries
  39entry_args = {}
  40
  41# True to use fake device-tree files for testing (see U_BOOT_DTB_DATA in
  42# ftest.py)
  43use_fake_dtb = False
  44
  45# The DTB which contains the full image information
  46main_dtb = None
  47
  48# Allow entries to expand after they have been packed. This is detected and
  49# forces a re-pack. If not allowed, any attempted expansion causes an error in
  50# Entry.ProcessContentsUpdate()
  51allow_entry_expansion = True
  52
  53# Don't allow entries to contract after they have been packed. Instead just
  54# leave some wasted space. If allowed, this is detected and forces a re-pack,
  55# but may result in entries that oscillate in size, thus causing a pack error.
  56# An example is a compressed device tree where the original offset values
  57# result in a larger compressed size than the new ones, but then after updating
  58# to the new ones, the compressed size increases, etc.
  59allow_entry_contraction = False
  60
  61# Number of threads to use for binman (None means machine-dependent)
  62num_threads = None
  63
  64
  65class Timing:
  66    """Holds information about an operation that is being timed
  67
  68    Properties:
  69        name: Operation name (only one of each name is stored)
  70        start: Start time of operation in seconds (None if not start)
  71        accum:: Amount of time spent on this operation so far, in seconds
  72    """
  73    def __init__(self, name):
  74        self.name = name
  75        self.start = None # cause an error if TimingStart() is not called
  76        self.accum = 0.0
  77
  78
  79# Holds timing info for each name:
  80#    key: name of Timing info (Timing.name)
  81#    value: Timing object
  82timing_info = {}
  83
  84
  85def GetFdtForEtype(etype):
  86    """Get the Fdt object for a particular device-tree entry
  87
  88    Binman keeps track of at least one device-tree file called u-boot.dtb but
  89    can also have others (e.g. for SPL). This function looks up the given
  90    entry and returns the associated Fdt object.
  91
  92    Args:
  93        etype: Entry type of device tree (e.g. 'u-boot-dtb')
  94
  95    Returns:
  96        Fdt object associated with the entry type
  97    """
  98    value = output_fdt_info.get(etype);
  99    if not value:
 100        return None
 101    return value[0]
 102
 103def GetFdtPath(etype):
 104    """Get the full pathname of a particular Fdt object
 105
 106    Similar to GetFdtForEtype() but returns the pathname associated with the
 107    Fdt.
 108
 109    Args:
 110        etype: Entry type of device tree (e.g. 'u-boot-dtb')
 111
 112    Returns:
 113        Full path name to the associated Fdt
 114    """
 115    return output_fdt_info[etype][0]._fname
 116
 117def GetFdtContents(etype='u-boot-dtb'):
 118    """Looks up the FDT pathname and contents
 119
 120    This is used to obtain the Fdt pathname and contents when needed by an
 121    entry. It supports a 'fake' dtb, allowing tests to substitute test data for
 122    the real dtb.
 123
 124    Args:
 125        etype: Entry type to look up (e.g. 'u-boot.dtb').
 126
 127    Returns:
 128        tuple:
 129            pathname to Fdt
 130            Fdt data (as bytes)
 131    """
 132    if etype not in output_fdt_info:
 133        return None, None
 134    if not use_fake_dtb:
 135        pathname = GetFdtPath(etype)
 136        data = GetFdtForEtype(etype).GetContents()
 137    else:
 138        fname = output_fdt_info[etype][1]
 139        pathname = tools.GetInputFilename(fname)
 140        data = tools.ReadFile(pathname)
 141    return pathname, data
 142
 143def UpdateFdtContents(etype, data):
 144    """Update the contents of a particular device tree
 145
 146    The device tree is updated and written back to its file. This affects what
 147    is returned from future called to GetFdtContents(), etc.
 148
 149    Args:
 150        etype: Entry type (e.g. 'u-boot-dtb')
 151        data: Data to replace the DTB with
 152    """
 153    dtb, fname = output_fdt_info[etype]
 154    dtb_fname = dtb.GetFilename()
 155    tools.WriteFile(dtb_fname, data)
 156    dtb = fdt.FdtScan(dtb_fname)
 157    output_fdt_info[etype] = [dtb, fname]
 158
 159def SetEntryArgs(args):
 160    """Set the value of the entry args
 161
 162    This sets up the entry_args dict which is used to supply entry arguments to
 163    entries.
 164
 165    Args:
 166        args: List of entry arguments, each in the format "name=value"
 167    """
 168    global entry_args
 169
 170    entry_args = {}
 171    tout.Debug('Processing entry args:')
 172    if args:
 173        for arg in args:
 174            m = re.match('([^=]*)=(.*)', arg)
 175            if not m:
 176                raise ValueError("Invalid entry arguemnt '%s'" % arg)
 177            name, value = m.groups()
 178            tout.Debug('   %20s = %s' % (name, value))
 179            entry_args[name] = value
 180    tout.Debug('Processing entry args done')
 181
 182def GetEntryArg(name):
 183    """Get the value of an entry argument
 184
 185    Args:
 186        name: Name of argument to retrieve
 187
 188    Returns:
 189        String value of argument
 190    """
 191    return entry_args.get(name)
 192
 193def GetEntryArgBool(name):
 194    """Get the value of an entry argument as a boolean
 195
 196    Args:
 197        name: Name of argument to retrieve
 198
 199    Returns:
 200        False if the entry argument is consider False (empty, '0' or 'n'), else
 201            True
 202    """
 203    val = GetEntryArg(name)
 204    return val and val not in ['n', '0']
 205
 206def Prepare(images, dtb):
 207    """Get device tree files ready for use
 208
 209    This sets up a set of device tree files that can be retrieved by
 210    GetAllFdts(). This includes U-Boot proper and any SPL device trees.
 211
 212    Args:
 213        images: List of images being used
 214        dtb: Main dtb
 215    """
 216    global output_fdt_info, main_dtb, fdt_path_prefix
 217    # Import these here in case libfdt.py is not available, in which case
 218    # the above help option still works.
 219    from dtoc import fdt
 220    from dtoc import fdt_util
 221
 222    # If we are updating the DTBs we need to put these updated versions
 223    # where Entry_blob_dtb can find them. We can ignore 'u-boot.dtb'
 224    # since it is assumed to be the one passed in with options.dt, and
 225    # was handled just above.
 226    main_dtb = dtb
 227    output_fdt_info.clear()
 228    fdt_path_prefix = ''
 229    output_fdt_info['u-boot-dtb'] = [dtb, 'u-boot.dtb']
 230    if use_fake_dtb:
 231        for etype, fname in DTB_TYPE_FNAME.items():
 232            output_fdt_info[etype] = [dtb, fname]
 233    else:
 234        fdt_set = {}
 235        for etype, fname in DTB_TYPE_FNAME.items():
 236            infile = tools.GetInputFilename(fname, allow_missing=True)
 237            if infile and os.path.exists(infile):
 238                fname_dtb = fdt_util.EnsureCompiled(infile)
 239                out_fname = tools.GetOutputFilename('%s.out' %
 240                        os.path.split(fname)[1])
 241                tools.WriteFile(out_fname, tools.ReadFile(fname_dtb))
 242                other_dtb = fdt.FdtScan(out_fname)
 243                output_fdt_info[etype] = [other_dtb, out_fname]
 244
 245
 246def PrepareFromLoadedData(image):
 247    """Get device tree files ready for use with a loaded image
 248
 249    Loaded images are different from images that are being created by binman,
 250    since there is generally already an fdtmap and we read the description from
 251    that. This provides the position and size of every entry in the image with
 252    no calculation required.
 253
 254    This function uses the same output_fdt_info[] as Prepare(). It finds the
 255    device tree files, adds a reference to the fdtmap and sets the FDT path
 256    prefix to translate from the fdtmap (where the root node is the image node)
 257    to the normal device tree (where the image node is under a /binman node).
 258
 259    Args:
 260        images: List of images being used
 261    """
 262    global output_fdt_info, main_dtb, fdt_path_prefix
 263
 264    tout.Info('Preparing device trees')
 265    output_fdt_info.clear()
 266    fdt_path_prefix = ''
 267    output_fdt_info['fdtmap'] = [image.fdtmap_dtb, 'u-boot.dtb']
 268    main_dtb = None
 269    tout.Info("   Found device tree type 'fdtmap' '%s'" % image.fdtmap_dtb.name)
 270    for etype, value in image.GetFdts().items():
 271        entry, fname = value
 272        out_fname = tools.GetOutputFilename('%s.dtb' % entry.etype)
 273        tout.Info("   Found device tree type '%s' at '%s' path '%s'" %
 274                  (etype, out_fname, entry.GetPath()))
 275        entry._filename = entry.GetDefaultFilename()
 276        data = entry.ReadData()
 277
 278        tools.WriteFile(out_fname, data)
 279        dtb = fdt.Fdt(out_fname)
 280        dtb.Scan()
 281        image_node = dtb.GetNode('/binman')
 282        if 'multiple-images' in image_node.props:
 283            image_node = dtb.GetNode('/binman/%s' % image.image_node)
 284        fdt_path_prefix = image_node.path
 285        output_fdt_info[etype] = [dtb, None]
 286    tout.Info("   FDT path prefix '%s'" % fdt_path_prefix)
 287
 288
 289def GetAllFdts():
 290    """Yield all device tree files being used by binman
 291
 292    Yields:
 293        Device trees being used (U-Boot proper, SPL, TPL)
 294    """
 295    if main_dtb:
 296        yield main_dtb
 297    for etype in output_fdt_info:
 298        dtb = output_fdt_info[etype][0]
 299        if dtb != main_dtb:
 300            yield dtb
 301
 302def GetUpdateNodes(node, for_repack=False):
 303    """Yield all the nodes that need to be updated in all device trees
 304
 305    The property referenced by this node is added to any device trees which
 306    have the given node. Due to removable of unwanted notes, SPL and TPL may
 307    not have this node.
 308
 309    Args:
 310        node: Node object in the main device tree to look up
 311        for_repack: True if we want only nodes which need 'repack' properties
 312            added to them (e.g. 'orig-offset'), False to return all nodes. We
 313            don't add repack properties to SPL/TPL device trees.
 314
 315    Yields:
 316        Node objects in each device tree that is in use (U-Boot proper, which
 317            is node, SPL and TPL)
 318    """
 319    yield node
 320    for entry_type, (dtb, fname) in output_fdt_info.items():
 321        if dtb != node.GetFdt():
 322            if for_repack and entry_type != 'u-boot-dtb':
 323                continue
 324            other_node = dtb.GetNode(fdt_path_prefix + node.path)
 325            if other_node:
 326                yield other_node
 327
 328def AddZeroProp(node, prop, for_repack=False):
 329    """Add a new property to affected device trees with an integer value of 0.
 330
 331    Args:
 332        prop_name: Name of property
 333        for_repack: True is this property is only needed for repacking
 334    """
 335    for n in GetUpdateNodes(node, for_repack):
 336        n.AddZeroProp(prop)
 337
 338def AddSubnode(node, name):
 339    """Add a new subnode to a node in affected device trees
 340
 341    Args:
 342        node: Node to add to
 343        name: name of node to add
 344
 345    Returns:
 346        New subnode that was created in main tree
 347    """
 348    first = None
 349    for n in GetUpdateNodes(node):
 350        subnode = n.AddSubnode(name)
 351        if not first:
 352            first = subnode
 353    return first
 354
 355def AddString(node, prop, value):
 356    """Add a new string property to affected device trees
 357
 358    Args:
 359        prop_name: Name of property
 360        value: String value (which will be \0-terminated in the DT)
 361    """
 362    for n in GetUpdateNodes(node):
 363        n.AddString(prop, value)
 364
 365def AddInt(node, prop, value):
 366    """Add a new string property to affected device trees
 367
 368    Args:
 369        prop_name: Name of property
 370        val: Integer value of property
 371    """
 372    for n in GetUpdateNodes(node):
 373        n.AddInt(prop, value)
 374
 375def SetInt(node, prop, value, for_repack=False):
 376    """Update an integer property in affected device trees with an integer value
 377
 378    This is not allowed to change the size of the FDT.
 379
 380    Args:
 381        prop_name: Name of property
 382        for_repack: True is this property is only needed for repacking
 383    """
 384    for n in GetUpdateNodes(node, for_repack):
 385        tout.Detail("File %s: Update node '%s' prop '%s' to %#x" %
 386                    (n.GetFdt().name, n.path, prop, value))
 387        n.SetInt(prop, value)
 388
 389def CheckAddHashProp(node):
 390    hash_node = node.FindNode('hash')
 391    if hash_node:
 392        algo = hash_node.props.get('algo')
 393        if not algo:
 394            return "Missing 'algo' property for hash node"
 395        if algo.value == 'sha256':
 396            size = 32
 397        else:
 398            return "Unknown hash algorithm '%s'" % algo
 399        for n in GetUpdateNodes(hash_node):
 400            n.AddEmptyProp('value', size)
 401
 402def CheckSetHashValue(node, get_data_func):
 403    hash_node = node.FindNode('hash')
 404    if hash_node:
 405        algo = hash_node.props.get('algo').value
 406        if algo == 'sha256':
 407            m = hashlib.sha256()
 408            m.update(get_data_func())
 409            data = m.digest()
 410        for n in GetUpdateNodes(hash_node):
 411            n.SetData('value', data)
 412
 413def SetAllowEntryExpansion(allow):
 414    """Set whether post-pack expansion of entries is allowed
 415
 416    Args:
 417       allow: True to allow expansion, False to raise an exception
 418    """
 419    global allow_entry_expansion
 420
 421    allow_entry_expansion = allow
 422
 423def AllowEntryExpansion():
 424    """Check whether post-pack expansion of entries is allowed
 425
 426    Returns:
 427        True if expansion should be allowed, False if an exception should be
 428            raised
 429    """
 430    return allow_entry_expansion
 431
 432def SetAllowEntryContraction(allow):
 433    """Set whether post-pack contraction of entries is allowed
 434
 435    Args:
 436       allow: True to allow contraction, False to raise an exception
 437    """
 438    global allow_entry_contraction
 439
 440    allow_entry_contraction = allow
 441
 442def AllowEntryContraction():
 443    """Check whether post-pack contraction of entries is allowed
 444
 445    Returns:
 446        True if contraction should be allowed, False if an exception should be
 447            raised
 448    """
 449    return allow_entry_contraction
 450
 451def SetThreads(threads):
 452    """Set the number of threads to use when building sections
 453
 454    Args:
 455        threads: Number of threads to use (None for default, 0 for
 456            single-threaded)
 457    """
 458    global num_threads
 459
 460    num_threads = threads
 461
 462def GetThreads():
 463    """Get the number of threads to use when building sections
 464
 465    Returns:
 466        Number of threads to use (None for default, 0 for single-threaded)
 467    """
 468    return num_threads
 469
 470def GetTiming(name):
 471    """Get the timing info for a particular operation
 472
 473    The object is created if it does not already exist.
 474
 475    Args:
 476        name: Operation name to get
 477
 478    Returns:
 479        Timing object for the current thread
 480    """
 481    threaded_name = '%s:%d' % (name, threading.get_ident())
 482    timing = timing_info.get(threaded_name)
 483    if not timing:
 484        timing = Timing(threaded_name)
 485        timing_info[threaded_name] = timing
 486    return timing
 487
 488def TimingStart(name):
 489    """Start the timer for an operation
 490
 491    Args:
 492        name: Operation name to start
 493    """
 494    timing = GetTiming(name)
 495    timing.start = time.monotonic()
 496
 497def TimingAccum(name):
 498    """Stop and accumlate the time for an operation
 499
 500    This measures the time since the last TimingStart() and adds that to the
 501    accumulated time.
 502
 503    Args:
 504        name: Operation name to start
 505    """
 506    timing = GetTiming(name)
 507    timing.accum += time.monotonic() - timing.start
 508
 509def TimingShow():
 510    """Show all timing information"""
 511    duration = defaultdict(float)
 512    for threaded_name, timing in timing_info.items():
 513        name = threaded_name.split(':')[0]
 514        duration[name] += timing.accum
 515
 516    for name, seconds in duration.items():
 517        print('%10s: %10.1fms' % (name, seconds * 1000))
 518