scripts: dts: Replace 'sub-node:' with more general 'child-binding:'

Deprecate 'sub-node:' and add a more general 'child-binding:' mechanism
to bindings. Keep supporting 'sub-node:', but print a deprecation
warning when it's used.

Like 'sub-node:', 'child-binding:' gives a binding to child nodes, but
the binding is required to be a complete binding, and is treated (and
checked) like a normal binding.

'child-binding:' can in turn contain another 'child-binding:', up to any
number of levels. This is automatic from treating it like a normal
binding, and from the code initializing parent Devices before child
Devices.

This lets nodes give bindings to grandchildren.

For example, take this devicetree fragment:

    parent {
            compatible = "foo";
            child-1 {
                    grandchild-1 {
                            ...
                    };
                    grandchild-2 {
                            ...
                    };
            };
            child-2 {
                    grandchild-3 {
                            ...
                    };
            };
    };

The binding for 'foo' could provide bindings for grandchild-1/2/3 like
this:

    compatible: "foo"

    # Binding for children
    child-binding:
        title: ...
        description: ...

        ...

        # Binding for grandchildren
        child-binding:
            title: ...
            description: ...

            properties:
                ...

Due to implementation issues with the old devicetree scripts, only two
levels of 'child-binding:' is supported for now. This limitation will go
away in Zephyr 2.2.

Piggyback shortening 'description:' and 'title:' in some bindings that
provide child bindings. This makes the generated header a bit neater.

Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2019-09-17 18:24:30 +02:00 committed by Kumar Gala
commit 0b1ab4ab09
12 changed files with 229 additions and 85 deletions

View file

@ -60,18 +60,6 @@ child-bus: <string describing bus type, e.g. "i2c">
# on.
parent-bus: <string describing bus type, e.g. "i2c">
# 'sub-node' is used to simplify cases where a node has children that can all
# use the same binding. The contents of 'sub-node' becomes the binding for each
# child node.
#
# The example below is for a binding for pwm-leds where the child nodes are
# required to have a 'pwms' property.
sub-node:
properties:
pwms:
type: compound
required: true
# 'properties' describes properties on the node, e.g.
#
# reg = <1 2>;
@ -207,6 +195,66 @@ properties:
required: false
default: [0x12, 0x34] # Same as 'uint8-array-with-default = [12 34]'
# 'child-binding' can be used when a node has children that all share the same
# properties. Each child gets the contents of 'child-binding' as its binding
# (though an explicit 'compatible = ...' on the child node takes precedence, if
# a binding is found for it).
#
# The example below is for a binding for PWM LEDs, where the child nodes are
# required to have a 'pwms' property. It corresponds to this .dts structure
# (assuming the binding has 'compatible: "pwm-leds"'):
#
# pwmleds {
# compatible = "pwm-leds";
#
# red_pwm_led {
# pwms = <&pwm3 4 15625000>;
# };
# green_pwm_led {
# pwms = <&pwm3 0 15625000>;
# };
# ...
# };
child-binding:
title: PWM LED
description: LED that uses PWM
properties:
pwms:
type: phandle-array
required: true
# 'child-binding' also works recursively. For example, the binding below would
# provide a binding for the 'grandchild' node in this .dts (assuming
# 'compatible: "foo"'):
#
# parent {
# compatible = "foo";
# child {
# grandchild {
# prop = <123>;
# };
# };
# }
#
# WARNING: Due to implementation issues with legacy code, only up to two levels
# of 'child-binding:' nesting (like below) is supported. This restriction will
# go away in Zephyr 2.2.
child-binding:
title: ...
description: ...
...
child-binding:
title: ...
description: ...
properties:
prop:
type: int
required: true
# If the binding describes an interrupt controller, GPIO controller, pinmux
# device, or any other node referenced by other nodes, then #cells should be
# given.

View file

@ -1,14 +1,15 @@
# Copyright (c) 2018, Linaro Limited
# SPDX-License-Identifier: Apache-2.0
title: GPIO KEYS
title: GPIO KEYS parent
description: >
This is a representation of the GPIO KEYS nodes
description: GPIO KEYS parent node
compatible: "gpio-keys"
sub-node:
child-binding:
title: GPIO KEYS node
description: GPIO KEYS child node
properties:
gpios:
type: phandle-array

View file

@ -1,14 +1,15 @@
# Copyright (c) 2018, Linaro Limited
# SPDX-License-Identifier: Apache-2.0
title: GPIO LED
title: GPIO LEDs
description: >
This is a representation of the LED GPIO nodes
description: GPIO LEDs parent node
compatible: "gpio-leds"
sub-node:
child-binding:
title: GPIO LED node
description: GPIO LED child node
properties:
gpios:
type: phandle-array

View file

