dts: edtlib: Support giving missing properties a default value

For missing optional properties, it can be handy to generate a default
value instead of no value, to cut down on #ifdefs.

Allow a default value to be specified in the binding, via a new
'default: <default value>' setting for properties in bindings.
Defaults are supported for both scalar and array types. YAML arrays are
used to specify the value for array types.

'default:' also appears in json-schema, with the same meaning.

Include misc. sanity checks, like the 'default' value matching 'type'.

The documentation changes in binding-template.yaml explain the syntax.

Suggested by Peter A. Bigot in
https://github.com/zephyrproject-rtos/zephyr/issues/17829.

Fixes: #17829
Signed-off-by: Ulf Magnusson <Ulf.Magnusson@nordicsemi.no>
This commit is contained in:
Ulf Magnusson 2019-08-29 22:21:33 +02:00 committed by Kumar Gala
commit ff1f75293e
5 changed files with 191 additions and 12 deletions

View file

@ -87,6 +87,7 @@ sub-node:
# ...
# - <itemN>
# const: <string | int>
# default: <default>
#
# 'type: uint8-array' is for what the device tree specification calls
# 'bytestring'. Properties of type 'uint8-array' should be set like this:
@ -121,6 +122,18 @@ sub-node:
# foo = <&label1 &label2>, <&label3 &label4>; // Okay for 'type: phandles'
# foo = <&label1 1 2>, <&label2 3 4>; // Okay for 'type: phandle-array'
# etc.
#
# The optional 'default:' setting gives a value that will be used if the
# property is missing from the device tree node. If 'default: <default>' is
# given for a property <prop> and <prop> is missing, then the output will be as
# if '<prop> = <default>' had appeared (except YAML data types are used for the
# default value).
#
# Note that it only makes sense to combine 'default:' with 'required: false'.
# Combining it with 'required: true' will raise an error.
#
# See below for examples of 'default:'. Putting 'default:' on any property type
# besides those used in the examples will raise an error.
properties:
# Describes a property like 'current-speed = <115200>;'. We pretend that
# it's obligatory for the example node and set 'required: true'.
@ -154,6 +167,31 @@ properties:
required: true
const: 1
int-with-default:
type: int
required: false
default: 123
array-with-default:
type: array
required: false
default: [1, 2, 3] # Same as 'array-with-default = <1 2 3>'
string-with-default:
type: string
required: false
default: "foo"
string-array-with-default:
type: string-array
required: false
default: ["foo", "bar"] # Same as 'string-array-with-default = "foo", "bar"'
uint8-array-with-default:
type: uint8-array
required: false
default: [0x12, 0x34] # Same as 'uint8-array-with-default = [12 34]'
# 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

