dtoc: Set up the uclasses that are used
authorSimon Glass <sjg@chromium.org>
Wed, 3 Feb 2021 13:01:10 +0000 (06:01 -0700)
committerSimon Glass <sjg@chromium.org>
Mon, 22 Mar 2021 06:23:27 +0000 (19:23 +1300)
We only care about uclasses that are actually used. This is determined by
the drivers that use them. Check all the used drivers and build a list of
'valid' uclasses.

Also add references to the uclasses so we can generate C code that uses
them. Attach a uclass to each valid driver.

For the tests, now that we have uclasses we must create an explicit test
for the case where a node does not have one. This should only happen if
the source code does not build, or the source-code scanning fails to find
it.

Signed-off-by: Simon Glass <sjg@chromium.org>
tools/dtoc/dtb_platdata.py
tools/dtoc/src_scan.py
tools/dtoc/test_dtoc.py
tools/dtoc/test_src_scan.py

index 9e99c63ae70a304ddcaacd985701bac561f0817b..af21156659bbe32c887d880c32e675aea3f98f15 100644 (file)
@@ -151,6 +151,8 @@ class DtbPlatdata():
                         key (str): Field name
                         value: Prop object with field information
         _basedir (str): Base directory of source tree
+        _valid_uclasses (list of src_scan.Uclass): List of uclasses needed for
+            the selected devices (see _valid_node), in alphabetical order
     """
     def __init__(self, scan, dtb_fname, include_disabled):
         self._scan = scan
@@ -164,6 +166,7 @@ class DtbPlatdata():
         self._dirnames = [None] * len(Ftype)
         self._struct_data = collections.OrderedDict()
         self._basedir = None
+        self._valid_uclasses = None
 
     def setup_output_dirs(self, output_dirs):
         """Set up the output directories
@@ -677,23 +680,12 @@ class DtbPlatdata():
             elif result is False:
                 print("Could not find uclass for alias '%s'" % prop.name)
 
-    def assign_seq(self):
+    def assign_seqs(self):
         """Assign a sequence number to each node"""
         for node in self._valid_nodes_unsorted:
-            if node.driver and node.seq == -1 and node.uclass:
-                uclass = node.uclass
-                num = uclass.alias_path_to_num.get(node.path)
-                if num is not None:
-                    node.seq = num
-                else:
-                    # Dynamically allocate the next available value after all
-                    # existing ones
-                    for seq in range(1000):
-                        if seq not in uclass.alias_num_to_node:
-                            break
-                    node.seq = seq
-                    uclass.alias_path_to_num[node.path] = seq
-                    uclass.alias_num_to_node[seq] = node
+            seq = self._scan.assign_seq(node)
+            if seq is not None:
+                node.seq = seq
 
     def process_nodes(self, need_drivers):
         nodes_to_output = list(self._valid_nodes)
@@ -710,6 +702,16 @@ class DtbPlatdata():
                 raise ValueError("Cannot parse/find driver for '%s'" %
                                  node.struct_name)
             node.driver = driver
+            uclass = self._scan._uclass.get(driver.uclass_id)
+            if not uclass:
+                raise ValueError("Cannot parse/find uclass '%s' for driver '%s'" %
+                                (driver.uclass_id, node.struct_name))
+            node.uclass = uclass
+            node.uclass_seq = len(node.uclass.devs)
+            node.uclass.devs.append(node)
+            uclass.node_refs[node.uclass_seq] = \
+                '&%s->uclass_node' % node.dev_ref
+
             parent_driver = None
             if node.parent in self._valid_nodes:
                 parent_driver = self._scan.get_driver(node.parent.struct_name)
@@ -730,6 +732,18 @@ class DtbPlatdata():
             node.child_refs[-1] = ref
             node.child_refs[len(node.child_devs)] = ref
 
