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