@ -1,14 +1,15 @@
# Copyright (c) 2018, Linaro Limited
# SPDX-License-Identifier: Apache-2.0
title: PWM LED
title: PWM LEDs
description: >
This is a representation of the PWM GPIO nodes
description: PWM LEDs parent node
compatible: "pwm-leds"
sub-node:
child-binding:
title: PWM LED node
description: PWM LED child node
properties:
pwms:
type: phandle-array

View file

@ -1,7 +1,6 @@
title: Flash Partitions
title: Flash partitions parent
description: >
This binding gives a base FLASH partition description
description: Flash partitions parent node
compatible: "fixed-partitions"
@ -16,7 +15,9 @@ properties:
required: false
description: number of size cells in reg property
sub-node:
child-binding:
title: Flash partition
description: Flash partition child node
properties:
label:
required: true

View file

@ -15,9 +15,9 @@ information related to the device, derived from both the device tree and from
the binding for the device.
Bindings are files that describe device tree nodes. Device tree nodes are
usually mapped to bindings via their 'compatible = "..."' property, but binding
data can also come from a 'sub-node:' key in the binding for the parent device
tree node.
usually mapped to bindings via their 'compatible = "..."' property, but a
binding can also come from a 'child-binding:' key in the binding for the parent
device tree node.
The top-level entry point of the library is the EDT class. EDT.__init__() takes
a .dts file to parse and the path of a directory containing bindings.
@ -556,29 +556,47 @@ class Device:
return
else:
# No 'compatible' property. See if the parent has a 'sub-node:' key
# that gives the binding.
# No 'compatible' property. See if the parent binding has a
# 'child-binding:' key that gives the binding (or a legacy
# 'sub-node:' key).
self.compats = []
if self.parent and self.parent._binding and \
"sub-node" in self.parent._binding:
# Binding found
self._binding = self.parent._binding["sub-node"]
binding_from_parent = self._binding_from_parent()
if binding_from_parent:
self._binding = binding_from_parent
self.binding_path = self.parent.binding_path
self.description = self.parent._binding.get("description")
if self.description:
self.description = self.description.rstrip()
self.matching_compat = self.parent.matching_compat
self.description = self._binding["description"]
return
# No binding found
self.matching_compat = self._binding = self.binding_path = \
self._binding = self.binding_path = self.matching_compat = \
self.description = None
def _binding_from_parent(self):
# Returns the binding from 'child-binding:' in the parent node's
# binding (or from the legacy 'sub-node:' key), or None if missing
if not self.parent:
return None
pbinding = self.parent._binding
if not pbinding:
return None
if "child-binding" in pbinding:
return pbinding["child-binding"]
# Backwards compatibility
if "sub-node" in pbinding:
return {"title": pbinding["title"],
"description": pbinding["description"],
"properties": pbinding["sub-node"]["properties"]}
return None
def _bus_from_parent_binding(self):
# _init_binding() helper. Returns the bus specified by 'child-bus: ...'
# in the parent binding (or the legacy 'child: bus: ...'), or None if
@ -1439,7 +1457,8 @@ def _check_binding(binding, binding_path):
.format(prop, binding_path))
ok_top = {"title", "description", "compatible", "properties", "#cells",
"parent-bus", "child-bus", "parent", "child", "sub-node"}
"parent-bus", "child-bus", "parent", "child", "child-binding",
"sub-node"}
for prop in binding:
if prop not in ok_top:
@ -1471,7 +1490,19 @@ def _check_binding(binding, binding_path):
_check_binding_properties(binding, binding_path)
if "child-binding" in binding:
if not isinstance(binding["child-binding"], dict):
_err("malformed 'child-binding:' in {}, expected a binding "
"(dictionary with keys/values)".format(binding_path))
_check_binding(binding["child-binding"], binding_path)
if "sub-node" in binding:
_warn("'sub-node: properties: ...' in {} is deprecated and will be "
"removed - please give a full binding for the child node in "
"'child-binding:' instead (see binding-template.yaml)"
.format(binding_path))
if binding["sub-node"].keys() != {"properties"}:
_err("expected (just) 'properties:' in 'sub-node:' in {}"
.format(binding_path))

View file

