Browse Source

Devicetree: Devicetree Bindings: Support enums for array like dt props

It is currently impossible to use enum with any array like type (i.e.
string-array and array, these are the only ones that make sense) in the
devicetree and dt-bindings.
However, there is no such remark in the dt-bindings section of the docs.
Since this is a feature that comes in very handy and is implemented
fairly easily, I adjusted the scripts for this.

It is now possible to do something like this.
```yaml
compatible = "enums"

properties:
  array-enum:
    type: string-array
    enum:
      - bar
      - foo
      - baz
      - zoo
```
```dts
/ {
	enums {
		compatible = "enums";
		array-enum = "foo", "bar";
	};
};
```

Signed-off-by: Joel Hirsbrunner <jhirsbrunner@baumer.com>
pull/79634/head
Joel Hirsbrunner 9 months ago committed by Anas Nashif
parent
commit
8b02bc9392
  1. 105
      scripts/dts/gen_defines.py
  2. 44
      scripts/dts/python-devicetree/src/devicetree/edtlib.py

105
scripts/dts/gen_defines.py

@ -588,12 +588,7 @@ def write_vanilla_props(node: edtlib.Node) -> None: @@ -588,12 +588,7 @@ def write_vanilla_props(node: edtlib.Node) -> None:
macro2val[macro] = val
if prop.spec.type == 'string':
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UNQUOTED
macro2val[macro + "_STRING_UNQUOTED"] = escape_unquoted(prop.val)
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_TOKEN
macro2val[macro + "_STRING_TOKEN"] = prop.val_as_token
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UPPER_TOKEN
macro2val[macro + "_STRING_UPPER_TOKEN"] = prop.val_as_token.upper()
macro2val.update(string_macros(macro, prop.val))
# DT_N_<node-id>_P_<prop-id>_IDX_0:
# DT_N_<node-id>_P_<prop-id>_IDX_0_EXISTS:
# Allows treating the string like a degenerate case of a
@ -601,45 +596,13 @@ def write_vanilla_props(node: edtlib.Node) -> None: @@ -601,45 +596,13 @@ def write_vanilla_props(node: edtlib.Node) -> None:
macro2val[macro + "_IDX_0"] = quote_str(prop.val)
macro2val[macro + "_IDX_0_EXISTS"] = 1
if prop.enum_index is not None:
# DT_N_<node-id>_P_<prop-id>_ENUM_IDX
macro2val[macro + "_ENUM_IDX"] = prop.enum_index
spec = prop.spec
if spec.enum_tokenizable:
as_token = prop.val_as_token
# DT_N_<node-id>_P_<prop-id>_ENUM_VAL_<val>_EXISTS 1
macro2val[macro + f"_ENUM_VAL_{as_token}_EXISTS"] = 1
# DT_N_<node-id>_P_<prop-id>_ENUM_TOKEN
macro2val[macro + "_ENUM_TOKEN"] = as_token
if spec.enum_upper_tokenizable:
# DT_N_<node-id>_P_<prop-id>_ENUM_UPPER_TOKEN
macro2val[macro + "_ENUM_UPPER_TOKEN"] = as_token.upper()
else:
# DT_N_<node-id>_P_<prop-id>_ENUM_VAL_<val>_EXISTS 1
macro2val[macro + f"_ENUM_VAL_{prop.val}_EXISTS"] = 1
if prop.enum_indices is not None:
macro2val.update(enum_macros(prop, macro))
if "phandle" in prop.type:
macro2val.update(phandle_macros(prop, macro))
elif "array" in prop.type:
for i, subval in enumerate(prop.val):
# DT_N_<node-id>_P_<prop-id>_IDX_<i>
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
if isinstance(subval, str):
macro2val[macro + f"_IDX_{i}"] = quote_str(subval)
subval_as_token = edtlib.str_as_token(subval)
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UNQUOTED
macro2val[macro + f"_IDX_{i}_STRING_UNQUOTED"] = escape_unquoted(subval)
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_TOKEN
macro2val[macro + f"_IDX_{i}_STRING_TOKEN"] = subval_as_token
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UPPER_TOKEN
macro2val[macro + f"_IDX_{i}_STRING_UPPER_TOKEN"] = subval_as_token.upper()
else:
macro2val[macro + f"_IDX_{i}"] = subval
macro2val[macro + f"_IDX_{i}_EXISTS"] = 1
macro2val.update(array_macros(prop, macro))
plen = prop_len(prop)
if plen is not None:
@ -681,6 +644,66 @@ def write_vanilla_props(node: edtlib.Node) -> None: @@ -681,6 +644,66 @@ def write_vanilla_props(node: edtlib.Node) -> None:
out_comment("(No generic property macros)")
def string_macros(macro: str, val: str):
# Returns a dict of macros for a string 'val'.
# The 'macro' argument is the N_<node-id>_P_<prop-id>... part.
as_token = edtlib.str_as_token(val)
return {
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UNQUOTED
f"{macro}_STRING_UNQUOTED": escape_unquoted(val),
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_TOKEN
f"{macro}_STRING_TOKEN": as_token,
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_UPPER_TOKEN
f"{macro}_STRING_UPPER_TOKEN": as_token.upper()}
def enum_macros(prop: edtlib.Property, macro: str):
# Returns a dict of macros for property 'prop' with a defined enum in their dt-binding.
# The 'macro' argument is the N_<node-id>_P_<prop-id> part.
spec = prop.spec
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_IDX
ret = {f"{macro}_IDX_{i}_ENUM_IDX": index for i, index in enumerate(prop.enum_indices)}
val = prop.val_as_tokens if spec.enum_tokenizable else (prop.val if isinstance(prop.val, list) else [prop.val])
for i, subval in enumerate(val):
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
ret[macro + f"_IDX_{i}_EXISTS"] = 1
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_VAL_<val>_EXISTS 1
ret[macro + f"_IDX_{i}_ENUM_VAL_{subval}_EXISTS"] = 1
if not spec.enum_tokenizable:
continue
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_TOKEN
ret[macro + f"_IDX_{i}_ENUM_TOKEN"] = subval
if spec.enum_upper_tokenizable:
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_ENUM_UPPER_TOKEN
ret[macro + f"_IDX_{i}_ENUM_UPPER_TOKEN"] = subval.upper()
return ret
def array_macros(prop: edtlib.Property, macro: str):
# Returns a dict of macros for array property 'prop'.
# The 'macro' argument is the N_<node-id>_P_<prop-id> part.
ret = {}
for i, subval in enumerate(prop.val):
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_EXISTS
ret[macro + f"_IDX_{i}_EXISTS"] = 1
# DT_N_<node-id>_P_<prop-id>_IDX_<i>
if isinstance(subval, str):
ret[macro + f"_IDX_{i}"] = quote_str(subval)
# DT_N_<node-id>_P_<prop-id>_IDX_<i>_STRING_...
ret.update(string_macros(macro + f"_IDX_{i}", subval))
else:
ret[macro + f"_IDX_{i}"] = subval
return ret
def write_dep_info(node: edtlib.Node) -> None:
# Write dependency-related information about the node.

