2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2017 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
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
556 compat_m = re_compat.search(line)
558 compat_dict[compat_m.group(1)] = compat_m.group(3)
560 of_match[ids_name] = compat_dict
563 driver_name = driver_match.group(1)
564 driver = Driver(driver_name, fname)
566 ids_m = re_ids.search(line)
567 m_alias = re_alias.match(line)
569 ids_name = ids_m.group(1)
571 self._driver_aliases[m_alias.group(2)] = m_alias.group(1)
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]
578 # If the original driver matches our phase, use it
579 if orig.phase == self._phase:
580 orig.dups.append(driver)
583 # Otherwise use the new driver, which is assumed to match
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)
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))
606 def scan_driver(self, fname):
607 """Scan a driver file to build a list of driver names and aliases
609 It updates the following members:
610 _drivers - updated with new Driver records for each driver found
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
617 fname: Driver filename to scan
619 with open(fname, encoding='utf-8') as inf:
622 except UnicodeDecodeError:
623 # This seems to happen on older Python versions
624 print("Skipping file '%s' due to unicode error" % fname)
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)
634 def scan_header(self, fname):
635 """Scan a header file to build a list of struct definitions
637 It updates the following members:
638 _structs - updated with new Struct records for each struct found
642 fname: header filename to scan
644 with open(fname, encoding='utf-8') as inf:
647 except UnicodeDecodeError:
648 # This seems to happen on older Python versions
649 print("Skipping file '%s' due to unicode error" % fname)
652 # If this file has any U_BOOT_DRIVER() declarations, process it to
653 # obtain driver information
655 self._parse_structs(fname, buff)
657 def scan_drivers(self):
658 """Scan the driver folders to build a list of driver names and aliases
660 This procedure will populate self._drivers and self._driver_aliases
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'):
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:
678 self.scan_driver(fname)
680 self.scan_driver(self._basedir + '/' + fname)
682 # Get the uclass for each driver
683 # TODO: Can we just get the uclass for the ones we use, e.g. in
685 for driver in self._drivers.values():
686 driver.uclass = self._uclass.get(driver.uclass_id)
688 def mark_used(self, nodes):
689 """Mark the drivers associated with a list of nodes as 'used'
691 This takes a list of nodes, finds the driver for each one and marks it
694 If two used drivers have the same name, issue a warning.
697 nodes (list of None): Nodes that are in use
699 # Figure out which drivers we actually use
701 struct_name, _ = self.get_normalized_compat_name(node)
702 driver = self._drivers.get(struct_name)
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])))
710 def add_uclass_alias(self, name, num, node):
711 """Add an alias to a uclass
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
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
723 for uclass in self._uclass.values():
724 if uclass.name == name:
727 uclass.alias_num_to_node[int(num)] = node
728 uclass.alias_path_to_num[node.path] = int(num)
732 def assign_seq(self, node):
733 """Figure out the sequence number for a node
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.
738 It updates the uclass alias_path_to_num and alias_num_to_node
741 node (Node): Node object to look up
743 if node.driver and node.seq == -1 and node.uclass:
745 num = uclass.alias_path_to_num.get(node.path)
749 # Dynamically allocate the next available value after all
751 if uclass.alias_num_to_node:
752 start = max(uclass.alias_num_to_node.keys())
755 for seq in range(start + 1, 1000):
756 if seq not in uclass.alias_num_to_node:
758 uclass.alias_path_to_num[node.path] = seq
759 uclass.alias_num_to_node[seq] = node