]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
dtoc: Support copying the contents of a node into another
authorSimon Glass <sjg@chromium.org>
Tue, 18 Jul 2023 13:24:02 +0000 (07:24 -0600)
committerSimon Glass <sjg@chromium.org>
Thu, 20 Jul 2023 20:10:58 +0000 (14:10 -0600)
This permits implementation of a simple templating system, where a node
can be reused as a base for others.

For now this adds new subnodes after any existing ones.

Signed-off-by: Simon Glass <sjg@chromium.org>
tools/dtoc/fdt.py
tools/dtoc/test/dtoc_test_copy.dts [new file with mode: 0644]
tools/dtoc/test_fdt.py

index a8e05349a7205cccf7cf2d1ae72355716c48e2a9..f4d84083e491eb3770795542effbc8e1a4385360 100644 (file)
@@ -13,6 +13,7 @@ from dtoc import fdt_util
 import libfdt
 from libfdt import QUIET_NOTFOUND
 from u_boot_pylib import tools
+from u_boot_pylib import tout
 
 # This deals with a device tree, presenting it as an assortment of Node and
 # Prop objects, representing nodes and properties, respectively. This file
@@ -264,6 +265,13 @@ class Prop:
                 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
             self.dirty = False
 
+    def purge(self):
+        """Set a property offset to None
+
+        The property remains in the tree structure and will be recreated when
+        the FDT is synced
+        """
+        self._offset = None
 
 class Node:
     """A device tree node
@@ -534,8 +542,8 @@ class Node:
         """
         return self.AddData(prop_name, struct.pack('>I', val))
 
-    def AddSubnode(self, name):
-        """Add a new subnode to the node
+    def Subnode(self, name):
+        """Create new subnode for the node
 
         Args:
             name: name of node to add
@@ -544,10 +552,72 @@ class Node:
             New subnode that was created
         """
         path = self.path + '/' + name
-        subnode = Node(self._fdt, self, None, name, path)
+        return Node(self._fdt, self, None, name, path)
+
+    def AddSubnode(self, name):
+        """Add a new subnode to the node, after all other subnodes
+
+        Args:
+            name: name of node to add
+
+        Returns:
+            New subnode that was created
+        """
+        subnode = self.Subnode(name)
         self.subnodes.append(subnode)
         return subnode
 
+    def insert_subnode(self, name):
+        """Add a new subnode to the node, before all other subnodes
+
+        This deletes other subnodes and sets their offset to None, so that they
+        will be recreated after this one.
+
+        Args:
+            name: name of node to add
+
+        Returns:
+            New subnode that was created
+        """
+        # Deleting a node invalidates the offsets of all following nodes, so
+        # process in reverse order so that the offset of each node remains valid
+        # until deletion.
+        for subnode in reversed(self.subnodes):
+            subnode.purge(True)
+        subnode = self.Subnode(name)
+        self.subnodes.insert(0, subnode)
+        return subnode
+
+    def purge(self, delete_it=False):
+        """Purge this node, setting offset to None and deleting from FDT"""
+        if self._offset is not None:
+            if delete_it:
+                CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
+                     "Node '%s': delete" % self.path)
+            self._offset = None
+            self._fdt.Invalidate()
+
+        for prop in self.props.values():
+            prop.purge()
+
+        for subnode in self.subnodes:
+            subnode.purge(False)
+
+    def move_to_first(self):
+        """Move the current node to first in its parent's node list"""
+        parent = self.parent
+        if parent.subnodes and parent.subnodes[0] == self:
+            return
+        for subnode in reversed(parent.subnodes):
+            subnode.purge(True)
+
+        new_subnodes = [self]
+        for subnode in parent.subnodes:
+            #subnode.purge(False)
+            if subnode != self:
+                new_subnodes.append(subnode)
+        parent.subnodes = new_subnodes
+
     def Delete(self):
         """Delete a node
 
@@ -635,6 +705,49 @@ class Node:
             prop.Sync(auto_resize)
         return added
 
+    def merge_props(self, src):
+        """Copy missing properties (except 'phandle') from another node
+
+        Args:
+            src (Node): Node containing properties to copy
+
+        Adds properties which are present in src but not in this node. Any
+        'phandle' property is not copied since this might result in two nodes
+        with the same phandle, thus making phandle references ambiguous.
+        """
+        for name, src_prop in src.props.items():
+            if name != 'phandle' and name not in self.props:
+                self.props[name] = Prop(self, None, name, src_prop.bytes)
+
+    def copy_node(self, src):
+        """Copy a node and all its subnodes into this node
+
+        Args:
+            src (Node): Node to copy
+
+        Returns:
+            Node: Resulting destination node
+
+        This works recursively.
+
+        The new node is put before all other nodes. If the node already
+        exists, just its subnodes and properties are copied, placing them before
+        any existing subnodes. Properties which exist in the destination node
+        already are not copied.
+        """
+        dst = self.FindNode(src.name)
+        if dst:
+            dst.move_to_first()
+        else:
+            dst = self.insert_subnode(src.name)
+        dst.merge_props(src)
+
+        # Process in reverse order so that they appear correctly in the result,
+        # since copy_node() puts the node first in the list
+        for node in reversed(src.subnodes):
+            dst.copy_node(node)
+        return dst
+
 
 class Fdt:
     """Provides simple access to a flat device tree blob using libfdts.
diff --git a/tools/dtoc/test/dtoc_test_copy.dts b/tools/dtoc/test/dtoc_test_copy.dts
new file mode 100644 (file)
index 0000000..85e2c34
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Test device tree file for dtoc
+ *
+ * Copyright 2017 Google, Inc
+ */
+
+/dts-v1/;
+
+/ {
+       #address-cells = <1>;
+       #size-cells = <1>;
+       reference = <&over>;    /* nake sure that the 'over' phandle exists */
+
+       dest {
+               bootph-all;
+               compatible = "sandbox,spl-test";
+               stringarray = "one";
+               longbytearray = [09 0a 0b 0c 0d 0e 0f 10];
+               maybe-empty-int = <1>;
+
+               first@0 {
+                       a-prop = <456>;
+                       b-prop = <1>;
+               };
+
+               existing {
+               };
+
+               base {
+                       second {
+                               second3 {
+                               };
+
+                               second2 {
+                                       new-prop;
+                               };
+
+                               second1 {
+                                       new-prop;
+                               };
+
+                               second4 {
+                               };
+                       };
+               };
+       };
+
+       base {
+               compatible = "sandbox,i2c";
+               bootph-all;
+               #address-cells = <1>;
+               #size-cells = <0>;
+               over: over {
+                       compatible = "sandbox,pmic";
+                       bootph-all;
+                       reg = <9>;
+                       low-power;
+               };
+
+               first@0 {
+                       reg = <0>;
+                       a-prop = <123>;
+               };
+
+               second: second {
+                       second1 {
+                               some-prop;
+                       };
+
+                       second2 {
+                               some-prop;
+                       };
+               };
+       };
+};
index 4fe8d12c403a07acec5fc971b08c1ef7258e93ca..ebc5297b5067acdfb800c83e043e6a9ad29bcff4 100755 (executable)
@@ -306,6 +306,80 @@ class TestNode(unittest.TestCase):
         self.assertIn("Internal error, node '/spl-test' name mismatch 'i2c@0'",
                       str(exc.exception))
 
+    def test_copy_node(self):
+        """Test copy_node() function"""
+        def do_copy_checks(dtb, dst, expect_none):
+            self.assertEqual(
+                ['/dest/base', '/dest/first@0', '/dest/existing'],
+                [n.path for n in dst.subnodes])
+
+            chk = dtb.GetNode('/dest/base')
+            self.assertTrue(chk)
+            self.assertEqual(
+                {'compatible', 'bootph-all', '#address-cells', '#size-cells'},
+                chk.props.keys())
+
+            # Check the first property
+            prop = chk.props['bootph-all']
+            self.assertEqual('bootph-all', prop.name)
+            self.assertEqual(True, prop.value)
+            self.assertEqual(chk.path, prop._node.path)
+
+            # Check the second property
+            prop2 = chk.props['compatible']
+            self.assertEqual('compatible', prop2.name)
+            self.assertEqual('sandbox,i2c', prop2.value)
+            self.assertEqual(chk.path, prop2._node.path)
+
+            base = chk.FindNode('base')
+            self.assertTrue(chk)
+
+            first = dtb.GetNode('/dest/base/first@0')
+            self.assertTrue(first)
+            over = dtb.GetNode('/dest/base/over')
+            self.assertTrue(over)
+
+            # Make sure that the phandle for 'over' is not copied
+            self.assertNotIn('phandle', over.props.keys())
+
+            second = dtb.GetNode('/dest/base/second')
+            self.assertTrue(second)
+            self.assertEqual([over.name, first.name, second.name],
+                             [n.name for n in chk.subnodes])
+            self.assertEqual(chk, over.parent)
+            self.assertEqual(
+                {'bootph-all', 'compatible', 'reg', 'low-power'},
+                over.props.keys())
+
+            if expect_none:
+                self.assertIsNone(prop._offset)
+                self.assertIsNone(prop2._offset)
+                self.assertIsNone(over._offset)
+            else:
+                self.assertTrue(prop._offset)
+                self.assertTrue(prop2._offset)
+                self.assertTrue(over._offset)
+
+            # Now check ordering of the subnodes
+            self.assertEqual(
+                ['second1', 'second2', 'second3', 'second4'],
+                [n.name for n in second.subnodes])
+
+        dtb = fdt.FdtScan(find_dtb_file('dtoc_test_copy.dts'))
+        tmpl = dtb.GetNode('/base')
+        dst = dtb.GetNode('/dest')
+        dst.copy_node(tmpl)
+
+        do_copy_checks(dtb, dst, expect_none=True)
+
+        dtb.Sync(auto_resize=True)
+
+        # Now check that the FDT looks correct
+        new_dtb = fdt.Fdt.FromData(dtb.GetContents())
+        new_dtb.Scan()
+        dst = new_dtb.GetNode('/dest')
+        do_copy_checks(new_dtb, dst, expect_none=False)
+
 
 class TestProp(unittest.TestCase):
     """Test operation of the Prop class"""