+        uclass_set = set()
+        for driver in self._scan._drivers.values():
+            if driver.used and driver.uclass:
+                uclass_set.add(driver.uclass)
+        self._valid_uclasses = sorted(list(uclass_set),
+                                      key=lambda uc: uc.uclass_id)
+
+        for seq, uclass in enumerate(uclass_set):
+            ref = '&DM_UCLASS_REF(%s)->dev_head' % uclass.name
+            uclass.node_refs[-1] = ref
+            uclass.node_refs[len(uclass.devs)] = ref
+
     def output_node(self, node):
         """Output the C code for a node
 
@@ -833,7 +847,7 @@ def run_steps(args, dtb_file, include_disabled, output, output_dirs, phase,
     plat.scan_phandles()
     plat.process_nodes(False)
     plat.read_aliases()
-    plat.assign_seq()
+    plat.assign_seqs()
 
     cmds = args[0].split(',')
     if 'all' in cmds:
index bb22b0b64ff84b0934415d38f425e97a1cc895af..8619206a8d481f49834dd9f6fa30d6f3ba78e7d1 100644 (file)
@@ -74,6 +74,7 @@ class Driver:
             found after this one
         warn_dups (bool): True if the duplicates are not distinguisble using
             the phase
+        uclass (Uclass): uclass for this driver
     """
     def __init__(self, name, fname):
         self.name = name
@@ -89,6 +90,7 @@ class Driver:
         self.headers = []
         self.dups = []
         self.warn_dups = False
