1#!/usr/bin/python 2# SPDX-License-Identifier: GPL-2.0+ 3# 4# Copyright (C) 2017 Google, Inc 5# Written by Simon Glass <sjg@chromium.org> 6# 7 8"""Scanning of U-Boot source for drivers and structs 9 10This scans the source tree to find out things about all instances of 11U_BOOT_DRIVER(), UCLASS_DRIVER and all struct declarations in header files. 12 13See doc/driver-model/of-plat.rst for more informaiton 14""" 15 16import os 17import re 18import sys 19 20 21def conv_name_to_c(name): 22 """Convert a device-tree name to a C identifier 23 24 This uses multiple replace() calls instead of re.sub() since it is faster 25 (400ms for 1m calls versus 1000ms for the 're' version). 26 27 Args: 28 name (str): Name to convert 29 Return: 30 str: String containing the C version of this name 31 """ 32 new = name.replace('@', '_at_') 33 new = new.replace('-', '_') 34 new = new.replace(',', '_') 35 new = new.replace('.', '_') 36 if new == '/': 37 return 'root' 38 return new 39 40def get_compat_name(node): 41 """Get the node's list of compatible string as a C identifiers 42 43 Args: 44 node (fdt.Node): Node object to check 45 Return: 46 list of str: List of C identifiers for all the compatible strings 47 """ 48 compat = node.props['compatible'].value 49 if not isinstance(compat, list): 50 compat = [compat] 51 return [conv_name_to_c(c) for c in compat] 52 53 54class Driver: 55 """Information about a driver in U-Boot 56 57 Attributes: 58 name: Name of driver. For U_BOOT_DRIVER(x) this is 'x' 59 fname: Filename where the driver was found 60 uclass_id: Name of uclass, e.g. 'UCLASS_I2C' 61 compat: Driver data for each compatible string: 62 key: Compatible string, e.g. 'rockchip,rk3288-grf' 63 value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None 64 fname: Filename where the driver was found 65 priv (str): struct name of the priv_auto member, e.g. 'serial_priv' 66 plat (str): struct name of the plat_auto member, e.g. 'serial_plat' 67 child_priv (str): struct name of the per_child_auto member, 68 e.g. 'pci_child_priv' 69 child_plat (str): struct name of the per_child_plat_auto member, 70 e.g. 'pci_child_plat' 71 used (bool): True if the driver is used by the structs being output 72 phase (str): Which phase of U-Boot to use this driver 73 headers (list): List of header files needed for this driver (each a str) 74 e.g. ['<asm/cpu.h>'] 75 dups (list): Driver objects with the same name as this one, that were 76 found after this one 77 warn_dups (bool): True if the duplicates are not distinguisble using 78 the phase 79 uclass (Uclass): uclass for this driver 80 """ 81 def __init__(self, name, fname): 82 self.name = name 83 self.fname = fname 84 self.uclass_id = None 85 self.compat = None 86 self.priv = '' 87 self.plat = '' 88 self.child_priv = '' 89 self.child_plat = '' 90 self.used = False 91 self.phase = '' 92 self.headers = [] 93 self.dups = [] 94 self.warn_dups = False 95 self.uclass = None 96 97 def __eq__(self, other): 98 return (self.name == other.name and 99 self.uclass_id == other.uclass_id and 100 self.compat == other.compat and 101 self.priv == other.priv and 102 self.plat == other.plat and 103 self.used == other.used) 104 105 def __repr__(self): 106 return ("Driver(name='%s', used=%s, uclass_id='%s', compat=%s, priv=%s)" % 107 (self.name, self.used, self.uclass_id, self.compat, self.priv)) 108 109 110class UclassDriver: 111 """Holds information about a uclass driver 112 113 Attributes: 114 name: Uclass name, e.g. 'i2c' if the driver is for UCLASS_I2C 115 uclass_id: Uclass ID, e.g. 'UCLASS_I2C' 116 priv: struct name of the private data, e.g. 'i2c_priv' 117 per_dev_priv (str): struct name of the priv_auto member, e.g. 'spi_info' 118 per_dev_plat (str): struct name of the plat_auto member, e.g. 'i2c_chip' 119 per_child_priv (str): struct name of the per_child_auto member, 120 e.g. 'pci_child_priv' 121 per_child_plat (str): struct name of the per_child_plat_auto member, 122 e.g. 'pci_child_plat' 123 alias_num_to_node (dict): Aliases for this uclasses (for sequence 124 numbers) 125 key (int): Alias number, e.g. 2 for "pci2" 126 value (str): Node the alias points to 127 alias_path_to_num (dict): Convert a path to an alias number 128 key (str): Full path to node (e.g. '/soc/pci') 129 seq (int): Alias number, e.g. 2 for "pci2" 130 devs (list): List of devices in this uclass, each a Node 131 node_refs (dict): References in the linked list of devices: 132 key (int): Sequence number (0=first, n-1=last, -1=head, n=tail) 133 value (str): Reference to the device at that position 134 """ 135 def __init__(self, name): 136 self.name = name 137 self.uclass_id = None 138 self.priv = '' 139 self.per_dev_priv = '' 140 self.per_dev_plat = '' 141 self.per_child_priv = '' 142 self.per_child_plat = '' 143 self.alias_num_to_node = {} 144 self.alias_path_to_num = {} 145 self.devs = [] 146 self.node_refs = {} 147 148 def __eq__(self, other): 149 return (self.name == other.name and 150 self.uclass_id == other.uclass_id and 151 self.priv == other.priv) 152 153 def __repr__(self): 154 return ("UclassDriver(name='%s', uclass_id='%s')" % 155 (self.name, self.uclass_id)) 156 157 def __hash__(self): 158 # We can use the uclass ID since it is unique among uclasses 159 return hash(self.uclass_id) 160 161 162class Struct: 163 """Holds information about a struct definition 164 165 Attributes: 166 name: Struct name, e.g. 'fred' if the struct is 'struct fred' 167 fname: Filename containing the struct, in a format that C files can 168 include, e.g. 'asm/clk.h' 169 """ 170 def __init__(self, name, fname): 171 self.name = name 172 self.fname =fname 173 174 def __repr__(self): 175 return ("Struct(name='%s', fname='%s')" % (self.name, self.fname)) 176 177 178class Scanner: 179 """Scanning of the U-Boot source tree 180 181 Properties: 182 _basedir (str): Base directory of U-Boot source code. Defaults to the 183 grandparent of this file's directory 184 _drivers: Dict of valid driver names found in drivers/ 185 key: Driver name 186 value: Driver for that driver 187 _driver_aliases: Dict that holds aliases for driver names 188 key: Driver alias declared with 189 DM_DRIVER_ALIAS(driver_alias, driver_name) 190 value: Driver name declared with U_BOOT_DRIVER(driver_name) 191 _drivers_additional (list or str): List of additional drivers to use 192 during scanning 193 _of_match: Dict holding information about compatible strings 194 key: Name of struct udevice_id variable 195 value: Dict of compatible info in that variable: 196 key: Compatible string, e.g. 'rockchip,rk3288-grf' 197 value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None 198 _compat_to_driver: Maps compatible strings to Driver 199 _uclass: Dict of uclass information 200 key: uclass name, e.g. 'UCLASS_I2C' 201 value: UClassDriver 202 _structs: Dict of all structs found in U-Boot: 203 key: Name of struct 204 value: Struct object 205 _phase: The phase of U-Boot that we are generating data for, e.g. 'spl' 206 or 'tpl'. None if not known 207 """ 208 def __init__(self, basedir, drivers_additional, phase=''): 209 """Set up a new Scanner 210 """ 211 if not basedir: 212 basedir = sys.argv[0].replace('tools/dtoc/dtoc', '') 213 if basedir == '': 214 basedir = './' 215 self._basedir = basedir 216 self._drivers = {} 217 self._driver_aliases = {} 218 self._drivers_additional = drivers_additional or [] 219 self._missing_drivers = set() 220 self._of_match = {} 221 self._compat_to_driver = {} 222 self._uclass = {} 223 self._structs = {} 224 self._phase = phase 225 226 def get_driver(self, name): 227 """Get a driver given its name 228 229 Args: 230 name (str): Driver name 231 232 Returns: 233 Driver: Driver or None if not found 234 """ 235 return self._drivers.get(name) 236 237 def get_normalized_compat_name(self, node): 238 """Get a node's normalized compat name 239 240 Returns a valid driver name by retrieving node's list of compatible 241 string as a C identifier and performing a check against _drivers 242 and a lookup in driver_aliases printing a warning in case of failure. 243 244 Args: 245 node (Node): Node object to check 246 Return: 247 Tuple: 248 Driver name associated with the first compatible string 249 List of C identifiers for all the other compatible strings 250 (possibly empty) 251 In case of no match found, the return will be the same as 252 get_compat_name() 253 """ 254 if not node.parent: 255 compat_list_c = ['root_driver'] 256 else: 257 compat_list_c = get_compat_name(node) 258 259 for compat_c in compat_list_c: 260 if not compat_c in self._drivers.keys(): 261 compat_c = self._driver_aliases.get(compat_c) 262 if not compat_c: 263 continue 264 265 aliases_c = compat_list_c 266 if compat_c in aliases_c: 267 aliases_c.remove(compat_c) 268 return compat_c, aliases_c 269 270 self._missing_drivers.add(compat_list_c[0]) 271 272 return compat_list_c[0], compat_list_c[1:] 273 274 def _parse_structs(self, fname, buff): 275 """Parse a H file to extract struct definitions contained within 276 277 This parses 'struct xx {' definitions to figure out what structs this 278 header defines. 279 280 Args: 281 buff (str): Contents of file 282 fname (str): Filename (to use when printing errors) 283 """ 284 structs = {} 285 286 re_struct = re.compile('^struct ([a-z0-9_]+) {$') 287 re_asm = re.compile('../arch/[a-z0-9]+/include/asm/(.*)') 288 prefix = '' 289 for line in buff.splitlines(): 290 # Handle line continuation 291 if prefix: 292 line = prefix + line 293 prefix = '' 294 if line.endswith('\\'): 295 prefix = line[:-1] 296 continue 297 298 m_struct = re_struct.match(line) 299 if m_struct: 300 name = m_struct.group(1) 301 include_dir = os.path.join(self._basedir, 'include') 302 rel_fname = os.path.relpath(fname, include_dir) 303 m_asm = re_asm.match(rel_fname) 304 if m_asm: 305 rel_fname = 'asm/' + m_asm.group(1) 306 structs[name] = Struct(name, rel_fname) 307 self._structs.update(structs) 308 309 @classmethod 310 def _get_re_for_member(cls, member): 311 """_get_re_for_member: Get a compiled regular expression 312 313 Args: 314 member (str): Struct member name, e.g. 'priv_auto' 315 316 Returns: 317 re.Pattern: Compiled regular expression that parses: 318 319 .member = sizeof(struct fred), 320 321 and returns "fred" as group 1 322 """ 323 return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member) 324 325 def _parse_uclass_driver(self, fname, buff): 326 """Parse a C file to extract uclass driver information contained within 327 328 This parses UCLASS_DRIVER() structs to obtain various pieces of useful 329 information. 330 331 It updates the following member: 332 _uclass: Dict of uclass information 333 key: uclass name, e.g. 'UCLASS_I2C' 334 value: UClassDriver 335 336 Args: 337 fname (str): Filename being parsed (used for warnings) 338 buff (str): Contents of file 339 """ 340 uc_drivers = {} 341 342 # Collect the driver name and associated Driver 343 driver = None 344 re_driver = re.compile(r'^UCLASS_DRIVER\((.*)\)') 345 346 # Collect the uclass ID, e.g. 'UCLASS_SPI' 347 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)') 348 349 # Matches the header/size information for uclass-private data 350 re_priv = self._get_re_for_member('priv_auto') 351 352 # Set up parsing for the auto members 353 re_per_device_priv = self._get_re_for_member('per_device_auto') 354 re_per_device_plat = self._get_re_for_member('per_device_plat_auto') 355 re_per_child_priv = self._get_re_for_member('per_child_auto') 356 re_per_child_plat = self._get_re_for_member('per_child_plat_auto') 357 358 prefix = '' 359 for line in buff.splitlines(): 360 # Handle line continuation 361 if prefix: 362 line = prefix + line 363 prefix = '' 364 if line.endswith('\\'): 365 prefix = line[:-1] 366 continue 367 368 driver_match = re_driver.search(line) 369 370 # If we have seen UCLASS_DRIVER()... 371 if driver: 372 m_id = re_id.search(line) 373 m_priv = re_priv.match(line) 374 m_per_dev_priv = re_per_device_priv.match(line) 375 m_per_dev_plat = re_per_device_plat.match(line) 376 m_per_child_priv = re_per_child_priv.match(line) 377 m_per_child_plat = re_per_child_plat.match(line) 378 if m_id: 379 driver.uclass_id = m_id.group(1) 380 elif m_priv: 381 driver.priv = m_priv.group(1) 382 elif m_per_dev_priv: 383 driver.per_dev_priv = m_per_dev_priv.group(1) 384 elif m_per_dev_plat: 385 driver.per_dev_plat = m_per_dev_plat.group(1) 386 elif m_per_child_priv: 387 driver.per_child_priv = m_per_child_priv.group(1) 388 elif m_per_child_plat: 389 driver.per_child_plat = m_per_child_plat.group(1) 390 elif '};' in line: 391 if not driver.uclass_id: 392 raise ValueError( 393 "%s: Cannot parse uclass ID in driver '%s'" % 394 (fname, driver.name)) 395 uc_drivers[driver.uclass_id] = driver 396 driver = None 397 398 elif driver_match: 399 driver_name = driver_match.group(1) 400 driver = UclassDriver(driver_name) 401 402 self._uclass.update(uc_drivers) 403 404 def _parse_driver(self, fname, buff): 405 """Parse a C file to extract driver information contained within 406 407 This parses U_BOOT_DRIVER() structs to obtain various pieces of useful 408 information. 409 410 It updates the following members: 411 _drivers - updated with new Driver records for each driver found 412 in the file 413 _of_match - updated with each compatible string found in the file 414 _compat_to_driver - Maps compatible string to Driver 415 _driver_aliases - Maps alias names to driver name 416 417 Args: 418 fname (str): Filename being parsed (used for warnings) 419 buff (str): Contents of file 420 421 Raises: 422 ValueError: Compatible variable is mentioned in .of_match in 423 U_BOOT_DRIVER() but not found in the file 424 """ 425 # Dict holding information about compatible strings collected in this 426 # function so far 427 # key: Name of struct udevice_id variable 428 # value: Dict of compatible info in that variable: 429 # key: Compatible string, e.g. 'rockchip,rk3288-grf' 430 # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None 431 of_match = {} 432 433 # Dict holding driver information collected in this function so far 434 # key: Driver name (C name as in U_BOOT_DRIVER(xxx)) 435 # value: Driver 436 drivers = {} 437 438 # Collect the driver info 439 driver = None 440 re_driver = re.compile(r'^U_BOOT_DRIVER\((.*)\)') 441 442 # Collect the uclass ID, e.g. 'UCLASS_SPI' 443 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)') 444 445 # Collect the compatible string, e.g. 'rockchip,rk3288-grf' 446 compat = None 447 re_compat = re.compile(r'{\s*.compatible\s*=\s*"(.*)"\s*' 448 r'(,\s*.data\s*=\s*(\S*))?\s*},') 449 450 # This is a dict of compatible strings that were found: 451 # key: Compatible string, e.g. 'rockchip,rk3288-grf' 452 # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None 453 compat_dict = {} 454 455 # Holds the var nane of the udevice_id list, e.g. 456 # 'rk3288_syscon_ids_noc' in 457 # static const struct udevice_id rk3288_syscon_ids_noc[] = { 458 ids_name = None 459 re_ids = re.compile(r'struct udevice_id (.*)\[\]\s*=') 460 461 # Matches the references to the udevice_id list 462 re_of_match = re.compile( 463 r'\.of_match\s*=\s*(of_match_ptr\()?([a-z0-9_]+)(\))?,') 464 465 re_phase = re.compile('^\s*DM_PHASE\((.*)\).*$') 466 re_hdr = re.compile('^\s*DM_HEADER\((.*)\).*$') 467 re_alias = re.compile(r'DM_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)') 468 469 # Matches the struct name for priv, plat 470 re_priv = self._get_re_for_member('priv_auto') 471 re_plat = self._get_re_for_member('plat_auto') 472 re_child_priv = self._get_re_for_member('per_child_auto') 473 re_child_plat = self._get_re_for_member('per_child_plat_auto') 474 475 prefix = '' 476 for line in buff.splitlines(): 477 # Handle line continuation 478 if prefix: 479 line = prefix + line 480 prefix = '' 481 if line.endswith('\\'): 482 prefix = line[:-1] 483 continue 484 485 driver_match = re_driver.search(line) 486 487 # If this line contains U_BOOT_DRIVER()... 488 if driver: 489 m_id = re_id.search(line) 490 m_of_match = re_of_match.search(line) 491 m_priv = re_priv.match(line) 492 m_plat = re_plat.match(line) 493 m_cplat = re_child_plat.match(line) 494 m_cpriv = re_child_priv.match(line) 495 m_phase = re_phase.match(line) 496 m_hdr = re_hdr.match(line) 497 if m_priv: 498 driver.priv = m_priv.group(1) 499 elif m_plat: 500 driver.plat = m_plat.group(1) 501 elif m_cplat: 502 driver.child_plat = m_cplat.group(1) 503 elif m_cpriv: 504 driver.child_priv = m_cpriv.group(1) 505 elif m_id: 506 driver.uclass_id = m_id.group(1) 507 elif m_of_match: 508 compat = m_of_match.group(2) 509 elif m_phase: 510 driver.phase = m_phase.group(1) 511 elif m_hdr: 512 driver.headers.append(m_hdr.group(1)) 513 elif '};' in line: 514 is_root = driver.name == 'root_driver' 515 if driver.uclass_id and (compat or is_root): 516 if not is_root: 517 if compat not in of_match: 518 raise ValueError( 519 "%s: Unknown compatible var '%s' (found: %s)" % 520 (fname, compat, ','.join(of_match.keys()))) 521 driver.compat = of_match[compat] 522 523 # This needs to be deterministic, since a driver may 524 # have multiple compatible strings pointing to it. 525 # We record the one earliest in the alphabet so it 526 # will produce the same result on all machines. 527 for compat_id in of_match[compat]: 528 old = self._compat_to_driver.get(compat_id) 529 if not old or driver.name < old.name: 530 self._compat_to_driver[compat_id] = driver 531 drivers[driver.name] = driver 532 else: 533 # The driver does not have a uclass or compat string. 534 # The first is required but the second is not, so just 535 # ignore this. 536 pass 537 driver = None 538 ids_name = None 539 compat = None 540 compat_dict = {} 541 542 elif ids_name: 543 compat_m = re_compat.search(line) 544 if compat_m: 545 compat_dict[compat_m.group(1)] = compat_m.group(3) 546 elif '};' in line: 547 of_match[ids_name] = compat_dict 548 ids_name = None 549 elif driver_match: 550 driver_name = driver_match.group(1) 551 driver = Driver(driver_name, fname) 552 else: 553 ids_m = re_ids.search(line) 554 m_alias = re_alias.match(line) 555 if ids_m: 556 ids_name = ids_m.group(1) 557 elif m_alias: 558 self._driver_aliases[m_alias[2]] = m_alias[1] 559 560 # Make the updates based on what we found 561 for driver in drivers.values(): 562 if driver.name in self._drivers: 563 orig = self._drivers[driver.name] 564 if self._phase: 565 # If the original driver matches our phase, use it 566 if orig.phase == self._phase: 567 orig.dups.append(driver) 568 continue 569 570 # Otherwise use the new driver, which is assumed to match 571 else: 572 # We have no way of distinguishing them 573 driver.warn_dups = True 574 driver.dups.append(orig) 575 self._drivers[driver.name] = driver 576 self._of_match.update(of_match) 577 578 def show_warnings(self): 579 """Show any warnings that have been collected""" 580 for name in sorted(list(self._missing_drivers)): 581 print('WARNING: the driver %s was not found in the driver list' 582 % name) 583 584 def scan_driver(self, fname): 585 """Scan a driver file to build a list of driver names and aliases 586 587 It updates the following members: 588 _drivers - updated with new Driver records for each driver found 589 in the file 590 _of_match - updated with each compatible string found in the file 591 _compat_to_driver - Maps compatible string to Driver 592 _driver_aliases - Maps alias names to driver name 593 594 Args 595 fname: Driver filename to scan 596 """ 597 with open(fname, encoding='utf-8') as inf: 598 try: 599 buff = inf.read() 600 except UnicodeDecodeError: 601 # This seems to happen on older Python versions 602 print("Skipping file '%s' due to unicode error" % fname) 603 return 604 605 # If this file has any U_BOOT_DRIVER() declarations, process it to 606 # obtain driver information 607 if 'U_BOOT_DRIVER' in buff: 608 self._parse_driver(fname, buff) 609 if 'UCLASS_DRIVER' in buff: 610 self._parse_uclass_driver(fname, buff) 611 612 def scan_header(self, fname): 613 """Scan a header file to build a list of struct definitions 614 615 It updates the following members: 616 _structs - updated with new Struct records for each struct found 617 in the file 618 619 Args 620 fname: header filename to scan 621 """ 622 with open(fname, encoding='utf-8') as inf: 623 try: 624 buff = inf.read() 625 except UnicodeDecodeError: 626 # This seems to happen on older Python versions 627 print("Skipping file '%s' due to unicode error" % fname) 628 return 629 630 # If this file has any U_BOOT_DRIVER() declarations, process it to 631 # obtain driver information 632 if 'struct' in buff: 633 self._parse_structs(fname, buff) 634 635 def scan_drivers(self): 636 """Scan the driver folders to build a list of driver names and aliases 637 638 This procedure will populate self._drivers and self._driver_aliases 639 """ 640 for (dirpath, _, filenames) in os.walk(self._basedir): 641 rel_path = dirpath[len(self._basedir):] 642 if rel_path.startswith('/'): 643 rel_path = rel_path[1:] 644 if rel_path.startswith('build') or rel_path.startswith('.git'): 645 continue 646 for fname in filenames: 647 pathname = dirpath + '/' + fname 648 if fname.endswith('.c'): 649 self.scan_driver(pathname) 650 elif fname.endswith('.h'): 651 self.scan_header(pathname) 652 for fname in self._drivers_additional: 653 if not isinstance(fname, str) or len(fname) == 0: 654 continue 655 if fname[0] == '/': 656 self.scan_driver(fname) 657 else: 658 self.scan_driver(self._basedir + '/' + fname) 659 660 # Get the uclass for each driver 661 # TODO: Can we just get the uclass for the ones we use, e.g. in 662 # mark_used()? 663 for driver in self._drivers.values(): 664 driver.uclass = self._uclass.get(driver.uclass_id) 665 666 def mark_used(self, nodes): 667 """Mark the drivers associated with a list of nodes as 'used' 668 669 This takes a list of nodes, finds the driver for each one and marks it 670 as used. 671 672 If two used drivers have the same name, issue a warning. 673 674 Args: 675 nodes (list of None): Nodes that are in use 676 """ 677 # Figure out which drivers we actually use 678 for node in nodes: 679 struct_name, _ = self.get_normalized_compat_name(node) 680 driver = self._drivers.get(struct_name) 681 if driver: 682 driver.used = True 683 if driver.dups and driver.warn_dups: 684 print("Warning: Duplicate driver name '%s' (orig=%s, dups=%s)" % 685 (driver.name, driver.fname, 686 ', '.join([drv.fname for drv in driver.dups]))) 687 688 def add_uclass_alias(self, name, num, node): 689 """Add an alias to a uclass 690 691 Args: 692 name: Name of uclass, e.g. 'i2c' 693 num: Alias number, e.g. 2 for alias 'i2c2' 694 node: Node the alias points to, or None if None 695 696 Returns: 697 True if the node was added 698 False if the node was not added (uclass of that name not found) 699 None if the node could not be added because it was None 700 """ 701 for uclass in self._uclass.values(): 702 if uclass.name == name: 703 if node is None: 704 return None 705 uclass.alias_num_to_node[int(num)] = node 706 uclass.alias_path_to_num[node.path] = int(num) 707 return True 708 return False 709 710 def assign_seq(self, node): 711 """Figure out the sequence number for a node 712 713 This looks in the node's uclass and assigns a sequence number if needed, 714 based on the aliases and other nodes in that uclass. 715 716 It updates the uclass alias_path_to_num and alias_num_to_node 717 718 Args: 719 node (Node): Node object to look up 720 """ 721 if node.driver and node.seq == -1 and node.uclass: 722 uclass = node.uclass 723 num = uclass.alias_path_to_num.get(node.path) 724 if num is not None: 725 return num 726 else: 727 # Dynamically allocate the next available value after all 728 # existing ones 729 if uclass.alias_num_to_node: 730 start = max(uclass.alias_num_to_node.keys()) 731 else: 732 start = -1 733 for seq in range(start + 1, 1000): 734 if seq not in uclass.alias_num_to_node: 735 break 736 uclass.alias_path_to_num[node.path] = seq 737 uclass.alias_num_to_node[seq] = node 738 return seq 739 return None 740