@ -591,7 +591,8 @@ class Device:
val = self._prop_val(
name, prop_type,
options.get("required") or options.get("category") == "required")
options.get("required") or options.get("category") == "required",
options.get("default"))
if val is None:
# 'required: false' property that wasn't there, or a property type
@ -629,8 +630,21 @@ class Device:
self.props[name] = prop
def _prop_val(self, name, prop_type, required):
def _prop_val(self, name, prop_type, required, default):
# _init_prop() helper for getting the property's value
#
# name:
# Property name from binding
#
# prop_type:
# Property type from binding (a string like "int")
#
# optional:
# True if the property isn't required to exist
#
# default:
# Default value to use when the property doesn't exist, or None if
# the binding doesn't give a default value
node = self._node
prop = node.props.get(name)
@ -650,6 +664,15 @@ class Device:
"does not appear in {!r}".format(
name, self.binding_path, node))
if default is not None:
# YAML doesn't have a native format for byte arrays. We need to
# convert those from an array like [0x12, 0x34, ...]. The
# format has already been checked in
# _check_prop_type_and_default().
if prop_type == "uint8-array":
return bytes(default)
return default
return None
if prop_type == "int":
@ -683,14 +706,13 @@ class Device:
.format(name, node.path, node.dt.filename, prop))
return None
if prop_type == "compound":
# Dummy type for properties like that don't fit any of the patterns
# above, so that we can require all entries in 'properties:' to
# have a 'type: ...'. No Property object is created for it.
return None
_err("'{}' in 'properties:' in {} has unknown type '{}'"
.format(name, self.binding_path, prop_type))
# prop_type == "compound". We have already checked that the 'type:'
# value is valid, in _check_binding().
#
# 'compound' is a dummy type for properties that don't fit any of the
# patterns above, so that we can require all entries in 'properties:'
# to have a 'type: ...'. No Property object is created for it.
return None
def _init_regs(self):
# Initializes self.regs
@ -1315,7 +1337,6 @@ def _check_binding(binding, binding_path):
if prop not in binding:
_err("missing '{}' property in {}".format(prop, binding_path))
for prop in "title", "description":
if not isinstance(binding[prop], str) or not binding[prop]:
_err("missing, malformed, or empty '{}' in {}"
.format(prop, binding_path))
@ -1356,7 +1377,7 @@ def _check_binding_properties(binding, binding_path):
return
ok_prop_keys = {"description", "type", "required", "category",
"constraint", "enum", "const"}
"constraint", "enum", "const", "default"}
for prop_name, options in binding["properties"].items():
for key in options:
@ -1373,6 +1394,11 @@ def _check_binding_properties(binding, binding_path):
key, prop_name, binding_path,
", ".join(ok_prop_keys)))
_check_prop_type_and_default(
prop_name, options.get("type"),
options.get("required") or options.get("category") == "required",
options.get("default"), binding_path)
if "required" in options and not isinstance(options["required"], bool):
_err("malformed 'required:' setting '{}' for '{}' in 'properties' "
"in {}, expected true/false"
@ -1392,6 +1418,67 @@ def _check_binding_properties(binding, binding_path):
.format(binding_path, prop_name))
def _check_prop_type_and_default(prop_name, prop_type, required, default,
binding_path):
# _check_binding() helper. Checks 'type:' and 'default:' for the property
# named 'prop_name'
if prop_type is None:
_err("missing 'type:' for '{}' in 'properties' in {}"
.format(prop_name, binding_path))
ok_types = {"boolean", "int", "array", "uint8-array", "string",
"string-array", "phandle", "phandles", "phandle-array",
"compound"}
if prop_type not in ok_types:
_err("'{}' in 'properties:' in {} has unknown type '{}', expected one "
"of {}".format(prop_name, binding_path, prop_type,
", ".join(ok_types)))
# Check default
if default is None:
return
if required:
_err("'default:' for '{}' in 'properties:' in {} is meaningless in "
"combination with 'required: true'"
.format(prop_name, binding_path))
if prop_type in {"boolean", "compound", "phandle", "phandles",
"phandle-array"}:
_err("'default:' can't be combined with 'type: {}' for '{}' in "
"'properties:' in {}".format(prop_type, prop_name, binding_path))
def ok_default():
# Returns True if 'default' is an okay default for the property's type
if prop_type == "int" and isinstance(default, int) or \
prop_type == "string" and isinstance(default, str):
return True
# array, uint8-array, or string-array
if not isinstance(default, list):
return False
if prop_type == "array" and \
all(isinstance(val, int) for val in default):
return True
if prop_type == "uint8-array" and \
all(isinstance(val, int) and 0 <= val <= 255 for val in default):
return True
# string-array
return all(isinstance(val, str) for val in default)
if not ok_default():
_err("'default: {}' is invalid for '{}' in 'properties:' in {}, which "
"has type {}".format(default, prop_name, binding_path, prop_type))
def _translate(addr, node):
# Recursively translates 'addr' on 'node' to the address space(s) of its
# parent(s), by looking at 'ranges' properties. Returns the translated

View file

@ -0,0 +1,37 @@
# SPDX-License-Identifier: BSD-3-Clause
title: Property default value test
description: Property default value test
compatible: "defaults"
properties:
int:
type: int
required: false
default: 123
array:
type: array
required: false
default: [1, 2, 3]
uint8-array:
type: uint8-array
required: false
default: [0x89, 0xAB, 0xCD]
string:
type: string
required: false
default: "hello"
string-array:
type: string-array
required: false
default: ["hello", "there"]
default-not-used:
type: int
required: false
default: 123

View file

@ -311,6 +311,16 @@
};
};
//
// For testing Device.props with 'default:' values in binding
//
defaults {
compatible = "defaults";
// Should override the 'default:' in the binding
default-not-used = <234>;
};
//
// Parent with 'sub-node:' in binding
//

View file

@ -122,6 +122,13 @@ def run():
verify_streq(edt.get_dev("/props").props,
r"{'nonexistent-boolean': <Property, name: nonexistent-boolean, type: boolean, value: False>, 'existent-boolean': <Property, name: existent-boolean, type: boolean, value: True>, 'int': <Property, name: int, type: int, value: 1>, 'array': <Property, name: array, type: array, value: [1, 2, 3]>, 'uint8-array': <Property, name: uint8-array, type: uint8-array, value: b'\x124'>, 'string': <Property, name: string, type: string, value: 'foo'>, 'string-array': <Property, name: string-array, type: string-array, value: ['foo', 'bar', 'baz']>, 'phandle-ref': <Property, name: phandle-ref, type: phandle, value: <Device /props/node in 'test.dts', no binding>>, 'phandle-refs': <Property, name: phandle-refs, type: phandles, value: [<Device /props/node in 'test.dts', no binding>, <Device /props/node2 in 'test.dts', no binding>]>}")
#
# Test property default values given in bindings
#
verify_streq(edt.get_dev("/defaults").props,
r"{'int': <Property, name: int, type: int, value: 123>, 'array': <Property, name: array, type: array, value: [1, 2, 3]>, 'uint8-array': <Property, name: uint8-array, type: uint8-array, value: b'\x89\xab\xcd'>, 'string': <Property, name: string, type: string, value: 'hello'>, 'string-array': <Property, name: string-array, type: string-array, value: ['hello', 'there']>, 'default-not-used': <Property, name: default-not-used, type: int, value: 234>}")
#
# Test having multiple directories with bindings, with a different .dts file
#