From: Simon Glass Date: Tue, 18 Jul 2023 13:24:02 +0000 (-0600) Subject: dtoc: Support copying the contents of a node into another X-Git-Url: http://git.dujemihanovic.xyz/?a=commitdiff_plain;h=4df457b657b6d0c10031ab4b1eb414e3ea6c3819;p=u-boot.git dtoc: Support copying the contents of a node into another 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 --- diff --git a/tools/dtoc/fdt.py b/tools/dtoc/fdt.py index a8e05349a7..f4d84083e4 100644 --- a/tools/dtoc/fdt.py +++ b/tools/dtoc/fdt.py @@ -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 index 0000000000..85e2c342a7 --- /dev/null +++ b/tools/dtoc/test/dtoc_test_copy.dts @@ -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; + }; + }; + }; +}; diff --git a/tools/dtoc/test_fdt.py b/tools/dtoc/test_fdt.py index 4fe8d12c40..ebc5297b50 100755 --- a/tools/dtoc/test_fdt.py +++ b/tools/dtoc/test_fdt.py @@ -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"""