
At present we simply record the name of a driver parsed from its implementation file. We also need to get the uclass and a few other things so we can instantiate devices at build time. Add support for collecting this information. This requires parsing each driver file.
Signed-off-by: Simon Glass sjg@chromium.org ---
(no changes since v1)
tools/dtoc/dtb_platdata.py | 171 ++++++++++++++++++++++++++++++++++--- tools/dtoc/test_dtoc.py | 101 +++++++++++++++++++++- 2 files changed, 258 insertions(+), 14 deletions(-)
diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py index 5b1bb7e5fd9..51c4d1cae00 100644 --- a/tools/dtoc/dtb_platdata.py +++ b/tools/dtoc/dtb_platdata.py @@ -69,15 +69,26 @@ class Driver:
Attributes: name: Name of driver. For U_BOOT_DRIVER(x) this is 'x' + uclass_id: Name of uclass (e.g. 'UCLASS_I2C') + compat: Driver data for each compatible string: + key: Compatible string, e.g. 'rockchip,rk3288-grf' + value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None """ - def __init__(self, name): + def __init__(self, name, uclass_id, compat): self.name = name + self.uclass_id = uclass_id + self.compat = compat + self.priv_size = 0
def __eq__(self, other): - return self.name == other.name + return (self.name == other.name and + self.uclass_id == other.uclass_id and + self.compat == other.compat and + self.priv_size == other.priv_size)
def __repr__(self): - return "Driver(name='%s')" % self.name + return ("Driver(name='%s', uclass_id='%s', compat=%s, priv_size=%s)" % + (self.name, self.uclass_id, self.compat, self.priv_size))
def conv_name_to_c(name): @@ -180,6 +191,12 @@ class DtbPlatdata(): U_BOOT_DRIVER_ALIAS(driver_alias, driver_name) value: Driver name declared with U_BOOT_DRIVER(driver_name) _drivers_additional: List of additional drivers to use during scanning + _of_match: Dict holding information about compatible strings + key: Name of struct udevice_id variable + value: Dict of compatible info in that variable: + key: Compatible string, e.g. 'rockchip,rk3288-grf' + value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + _compat_to_driver: Maps compatible strings to Driver """ def __init__(self, dtb_fname, include_disabled, warning_disabled, drivers_additional=None): @@ -193,6 +210,8 @@ class DtbPlatdata(): self._drivers = {} self._driver_aliases = {} self._drivers_additional = drivers_additional or [] + self._of_match = {} + self._compat_to_driver = {}
def get_normalized_compat_name(self, node): """Get a node's normalized compat name @@ -331,10 +350,144 @@ class DtbPlatdata(): return PhandleInfo(max_args, args) return None
+ def _parse_driver(self, fname, buff): + """Parse a C file to extract driver information contained within + + This parses U_BOOT_DRIVER() structs to obtain various pieces of useful + information. + + It updates the following members: + _drivers - updated with new Driver records for each driver found + in the file + _of_match - updated with each compatible string found in the file + _compat_to_driver - Maps compatible string to Driver + + Args: + fname (str): Filename being parsed (used for warnings) + buff (str): Contents of file + + Raises: + ValueError: Compatible variable is mentioned in .of_match in + U_BOOT_DRIVER() but not found in the file + """ + # Dict holding information about compatible strings collected in this + # function so far + # key: Name of struct udevice_id variable + # value: Dict of compatible info in that variable: + # key: Compatible string, e.g. 'rockchip,rk3288-grf' + # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + of_match = {} + + # Dict holding driver information collected in this function so far + # key: Driver name (C name as in U_BOOT_DRIVER(xxx)) + # value: Driver + drivers = {} + + # Collect the driver name (None means not found yet) + driver_name = None + re_driver = re.compile(r'U_BOOT_DRIVER((.*))') + + # Collect the uclass ID, e.g. 'UCLASS_SPI' + uclass_id = None + re_id = re.compile(r'\s*.id\s*=\s*(UCLASS_[A-Z0-9_]+)') + + # Collect the compatible string, e.g. 'rockchip,rk3288-grf' + compat = None + re_compat = re.compile(r'{\s*.compatible\s*=\s*"(.*)"\s*' + r'(,\s*.data\s*=\s*(.*))?\s*},') + + # This is a dict of compatible strings that were found: + # key: Compatible string, e.g. 'rockchip,rk3288-grf' + # value: Driver data, e,g, 'ROCKCHIP_SYSCON_GRF', or None + compat_dict = {} + + # Holds the var nane of the udevice_id list, e.g. + # 'rk3288_syscon_ids_noc' in + # static const struct udevice_id rk3288_syscon_ids_noc[] = { + ids_name = None + re_ids = re.compile(r'struct udevice_id (.*)[]\s*=') + + # Matches the references to the udevice_id list + re_of_match = re.compile(r'.of_match\s*=\s*([a-z0-9_]+),') + + # Matches the header/size information for priv + re_priv = re.compile(r'^\s*DM_PRIV((.*))$') + 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 U_BOOT_DRIVER()... + if driver_name: + id_m = re_id.search(line) + id_of_match = re_of_match.search(line) + if id_m: + uclass_id = id_m.group(1) + elif id_of_match: + compat = id_of_match.group(1) + elif '};' in line: + if uclass_id and compat: + if compat not in of_match: + raise ValueError( + "%s: Unknown compatible var '%s' (found: %s)" % + (fname, compat, ','.join(of_match.keys()))) + driver = Driver(driver_name, uclass_id, + of_match[compat]) + drivers[driver_name] = driver + + # This needs to be deterministic, since a driver may + # have multiple compatible strings pointing to it. + # We record the one earliest in the alphabet so it + # will produce the same result on all machines. + for compat_id in of_match[compat]: + old = self._compat_to_driver.get(compat_id) + if not old or driver.name < old.name: + self._compat_to_driver[compat_id] = driver + else: + # The driver does not have a uclass or compat string. + # The first is required but the second is not, so just + # ignore this. + pass + driver_name = None + uclass_id = None + ids_name = None + compat = None + compat_dict = {} + + elif ids_name: + compat_m = re_compat.search(line) + if compat_m: + compat_dict[compat_m.group(1)] = compat_m.group(3) + elif '};' in line: + of_match[ids_name] = compat_dict + ids_name = None + elif driver_match: + driver_name = driver_match.group(1) + else: + ids_m = re_ids.search(line) + if ids_m: + ids_name = ids_m.group(1) + + # Make the updates based on what we found + self._drivers.update(drivers) + self._of_match.update(of_match) + def scan_driver(self, fname): """Scan a driver file to build a list of driver names and aliases
- This procedure will populate self._drivers and self._driver_aliases + It updates the following members: + _drivers - updated with new Driver records for each driver found + in the file + _of_match - updated with each compatible string found in the file + _compat_to_driver - Maps compatible string to Driver + _driver_aliases - Maps alias names to driver name
Args fname: Driver filename to scan @@ -347,12 +500,10 @@ class DtbPlatdata(): print("Skipping file '%s' due to unicode error" % fname) return
- # The following re will search for driver names declared as - # U_BOOT_DRIVER(driver_name) - drivers = re.findall(r'U_BOOT_DRIVER((.*))', buff) - - for driver in drivers: - self._drivers[driver] = Driver(driver) + # If this file has any U_BOOT_DRIVER() declarations, process it to + # obtain driver information + if 'U_BOOT_DRIVER' in buff: + self._parse_driver(fname, buff)
# The following re will search for driver aliases declared as # U_BOOT_DRIVER_ALIAS(alias, driver_name) diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py index c76942c9e2d..89192797781 100755 --- a/tools/dtoc/test_dtoc.py +++ b/tools/dtoc/test_dtoc.py @@ -18,6 +18,7 @@ import unittest
from dtoc import dtb_platdata from dtb_platdata import conv_name_to_c +from dtb_platdata import Driver from dtb_platdata import get_compat_name from dtb_platdata import get_value from dtb_platdata import tab_to @@ -71,6 +72,17 @@ def get_dtb_file(dts_fname, capture_stderr=False): capture_stderr=capture_stderr)
+class FakeNode: + """Fake Node object for testing""" + def __init__(self): + pass + +class FakeProp: + """Fake Prop object for testing""" + def __init__(self): + pass + + class TestDtoc(unittest.TestCase): """Tests for dtoc""" @classmethod @@ -909,10 +921,91 @@ U_BOOT_DEVICE(spl_test2) = {
def testDriver(self): """Test the Driver class""" - drv1 = dtb_platdata.Driver('fred') - drv2 = dtb_platdata.Driver('mary') - drv3 = dtb_platdata.Driver('fred') - self.assertEqual("Driver(name='fred')", str(drv1)) + i2c = 'I2C_UCLASS' + compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', + 'rockchip,rk3288-srf': None} + drv1 = dtb_platdata.Driver('fred', i2c, compat) + drv2 = dtb_platdata.Driver('mary', i2c, {}) + drv3 = dtb_platdata.Driver('fred', i2c, compat) + self.assertEqual( + "Driver(name='fred', uclass_id='I2C_UCLASS', " + "compat={'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', " + "'rockchip,rk3288-srf': None}, priv_size=0)", str(drv1)) self.assertEqual(drv1, drv3) self.assertNotEqual(drv1, drv2) self.assertNotEqual(drv2, drv3) + + def testScan(self): + """Test scanning of a driver""" + fname = os.path.join(our_path, '..', '..', 'drivers/i2c/tegra_i2c.c') + buff = tools.ReadFile(fname, False) + dpd = dtb_platdata.DtbPlatdata(None, False, False) + dpd._parse_driver(fname, buff) + self.assertIn('i2c_tegra', dpd._drivers) + drv = dpd._drivers['i2c_tegra'] + self.assertEqual('i2c_tegra', drv.name) + self.assertEqual('UCLASS_I2C', drv.uclass_id) + self.assertEqual( + {'nvidia,tegra114-i2c': 'TYPE_114 ', + 'nvidia,tegra20-i2c': 'TYPE_STD ', + 'nvidia,tegra20-i2c-dvc': 'TYPE_DVC '}, drv.compat) + self.assertEqual(0, drv.priv_size) + self.assertEqual(1, len(dpd._drivers)) + + def testNormalizedName(self): + """Test operation of get_normalized_compat_name()""" + prop = FakeNode() + prop.name = 'compatible' + prop.value = 'rockchip,rk3288-grf' + node = FakeProp() + node.props = {'compatible': prop} + dpd = dtb_platdata.DtbPlatdata(None, False, False) + with test_util.capture_sys_output() as (stdout, stderr): + name, aliases = dpd.get_normalized_compat_name(node) + self.assertEqual('rockchip_rk3288_grf', name) + self.assertEqual([], aliases) + self.assertEqual( + 'WARNING: the driver rockchip_rk3288_grf was not found in the driver list', + stdout.getvalue().strip()) + + i2c = 'I2C_UCLASS' + compat = {'rockchip,rk3288-grf': 'ROCKCHIP_SYSCON_GRF', + 'rockchip,rk3288-srf': None} + drv = dtb_platdata.Driver('fred', i2c, compat) + dpd._drivers['rockchip_rk3288_grf'] = drv + + dpd._driver_aliases['rockchip_rk3288_srf'] = 'rockchip_rk3288_grf' + + with test_util.capture_sys_output() as (stdout, stderr): + name, aliases = dpd.get_normalized_compat_name(node) + self.assertEqual('', stdout.getvalue().strip()) + self.assertEqual('rockchip_rk3288_grf', name) + self.assertEqual([], aliases) + + prop.value = 'rockchip,rk3288-srf' + with test_util.capture_sys_output() as (stdout, stderr): + name, aliases = dpd.get_normalized_compat_name(node) + self.assertEqual('', stdout.getvalue().strip()) + self.assertEqual('rockchip_rk3288_grf', name) + self.assertEqual(['rockchip_rk3288_srf'], aliases) + + def testScanErrors(self): + """Test detection of scanning errors""" + buff = ''' +static const struct udevice_id tegra_i2c_ids2[] = { + { .compatible = "nvidia,tegra114-i2c", .data = TYPE_114 }, + { } +}; + +U_BOOT_DRIVER(i2c_tegra) = { + .name = "i2c_tegra", + .id = UCLASS_I2C, + .of_match = tegra_i2c_ids, +}; +''' + dpd = dtb_platdata.DtbPlatdata(None, False, False) + with self.assertRaises(ValueError) as e: + dpd._parse_driver('file.c', buff) + self.assertIn( + "file.c: Unknown compatible var 'tegra_i2c_ids' (found: tegra_i2c_ids2)", + str(e.exception))