dts: Add support for specifier-space property.

Currently all the *-names and *-cells properties are derived from the
name of the base <name>s property. This is a limitation because:

- It forces the base property name to be plural ending in -s
- It doesn't allow the english exception of plural words ending in -es

With this patch we add one additional property 'specifier-space' that
can be used to explicitly specify the base property name.

Signed-off-by: Carlo Caione <ccaione@baylibre.com>
Suggested-by: Martí Bolívar <marti.bolivar@nordicsemi.no>
This commit is contained in:
Carlo Caione 2021-10-01 13:45:50 +02:00 committed by Kumar Gala
parent 8ed9f64064
commit 42105ed882
8 changed files with 129 additions and 40 deletions

View file

@ -807,6 +807,9 @@ Because other property names are derived from the name of the property by
removing the final ``s``, the property name must end in ``s``. An error is
raised if it doesn't.
An alternative is using a ``specifier-space`` property to indicate the base
property name for ``*-names`` and ``*-cells``.
``*-gpios`` properties are special-cased so that e.g. ``foo-gpios`` resolves to
``#gpio-cells`` rather than ``#foo-gpio-cells``.

View file

@ -23,3 +23,6 @@ gpio-cells:
foo-cells:
- foocell
baz-cells:
- bazcell

View file

@ -17,3 +17,7 @@ properties:
foo-names: {type: "string-array"}
pwms: {type: "phandle-array"}
pwm-names: {type: "string-array"}
bar:
type: "phandle-array"
specifier-space: baz
baz-names: {type: "string-array"}

View file

