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:
parent
c333e8e8a4
commit
ff1f75293e
5 changed files with 191 additions and 12 deletions
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
37
scripts/dts/test-bindings/defaults.yaml
Normal file
37
scripts/dts/test-bindings/defaults.yaml
Normal 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
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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
|
||||
#
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue