dtoc: Read aliases for uclasses

Scan the aliases in the device tree to establish the number of devices
within each uclass, and the sequence number of each.

Signed-off-by: Simon Glass <sjg@chromium.org>
diff --git a/tools/dtoc/dtb_platdata.py b/tools/dtoc/dtb_platdata.py
index ef0454c..f6dcf47 100644
--- a/tools/dtoc/dtb_platdata.py
+++ b/tools/dtoc/dtb_platdata.py
@@ -647,6 +647,29 @@
             self._output_prop(node, node.props[pname])
         self.buf('};\n')
 
+    def read_aliases(self):
+        """Read the aliases and attach the information to self._alias
+
+        Raises:
+            ValueError: The alias path is not found
+        """
+        alias_node = self._fdt.GetNode('/aliases')
+        if not alias_node:
+            return
+        re_num = re.compile('(^[a-z0-9-]+[a-z]+)([0-9]+)$')
+        for prop in alias_node.props.values():
+            m_alias = re_num.match(prop.name)
+            if not m_alias:
+                raise ValueError("Cannot decode alias '%s'" % prop.name)
+            name, num = m_alias.groups()
+            node = self._fdt.GetNode(prop.value)
+            result = self._scan.add_uclass_alias(name, num, node)
+            if result is None:
+                raise ValueError("Alias '%s' path '%s' not found" %
+                                 (prop.name, prop.value))
+            elif result is False:
+                print("Could not find uclass for alias '%s'" % prop.name)
+
     def process_nodes(self, need_drivers):
         nodes_to_output = list(self._valid_nodes)
 
@@ -757,6 +780,9 @@
         scan (src_src.Scanner): Scanner from a previous run. This can help speed
             up tests. Use None for normal operation
 
+    Returns:
+        DtbPlatdata object
+
     Raises:
         ValueError: if args has no command, or an unknown command
     """
@@ -782,6 +808,7 @@
     plat.scan_phandles()
     if do_process:
         plat.process_nodes(False)
+    plat.read_aliases()
 
     cmds = args[0].split(',')
     if 'all' in cmds:
@@ -796,3 +823,4 @@
         plat.out_header(outfile)
         outfile.method(plat)
     plat.finish_output()
+    return plat
diff --git a/tools/dtoc/src_scan.py b/tools/dtoc/src_scan.py
index fb78536..a275032 100644
--- a/tools/dtoc/src_scan.py
+++ b/tools/dtoc/src_scan.py
@@ -116,6 +116,13 @@
             e.g. 'pci_child_priv'
         per_child_plat (str): struct name of the per_child_plat_auto member,
             e.g. 'pci_child_plat'
+        alias_num_to_node (dict): Aliases for this uclasses (for sequence
+                numbers)
+            key (int): Alias number, e.g. 2 for "pci2"
+            value (str): Node the alias points to
+        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"
     """
     def __init__(self, name):
         self.name = name
@@ -125,6 +132,8 @@
         self.per_dev_plat = ''
         self.per_child_priv = ''
         self.per_child_plat = ''