@ -971,7 +971,7 @@ class Node:
continue
prop_spec = _DEFAULT_PROP_SPECS[name]
val = self._prop_val(name, prop_spec.type, False, False, None,
err_on_deprecated)
None, err_on_deprecated)
self.props[name] = Property(prop_spec, val, self)
def _init_prop(self, prop_spec, err_on_deprecated):
@ -985,7 +985,7 @@ class Node:
val = self._prop_val(name, prop_type, prop_spec.deprecated,
prop_spec.required, prop_spec.default,
err_on_deprecated)
prop_spec.specifier_space, err_on_deprecated)
if val is None:
# 'required: false' property that wasn't there, or a property type
@ -1013,7 +1013,7 @@ class Node:
self.props[name] = Property(prop_spec, val, self)
def _prop_val(self, name, prop_type, deprecated, required, default,
err_on_deprecated):
specifier_space, err_on_deprecated):
# _init_prop() helper for getting the property's value
#
# name:
@ -1032,6 +1032,9 @@ class Node:
# Default value to use when the property doesn't exist, or None if
# the binding doesn't give a default value
#
# specifier_space:
# Property specifier-space from binding (if prop_type is "phandle-array")
#
# err_on_deprecated:
# If True, a deprecated property is an error instead of warning.
@ -1100,7 +1103,7 @@ class Node:
f"with '{name} = < &foo ... &bar 1 ... &baz 2 3 >' "
f"(a mix of phandles and numbers), not '{prop}'")
return self._standard_phandle_val_list(prop)
return self._standard_phandle_val_list(prop, specifier_space)
if prop_type == "path":
return self.edt._node2enode[prop.to_path()]
@ -1212,41 +1215,59 @@ class Node:
_add_names(node, "interrupt", self.interrupts)
def _standard_phandle_val_list(self, prop):
def _standard_phandle_val_list(self, prop, specifier_space):
# Parses a property like
#
# <name>s = <phandle value phandle value ...>
# (e.g., pwms = <&foo 1 2 &bar 3 4>)
# <prop.name> = <phandle cell phandle cell ...>;
#
# , where each phandle points to a node that has a
# where each phandle points to a controller node that has a
#
# #<name>-cells = <size>
# #<specifier_space>-cells = <size>;
#
# property that gives the number of cells in the value after the
# phandle. These values are given names in *-cells in the binding for
# the controller.
# controller's phandle in the property.
#
# E.g. with a property like
#
# pwms = <&foo 1 2 &bar 3>;
#
# If 'specifier_space' is "pwm", then we should have this elsewhere
# in the tree:
#
# foo: ... {
# #pwm-cells = <2>;
# };
#
# bar: ... {
# #pwm-cells = <1>;
# };
#
# These values can be given names using the <specifier_space>-names:
# list in the binding for the phandle nodes.
#
# Also parses any
#
# <name>-names = "...", "...", ...
# <specifier_space>-names = "...", "...", ...
#
# Returns a list of Optional[ControllerAndData] instances.
# An index is None if the underlying phandle-array element
# is unspecified.
#
# An index is None if the underlying phandle-array element is
# unspecified.
if not specifier_space:
if prop.name.endswith("gpios"):
# There's some slight special-casing for *-gpios properties in that
# e.g. foo-gpios still maps to #gpio-cells rather than
# #foo-gpio-cells
basename = "gpio"
specifier_space = "gpio"
else:
# Strip -s. We've already checked that the property names end in -s
# in _check_prop_type_and_default().
basename = prop.name[:-1]
# Strip -s. We've already checked that property names end in -s
# if there is no specifier space in _check_prop_type_and_default().
specifier_space = prop.name[:-1]
res = []
for item in _phandle_val_list(prop, basename):
for item in _phandle_val_list(prop, specifier_space):
if item is None:
res.append(None)
continue
@ -1254,17 +1275,18 @@ class Node:
controller_node, data = item
mapped_controller, mapped_data = \
_map_phandle_array_entry(prop.node, controller_node, data,
basename)
specifier_space)
entry = ControllerAndData()
entry.node = self
entry.controller = self.edt._node2enode[mapped_controller]
entry.basename = specifier_space
entry.data = self._named_cells(entry.controller, mapped_data,
basename)
specifier_space)
res.append(entry)
_add_names(self._node, basename, res)
_add_names(self._node, specifier_space, res)
return res
@ -1354,6 +1376,9 @@ class ControllerAndData:
The name of the entry as given in
'interrupt-names'/'gpio-names'/'pwm-names'/etc., or None if there is no
*-names property
basename:
Basename for the controller when supporting named cells
"""
def __repr__(self):
fields = []
@ -1810,7 +1835,8 @@ class Binding:
return
ok_prop_keys = {"description", "type", "required",
"enum", "const", "default", "deprecated"}
"enum", "const", "default", "deprecated",
"specifier-space"}
for prop_name, options in raw["properties"].items():
for key in options:
@ -1820,9 +1846,7 @@ class Binding:
f"expected one of {', '.join(ok_prop_keys)}")
_check_prop_type_and_default(
prop_name, options.get("type"),
options.get("default"),
self.path)
prop_name, options, self.path)
for true_false_opt in ["required", "deprecated"]:
if true_false_opt in options:
@ -1922,6 +1946,9 @@ class PropertySpec:
required:
True if the property is marked required; False otherwise.
specifier_space:
The specifier space for the property as given in the binding, or None.
"""
def __init__(self, name, binding):
@ -2001,6 +2028,12 @@ class PropertySpec:
"See the class docstring"
return self._raw.get("deprecated", False)
@property
def specifier_space(self):
"See the class docstring"
return self._raw.get("specifier-space")
class EDTError(Exception):
"Exception raised for devicetree- and binding-related errors"
@ -2225,9 +2258,12 @@ def _binding_include(loader, node):
_binding_inc_error("unrecognised node type in !include statement")
def _check_prop_type_and_default(prop_name, prop_type, default, binding_path):
# Binding._check_properties() helper. Checks 'type:' and 'default:' for the
# property named 'prop_name'
def _check_prop_type_and_default(prop_name, options, binding_path):
# Binding._check_properties() helper. Checks 'type:', 'default:' and
# 'specifier-space:' for the property named 'prop_name'
prop_type = options.get("type")
default = options.get("default")
if prop_type is None:
_err(f"missing 'type:' for '{prop_name}' in 'properties' in "
@ -2242,13 +2278,15 @@ def _check_prop_type_and_default(prop_name, prop_type, default, binding_path):
f"has unknown type '{prop_type}', expected one of " +
", ".join(ok_types))
if prop_type == "phandle-array" and not prop_name.endswith("s"):
_err(f"'{prop_name}' in 'properties:' in {binding_path} "
"is 'type: phandle-array', but its "
"name does not end in -s. This is required since property names "
"like '#pwm-cells' and 'pwm-names' get derived from 'pwms', for "
"example.")
if "specifier-space" in options and prop_type != "phandle-array":
_err(f"'specifier-space' in 'properties: {prop_name}' "
f"has type '{prop_type}', expected 'phandle-array'")
if prop_type == "phandle-array":
if not prop_name.endswith("s") and not "specifier-space" in options:
_err(f"'{prop_name}' in 'properties:' in {binding_path} "
f"has type 'phandle-array' and its name does not end in 's', "
f"but no 'specifier-space' was provided.")
# Check default
if default is None:

View file

@ -0,0 +1,9 @@
# SPDX-License-Identifier: BSD-3-Clause
description: Device.wrong_phandle_array_name test
compatible: "wrong_phandle_array_name"
properties:
wrong-phandle-array-name:
type: phandle-array

View file

@ -0,0 +1,10 @@
# SPDX-License-Identifier: BSD-3-Clause
description: Device.wrong_specifier_space_type test
compatible: "wrong_specifier_space_type"
properties:
wrong-type-for-specifier-space:
type: phandle
specifier-space: foobar

View file

@ -357,6 +357,8 @@ def test_nexus():
assert str(edt.get_node("/gpio-map/source").props["foo-gpios"]) == \
f"<Property, name: foo-gpios, type: phandle-array, value: [<ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding {filename}>, data: OrderedDict([('val', 6)])>, <ControllerAndData, controller: <Node /gpio-map/destination in 'test.dts', binding {filename}>, data: OrderedDict([('val', 5)])>]>"
assert str(edt.get_node("/gpio-map/source").props["foo-gpios"].val[0].basename) == f"gpio"
def test_prop_defaults():
'''Test property default values given in bindings'''
with from_here():
@ -516,6 +518,22 @@ def test_bad_compatible(tmp_path):
dts_file,
r"node '/foo' compatible 'no, whitespace' must match this regular expression: '^[a-zA-Z][a-zA-Z0-9,+\-._]+$'")
def test_wrong_props():
'''Test Node.wrong_props (derived from DT and 'properties:' in the binding)'''
with from_here():
with pytest.raises(edtlib.EDTError) as e:
edtlib.Binding("test-wrong-bindings/wrong-specifier-space-type.yaml", None)
assert ("'specifier-space' in 'properties: wrong-type-for-specifier-space' has type 'phandle', expected 'phandle-array'"
in str(e.value))
with pytest.raises(edtlib.EDTError) as e:
edtlib.Binding("test-wrong-bindings/wrong-phandle-array-name.yaml", None)
value_str = str(e.value)
assert value_str.startswith("'wrong-phandle-array-name' in 'properties:'")
assert value_str.endswith("but no 'specifier-space' was provided.")
def verify_error(dts, dts_file, expected_err):
# Verifies that parsing a file 'dts_file' with the contents 'dts'
# (a string) raises an EDTError with the message 'expected_err'.

View file

@ -58,6 +58,8 @@
foo-names = "A", "b-c";
pwms = <&test_pwm1 8 200 3>, <&test_pwm2 5 100 1>;
pwm-names = "red", "green";
bar = <&test_gpio_1 200>, <&test_gpio_2 210>;
baz-names = "john", "doe";
};
test_enum_0: enum-0 {
@ -122,6 +124,7 @@
reg = < 0xdeadbeef 0x1000 >;
#gpio-cells = < 0x2 >;
#foo-cells = < 0x1 >;
#baz-cells = < 0x1 >;
label = "TEST_GPIO_1";
interrupts = <4 3>;
status = "okay";
@ -134,6 +137,7 @@
reg-names = "one", "two";
#gpio-cells = < 0x2 >;
#foo-cells = < 0x1 >;
#baz-cells = < 0x1 >;
interrupts = <5 2>;
label = "TEST_GPIO_2";
status = "okay";