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