blob: 6e8e1ba51a092f32fdc536509907ca4b1dffaa3a [file] [log] [blame]
Simon Glassa542a702020-12-28 20:35:06 -07001#!/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
Simon Glass1b5fe112021-07-04 12:19:45 -060016import collections
Simon Glassa542a702020-12-28 20:35:06 -070017import 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('.', '_')
Simon Glass50aae3e2021-02-03 06:01:11 -070037 if new == '/':
38 return 'root'
Simon Glassa542a702020-12-28 20:35:06 -070039 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'
Simon Glassc58662f2021-02-03 06:00:50 -070060 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'
Simon Glassc8b19b02021-02-03 06:00:53 -070067 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'
Simon Glassb9319c42021-02-03 06:01:01 -070072 used (bool): True if the driver is used by the structs being output
Simon Glassb00f0062021-02-03 06:01:02 -070073 phase (str): Which phase of U-Boot to use this driver
Simon Glass735ddfc2021-02-03 06:01:04 -070074 headers (list): List of header files needed for this driver (each a str)
75 e.g. ['<asm/cpu.h>']
Simon Glass1d972692021-02-03 06:01:06 -070076 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
Simon Glass337d6972021-02-03 06:01:10 -070080 uclass (Uclass): uclass for this driver
Simon Glassa542a702020-12-28 20:35:06 -070081 """
Simon Glassc58662f2021-02-03 06:00:50 -070082 def __init__(self, name, fname):
Simon Glassa542a702020-12-28 20:35:06 -070083 self.name = name
Simon Glassc58662f2021-02-03 06:00:50 -070084 self.fname = fname
85 self.uclass_id = None
86 self.compat = None
87 self.priv = ''
Simon Glassc8b19b02021-02-03 06:00:53 -070088 self.plat = ''
89 self.child_priv = ''
90 self.child_plat = ''
Simon Glassb9319c42021-02-03 06:01:01 -070091 self.used = False
Simon Glassb00f0062021-02-03 06:01:02 -070092 self.phase = ''
Simon Glass735ddfc2021-02-03 06:01:04 -070093 self.headers = []
Simon Glass1d972692021-02-03 06:01:06 -070094 self.dups = []
95 self.warn_dups = False
Simon Glass337d6972021-02-03 06:01:10 -070096 self.uclass = None
Simon Glassa542a702020-12-28 20:35:06 -070097
98 def __eq__(self, other):
Simon Glassc58662f2021-02-03 06:00:50 -070099 return (self.name == other.name and
100 self.uclass_id == other.uclass_id and
101 self.compat == other.compat and
Simon Glassc8b19b02021-02-03 06:00:53 -0700102 self.priv == other.priv and
Simon Glassb9319c42021-02-03 06:01:01 -0700103 self.plat == other.plat and
104 self.used == other.used)
Simon Glassa542a702020-12-28 20:35:06 -0700105
106 def __repr__(self):
Simon Glassb9319c42021-02-03 06:01:01 -0700107 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))
Simon Glassa542a702020-12-28 20:35:06 -0700109
110
Simon Glass1a8b4b92021-02-03 06:00:54 -0700111class 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'
Simon Glass05953522021-02-03 06:01:07 -0700124 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"
Simon Glass337d6972021-02-03 06:01:10 -0700131 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
Simon Glass1a8b4b92021-02-03 06:00:54 -0700135 """
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 = ''
Simon Glass05953522021-02-03 06:01:07 -0700144 self.alias_num_to_node = {}
145 self.alias_path_to_num = {}
Simon Glass337d6972021-02-03 06:01:10 -0700146 self.devs = []
147 self.node_refs = {}
Simon Glass1a8b4b92021-02-03 06:00:54 -0700148
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
Simon Glassacf5cb82021-02-03 06:00:55 -0700163class 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
Simon Glassa542a702020-12-28 20:35:06 -0700179class 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
Simon Glass1b5fe112021-07-04 12:19:45 -0600194 _warnings: Dict of warnings found:
195 key: Driver name
196 value: Set of warnings
Simon Glassc58662f2021-02-03 06:00:50 -0700197 _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
Simon Glass1a8b4b92021-02-03 06:00:54 -0700203 _uclass: Dict of uclass information
204 key: uclass name, e.g. 'UCLASS_I2C'
205 value: UClassDriver
Simon Glassacf5cb82021-02-03 06:00:55 -0700206 _structs: Dict of all structs found in U-Boot:
207 key: Name of struct
208 value: Struct object
Simon Glassb00f0062021-02-03 06:01:02 -0700209 _phase: The phase of U-Boot that we are generating data for, e.g. 'spl'
210 or 'tpl'. None if not known
Simon Glassa542a702020-12-28 20:35:06 -0700211 """
Simon Glass0c59ace2021-03-26 16:17:25 +1300212 def __init__(self, basedir, drivers_additional, phase=''):
Simon Glassa542a702020-12-28 20:35:06 -0700213 """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 []
Simon Glass0c59ace2021-03-26 16:17:25 +1300223 self._missing_drivers = set()
Simon Glass1b5fe112021-07-04 12:19:45 -0600224 self._warnings = collections.defaultdict(set)
Simon Glassc58662f2021-02-03 06:00:50 -0700225 self._of_match = {}
226 self._compat_to_driver = {}
Simon Glass1a8b4b92021-02-03 06:00:54 -0700227 self._uclass = {}
Simon Glassacf5cb82021-02-03 06:00:55 -0700228 self._structs = {}
Simon Glassb00f0062021-02-03 06:01:02 -0700229 self._phase = phase
Simon Glassa542a702020-12-28 20:35:06 -0700230
Simon Glassfd471e22021-02-03 06:01:00 -0700231 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
Simon Glassa542a702020-12-28 20:35:06 -0700242 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 """
Simon Glass50aae3e2021-02-03 06:01:11 -0700259 if not node.parent:
260 compat_list_c = ['root_driver']
261 else:
262 compat_list_c = get_compat_name(node)
Simon Glassa542a702020-12-28 20:35:06 -0700263
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
Simon Glass1b5fe112021-07-04 12:19:45 -0600275 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)
Simon Glassa542a702020-12-28 20:35:06 -0700279
280 return compat_list_c[0], compat_list_c[1:]
281
Simon Glassacf5cb82021-02-03 06:00:55 -0700282 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
Simon Glassc58662f2021-02-03 06:00:50 -0700317 @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
Simon Glass1a8b4b92021-02-03 06:00:54 -0700333 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
Simon Glass1712f8b2021-02-03 06:01:08 -0700352 re_driver = re.compile(r'^UCLASS_DRIVER\((.*)\)')
Simon Glass1a8b4b92021-02-03 06:00:54 -0700353
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
Simon Glassc58662f2021-02-03 06:00:50 -0700412 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
Simon Glass8d6f2d32021-02-03 06:01:05 -0700423 _driver_aliases - Maps alias names to driver name
Simon Glassc58662f2021-02-03 06:00:50 -0700424
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
Simon Glass1712f8b2021-02-03 06:01:08 -0700448 re_driver = re.compile(r'^U_BOOT_DRIVER\((.*)\)')
Simon Glassc58662f2021-02-03 06:00:50 -0700449
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
Simon Glass893142a2021-07-04 12:19:46 -0600455 re_compat = re.compile(r'{\s*\.compatible\s*=\s*"(.*)"\s*'
456 r'(,\s*\.data\s*=\s*(\S*))?\s*},')
Simon Glassc58662f2021-02-03 06:00:50 -0700457
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
Simon Glassb00f0062021-02-03 06:01:02 -0700473 re_phase = re.compile('^\s*DM_PHASE\((.*)\).*$')
Simon Glass735ddfc2021-02-03 06:01:04 -0700474 re_hdr = re.compile('^\s*DM_HEADER\((.*)\).*$')
Simon Glass8d6f2d32021-02-03 06:01:05 -0700475 re_alias = re.compile(r'DM_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)')
Simon Glassb00f0062021-02-03 06:01:02 -0700476
Simon Glassc8b19b02021-02-03 06:00:53 -0700477 # Matches the struct name for priv, plat
Simon Glassc58662f2021-02-03 06:00:50 -0700478 re_priv = self._get_re_for_member('priv_auto')
Simon Glassc8b19b02021-02-03 06:00:53 -0700479 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')
Simon Glassc58662f2021-02-03 06:00:50 -0700482
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)
Simon Glassc8b19b02021-02-03 06:00:53 -0700500 m_plat = re_plat.match(line)
501 m_cplat = re_child_plat.match(line)
502 m_cpriv = re_child_priv.match(line)
Simon Glassb00f0062021-02-03 06:01:02 -0700503 m_phase = re_phase.match(line)
Simon Glass735ddfc2021-02-03 06:01:04 -0700504 m_hdr = re_hdr.match(line)
Simon Glassc58662f2021-02-03 06:00:50 -0700505 if m_priv:
506 driver.priv = m_priv.group(1)
Simon Glassc8b19b02021-02-03 06:00:53 -0700507 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)
Simon Glassc58662f2021-02-03 06:00:50 -0700513 elif m_id:
514 driver.uclass_id = m_id.group(1)
515 elif m_of_match:
516 compat = m_of_match.group(2)
Simon Glassb00f0062021-02-03 06:01:02 -0700517 elif m_phase:
518 driver.phase = m_phase.group(1)
Simon Glass735ddfc2021-02-03 06:01:04 -0700519 elif m_hdr:
520 driver.headers.append(m_hdr.group(1))
Simon Glassc58662f2021-02-03 06:00:50 -0700521 elif '};' in line:
Simon Glass50aae3e2021-02-03 06:01:11 -0700522 is_root = driver.name == 'root_driver'
523 if driver.uclass_id and (compat or is_root):
524 if not is_root:
525 if compat not in of_match:
526 raise ValueError(
527 "%s: Unknown compatible var '%s' (found: %s)" %
528 (fname, compat, ','.join(of_match.keys())))
529 driver.compat = of_match[compat]
Simon Glassc58662f2021-02-03 06:00:50 -0700530
Simon Glass50aae3e2021-02-03 06:01:11 -0700531 # This needs to be deterministic, since a driver may
532 # have multiple compatible strings pointing to it.
533 # We record the one earliest in the alphabet so it
534 # will produce the same result on all machines.
535 for compat_id in of_match[compat]:
536 old = self._compat_to_driver.get(compat_id)
537 if not old or driver.name < old.name:
538 self._compat_to_driver[compat_id] = driver
Simon Glassc58662f2021-02-03 06:00:50 -0700539 drivers[driver.name] = driver
540 else:
541 # The driver does not have a uclass or compat string.
542 # The first is required but the second is not, so just
543 # ignore this.
544 pass
545 driver = None
546 ids_name = None
547 compat = None
548 compat_dict = {}
549
550 elif ids_name:
551 compat_m = re_compat.search(line)
552 if compat_m:
553 compat_dict[compat_m.group(1)] = compat_m.group(3)
554 elif '};' in line:
555 of_match[ids_name] = compat_dict
556 ids_name = None
557 elif driver_match:
558 driver_name = driver_match.group(1)
559 driver = Driver(driver_name, fname)
560 else:
561 ids_m = re_ids.search(line)
Simon Glass8d6f2d32021-02-03 06:01:05 -0700562 m_alias = re_alias.match(line)
Simon Glassc58662f2021-02-03 06:00:50 -0700563 if ids_m:
564 ids_name = ids_m.group(1)
Simon Glass8d6f2d32021-02-03 06:01:05 -0700565 elif m_alias:
Simon Glassc7967652021-07-04 12:19:43 -0600566 self._driver_aliases[m_alias.group(2)] = m_alias.group(1)
Simon Glassc58662f2021-02-03 06:00:50 -0700567
568 # Make the updates based on what we found
Simon Glass1d972692021-02-03 06:01:06 -0700569 for driver in drivers.values():
570 if driver.name in self._drivers:
571 orig = self._drivers[driver.name]
572 if self._phase:
573 # If the original driver matches our phase, use it
574 if orig.phase == self._phase:
575 orig.dups.append(driver)
576 continue
577
578 # Otherwise use the new driver, which is assumed to match
579 else:
580 # We have no way of distinguishing them
581 driver.warn_dups = True
582 driver.dups.append(orig)
583 self._drivers[driver.name] = driver
Simon Glassc58662f2021-02-03 06:00:50 -0700584 self._of_match.update(of_match)
585
Simon Glass0c59ace2021-03-26 16:17:25 +1300586 def show_warnings(self):
587 """Show any warnings that have been collected"""
Simon Glass1b5fe112021-07-04 12:19:45 -0600588 used_drivers = [drv.name for drv in self._drivers.values() if drv.used]
589 missing = self._missing_drivers
590 for name in sorted(self._warnings.keys()):
591 if name in missing or name in used_drivers:
592 warns = sorted(list(self._warnings[name]))
593 # For now there is only ever one warning
594 print('%s: %s' % (name, warns[0]))
595 indent = ' ' * len(name)
596 if name in missing:
597 missing.remove(name)
598 print()
Simon Glass0c59ace2021-03-26 16:17:25 +1300599
Simon Glassa542a702020-12-28 20:35:06 -0700600 def scan_driver(self, fname):
601 """Scan a driver file to build a list of driver names and aliases
602
Simon Glassc58662f2021-02-03 06:00:50 -0700603 It updates the following members:
604 _drivers - updated with new Driver records for each driver found
605 in the file
606 _of_match - updated with each compatible string found in the file
607 _compat_to_driver - Maps compatible string to Driver
608 _driver_aliases - Maps alias names to driver name
Simon Glassa542a702020-12-28 20:35:06 -0700609
610 Args
611 fname: Driver filename to scan
612 """
613 with open(fname, encoding='utf-8') as inf:
614 try:
615 buff = inf.read()
616 except UnicodeDecodeError:
617 # This seems to happen on older Python versions
618 print("Skipping file '%s' due to unicode error" % fname)
619 return
620
Simon Glassc58662f2021-02-03 06:00:50 -0700621 # If this file has any U_BOOT_DRIVER() declarations, process it to
622 # obtain driver information
623 if 'U_BOOT_DRIVER' in buff:
624 self._parse_driver(fname, buff)
Simon Glass1a8b4b92021-02-03 06:00:54 -0700625 if 'UCLASS_DRIVER' in buff:
626 self._parse_uclass_driver(fname, buff)
Simon Glassa542a702020-12-28 20:35:06 -0700627
Simon Glassacf5cb82021-02-03 06:00:55 -0700628 def scan_header(self, fname):
629 """Scan a header file to build a list of struct definitions
630
631 It updates the following members:
632 _structs - updated with new Struct records for each struct found
633 in the file
634
635 Args
636 fname: header filename to scan
637 """
638 with open(fname, encoding='utf-8') as inf:
639 try:
640 buff = inf.read()
641 except UnicodeDecodeError:
642 # This seems to happen on older Python versions
643 print("Skipping file '%s' due to unicode error" % fname)
644 return
645
646 # If this file has any U_BOOT_DRIVER() declarations, process it to
647 # obtain driver information
648 if 'struct' in buff:
649 self._parse_structs(fname, buff)
650
Simon Glassa542a702020-12-28 20:35:06 -0700651 def scan_drivers(self):
652 """Scan the driver folders to build a list of driver names and aliases
653
654 This procedure will populate self._drivers and self._driver_aliases
655 """
656 for (dirpath, _, filenames) in os.walk(self._basedir):
Simon Glass36b22202021-02-03 06:00:52 -0700657 rel_path = dirpath[len(self._basedir):]
658 if rel_path.startswith('/'):
659 rel_path = rel_path[1:]
660 if rel_path.startswith('build') or rel_path.startswith('.git'):
661 continue
Simon Glassa542a702020-12-28 20:35:06 -0700662 for fname in filenames:
Simon Glassacf5cb82021-02-03 06:00:55 -0700663 pathname = dirpath + '/' + fname
664 if fname.endswith('.c'):
665 self.scan_driver(pathname)
666 elif fname.endswith('.h'):
667 self.scan_header(pathname)
Simon Glassa542a702020-12-28 20:35:06 -0700668 for fname in self._drivers_additional:
669 if not isinstance(fname, str) or len(fname) == 0:
670 continue
671 if fname[0] == '/':
672 self.scan_driver(fname)
673 else:
674 self.scan_driver(self._basedir + '/' + fname)
Simon Glassb9319c42021-02-03 06:01:01 -0700675
Simon Glass337d6972021-02-03 06:01:10 -0700676 # Get the uclass for each driver
677 # TODO: Can we just get the uclass for the ones we use, e.g. in
678 # mark_used()?
679 for driver in self._drivers.values():
680 driver.uclass = self._uclass.get(driver.uclass_id)
681
Simon Glassb9319c42021-02-03 06:01:01 -0700682 def mark_used(self, nodes):
683 """Mark the drivers associated with a list of nodes as 'used'
684
685 This takes a list of nodes, finds the driver for each one and marks it
686 as used.
687
Simon Glass1d972692021-02-03 06:01:06 -0700688 If two used drivers have the same name, issue a warning.
689
Simon Glassb9319c42021-02-03 06:01:01 -0700690 Args:
691 nodes (list of None): Nodes that are in use
692 """
693 # Figure out which drivers we actually use
694 for node in nodes:
695 struct_name, _ = self.get_normalized_compat_name(node)
696 driver = self._drivers.get(struct_name)
697 if driver:
698 driver.used = True
Simon Glass1d972692021-02-03 06:01:06 -0700699 if driver.dups and driver.warn_dups:
700 print("Warning: Duplicate driver name '%s' (orig=%s, dups=%s)" %
701 (driver.name, driver.fname,
702 ', '.join([drv.fname for drv in driver.dups])))
Simon Glass05953522021-02-03 06:01:07 -0700703
704 def add_uclass_alias(self, name, num, node):
705 """Add an alias to a uclass
706
707 Args:
708 name: Name of uclass, e.g. 'i2c'
709 num: Alias number, e.g. 2 for alias 'i2c2'
710 node: Node the alias points to, or None if None
711
712 Returns:
713 True if the node was added
714 False if the node was not added (uclass of that name not found)
715 None if the node could not be added because it was None
716 """
717 for uclass in self._uclass.values():
718 if uclass.name == name:
719 if node is None:
720 return None
721 uclass.alias_num_to_node[int(num)] = node
722 uclass.alias_path_to_num[node.path] = int(num)
723 return True
724 return False
Simon Glass337d6972021-02-03 06:01:10 -0700725
726 def assign_seq(self, node):
727 """Figure out the sequence number for a node
728
729 This looks in the node's uclass and assigns a sequence number if needed,
730 based on the aliases and other nodes in that uclass.
731
732 It updates the uclass alias_path_to_num and alias_num_to_node
733
734 Args:
735 node (Node): Node object to look up
736 """
737 if node.driver and node.seq == -1 and node.uclass:
738 uclass = node.uclass
739 num = uclass.alias_path_to_num.get(node.path)
740 if num is not None:
741 return num
742 else:
743 # Dynamically allocate the next available value after all
744 # existing ones
745 if uclass.alias_num_to_node:
746 start = max(uclass.alias_num_to_node.keys())
747 else:
748 start = -1
749 for seq in range(start + 1, 1000):
750 if seq not in uclass.alias_num_to_node:
751 break
752 uclass.alias_path_to_num[node.path] = seq
753 uclass.alias_num_to_node[seq] = node
754 return seq
755 return None