]> Git Repo - J-u-boot.git/blame - tools/dtoc/src_scan.py
dtoc: Rename sandbox_i2c_test and sandbox_pmic_test
[J-u-boot.git] / tools / dtoc / src_scan.py
CommitLineData
a542a70c
SG
1#!/usr/bin/python
2# SPDX-License-Identifier: GPL-2.0+
3#
4# Copyright (C) 2017 Google, Inc
5# Written by Simon Glass <[email protected]>
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
16import os
17import re
18import sys
19
20
21def conv_name_to_c(name):
22 """Convert a device-tree name to a C identifier
23
24 This uses multiple replace() calls instead of re.sub() since it is faster
25 (400ms for 1m calls versus 1000ms for the 're' version).
26
27 Args:
28 name (str): Name to convert
29 Return:
30 str: String containing the C version of this name
31 """
32 new = name.replace('@', '_at_')
33 new = new.replace('-', '_')
34 new = new.replace(',', '_')
35 new = new.replace('.', '_')
36 return new
37
38def get_compat_name(node):
39 """Get the node's list of compatible string as a C identifiers
40
41 Args:
42 node (fdt.Node): Node object to check
43 Return:
44 list of str: List of C identifiers for all the compatible strings
45 """
46 compat = node.props['compatible'].value
47 if not isinstance(compat, list):
48 compat = [compat]
49 return [conv_name_to_c(c) for c in compat]
50
51
52class Driver:
53 """Information about a driver in U-Boot
54
55 Attributes:
56 name: Name of driver. For U_BOOT_DRIVER(x) this is 'x'
c58662fc
SG
57 fname: Filename where the driver was found
58 uclass_id: Name of uclass, e.g. 'UCLASS_I2C'
59 compat: Driver data for each compatible string:
60 key: Compatible string, e.g. 'rockchip,rk3288-grf'
61 value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
62 fname: Filename where the driver was found
63 priv (str): struct name of the priv_auto member, e.g. 'serial_priv'
c8b19b06
SG
64 plat (str): struct name of the plat_auto member, e.g. 'serial_plat'
65 child_priv (str): struct name of the per_child_auto member,
66 e.g. 'pci_child_priv'
67 child_plat (str): struct name of the per_child_plat_auto member,
68 e.g. 'pci_child_plat'
a542a70c 69 """
c58662fc 70 def __init__(self, name, fname):
a542a70c 71 self.name = name
c58662fc
SG
72 self.fname = fname
73 self.uclass_id = None
74 self.compat = None
75 self.priv = ''
c8b19b06
SG
76 self.plat = ''
77 self.child_priv = ''
78 self.child_plat = ''
a542a70c
SG
79
80 def __eq__(self, other):
c58662fc
SG
81 return (self.name == other.name and
82 self.uclass_id == other.uclass_id and
83 self.compat == other.compat and
c8b19b06
SG
84 self.priv == other.priv and
85 self.plat == other.plat)
a542a70c
SG
86
87 def __repr__(self):
c58662fc
SG
88 return ("Driver(name='%s', uclass_id='%s', compat=%s, priv=%s)" %
89 (self.name, self.uclass_id, self.compat, self.priv))
a542a70c
SG
90
91
1a8b4b9d
SG
92class UclassDriver:
93 """Holds information about a uclass driver
94
95 Attributes:
96 name: Uclass name, e.g. 'i2c' if the driver is for UCLASS_I2C
97 uclass_id: Uclass ID, e.g. 'UCLASS_I2C'
98 priv: struct name of the private data, e.g. 'i2c_priv'
99 per_dev_priv (str): struct name of the priv_auto member, e.g. 'spi_info'
100 per_dev_plat (str): struct name of the plat_auto member, e.g. 'i2c_chip'
101 per_child_priv (str): struct name of the per_child_auto member,
102 e.g. 'pci_child_priv'
103 per_child_plat (str): struct name of the per_child_plat_auto member,
104 e.g. 'pci_child_plat'
105 """
106 def __init__(self, name):
107 self.name = name
108 self.uclass_id = None
109 self.priv = ''
110 self.per_dev_priv = ''
111 self.per_dev_plat = ''
112 self.per_child_priv = ''
113 self.per_child_plat = ''
114
115 def __eq__(self, other):
116 return (self.name == other.name and
117 self.uclass_id == other.uclass_id and
118 self.priv == other.priv)
119
120 def __repr__(self):
121 return ("UclassDriver(name='%s', uclass_id='%s')" %
122 (self.name, self.uclass_id))
123
124 def __hash__(self):
125 # We can use the uclass ID since it is unique among uclasses
126 return hash(self.uclass_id)
127
128
acf5cb88
SG
129class Struct:
130 """Holds information about a struct definition
131
132 Attributes:
133 name: Struct name, e.g. 'fred' if the struct is 'struct fred'
134 fname: Filename containing the struct, in a format that C files can
135 include, e.g. 'asm/clk.h'
136 """
137 def __init__(self, name, fname):
138 self.name = name
139 self.fname =fname
140
141 def __repr__(self):
142 return ("Struct(name='%s', fname='%s')" % (self.name, self.fname))
143
144
a542a70c
SG
145class Scanner:
146 """Scanning of the U-Boot source tree
147
148 Properties:
149 _basedir (str): Base directory of U-Boot source code. Defaults to the
150 grandparent of this file's directory
151 _drivers: Dict of valid driver names found in drivers/
152 key: Driver name
153 value: Driver for that driver
154 _driver_aliases: Dict that holds aliases for driver names
155 key: Driver alias declared with
156 DM_DRIVER_ALIAS(driver_alias, driver_name)
157 value: Driver name declared with U_BOOT_DRIVER(driver_name)
10ea9c0b 158 _warning_disabled: true to disable warnings about driver names not found
a542a70c
SG
159 _drivers_additional (list or str): List of additional drivers to use
160 during scanning
c58662fc
SG
161 _of_match: Dict holding information about compatible strings
162 key: Name of struct udevice_id variable
163 value: Dict of compatible info in that variable:
164 key: Compatible string, e.g. 'rockchip,rk3288-grf'
165 value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
166 _compat_to_driver: Maps compatible strings to Driver
1a8b4b9d
SG
167 _uclass: Dict of uclass information
168 key: uclass name, e.g. 'UCLASS_I2C'
169 value: UClassDriver
acf5cb88
SG
170 _structs: Dict of all structs found in U-Boot:
171 key: Name of struct
172 value: Struct object
a542a70c 173 """
10ea9c0b 174 def __init__(self, basedir, warning_disabled, drivers_additional):
a542a70c
SG
175 """Set up a new Scanner
176 """
177 if not basedir:
178 basedir = sys.argv[0].replace('tools/dtoc/dtoc', '')
179 if basedir == '':
180 basedir = './'
181 self._basedir = basedir
182 self._drivers = {}
183 self._driver_aliases = {}
184 self._drivers_additional = drivers_additional or []
185 self._warning_disabled = warning_disabled
c58662fc
SG
186 self._of_match = {}
187 self._compat_to_driver = {}
1a8b4b9d 188 self._uclass = {}
acf5cb88 189 self._structs = {}
a542a70c
SG
190
191 def get_normalized_compat_name(self, node):
192 """Get a node's normalized compat name
193
194 Returns a valid driver name by retrieving node's list of compatible
195 string as a C identifier and performing a check against _drivers
196 and a lookup in driver_aliases printing a warning in case of failure.
197
198 Args:
199 node (Node): Node object to check
200 Return:
201 Tuple:
202 Driver name associated with the first compatible string
203 List of C identifiers for all the other compatible strings
204 (possibly empty)
205 In case of no match found, the return will be the same as
206 get_compat_name()
207 """
208 compat_list_c = get_compat_name(node)
209
210 for compat_c in compat_list_c:
211 if not compat_c in self._drivers.keys():
212 compat_c = self._driver_aliases.get(compat_c)
213 if not compat_c:
214 continue
215
216 aliases_c = compat_list_c
217 if compat_c in aliases_c:
218 aliases_c.remove(compat_c)
219 return compat_c, aliases_c
220
221 if not self._warning_disabled:
222 print('WARNING: the driver %s was not found in the driver list'
223 % (compat_list_c[0]))
224
225 return compat_list_c[0], compat_list_c[1:]
226
acf5cb88
SG
227 def _parse_structs(self, fname, buff):
228 """Parse a H file to extract struct definitions contained within
229
230 This parses 'struct xx {' definitions to figure out what structs this
231 header defines.
232
233 Args:
234 buff (str): Contents of file
235 fname (str): Filename (to use when printing errors)
236 """
237 structs = {}
238
239 re_struct = re.compile('^struct ([a-z0-9_]+) {$')
240 re_asm = re.compile('../arch/[a-z0-9]+/include/asm/(.*)')
241 prefix = ''
242 for line in buff.splitlines():
243 # Handle line continuation
244 if prefix:
245 line = prefix + line
246 prefix = ''
247 if line.endswith('\\'):
248 prefix = line[:-1]
249 continue
250
251 m_struct = re_struct.match(line)
252 if m_struct:
253 name = m_struct.group(1)
254 include_dir = os.path.join(self._basedir, 'include')
255 rel_fname = os.path.relpath(fname, include_dir)
256 m_asm = re_asm.match(rel_fname)
257 if m_asm:
258 rel_fname = 'asm/' + m_asm.group(1)
259 structs[name] = Struct(name, rel_fname)
260 self._structs.update(structs)
261
c58662fc
SG
262 @classmethod
263 def _get_re_for_member(cls, member):
264 """_get_re_for_member: Get a compiled regular expression
265
266 Args:
267 member (str): Struct member name, e.g. 'priv_auto'
268
269 Returns:
270 re.Pattern: Compiled regular expression that parses:
271
272 .member = sizeof(struct fred),
273
274 and returns "fred" as group 1
275 """
276 return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member)
277
1a8b4b9d
SG
278 def _parse_uclass_driver(self, fname, buff):
279 """Parse a C file to extract uclass driver information contained within
280
281 This parses UCLASS_DRIVER() structs to obtain various pieces of useful
282 information.
283
284 It updates the following member:
285 _uclass: Dict of uclass information
286 key: uclass name, e.g. 'UCLASS_I2C'
287 value: UClassDriver
288
289 Args:
290 fname (str): Filename being parsed (used for warnings)
291 buff (str): Contents of file
292 """
293 uc_drivers = {}
294
295 # Collect the driver name and associated Driver
296 driver = None
297 re_driver = re.compile(r'UCLASS_DRIVER\((.*)\)')
298
299 # Collect the uclass ID, e.g. 'UCLASS_SPI'
300 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
301
302 # Matches the header/size information for uclass-private data
303 re_priv = self._get_re_for_member('priv_auto')
304
305 # Set up parsing for the auto members
306 re_per_device_priv = self._get_re_for_member('per_device_auto')
307 re_per_device_plat = self._get_re_for_member('per_device_plat_auto')
308 re_per_child_priv = self._get_re_for_member('per_child_auto')
309 re_per_child_plat = self._get_re_for_member('per_child_plat_auto')
310
311 prefix = ''
312 for line in buff.splitlines():
313 # Handle line continuation
314 if prefix:
315 line = prefix + line
316 prefix = ''
317 if line.endswith('\\'):
318 prefix = line[:-1]
319 continue
320
321 driver_match = re_driver.search(line)
322
323 # If we have seen UCLASS_DRIVER()...
324 if driver:
325 m_id = re_id.search(line)
326 m_priv = re_priv.match(line)
327 m_per_dev_priv = re_per_device_priv.match(line)
328 m_per_dev_plat = re_per_device_plat.match(line)
329 m_per_child_priv = re_per_child_priv.match(line)
330 m_per_child_plat = re_per_child_plat.match(line)
331 if m_id:
332 driver.uclass_id = m_id.group(1)
333 elif m_priv:
334 driver.priv = m_priv.group(1)
335 elif m_per_dev_priv:
336 driver.per_dev_priv = m_per_dev_priv.group(1)
337 elif m_per_dev_plat:
338 driver.per_dev_plat = m_per_dev_plat.group(1)
339 elif m_per_child_priv:
340 driver.per_child_priv = m_per_child_priv.group(1)
341 elif m_per_child_plat:
342 driver.per_child_plat = m_per_child_plat.group(1)
343 elif '};' in line:
344 if not driver.uclass_id:
345 raise ValueError(
346 "%s: Cannot parse uclass ID in driver '%s'" %
347 (fname, driver.name))
348 uc_drivers[driver.uclass_id] = driver
349 driver = None
350
351 elif driver_match:
352 driver_name = driver_match.group(1)
353 driver = UclassDriver(driver_name)
354
355 self._uclass.update(uc_drivers)
356
c58662fc
SG
357 def _parse_driver(self, fname, buff):
358 """Parse a C file to extract driver information contained within
359
360 This parses U_BOOT_DRIVER() structs to obtain various pieces of useful
361 information.
362
363 It updates the following members:
364 _drivers - updated with new Driver records for each driver found
365 in the file
366 _of_match - updated with each compatible string found in the file
367 _compat_to_driver - Maps compatible string to Driver
368
369 Args:
370 fname (str): Filename being parsed (used for warnings)
371 buff (str): Contents of file
372
373 Raises:
374 ValueError: Compatible variable is mentioned in .of_match in
375 U_BOOT_DRIVER() but not found in the file
376 """
377 # Dict holding information about compatible strings collected in this
378 # function so far
379 # key: Name of struct udevice_id variable
380 # value: Dict of compatible info in that variable:
381 # key: Compatible string, e.g. 'rockchip,rk3288-grf'
382 # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
383 of_match = {}
384
385 # Dict holding driver information collected in this function so far
386 # key: Driver name (C name as in U_BOOT_DRIVER(xxx))
387 # value: Driver
388 drivers = {}
389
390 # Collect the driver info
391 driver = None
392 re_driver = re.compile(r'U_BOOT_DRIVER\((.*)\)')
393
394 # Collect the uclass ID, e.g. 'UCLASS_SPI'
395 re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
396
397 # Collect the compatible string, e.g. 'rockchip,rk3288-grf'
398 compat = None
399 re_compat = re.compile(r'{\s*.compatible\s*=\s*"(.*)"\s*'
400 r'(,\s*.data\s*=\s*(\S*))?\s*},')
401
402 # This is a dict of compatible strings that were found:
403 # key: Compatible string, e.g. 'rockchip,rk3288-grf'
404 # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
405 compat_dict = {}
406
407 # Holds the var nane of the udevice_id list, e.g.
408 # 'rk3288_syscon_ids_noc' in
409 # static const struct udevice_id rk3288_syscon_ids_noc[] = {
410 ids_name = None
411 re_ids = re.compile(r'struct udevice_id (.*)\[\]\s*=')
412
413 # Matches the references to the udevice_id list
414 re_of_match = re.compile(
415 r'\.of_match\s*=\s*(of_match_ptr\()?([a-z0-9_]+)(\))?,')
416
c8b19b06 417 # Matches the struct name for priv, plat
c58662fc 418 re_priv = self._get_re_for_member('priv_auto')
c8b19b06
SG
419 re_plat = self._get_re_for_member('plat_auto')
420 re_child_priv = self._get_re_for_member('per_child_auto')
421 re_child_plat = self._get_re_for_member('per_child_plat_auto')
c58662fc
SG
422
423 prefix = ''
424 for line in buff.splitlines():
425 # Handle line continuation
426 if prefix:
427 line = prefix + line
428 prefix = ''
429 if line.endswith('\\'):
430 prefix = line[:-1]
431 continue
432
433 driver_match = re_driver.search(line)
434
435 # If this line contains U_BOOT_DRIVER()...
436 if driver:
437 m_id = re_id.search(line)
438 m_of_match = re_of_match.search(line)
439 m_priv = re_priv.match(line)
c8b19b06
SG
440 m_plat = re_plat.match(line)
441 m_cplat = re_child_plat.match(line)
442 m_cpriv = re_child_priv.match(line)
c58662fc
SG
443 if m_priv:
444 driver.priv = m_priv.group(1)
c8b19b06
SG
445 elif m_plat:
446 driver.plat = m_plat.group(1)
447 elif m_cplat:
448 driver.child_plat = m_cplat.group(1)
449 elif m_cpriv:
450 driver.child_priv = m_cpriv.group(1)
c58662fc
SG
451 elif m_id:
452 driver.uclass_id = m_id.group(1)
453 elif m_of_match:
454 compat = m_of_match.group(2)
455 elif '};' in line:
456 if driver.uclass_id and compat:
457 if compat not in of_match:
458 raise ValueError(
459 "%s: Unknown compatible var '%s' (found: %s)" %
460 (fname, compat, ','.join(of_match.keys())))
461 driver.compat = of_match[compat]
462
463 # This needs to be deterministic, since a driver may
464 # have multiple compatible strings pointing to it.
465 # We record the one earliest in the alphabet so it
466 # will produce the same result on all machines.
467 for compat_id in of_match[compat]:
468 old = self._compat_to_driver.get(compat_id)
469 if not old or driver.name < old.name:
470 self._compat_to_driver[compat_id] = driver
471 drivers[driver.name] = driver
472 else:
473 # The driver does not have a uclass or compat string.
474 # The first is required but the second is not, so just
475 # ignore this.
476 pass
477 driver = None
478 ids_name = None
479 compat = None
480 compat_dict = {}
481
482 elif ids_name:
483 compat_m = re_compat.search(line)
484 if compat_m:
485 compat_dict[compat_m.group(1)] = compat_m.group(3)
486 elif '};' in line:
487 of_match[ids_name] = compat_dict
488 ids_name = None
489 elif driver_match:
490 driver_name = driver_match.group(1)
491 driver = Driver(driver_name, fname)
492 else:
493 ids_m = re_ids.search(line)
494 if ids_m:
495 ids_name = ids_m.group(1)
496
497 # Make the updates based on what we found
498 self._drivers.update(drivers)
499 self._of_match.update(of_match)
500
a542a70c
SG
501 def scan_driver(self, fname):
502 """Scan a driver file to build a list of driver names and aliases
503
c58662fc
SG
504 It updates the following members:
505 _drivers - updated with new Driver records for each driver found
506 in the file
507 _of_match - updated with each compatible string found in the file
508 _compat_to_driver - Maps compatible string to Driver
509 _driver_aliases - Maps alias names to driver name
a542a70c
SG
510
511 Args
512 fname: Driver filename to scan
513 """
514 with open(fname, encoding='utf-8') as inf:
515 try:
516 buff = inf.read()
517 except UnicodeDecodeError:
518 # This seems to happen on older Python versions
519 print("Skipping file '%s' due to unicode error" % fname)
520 return
521
c58662fc
SG
522 # If this file has any U_BOOT_DRIVER() declarations, process it to
523 # obtain driver information
524 if 'U_BOOT_DRIVER' in buff:
525 self._parse_driver(fname, buff)
1a8b4b9d
SG
526 if 'UCLASS_DRIVER' in buff:
527 self._parse_uclass_driver(fname, buff)
a542a70c
SG
528
529 # The following re will search for driver aliases declared as
530 # DM_DRIVER_ALIAS(alias, driver_name)
531 driver_aliases = re.findall(
532 r'DM_DRIVER_ALIAS\(\s*(\w+)\s*,\s*(\w+)\s*\)',
533 buff)
534
535 for alias in driver_aliases: # pragma: no cover
536 if len(alias) != 2:
537 continue
538 self._driver_aliases[alias[1]] = alias[0]
539
acf5cb88
SG
540 def scan_header(self, fname):
541 """Scan a header file to build a list of struct definitions
542
543 It updates the following members:
544 _structs - updated with new Struct records for each struct found
545 in the file
546
547 Args
548 fname: header filename to scan
549 """
550 with open(fname, encoding='utf-8') as inf:
551 try:
552 buff = inf.read()
553 except UnicodeDecodeError:
554 # This seems to happen on older Python versions
555 print("Skipping file '%s' due to unicode error" % fname)
556 return
557
558 # If this file has any U_BOOT_DRIVER() declarations, process it to
559 # obtain driver information
560 if 'struct' in buff:
561 self._parse_structs(fname, buff)
562
a542a70c
SG
563 def scan_drivers(self):
564 """Scan the driver folders to build a list of driver names and aliases
565
566 This procedure will populate self._drivers and self._driver_aliases
567 """
568 for (dirpath, _, filenames) in os.walk(self._basedir):
36b2220c
SG
569 rel_path = dirpath[len(self._basedir):]
570 if rel_path.startswith('/'):
571 rel_path = rel_path[1:]
572 if rel_path.startswith('build') or rel_path.startswith('.git'):
573 continue
a542a70c 574 for fname in filenames:
acf5cb88
SG
575 pathname = dirpath + '/' + fname
576 if fname.endswith('.c'):
577 self.scan_driver(pathname)
578 elif fname.endswith('.h'):
579 self.scan_header(pathname)
a542a70c
SG
580
581 for fname in self._drivers_additional:
582 if not isinstance(fname, str) or len(fname) == 0:
583 continue
584 if fname[0] == '/':
585 self.scan_driver(fname)
586 else:
587 self.scan_driver(self._basedir + '/' + fname)
This page took 0.090276 seconds and 4 git commands to generate.