dtoc: Support scanning of uclasses

Uclasses can have per-device private / platform data so dtoc needs to
scan these drivers. This allows it to find out the size of this data so
it can be allocated a build time.

Add a parser for uclass information, similar to drivers. Keep a dict of
the uclasses that were found.

Signed-off-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Simon Glass 2021-02-03 06:00:54 -07:00
parent c8b19b0694
commit 1a8b4b9d94
2 changed files with 177 additions and 0 deletions

View file

@ -89,6 +89,43 @@ class Driver:
(self.name, self.uclass_id, self.compat, self.priv)) (self.name, self.uclass_id, self.compat, self.priv))
class UclassDriver:
"""Holds information about a uclass driver
Attributes:
name: Uclass name, e.g. 'i2c' if the driver is for UCLASS_I2C
uclass_id: Uclass ID, e.g. 'UCLASS_I2C'
priv: struct name of the private data, e.g. 'i2c_priv'
per_dev_priv (str): struct name of the priv_auto member, e.g. 'spi_info'
per_dev_plat (str): struct name of the plat_auto member, e.g. 'i2c_chip'
per_child_priv (str): struct name of the per_child_auto member,
e.g. 'pci_child_priv'
per_child_plat (str): struct name of the per_child_plat_auto member,
e.g. 'pci_child_plat'
"""
def __init__(self, name):
self.name = name
self.uclass_id = None
self.priv = ''
self.per_dev_priv = ''
self.per_dev_plat = ''
self.per_child_priv = ''
self.per_child_plat = ''
def __eq__(self, other):
return (self.name == other.name and
self.uclass_id == other.uclass_id and
self.priv == other.priv)
def __repr__(self):
return ("UclassDriver(name='%s', uclass_id='%s')" %
(self.name, self.uclass_id))
def __hash__(self):
# We can use the uclass ID since it is unique among uclasses
return hash(self.uclass_id)
class Scanner: class Scanner:
"""Scanning of the U-Boot source tree """Scanning of the U-Boot source tree
@ -111,6 +148,9 @@ class Scanner:
key: Compatible string, e.g. 'rockchip,rk3288-grf' key: Compatible string, e.g. 'rockchip,rk3288-grf'
value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None
_compat_to_driver: Maps compatible strings to Driver _compat_to_driver: Maps compatible strings to Driver
_uclass: Dict of uclass information
key: uclass name, e.g. 'UCLASS_I2C'
value: UClassDriver
""" """
def __init__(self, basedir, warning_disabled, drivers_additional): def __init__(self, basedir, warning_disabled, drivers_additional):
"""Set up a new Scanner """Set up a new Scanner
@ -126,6 +166,7 @@ class Scanner:
self._warning_disabled = warning_disabled self._warning_disabled = warning_disabled
self._of_match = {} self._of_match = {}
self._compat_to_driver = {} self._compat_to_driver = {}
self._uclass = {}
def get_normalized_compat_name(self, node): def get_normalized_compat_name(self, node):
"""Get a node's normalized compat name """Get a node's normalized compat name
@ -179,6 +220,85 @@ class Scanner:
""" """
return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member) return re.compile(r'^\s*.%s\s*=\s*sizeof\(struct\s+(.*)\),$' % member)
def _parse_uclass_driver(self, fname, buff):
"""Parse a C file to extract uclass driver information contained within
This parses UCLASS_DRIVER() structs to obtain various pieces of useful
information.
It updates the following member:
_uclass: Dict of uclass information
key: uclass name, e.g. 'UCLASS_I2C'
value: UClassDriver
Args:
fname (str): Filename being parsed (used for warnings)
buff (str): Contents of file
"""
uc_drivers = {}
# Collect the driver name and associated Driver
driver = None
re_driver = re.compile(r'UCLASS_DRIVER\((.*)\)')
# Collect the uclass ID, e.g. 'UCLASS_SPI'
re_id = re.compile(r'\s*\.id\s*=\s*(UCLASS_[A-Z0-9_]+)')
# Matches the header/size information for uclass-private data
re_priv = self._get_re_for_member('priv_auto')
# Set up parsing for the auto members
re_per_device_priv = self._get_re_for_member('per_device_auto')
re_per_device_plat = self._get_re_for_member('per_device_plat_auto')
re_per_child_priv = self._get_re_for_member('per_child_auto')
re_per_child_plat = self._get_re_for_member('per_child_plat_auto')
prefix = ''
for line in buff.splitlines():
# Handle line continuation
if prefix:
line = prefix + line
prefix = ''
if line.endswith('\\'):
prefix = line[:-1]
continue
driver_match = re_driver.search(line)
# If we have seen UCLASS_DRIVER()...
if driver:
m_id = re_id.search(line)
m_priv = re_priv.match(line)
m_per_dev_priv = re_per_device_priv.match(line)
m_per_dev_plat = re_per_device_plat.match(line)
m_per_child_priv = re_per_child_priv.match(line)
m_per_child_plat = re_per_child_plat.match(line)
if m_id:
driver.uclass_id = m_id.group(1)
elif m_priv:
driver.priv = m_priv.group(1)
elif m_per_dev_priv:
driver.per_dev_priv = m_per_dev_priv.group(1)
elif m_per_dev_plat:
driver.per_dev_plat = m_per_dev_plat.group(1)
elif m_per_child_priv:
driver.per_child_priv = m_per_child_priv.group(1)
elif m_per_child_plat:
driver.per_child_plat = m_per_child_plat.group(1)
elif '};' in line:
if not driver.uclass_id:
raise ValueError(
"%s: Cannot parse uclass ID in driver '%s'" %
(fname, driver.name))
uc_drivers[driver.uclass_id] = driver
driver = None
elif driver_match:
driver_name = driver_match.group(1)
driver = UclassDriver(driver_name)
self._uclass.update(uc_drivers)
def _parse_driver(self, fname, buff): def _parse_driver(self, fname, buff):
"""Parse a C file to extract driver information contained within """Parse a C file to extract driver information contained within
@ -348,6 +468,8 @@ class Scanner:
# obtain driver information # obtain driver information
if 'U_BOOT_DRIVER' in buff: if 'U_BOOT_DRIVER' in buff:
self._parse_driver(fname, buff) self._parse_driver(fname, buff)
if 'UCLASS_DRIVER' in buff:
self._parse_uclass_driver(fname, buff)
# The following re will search for driver aliases declared as # The following re will search for driver aliases declared as
# DM_DRIVER_ALIAS(alias, driver_name) # DM_DRIVER_ALIAS(alias, driver_name)

View file

@ -7,6 +7,7 @@
This includes unit tests for scanning of the source code This includes unit tests for scanning of the source code
""" """
import copy
import os import os
import shutil import shutil
import tempfile import tempfile
@ -263,3 +264,57 @@ U_BOOT_DRIVER(testing) = {
self.assertEqual('some_cpriv', drv.child_priv) self.assertEqual('some_cpriv', drv.child_priv)
self.assertEqual('some_cplat', drv.child_plat) self.assertEqual('some_cplat', drv.child_plat)
self.assertEqual(1, len(scan._drivers)) self.assertEqual(1, len(scan._drivers))
def test_uclass_scan(self):
"""Test collection of uclass-driver info"""
buff = '''
UCLASS_DRIVER(i2c) = {
.id = UCLASS_I2C,
.name = "i2c",
.flags = DM_UC_FLAG_SEQ_ALIAS,
.priv_auto = sizeof(struct some_priv),
.per_device_auto = sizeof(struct per_dev_priv),
.per_device_plat_auto = sizeof(struct per_dev_plat),
.per_child_auto = sizeof(struct per_child_priv),
.per_child_plat_auto = sizeof(struct per_child_plat),
.child_post_bind = i2c_child_post_bind,
};
'''
scan = src_scan.Scanner(None, False, None)
scan._parse_uclass_driver('file.c', buff)
self.assertIn('UCLASS_I2C', scan._uclass)
drv = scan._uclass['UCLASS_I2C']
self.assertEqual('i2c', drv.name)
self.assertEqual('UCLASS_I2C', drv.uclass_id)
self.assertEqual('some_priv', drv.priv)
self.assertEqual('per_dev_priv', drv.per_dev_priv)
self.assertEqual('per_dev_plat', drv.per_dev_plat)
self.assertEqual('per_child_priv', drv.per_child_priv)
self.assertEqual('per_child_plat', drv.per_child_plat)
self.assertEqual(1, len(scan._uclass))
drv2 = copy.deepcopy(drv)
self.assertEqual(drv, drv2)
drv2.priv = 'other_priv'
self.assertNotEqual(drv, drv2)
# The hashes only depend on the uclass ID, so should be equal
self.assertEqual(drv.__hash__(), drv2.__hash__())
self.assertEqual("UclassDriver(name='i2c', uclass_id='UCLASS_I2C')",
str(drv))
def test_uclass_scan_errors(self):
"""Test detection of uclass scanning errors"""
buff = '''
UCLASS_DRIVER(i2c) = {
.name = "i2c",
};
'''
scan = src_scan.Scanner(None, False, None)
with self.assertRaises(ValueError) as exc:
scan._parse_uclass_driver('file.c', buff)
self.assertIn("file.c: Cannot parse uclass ID in driver 'i2c'",
str(exc.exception))