All of lore.kernel.org
 help / color / mirror / Atom feed
From: Simon Glass <sjg@chromium.org>
To: u-boot@lists.denx.de
Subject: [PATCH v2 12/21] dtoc: Scan drivers for available information
Date: Wed, 23 Dec 2020 08:11:24 -0700	[thread overview]
Message-ID: <20201223081127.v2.12.I1670e4e1d7dc726b21763ed8ac47644111930441@changeid> (raw)
In-Reply-To: <20201223151133.2205985-1-sjg@chromium.org>

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))
-- 
2.29.2.729.g45daf8777d-goog

  parent reply	other threads:[~2020-12-23 15:11 UTC|newest]

Thread overview: 40+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2020-12-23 15:11 [PATCH v2 00/21] dm: Preparation for enhanced of-platdata (part B) Simon Glass
2020-12-23 15:11 ` [PATCH v2 01/21] pinctrl: Drop post_bind() method when not needed Simon Glass
2020-12-23 15:11 ` [PATCH v2 02/21] sysreset: Use a shorter error with SPL Simon Glass
2020-12-23 15:11 ` [PATCH v2 03/21] arc: m68k: nds32: nios2: sh: xtensa: Add empty spl.h header Simon Glass
2020-12-23 15:11 ` [PATCH v2 04/21] timer: Use a shorter error in TPL Simon Glass
2020-12-23 15:11 ` [PATCH v2 05/21] test: Use a simple variable to record removed device Simon Glass
2020-12-23 15:11 ` [PATCH v2 06/21] test: Move some test drivers into their own file Simon Glass
2020-12-23 15:11 ` [PATCH v2 07/21] dtoc: Fix a few pylint warnings in dtb_platdata Simon Glass
2020-12-23 15:11 ` [PATCH v2 08/21] dtoc: Make _output_list a top-level function Simon Glass
2020-12-23 15:11 ` [PATCH v2 09/21] dtoc: Output the device in a separate function Simon Glass
2020-12-23 15:11 ` [PATCH v2 10/21] dtoc: Output the struct values " Simon Glass
2020-12-23 15:11 ` [PATCH v2 11/21] dtoc: Convert _drivers to a dict Simon Glass
2020-12-23 15:11 ` Simon Glass [this message]
2020-12-23 15:11 ` [PATCH v2 13/21] dtoc: Allow use of the of_match_ptr() macro Simon Glass
2020-12-23 15:11 ` [PATCH v2 14/21] x86: apl: Use const for driver operations Simon Glass
2020-12-23 15:11 ` [PATCH v2 15/21] x86: Move call64 into its own section Simon Glass
2020-12-23 15:11 ` [PATCH v2 16/21] x86: coral: Move fsp-m settings to a subnode Simon Glass
2020-12-23 15:11 ` [PATCH v2 17/21] x86: apl: Update hostbridge to remove unwanted TPL code Simon Glass
2020-12-23 15:11 ` [PATCH v2 18/21] x86: apl: Reduce size for TPL Simon Glass
2020-12-23 15:11 ` [PATCH v2 19/21] x86: pinctrl: Drop unlikely error messages from TPL Simon Glass
2020-12-23 15:11 ` [PATCH v2 20/21] x86: tpl: Remove unwanted devicetree string Simon Glass
2020-12-23 15:11 ` [PATCH v2 21/21] x86: Fix header guard in asm/pmu.h Simon Glass
2020-12-28 16:25 ` Simon Glass
2020-12-28 16:25 ` [PATCH v2 20/21] x86: tpl: Remove unwanted devicetree string Simon Glass
2020-12-28 16:25 ` [PATCH v2 19/21] x86: pinctrl: Drop unlikely error messages from TPL Simon Glass
2020-12-28 16:25 ` [PATCH v2 18/21] x86: apl: Reduce size for TPL Simon Glass
2020-12-28 16:25 ` [PATCH v2 17/21] x86: apl: Update hostbridge to remove unwanted TPL code Simon Glass
2020-12-28 16:25 ` [PATCH v2 16/21] x86: coral: Move fsp-m settings to a subnode Simon Glass
2020-12-28 16:25 ` [PATCH v2 15/21] x86: Move call64 into its own section Simon Glass
2020-12-28 16:25 ` [PATCH v2 14/21] x86: apl: Use const for driver operations Simon Glass
2020-12-28 16:25 ` [PATCH v2 11/21] dtoc: Convert _drivers to a dict Simon Glass
2020-12-28 16:25 ` [PATCH v2 10/21] dtoc: Output the struct values in a separate function Simon Glass
2020-12-28 16:25 ` [PATCH v2 09/21] dtoc: Output the device " Simon Glass
2020-12-28 16:25 ` [PATCH v2 08/21] dtoc: Make _output_list a top-level function Simon Glass
2020-12-28 16:25 ` [PATCH v2 07/21] dtoc: Fix a few pylint warnings in dtb_platdata Simon Glass
2020-12-28 16:25 ` [PATCH v2 06/21] test: Move some test drivers into their own file Simon Glass
2020-12-28 16:25 ` [PATCH v2 05/21] test: Use a simple variable to record removed device Simon Glass
2020-12-28 16:25 ` [PATCH v2 04/21] timer: Use a shorter error in TPL Simon Glass
2020-12-28 16:25 ` [PATCH v2 02/21] sysreset: Use a shorter error with SPL Simon Glass
2020-12-28 16:25 ` [PATCH v2 01/21] pinctrl: Drop post_bind() method when not needed Simon Glass

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20201223081127.v2.12.I1670e4e1d7dc726b21763ed8ac47644111930441@changeid \
    --to=sjg@chromium.org \
    --cc=u-boot@lists.denx.de \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.