blob: 3bef59d616ed9e1069051d26b70f16098b01c59a [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(
Simon Glass86ff01e2021-07-04 12:19:48 -0600471 r'\.of_match\s*=\s*(of_match_ptr\()?([a-z0-9_]+)([^,]*),')
Simon Glassc58662f2021-02-03 06:00:50 -0700472
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 Glass86ff01e2021-07-04 12:19:48 -0600517 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))
Simon Glassb00f0062021-02-03 06:01:02 -0700522 elif m_phase:
523 driver.phase = m_phase.group(1)
Simon Glass735ddfc2021-02-03 06:01:04 -0700524 elif m_hdr:
525 driver.headers.append(m_hdr.group(1))
Simon Glassc58662f2021-02-03 06:00:50 -0700526 elif '};' in line:
Simon Glass50aae3e2021-02-03 06:01:11 -0700527 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]
Simon Glassc58662f2021-02-03 06:00:50 -0700535
Simon Glass50aae3e2021-02-03 06:01:11 -0700536 # 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
Simon Glassc58662f2021-02-03 06:00:50 -0700544 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.
Simon Glass43ba4922021-07-04 12:19:49 -0600549 if not driver.uclass_id:
550 warn = 'Missing .uclass'
551 else:
552 warn = 'Missing .compatible'
553 self._warnings[driver.name].add('%s in %s' %
554 (warn, fname))
Simon Glassc58662f2021-02-03 06:00:50 -0700555 driver = None
556 ids_name = None
557 compat = None
558 compat_dict = {}
559
560 elif ids_name:
561 compat_m = re_compat.search(line)
562 if compat_m:
563 compat_dict[compat_m.group(1)] = compat_m.group(3)
564 elif '};' in line:
565 of_match[ids_name] = compat_dict
566 ids_name = None
567 elif driver_match:
568 driver_name = driver_match.group(1)
569 driver = Driver(driver_name, fname)
570 else:
571 ids_m = re_ids.search(line)
Simon Glass8d6f2d32021-02-03 06:01:05 -0700572 m_alias = re_alias.match(line)
Simon Glassc58662f2021-02-03 06:00:50 -0700573 if ids_m:
574 ids_name = ids_m.group(1)
Simon Glass8d6f2d32021-02-03 06:01:05 -0700575 elif m_alias:
Simon Glassc7967652021-07-04 12:19:43 -0600576 self._driver_aliases[m_alias.group(2)] = m_alias.group(1)
Simon Glassc58662f2021-02-03 06:00:50 -0700577
578 # Make the updates based on what we found
Simon Glass1d972692021-02-03 06:01:06 -0700579 for driver in drivers.values():
580 if driver.name in self._drivers:
581 orig = self._drivers[driver.name]
582 if self._phase:
583 # If the original driver matches our phase, use it
584 if orig.phase == self._phase:
585 orig.dups.append(driver)
586 continue
587
588 # Otherwise use the new driver, which is assumed to match
589 else:
590 # We have no way of distinguishing them
591 driver.warn_dups = True
592 driver.dups.append(orig)
593 self._drivers[driver.name] = driver
Simon Glassc58662f2021-02-03 06:00:50 -0700594 self._of_match.update(of_match)
595
Simon Glass0c59ace2021-03-26 16:17:25 +1300596 def show_warnings(self):
597 """Show any warnings that have been collected"""
Simon Glass1b5fe112021-07-04 12:19:45 -0600598 used_drivers = [drv.name for drv in self._drivers.values() if drv.used]
Simon Glass86ff01e2021-07-04 12:19:48 -0600599 missing = self._missing_drivers.copy()
Simon Glass1b5fe112021-07-04 12:19:45 -0600600 for name in sorted(self._warnings.keys()):
601 if name in missing or name in used_drivers:
602 warns = sorted(list(self._warnings[name]))
Simon Glass1b5fe112021-07-04 12:19:45 -0600603 print('%s: %s' % (name, warns[0]))
604 indent = ' ' * len(name)
Simon Glass86ff01e2021-07-04 12:19:48 -0600605 for warn in warns[1:]:
606 print('%-s: %s' % (indent, warn))
Simon Glass1b5fe112021-07-04 12:19:45 -0600607 if name in missing:
608 missing.remove(name)
609 print()
Simon Glass0c59ace2021-03-26 16:17:25 +1300610
Simon Glassa542a702020-12-28 20:35:06 -0700611 def scan_driver(self, fname):
612 """Scan a driver file to build a list of driver names and aliases
613
Simon Glassc58662f2021-02-03 06:00:50 -0700614 It updates the following members:
615 _drivers - updated with new Driver records for each driver found
616 in the file
617 _of_match - updated with each compatible string found in the file
618 _compat_to_driver - Maps compatible string to Driver
619 _driver_aliases - Maps alias names to driver name
Simon Glassa542a702020-12-28 20:35:06 -0700620
621 Args
622 fname: Driver filename to scan
623 """
624 with open(fname, encoding='utf-8') as inf:
625 try:
626 buff = inf.read()
627 except UnicodeDecodeError:
628 # This seems to happen on older Python versions
629 print("Skipping file '%s' due to unicode error" % fname)
630 return
631
Simon Glassc58662f2021-02-03 06:00:50 -0700632 # 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)
Simon Glass1a8b4b92021-02-03 06:00:54 -0700636 if 'UCLASS_DRIVER' in buff:
637 self._parse_uclass_driver(fname, buff)
Simon Glassa542a702020-12-28 20:35:06 -0700638
Simon Glassacf5cb82021-02-03 06:00:55 -0700639 def scan_header(self, fname):
640 """Scan a header file to build a list of struct definitions
641
642 It updates the following members:
643 _structs - updated with new Struct records for each struct found
644 in the file
645
646 Args
647 fname: header filename to scan
648 """
649 with open(fname, encoding='utf-8') as inf:
650 try:
651 buff = inf.read()
652 except UnicodeDecodeError:
653 # This seems to happen on older Python versions
654 print("Skipping file '%s' due to unicode error" % fname)
655 return
656
657 # If this file has any U_BOOT_DRIVER() declarations, process it to
658 # obtain driver information
659 if 'struct' in buff:
660 self._parse_structs(fname, buff)
661
Simon Glassa542a702020-12-28 20:35:06 -0700662 def scan_drivers(self):
663 """Scan the driver folders to build a list of driver names and aliases
664
665 This procedure will populate self._drivers and self._driver_aliases
666 """
667 for (dirpath, _, filenames) in os.walk(self._basedir):
Simon Glass36b22202021-02-03 06:00:52 -0700668 rel_path = dirpath[len(self._basedir):]
669 if rel_path.startswith('/'):
670 rel_path = rel_path[1:]
671 if rel_path.startswith('build') or rel_path.startswith('.git'):
672 continue
Simon Glassa542a702020-12-28 20:35:06 -0700673 for fname in filenames:
Simon Glassacf5cb82021-02-03 06:00:55 -0700674 pathname = dirpath + '/' + fname
675 if fname.endswith('.c'):
676 self.scan_driver(pathname)
677 elif fname.endswith('.h'):
678 self.scan_header(pathname)
Simon Glassa542a702020-12-28 20:35:06 -0700679 for fname in self._drivers_additional:
680 if not isinstance(fname, str) or len(fname) == 0:
681 continue
682 if fname[0] == '/':
683 self.scan_driver(fname)
684 else:
685 self.scan_driver(self._basedir + '/' + fname)
Simon Glassb9319c42021-02-03 06:01:01 -0700686
Simon Glass337d6972021-02-03 06:01:10 -0700687 # Get the uclass for each driver
688 # TODO: Can we just get the uclass for the ones we use, e.g. in
689 # mark_used()?
690 for driver in self._drivers.values():
691 driver.uclass = self._uclass.get(driver.uclass_id)
692
Simon Glassb9319c42021-02-03 06:01:01 -0700693 def mark_used(self, nodes):
694 """Mark the drivers associated with a list of nodes as 'used'
695
696 This takes a list of nodes, finds the driver for each one and marks it
697 as used.
698
Simon Glass1d972692021-02-03 06:01:06 -0700699 If two used drivers have the same name, issue a warning.
700
Simon Glassb9319c42021-02-03 06:01:01 -0700701 Args:
702 nodes (list of None): Nodes that are in use
703 """
704 # Figure out which drivers we actually use
705 for node in nodes:
706 struct_name, _ = self.get_normalized_compat_name(node)
707 driver = self._drivers.get(struct_name)
708 if driver:
709 driver.used = True
Simon Glass1d972692021-02-03 06:01:06 -0700710 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])))
Simon Glass05953522021-02-03 06:01:07 -0700714
715 def add_uclass_alias(self, name, num, node):
716 """Add an alias to a uclass
717
718 Args:
719 name: Name of uclass, e.g. 'i2c'
720 num: Alias number, e.g. 2 for alias 'i2c2'
721 node: Node the alias points to, or None if None
722
723 Returns:
724 True if the node was added
725 False if the node was not added (uclass of that name not found)
726 None if the node could not be added because it was None
727 """
728 for uclass in self._uclass.values():
729 if uclass.name == name:
730 if node is None:
731 return None
732 uclass.alias_num_to_node[int(num)] = node
733 uclass.alias_path_to_num[node.path] = int(num)
734 return True
735 return False
Simon Glass337d6972021-02-03 06:01:10 -0700736
737 def assign_seq(self, node):
738 """Figure out the sequence number for a node
739
740 This looks in the node's uclass and assigns a sequence number if needed,
741 based on the aliases and other nodes in that uclass.
742
743 It updates the uclass alias_path_to_num and alias_num_to_node
744
745 Args:
746 node (Node): Node object to look up
747 """
748 if node.driver and node.seq == -1 and node.uclass:
749 uclass = node.uclass
750 num = uclass.alias_path_to_num.get(node.path)
751 if num is not None:
752 return num
753 else:
754 # Dynamically allocate the next available value after all
755 # existing ones
756 if uclass.alias_num_to_node:
757 start = max(uclass.alias_num_to_node.keys())
758 else:
759 start = -1
760 for seq in range(start + 1, 1000):
761 if seq not in uclass.alias_num_to_node:
762 break
763 uclass.alias_path_to_num[node.path] = seq
764 uclass.alias_num_to_node[seq] = node
765 return seq
766 return None