@ -307,24 +307,46 @@ def get_binding(node_path):
if isinstance(compat, list):
compat = compat[0]
parent_path = get_parent_path(node_path)
parent_compat = get_compat(parent_path)
# Support two levels of recursive 'child-binding:'. The new scripts support
# any number of levels, but it gets a bit tricky to implement here, because
# nodes don't store their bindings.
if parent_compat in bindings:
parent_binding = bindings[parent_compat]
# see if we're a sub-node
if compat is None and 'sub-node' in parent_binding:
return parent_binding['sub-node']
parent_path = get_parent_path(node_path)
pparent_path = get_parent_path(parent_path)
parent_compat = get_compat(parent_path)
pparent_compat = get_compat(pparent_path) if pparent_path else None
if parent_compat in bindings or pparent_compat in bindings:
if compat is None:
# The node doesn't get a binding from 'compatible'. See if it gets
# one via 'sub-node' or 'child-binding'.
parent_binding = bindings.get(parent_compat)
if parent_binding:
for sub_key in 'sub-node', 'child-binding':
if sub_key in parent_binding:
return parent_binding[sub_key]
# Look for 'child-binding: child-binding: ...' in grandparent node
pparent_binding = bindings.get(pparent_compat)
if pparent_binding and 'child-binding' in pparent_binding:
pp_child_binding = pparent_binding['child-binding']
if 'child-binding' in pp_child_binding:
return pp_child_binding['child-binding']
# look for a bus-specific binding
if 'child-bus' in parent_binding:
bus = parent_binding['child-bus']
return bus_bindings[bus][compat]
parent_binding = bindings.get(parent_compat)
if parent_binding:
if 'child-bus' in parent_binding:
bus = parent_binding['child-bus']
return bus_bindings[bus][compat]
if 'child' in parent_binding and 'bus' in parent_binding['child']:
bus = parent_binding['child']['bus']
return bus_bindings[bus][compat]
if 'child' in parent_binding and 'bus' in parent_binding['child']:
bus = parent_binding['child']['bus']
return bus_bindings[bus][compat]
# No bus-specific binding found, look in the main dict.
if compat:

View file

@ -0,0 +1,22 @@
# SPDX-License-Identifier: BSD-3-Clause
title: child-binding test
description: child-binding test
compatible: "child-binding"
child-binding:
title: child node
description: child node
properties:
child-prop:
type: int
required: true
child-binding:
title: grandchild node
description: grandchild node
properties:
grandchild-prop:
type: int
required: true

View file

@ -22,3 +22,10 @@ properties:
optional:
type: int
category: optional
# Deprecated older 'child-binding'-alike
sub-node:
properties:
child-prop:
type: int
required: true

View file

@ -1,16 +0,0 @@
# SPDX-License-Identifier: BSD-3-Clause
title: Sub-node test
description: Sub-node test
compatible: "parent-with-sub-node"
sub-node:
properties:
foo:
required: true
type: int
bar:
required: true
type: int

View file

@ -340,14 +340,20 @@
};
//
// Parent with 'sub-node:' in binding
// Node with 'child-binding:' in binding (along with a recursive
// 'child-binding:')
//
parent-with-sub-node {
compatible = "parent-with-sub-node";
node {
foo = <1>;
bar = <2>;
child-binding {
compatible = "child-binding";
child-1 {
child-prop = <1>;
grandchild {
grandchild-prop = <2>;
};
};
child-2 {
child-prop = <3>;
};
};
@ -376,5 +382,8 @@
compatible = "deprecated";
required = <1>;
required-2 = <2>;
sub-node {
child-prop = <3>;
};
};
};

View file

@ -116,14 +116,31 @@ def run():
"test-bindings/device-on-bar-bus.yaml")
#
# Test 'sub-node:' in binding
# Test 'child-binding:'
#
verify_streq(edt.get_dev("/parent-with-sub-node/node").description,
"Sub-node test")
child1 = edt.get_dev("/child-binding/child-1")
child2 = edt.get_dev("/child-binding/child-2")
grandchild = edt.get_dev("/child-binding/child-1/grandchild")
verify_streq(edt.get_dev("/parent-with-sub-node/node").props,
"{'foo': <Property, name: foo, type: int, value: 1>, 'bar': <Property, name: bar, type: int, value: 2>}")
verify_streq(child1.binding_path, "test-bindings/child-binding.yaml")
verify_streq(child1.description, "child node")
verify_streq(child1.props, "{'child-prop': <Property, name: child-prop, type: int, value: 1>}")
verify_streq(child2.binding_path, "test-bindings/child-binding.yaml")
verify_streq(child2.description, "child node")
verify_streq(child2.props, "{'child-prop': <Property, name: child-prop, type: int, value: 3>}")
verify_streq(grandchild.binding_path, "test-bindings/child-binding.yaml")
verify_streq(grandchild.description, "grandchild node")
verify_streq(grandchild.props, "{'grandchild-prop': <Property, name: grandchild-prop, type: int, value: 2>}")
#
# Test deprecated 'sub-node:' key (replaced with 'child-binding:')
#
verify_streq(edt.get_dev("/deprecated/sub-node").props,
"{'child-prop': <Property, name: child-prop, type: int, value: 3>}")
#
# Test Device.property (derived from DT and 'properties:' in the binding)