2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2017 Google, Inc
8 """Scanning of U-Boot source for drivers and structs
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.
13 See doc/driver-model/of-plat.rst for more informaiton
22 def conv_name_to_c(name):
23 """Convert a device-tree name to a C identifier
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).
29 name (str): Name to convert
31 str: String containing the C version of this name
33 new = name.replace('@', '_at_')
34 new = new.replace('-', '_')
35 new = new.replace(',', '_')
36 new = new.replace('.', '_')
41 def get_compat_name(node):
42 """Get the node's list of compatible string as a C identifiers
45 node (fdt.Node): Node object to check
47 list of str: List of C identifiers for all the compatible strings
49 compat = node.props['compatible'].value
50 if not isinstance(compat, list):
52 return [conv_name_to_c(c) for c in compat]
56 """Information about a driver in U-Boot
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,
70 child_plat (str): struct name of the per_child_plat_auto member,
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)
76 dups (list): Driver objects with the same name as this one, that were
78 warn_dups (bool): True if the duplicates are not distinguisble using
80 uclass (Uclass): uclass for this driver
82 def __init__(self, name, fname):
95 self.warn_dups = False
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)
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))
112 """Holds information about a uclass driver
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
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
136 def __init__(self, name):
138 self.uclass_id = None
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 = {}
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)
155 return ("UclassDriver(name='%s', uclass_id='%s')" %
156 (self.name, self.uclass_id))
159 # We can use the uclass ID since it is unique among uclasses
160 return hash(self.uclass_id)
164 """Holds information about a struct definition
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'
171 def __init__(self, name, fname):
176 return ("Struct(name='%s', fname='%s')" % (self.name, self.fname))
180 """Scanning of the U-Boot source tree
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/
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
194 _warnings: Dict of warnings found:
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'
206 _structs: Dict of all structs found in U-Boot:
209 _phase: The phase of U-Boot that we are generating data for, e.g. 'spl'
210 or 'tpl'. None if not known
212 def __init__(self, basedir, drivers_additional, phase=''):
213 """Set up a new Scanner
216 basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
219 self._basedir = basedir
221 self._driver_aliases = {}
222 self._drivers_additional = drivers_additional or []
223 self._missing_drivers = set()
224 self._warnings = collections.defaultdict(set)
226 self._compat_to_driver = {}
231 def get_driver(self, name):
232 """Get a driver given its name
235 name (str): Driver name
238 Driver: Driver or None if not found
240 return self._drivers.get(name)
242 def get_normalized_compat_name(self, node):
243 """Get a node's normalized compat name
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.
250 node (Node): Node object to check
253 Driver name associated with the first compatible string
254 List of C identifiers for all the other compatible strings
256 In case of no match found, the return will be the same as
260 compat_list_c = ['root_driver']
262 compat_list_c = get_compat_name(node)
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)
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
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)
280 return compat_list_c[0], compat_list_c[1:]
282 def _parse_structs(self, fname, buff):
283 """Parse a H file to extract struct definitions contained within
285 This parses 'struct xx {' definitions to figure out what structs this
289 buff (str): Contents of file
290 fname (str): Filename (to use when printing errors)
294 re_struct = re.compile('^struct ([a-z0-9_]+) {$')
295 re_asm = re.compile('../arch/[a-z0-9]+/include/asm/(.*)')
297 for line in buff.splitlines():
298 # Handle line continuation
302 if line.endswith('\\'):
306 m_struct = re_struct.match(line)
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)
313 rel_fname = 'asm/' + m_asm.group(1)
314 structs[name] = Struct(name, rel_fname)
315 self._structs.update(structs)
318 def _get_re_for_member(cls, member):
319 """_get_re_for_member: Get a compiled regular expression
322 member (str): Struct member name, e.g. 'priv_auto'
325 re.Pattern: Compiled regular expression that parses:
327 .member = sizeof(struct fred),
329 and returns "fred" as group 1
331 return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member)
333 def _parse_uclass_driver(self, fname, buff):
334 """Parse a C file to extract uclass driver information contained within
336 This parses UCLASS_DRIVER() structs to obtain various pieces of useful
339 It updates the following member:
340 _uclass: Dict of uclass information
341 key: uclass name, e.g. 'UCLASS_I2C'
345 fname (str): Filename being parsed (used for warnings)
346 buff (str): Contents of file
350 # Collect the driver name and associated Driver
352 re_driver = re.compile(r'^UCLASS_DRIVER\((.*)\)')
354 # Collect the uclass ID, e.g. 'UCLASS_SPI'
355 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
357 # Matches the header/size information for uclass-private data
358 re_priv = self._get_re_for_member('priv_auto')
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')
367 for line in buff.splitlines():
368 # Handle line continuation
372 if line.endswith('\\'):
376 driver_match = re_driver.search(line)
378 # If we have seen UCLASS_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)
387 driver.uclass_id = m_id.group(1)
389 driver.priv = m_priv.group(1)
391 driver.per_dev_priv = m_per_dev_priv.group(1)
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)
399 if not driver.uclass_id:
401 "%s: Cannot parse uclass ID in driver '%s'" %
402 (fname, driver.name))
403 uc_drivers[driver.uclass_id] = driver
407 driver_name = driver_match.group(1)
408 driver = UclassDriver(driver_name)
410 self._uclass.update(uc_drivers)
412 def _parse_driver(self, fname, buff):
413 """Parse a C file to extract driver information contained within
415 This parses U_BOOT_DRIVER() structs to obtain various pieces of useful
418 It updates the following members:
419 _drivers - updated with new Driver records for each driver found
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
426 fname (str): Filename being parsed (used for warnings)
427 buff (str): Contents of file
430 ValueError: Compatible variable is mentioned in .of_match in
431 U_BOOT_DRIVER() but not found in the file
433 # Dict holding information about compatible strings collected in this
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
441 # Dict holding driver information collected in this function so far
442 # key: Driver name (C name as in U_BOOT_DRIVER(xxx))
446 # Collect the driver info
448 re_driver = re.compile(r'^U_BOOT_DRIVER\((.*)\)')
450 # Collect the uclass ID, e.g. 'UCLASS_SPI'
451 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
453 # Collect the compatible string, e.g. 'rockchip,rk3288-grf'
455 re_compat = re.compile(r'{\s*\.compatible\s*=\s*"(.*)"\s*'
456 r'(,\s*\.data\s*=\s*(\S*))?\s*},')
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
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[] = {
467 re_ids = re.compile(r'struct udevice_id (.*)\[\]\s*=')
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_]+)([^,]*),')
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*\)')
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')
484 for line in buff.splitlines():
485 # Handle line continuation
489 if line.endswith('\\'):
493 driver_match = re_driver.search(line)
495 # If this line contains U_BOOT_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)
506 driver.priv = m_priv.group(1)
508 driver.plat = m_plat.group(1)
510 driver.child_plat = m_cplat.group(1)
512 driver.child_priv = m_cpriv.group(1)
514 driver.uclass_id = m_id.group(1)
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))
523 driver.phase = m_phase.group(1)
525 driver.headers.append(m_hdr.group(1))
527 is_root = driver.name == 'root_driver'
528 if driver.uclass_id and (compat or is_root):
530 if compat not in of_match:
532 "%s: Unknown compatible var '%s' (found: %s)" %
533 (fname, compat, ','.join(of_match.keys())))
534 driver.compat = of_match[compat]
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
546 # The driver does not have a uclass or compat string.
547 # The first is required but the second is not, so just
549 if not driver.uclass_id:
550 warn = 'Missing .uclass'
552 warn = 'Missing .compatible'
553 self._warnings[driver.name].add('%s in %s' %
561 compat_m = re_compat.search(line)
563 compat_dict[compat_m.group(1)] = compat_m.group(3)
565 of_match[ids_name] = compat_dict
568 driver_name = driver_match.group(1)
569 driver = Driver(driver_name, fname)
571 ids_m = re_ids.search(line)
572 m_alias = re_alias.match(line)
574 ids_name = ids_m.group(1)
576 self._driver_aliases[m_alias.group(2)] = m_alias.group(1)
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]
583 # If the original driver matches our phase, use it
584 if orig.phase == self._phase:
585 orig.dups.append(driver)
588 # Otherwise use the new driver, which is assumed to match
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)
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))
611 def scan_driver(self, fname):
612 """Scan a driver file to build a list of driver names and aliases
614 It updates the following members:
615 _drivers - updated with new Driver records for each driver found
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
622 fname: Driver filename to scan
624 with open(fname, encoding='utf-8') as inf:
627 except UnicodeDecodeError:
628 # This seems to happen on older Python versions
629 print("Skipping file '%s' due to unicode error" % fname)
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)
639 def scan_header(self, fname):
640 """Scan a header file to build a list of struct definitions
642 It updates the following members:
643 _structs - updated with new Struct records for each struct found
647 fname: header filename to scan
649 with open(fname, encoding='utf-8') as inf:
652 except UnicodeDecodeError:
653 # This seems to happen on older Python versions
654 print("Skipping file '%s' due to unicode error" % fname)
657 # If this file has any U_BOOT_DRIVER() declarations, process it to
658 # obtain driver information
660 self._parse_structs(fname, buff)
662 def scan_drivers(self):
663 """Scan the driver folders to build a list of driver names and aliases
665 This procedure will populate self._drivers and self._driver_aliases
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'):
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:
683 self.scan_driver(fname)
685 self.scan_driver(self._basedir + '/' + fname)
687 # Get the uclass for each driver
688 # TODO: Can we just get the uclass for the ones we use, e.g. in
690 for driver in self._drivers.values():
691 driver.uclass = self._uclass.get(driver.uclass_id)
693 def mark_used(self, nodes):
694 """Mark the drivers associated with a list of nodes as 'used'
696 This takes a list of nodes, finds the driver for each one and marks it
699 If two used drivers have the same name, issue a warning.
702 nodes (list of None): Nodes that are in use
704 # Figure out which drivers we actually use
706 struct_name, _ = self.get_normalized_compat_name(node)
707 driver = self._drivers.get(struct_name)
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])))
715 def add_uclass_alias(self, name, num, node):
716 """Add an alias to a uclass
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
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
728 for uclass in self._uclass.values():
729 if uclass.name == name:
732 uclass.alias_num_to_node[int(num)] = node
733 uclass.alias_path_to_num[node.path] = int(num)
737 def assign_seq(self, node):
738 """Figure out the sequence number for a node
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.
743 It updates the uclass alias_path_to_num and alias_num_to_node
746 node (Node): Node object to look up
748 if node.driver and node.seq == -1 and node.uclass:
750 num = uclass.alias_path_to_num.get(node.path)
754 # Dynamically allocate the next available value after all
756 if uclass.alias_num_to_node:
757 start = max(uclass.alias_num_to_node.keys())
760 for seq in range(start + 1, 1000):
761 if seq not in uclass.alias_num_to_node:
763 uclass.alias_path_to_num[node.path] = seq
764 uclass.alias_num_to_node[seq] = node