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