+        self.alias_num_to_node = {}
+        self.alias_path_to_num = {}
 
     def __eq__(self, other):
         return (self.name == other.name and
@@ -622,7 +631,6 @@
                     self.scan_driver(pathname)
                 elif fname.endswith('.h'):
                     self.scan_header(pathname)
-
         for fname in self._drivers_additional:
             if not isinstance(fname, str) or len(fname) == 0:
                 continue
@@ -652,3 +660,25 @@
                     print("Warning: Duplicate driver name '%s' (orig=%s, dups=%s)" %
                           (driver.name, driver.fname,
                            ', '.join([drv.fname for drv in driver.dups])))
+
+    def add_uclass_alias(self, name, num, node):
+        """Add an alias to a uclass
+
+        Args:
+            name: Name of uclass, e.g. 'i2c'
+            num: Alias number, e.g. 2 for alias 'i2c2'
+            node: Node the alias points to, or None if None
+
+        Returns:
+            True if the node was added
+            False if the node was not added (uclass of that name not found)
+            None if the node could not be added because it was None
+        """
+        for uclass in self._uclass.values():
+            if uclass.name == name:
+                if node is None:
+                    return None
+                uclass.alias_num_to_node[int(num)] = node
+                uclass.alias_path_to_num[node.path] = int(num)
+                return True
+        return False
diff --git a/tools/dtoc/test/dtoc_test_alias_bad.dts b/tools/dtoc/test/dtoc_test_alias_bad.dts
new file mode 100644
index 0000000..d4f502a
--- /dev/null
+++ b/tools/dtoc/test/dtoc_test_alias_bad.dts
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test device tree file for dtoc
+ *
+ * Copyright 2017 Google, Inc
+ */
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	aliases {
+		testbus2 = &bus2;
+		testfdt1 = &testfdt_1;
+		i2c4- = &i2c;
+	};
+
+	spl-test {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		boolval;
+		intval = <1>;
+	};
+
+	i2c: i2c {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,i2c";
+		intval = <3>;
+	};
+
+	spl-test3 {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		stringarray = "one";
+		longbytearray = [09 0a 0b 0c 0d 0e 0f 10];
+	};
+
+	bus2: some-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "denx,u-boot-test-bus";
+		reg = <3 1>;
+		ping-expect = <4>;
+		ping-add = <4>;
+		testfdt_1: test {
+			compatible = "denx,u-boot-fdt-test", "google,another-fdt-test";
+			reg = <5>;
+			ping-expect = <5>;
+			ping-add = <5>;
+		};
+
+		test0 {
+			compatible = "google,another-fdt-test";
+		};
+	};
+};
diff --git a/tools/dtoc/test/dtoc_test_alias_bad_path.dts b/tools/dtoc/test/dtoc_test_alias_bad_path.dts
new file mode 100644
index 0000000..0beca4f
--- /dev/null
+++ b/tools/dtoc/test/dtoc_test_alias_bad_path.dts
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test device tree file for dtoc
+ *
+ * Copyright 2017 Google, Inc
+ */
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	aliases {
+		testbus2 = &bus2;
+		testfdt1 = &testfdt_1;
+		i2c4 = "/does/not/exist";
+	};
+
+	spl-test {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		boolval;
+		intval = <1>;
+	};
+
+	i2c: i2c {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,i2c";
+		intval = <3>;
+	};
+
+	spl-test3 {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		stringarray = "one";
+		longbytearray = [09 0a 0b 0c 0d 0e 0f 10];
+	};
+
+	bus2: some-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "denx,u-boot-test-bus";
+		reg = <3 1>;
+		ping-expect = <4>;
+		ping-add = <4>;
+		testfdt_1: test {
+			compatible = "denx,u-boot-fdt-test", "google,another-fdt-test";
+			reg = <5>;
+			ping-expect = <5>;
+			ping-add = <5>;
+		};
+
+		test0 {
+			compatible = "google,another-fdt-test";
+		};
+	};
+};
diff --git a/tools/dtoc/test/dtoc_test_alias_bad_uc.dts b/tools/dtoc/test/dtoc_test_alias_bad_uc.dts
new file mode 100644
index 0000000..ae64f5b
--- /dev/null
+++ b/tools/dtoc/test/dtoc_test_alias_bad_uc.dts
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test device tree file for dtoc
+ *
+ * Copyright 2017 Google, Inc
+ */
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	aliases {
+		testbus2 = &bus2;
+		testfdt1 = &testfdt_1;
+		other1 = &testfdt_1;
+	};
+
+	spl-test {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		boolval;
+		intval = <1>;
+	};
+
+	i2c: i2c {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,i2c";
+		intval = <3>;
+	};
+
+	spl-test3 {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		stringarray = "one";
+		longbytearray = [09 0a 0b 0c 0d 0e 0f 10];
+	};
+
+	bus2: some-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "denx,u-boot-test-bus";
+		reg = <3 1>;
+		ping-expect = <4>;
+		ping-add = <4>;
+		testfdt_1: test {
+			compatible = "denx,u-boot-fdt-test", "google,another-fdt-test";
+			reg = <5>;
+			ping-expect = <5>;
+			ping-add = <5>;
+		};
+
+		test0 {
+			compatible = "google,another-fdt-test";
+		};
+	};
+};
diff --git a/tools/dtoc/test/dtoc_test_inst.dts b/tools/dtoc/test/dtoc_test_inst.dts
new file mode 100644
index 0000000..b8177fc
--- /dev/null
+++ b/tools/dtoc/test/dtoc_test_inst.dts
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test device tree file for dtoc
+ *
+ * Copyright 2017 Google, Inc
+ */
+
+/dts-v1/;
+
+/ {
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	aliases {
+		testbus2 = &bus2;
+		testfdt1 = &testfdt_1;
+		i2c4 = &i2c;
+	};
+
+	spl-test {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		boolval;
+		intval = <1>;
+	};
+
+	i2c: i2c {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,i2c";
+		intval = <3>;
+	};
+
+	spl-test3 {
+		u-boot,dm-pre-reloc;
+		compatible = "sandbox,spl-test";
+		stringarray = "one";
+		longbytearray = [09 0a 0b 0c 0d 0e 0f 10];
+	};
+
+	bus2: some-bus {
+		#address-cells = <1>;
+		#size-cells = <0>;
+		compatible = "denx,u-boot-test-bus";
+		reg = <3 1>;
+		ping-expect = <4>;
+		ping-add = <4>;
+		testfdt_1: test {
+			compatible = "denx,u-boot-fdt-test", "google,another-fdt-test";
+			reg = <5>;
+			ping-expect = <5>;
+			ping-add = <5>;
+		};
+
+		test0 {
+			compatible = "google,another-fdt-test";
+		};
+	};
+};
diff --git a/tools/dtoc/test_dtoc.py b/tools/dtoc/test_dtoc.py
index c1fafb6..706cc39 100755
--- a/tools/dtoc/test_dtoc.py
+++ b/tools/dtoc/test_dtoc.py
@@ -137,9 +137,12 @@
             args (list of str): List of arguments for dtoc
             dtb_file (str): Filename of .dtb file
             output (str): Filename of output file