44
scripts/dts/python-devicetree/src/devicetree/edtlib.py

@ -601,10 +601,11 @@ class PropertySpec: @@ -601,10 +601,11 @@ class PropertySpec:
True if enum is not None and all the values in it are tokenizable;
False otherwise.
A property must have string type and an "enum:" in its binding to be
tokenizable. Additionally, the "enum:" values must be unique after
converting all non-alphanumeric characters to underscores (so "foo bar"
and "foo_bar" in the same "enum:" would not be tokenizable).
A property must have string or string-array type and an "enum:" in its
binding to be tokenizable. Additionally, the "enum:" values must be
unique after converting all non-alphanumeric characters to underscores
(so "foo bar" and "foo_bar" in the same "enum:" would not be
tokenizable).
enum_upper_tokenizable:
Like 'enum_tokenizable', with the additional restriction that the
@ -659,7 +660,7 @@ class PropertySpec: @@ -659,7 +660,7 @@ class PropertySpec:
def enum_tokenizable(self) -> bool:
"See the class docstring"
if not hasattr(self, '_enum_tokenizable'):
if self.type != 'string' or self.enum is None:
if self.type not in {'string', 'string-array'} or self.enum is None:
self._enum_tokenizable = False
else:
# Saving _as_tokens here lets us reuse it in
@ -764,14 +765,14 @@ class Property: @@ -764,14 +765,14 @@ class Property:
type:
Convenience for spec.type.
val_as_token:
The value of the property as a token, i.e. with non-alphanumeric
val_as_tokens:
The value of the property as a list of tokens, i.e. with non-alphanumeric
characters replaced with underscores. This is only safe to access
if 'spec.enum_tokenizable' returns True.
enum_index:
The index of 'val' in 'spec.enum' (which comes from the 'enum:' list
in the binding), or None if spec.enum is None.
enum_indices:
A list of indices of 'val' in 'spec.enum' (which comes from the 'enum:'
list in the binding), or None if spec.enum is None.
"""
spec: PropertySpec
@ -794,16 +795,20 @@ class Property: @@ -794,16 +795,20 @@ class Property:
return self.spec.type
@property
def val_as_token(self) -> str:
def val_as_tokens(self) -> List[str]:
"See the class docstring"
assert isinstance(self.val, str)
return str_as_token(self.val)
ret = []
for subval in self.val if isinstance(self.val, list) else [self.val]:
assert isinstance(subval, str)
ret.append(str_as_token(subval))
return ret
@property
def enum_index(self) -> Optional[int]:
def enum_indices(self) -> Optional[List[int]]:
"See the class docstring"
enum = self.spec.enum
return enum.index(self.val) if enum else None
val = self.val if isinstance(self.val, list) else [self.val]
return [enum.index(subval) for subval in val] if enum else None
@dataclass
@ -1519,10 +1524,11 @@ class Node: @@ -1519,10 +1524,11 @@ class Node:
return
enum = prop_spec.enum
if enum and val not in enum:
_err(f"value of property '{name}' on {self.path} in "
f"{self.edt.dts_path} ({val!r}) is not in 'enum' list in "
f"{self.binding_path} ({enum!r})")
for subval in val if isinstance(val, list) else [val]:
if enum and subval not in enum:
_err(f"value of property '{name}' on {self.path} in "
f"{self.edt.dts_path} ({subval!r}) is not in 'enum' list in "
f"{self.binding_path} ({enum!r})")
const = prop_spec.const
if const is not None and val != const:

Loading…
Cancel
Save