mirror of
https://github.com/AsahiLinux/u-boot
synced 2024-11-28 15:41:40 +00:00
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 <sjg@chromium.org>
This commit is contained in:
parent
e1ad57e7ef
commit
4df457b657
3 changed files with 266 additions and 3 deletions
|
@ -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.
|
||||
|
|
76
tools/dtoc/test/dtoc_test_copy.dts
Normal file
76
tools/dtoc/test/dtoc_test_copy.dts
Normal file
|
@ -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;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -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"""
|
||||
|
|
Loading…
Reference in a new issue