+
+        Returns:
+            DtbPlatdata object
         """
-        dtb_platdata.run_steps(args, dtb_file, False, output, [], None,
-                               warning_disabled=True, scan=copy_scan())
+        return dtb_platdata.run_steps(args, dtb_file, False, output, [], None,
+                                      warning_disabled=True, scan=copy_scan())
 
     def test_name(self):
         """Test conversion of device tree names to C identifiers"""
@@ -1040,3 +1043,52 @@
 
         gpio = scan._drivers['sandbox_gpio']
         self.assertFalse(gpio.used)
+
+    def test_alias_read(self):
+        """Test obtaining aliases"""
+        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_node = plat._fdt.GetNode('/some-bus/test')
+        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)
+
+        # Try adding an alias that doesn't exist
+        self.assertFalse(scan.add_uclass_alias('fred', 3, None))
+
+        # Try adding an alias for a missing node
+        self.assertIsNone(scan.add_uclass_alias('testfdt', 3, None))
+
+    def test_alias_read_bad(self):
+        """Test invalid alias property name"""
+        dtb_file = get_dtb_file('dtoc_test_alias_bad.dts')
+        output = tools.GetOutputFilename('output')
+        with self.assertRaises(ValueError) as exc:
+            plat = self.run_test(['struct'], dtb_file, output)
+        self.assertIn("Cannot decode alias 'i2c4-'", str(exc.exception))
+
+    def test_alias_read_bad_path(self):
+        """Test alias pointing to a non-existent node"""
+        # This line may produce a warning, so capture it:
+        # Warning (alias_paths): /aliases:i2c4: aliases property is not a valid
+        #    node (/does/not/exist)
+        dtb_file = get_dtb_file('dtoc_test_alias_bad_path.dts', True)
+
+        output = tools.GetOutputFilename('output')
+        with self.assertRaises(ValueError) as exc:
+            plat = self.run_test(['struct'], dtb_file, output)
+        self.assertIn("Alias 'i2c4' path '/does/not/exist' not found",
+                      str(exc.exception))
+
+    def test_alias_read_bad_uclass(self):
+        """Test alias for a uclass that doesn't exist"""
+        dtb_file = get_dtb_file('dtoc_test_alias_bad_uc.dts')
+        output = tools.GetOutputFilename('output')
+        with test_util.capture_sys_output() as (stdout, _):
+            plat = self.run_test(['struct'], dtb_file, output)
+        self.assertEqual("Could not find uclass for alias 'other1'",
+                         stdout.getvalue().strip())