dtoc: Detect unexpected suffix on .of_match
[platform/kernel/u-boot.git] / tools / dtoc / src_scan.py
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
10 This scans the source tree to find out things about all instances of
11 U_BOOT_DRIVER(), UCLASS_DRIVER and all struct declarations in header files.
12
13 See doc/driver-model/of-plat.rst for more informaiton
14 """
15
16 import collections
17 import os
18 import re
19 import sys
20
21
22 def 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
41 def 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
55 class 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
111 class 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
163 class 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
179 class 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                         pass
550                     driver = None
551                     ids_name = None
552                     compat = None
553                     compat_dict = {}
554
555             elif ids_name:
556                 compat_m = re_compat.search(line)
557                 if compat_m:
558                     compat_dict[compat_m.group(1)] = compat_m.group(3)
559                 elif '};' in line:
560                     of_match[ids_name] = compat_dict
561                     ids_name = None
562             elif driver_match:
563                 driver_name = driver_match.group(1)
564                 driver = Driver(driver_name, fname)
565             else:
566                 ids_m = re_ids.search(line)
567                 m_alias = re_alias.match(line)
568                 if ids_m:
569                     ids_name = ids_m.group(1)
570                 elif m_alias:
571                     self._driver_aliases[m_alias.group(2)] = m_alias.group(1)
572
573         # Make the updates based on what we found
574         for driver in drivers.values():
575             if driver.name in self._drivers:
576                 orig = self._drivers[driver.name]
577                 if self._phase:
578                     # If the original driver matches our phase, use it
579                     if orig.phase == self._phase:
580                         orig.dups.append(driver)
581                         continue
582
583                     # Otherwise use the new driver, which is assumed to match
584                 else:
585                     # We have no way of distinguishing them
586                     driver.warn_dups = True
587                 driver.dups.append(orig)
588             self._drivers[driver.name] = driver
589         self._of_match.update(of_match)
590
591     def show_warnings(self):
592         """Show any warnings that have been collected"""
593         used_drivers = [drv.name for drv in self._drivers.values() if drv.used]
594         missing = self._missing_drivers.copy()
595         for name in sorted(self._warnings.keys()):
596             if name in missing or name in used_drivers:
597                 warns = sorted(list(self._warnings[name]))
598                 print('%s: %s' % (name, warns[0]))
599                 indent = ' ' * len(name)
600                 for warn in warns[1:]:
601                     print('%-s: %s' % (indent, warn))
602                 if name in missing:
603                     missing.remove(name)
604                 print()
605
606     def scan_driver(self, fname):
607         """Scan a driver file to build a list of driver names and aliases
608
609         It updates the following members:
610             _drivers - updated with new Driver records for each driver found
611                 in the file
612             _of_match - updated with each compatible string found in the file
613             _compat_to_driver - Maps compatible string to Driver
614             _driver_aliases - Maps alias names to driver name
615
616         Args
617             fname: Driver filename to scan
618         """
619         with open(fname, encoding='utf-8') as inf:
620             try:
621                 buff = inf.read()
622             except UnicodeDecodeError:
623                 # This seems to happen on older Python versions
624                 print("Skipping file '%s' due to unicode error" % fname)
625                 return
626
627             # If this file has any U_BOOT_DRIVER() declarations, process it to
628             # obtain driver information
629             if 'U_BOOT_DRIVER' in buff:
630                 self._parse_driver(fname, buff)
631             if 'UCLASS_DRIVER' in buff:
632                 self._parse_uclass_driver(fname, buff)
633
634     def scan_header(self, fname):
635         """Scan a header file to build a list of struct definitions
636
637         It updates the following members:
638             _structs - updated with new Struct records for each struct found
639                 in the file
640
641         Args
642             fname: header filename to scan
643         """
644         with open(fname, encoding='utf-8') as inf:
645             try:
646                 buff = inf.read()
647             except UnicodeDecodeError:
648                 # This seems to happen on older Python versions
649                 print("Skipping file '%s' due to unicode error" % fname)
650                 return
651
652             # If this file has any U_BOOT_DRIVER() declarations, process it to
653             # obtain driver information
654             if 'struct' in buff:
655                 self._parse_structs(fname, buff)
656
657     def scan_drivers(self):
658         """Scan the driver folders to build a list of driver names and aliases
659
660         This procedure will populate self._drivers and self._driver_aliases
661         """
662         for (dirpath, _, filenames) in os.walk(self._basedir):
663             rel_path = dirpath[len(self._basedir):]
664             if rel_path.startswith('/'):
665                 rel_path = rel_path[1:]
666             if rel_path.startswith('build') or rel_path.startswith('.git'):
667                 continue
668             for fname in filenames:
669                 pathname = dirpath + '/' + fname
670                 if fname.endswith('.c'):
671                     self.scan_driver(pathname)
672                 elif fname.endswith('.h'):
673                     self.scan_header(pathname)
674         for fname in self._drivers_additional:
675             if not isinstance(fname, str) or len(fname) == 0:
676                 continue
677             if fname[0] == '/':
678                 self.scan_driver(fname)
679             else:
680                 self.scan_driver(self._basedir + '/' + fname)
681
682         # Get the uclass for each driver
683         # TODO: Can we just get the uclass for the ones we use, e.g. in
684         # mark_used()?
685         for driver in self._drivers.values():
686             driver.uclass = self._uclass.get(driver.uclass_id)
687
688     def mark_used(self, nodes):
689         """Mark the drivers associated with a list of nodes as 'used'
690
691         This takes a list of nodes, finds the driver for each one and marks it
692         as used.
693
694         If two used drivers have the same name, issue a warning.
695
696         Args:
697             nodes (list of None): Nodes that are in use
698         """
699         # Figure out which drivers we actually use
700         for node in nodes:
701             struct_name, _ = self.get_normalized_compat_name(node)
702             driver = self._drivers.get(struct_name)
703             if driver:
704                 driver.used = True
705                 if driver.dups and driver.warn_dups:
706                     print("Warning: Duplicate driver name '%s' (orig=%s, dups=%s)" %
707                           (driver.name, driver.fname,
708                            ', '.join([drv.fname for drv in driver.dups])))
709
710     def add_uclass_alias(self, name, num, node):
711         """Add an alias to a uclass
712
713         Args:
714             name: Name of uclass, e.g. 'i2c'
715             num: Alias number, e.g. 2 for alias 'i2c2'
716             node: Node the alias points to, or None if None
717
718         Returns:
719             True if the node was added
720             False if the node was not added (uclass of that name not found)
721             None if the node could not be added because it was None
722         """
723         for uclass in self._uclass.values():
724             if uclass.name == name:
725                 if node is None:
726                     return None
727                 uclass.alias_num_to_node[int(num)] = node
728                 uclass.alias_path_to_num[node.path] = int(num)
729                 return True
730         return False
731
732     def assign_seq(self, node):
733         """Figure out the sequence number for a node
734
735         This looks in the node's uclass and assigns a sequence number if needed,
736         based on the aliases and other nodes in that uclass.
737
738         It updates the uclass alias_path_to_num and alias_num_to_node
739
740         Args:
741             node (Node): Node object to look up
742         """
743         if node.driver and node.seq == -1 and node.uclass:
744             uclass = node.uclass
745             num = uclass.alias_path_to_num.get(node.path)
746             if num is not None:
747                 return num
748             else:
749                 # Dynamically allocate the next available value after all
750                 # existing ones
751                 if uclass.alias_num_to_node:
752                     start = max(uclass.alias_num_to_node.keys())
753                 else:
754                     start = -1
755                 for seq in range(start + 1, 1000):
756                     if seq not in uclass.alias_num_to_node:
757                         break
758                 uclass.alias_path_to_num[node.path] = seq
759                 uclass.alias_num_to_node[seq] = node
760                 return seq
761         return None