+        self.uclass = None
 
     def __eq__(self, other):
         return (self.name == other.name and
@@ -123,6 +125,10 @@ class UclassDriver:
         alias_path_to_num (dict): Convert a path to an alias number
             key (str): Full path to node (e.g. '/soc/pci')
             seq (int): Alias number, e.g. 2 for "pci2"
+        devs (list): List of devices in this uclass, each a Node
+        node_refs (dict): References in the linked list of devices:
+            key (int): Sequence number (0=first, n-1=last, -1=head, n=tail)
+            value (str): Reference to the device at that position
     """
     def __init__(self, name):
         self.name = name
@@ -134,6 +140,8 @@ class UclassDriver:
         self.per_child_plat = ''
         self.alias_num_to_node = {}
         self.alias_path_to_num = {}
+        self.devs = []
+        self.node_refs = {}
 
     def __eq__(self, other):
         return (self.name == other.name and
@@ -639,6 +647,12 @@ class Scanner:
             else:
                 self.scan_driver(self._basedir + '/' + fname)
 
+        # Get the uclass for each driver
+        # TODO: Can we just get the uclass for the ones we use, e.g. in
+        # mark_used()?
+        for driver in self._drivers.values():
+            driver.uclass = self._uclass.get(driver.uclass_id)
+
     def mark_used(self, nodes):
         """Mark the drivers associated with a list of nodes as 'used'
 
@@ -682,3 +696,34 @@ class Scanner:
                 uclass.alias_path_to_num[node.path] = int(num)
                 return True
         return False
+
+    def assign_seq(self, node):
+        """Figure out the sequence number for a node
+
+        This looks in the node's uclass and assigns a sequence number if needed,
+        based on the aliases and other nodes in that uclass.
+
+        It updates the uclass alias_path_to_num and alias_num_to_node
+
+        Args:
+            node (Node): Node object to look up
+        """
+        if node.driver and node.seq == -1 and node.uclass:
+            uclass = node.uclass
+            num = uclass.alias_path_to_num.get(node.path)
+            if num is not None:
+                return num
+            else:
+                # Dynamically allocate the next available value after all
+                # existing ones
+                if uclass.alias_num_to_node:
+                    start = max(uclass.alias_num_to_node.keys())
+                else:
+                    start = -1
+                for seq in range(start + 1, 1000):
+                    if seq not in uclass.alias_num_to_node:
+                        break
+                uclass.alias_path_to_num[node.path] = seq
+                uclass.alias_num_to_node[seq] = node
+                return seq
+        return None
index b4c0a042a9f935d303ebf6873b9cc4552aaf183e..b077cf0e76d5d4c4eff15fb1f957a195f129a70a 100755 (executable)
@@ -141,6 +141,8 @@ class TestDtoc(unittest.TestCase):
         Returns:
             DtbPlatdata object
         """
+        # Make a copy of the 'scan' object, since it includes uclasses and
+        # drivers, which get updated during execution.
         return dtb_platdata.run_steps(args, dtb_file, False, output, [], None,
                                       warning_disabled=True, scan=copy_scan())
 
@@ -1033,6 +1035,16 @@ U_BOOT_DRVINFO(spl_test2) = {
         self.assertIn("Cannot parse/find driver for 'sandbox_pmic",
                       str(exc.exception))
 
+    def test_process_nodes_bad_uclass(self):
+        plat, scan = self.setup_process_test()
+
+        self.assertIn('UCLASS_I2C', scan._uclass)
+        del scan._uclass['UCLASS_I2C']
+        with self.assertRaises(ValueError) as exc:
+            plat.process_nodes(True)
+        self.assertIn("Cannot parse/find uclass 'UCLASS_I2C' for driver 'sandbox_i2c'",
+                      str(exc.exception))
+
     def test_process_nodes_used(self):
         """Test processing nodes to add various info"""
         plat, scan = self.setup_process_test()
@@ -1052,10 +1064,13 @@ U_BOOT_DRVINFO(spl_test2) = {
 
         scan = plat._scan
         testfdt_node = plat._fdt.GetNode('/some-bus/test')
+        test0_node = plat._fdt.GetNode('/some-bus/test0')
         self.assertIn('UCLASS_TEST_FDT', scan._uclass)
         uc = scan._uclass['UCLASS_TEST_FDT']
-        self.assertEqual({1: testfdt_node}, uc.alias_num_to_node)
-        self.assertEqual({'/some-bus/test': 1}, uc.alias_path_to_num)
+        self.assertEqual({1: testfdt_node, 2: test0_node},
+                         uc.alias_num_to_node)
+        self.assertEqual({'/some-bus/test': 1, '/some-bus/test0': 2},
+                         uc.alias_path_to_num)
 
         # Try adding an alias that doesn't exist
         self.assertFalse(scan.add_uclass_alias('fred', 3, None))
@@ -1098,3 +1113,13 @@ U_BOOT_DRVINFO(spl_test2) = {
         dtb_file = get_dtb_file('dtoc_test_inst.dts')
         output = tools.GetOutputFilename('output')
         plat = self.run_test(['struct'], dtb_file, output)
+
+        scan = plat._scan
+        testfdt = plat._fdt.GetNode('/some-bus/test')
+        self.assertEqual(1, testfdt.seq)
+        i2c = plat._fdt.GetNode('/i2c')
+
+        # For now this uclass is not compiled in, so no sequence is assigned
+        self.assertEqual(4, i2c.seq)
+        spl = plat._fdt.GetNode('/spl-test')
+        self.assertEqual(0, spl.seq)
index 598ff256a60e7c4eaca2583da30759b960202d70..d32aa58400f4c107eb13203379d695571cb18be3 100644 (file)
@@ -466,3 +466,20 @@ U_BOOT_DRIVER(%s) = {
         with test_util.capture_sys_output() as (stdout, _):
             scan.mark_used([node])
         self.assertEqual('', stdout.getvalue().strip())
+
+    def test_sequence(self):
+        """Test assignment of sequence numnbers"""
+        scan = src_scan.Scanner(None, False, None, '')
+        node = FakeNode()
+        uc = src_scan.UclassDriver('UCLASS_I2C')
+        node.uclass = uc
+        node.driver = True
+        node.seq = -1
+        node.path = 'mypath'
+        uc.alias_num_to_node[2] = node
+
+        # This should assign 3 (after the 2 that exists)
+        seq = scan.assign_seq(node)
+        self.assertEqual(3, seq)
+        self.assertEqual({'mypath': 3}, uc.alias_path_to_num)
+        self.assertEqual({2: node, 3: node}, uc.alias_num_to_node)