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