diff --git a/doc/guides/dts/bindings.rst b/doc/guides/dts/bindings.rst index 5dcb6df5775cc..94e06c6079ca7 100644 --- a/doc/guides/dts/bindings.rst +++ b/doc/guides/dts/bindings.rst @@ -11,8 +11,9 @@ particular devicetree are useful to :ref:`device drivers ` or *Devicetree bindings* provide the other half of this information. Zephyr devicetree bindings are YAML files in a custom format (Zephyr does not use the -dt-schema tools used by the Linux kernel). The build system uses bindings -when generating code for :ref:`dt-from-c`. +dt-schema tools used by the Linux kernel). With one exception in +:ref:`dt-inferred-bindings` the build system uses bindings when generating +code for :ref:`dt-from-c`. .. _dt-binding-compat: @@ -73,3 +74,32 @@ Below is a template that shows the Zephyr bindings file syntax. It is stored in .. literalinclude:: ../../../dts/binding-template.yaml :language: yaml + +.. _dt-inferred-bindings: + +Inferred bindings +***************** + +For sample code and applications it can be inconvenient to define a devicetree +binding when only a few simple properties are needed, such as the identify of +a GPIO for an application task. The devicetree syntax allows inference of a +binding for properties based on the value observed. This inference is +supported only for the ``/zephyr,user`` node. The properties referenced can +be accessed through the standard devicetree macros, +e.g. ``DT_PROP(DT_PATH(zephyr_user), bytes)``. + +.. code-block:: DTS + + / { + zephyr,user { + boolean; + bytes = [81 82 83]; + number = <23>; + numbers = <1>, <2>, <3>; + string = "text"; + strings = "a", "b", "c"; + handle = <&gpio0>; + handles = <&gpio0>, <&gpio1>; + signal-gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>; + }; + }; diff --git a/scripts/dts/edtlib.py b/scripts/dts/edtlib.py index fc6c4d51c3a24..26c7a123e413a 100644 --- a/scripts/dts/edtlib.py +++ b/scripts/dts/edtlib.py @@ -83,8 +83,9 @@ except ImportError: from yaml import Loader -from dtlib import DT, DTError, to_num, to_nums, TYPE_EMPTY, TYPE_NUMS, \ - TYPE_PHANDLE, TYPE_PHANDLES_AND_NUMS +from dtlib import DT, DTError, to_num, to_nums, TYPE_EMPTY, TYPE_BYTES, \ + TYPE_NUM, TYPE_NUMS, TYPE_STRING, TYPE_STRINGS, \ + TYPE_PHANDLE, TYPE_PHANDLES, TYPE_PHANDLES_AND_NUMS from grutils import Graph @@ -157,9 +158,9 @@ class EDT: def __init__(self, dts, bindings_dirs, warn_file=None, warn_reg_unit_address_mismatch=True, default_prop_types=True, - support_fixed_partitions_on_any_bus=True): - """ - EDT constructor. This is the top-level entry point to the library. + support_fixed_partitions_on_any_bus=True, + infer_binding_for_paths=None): + """EDT constructor. This is the top-level entry point to the library. dts: Path to devicetree .dts file @@ -184,6 +185,12 @@ def __init__(self, dts, bindings_dirs, warn_file=None, If True, set the Node.bus for 'fixed-partitions' compatible nodes to None. This allows 'fixed-partitions' binding to match regardless of the bus the 'fixed-partition' is under. + + infer_binding_for_paths (default: None): + An iterable of devicetree paths identifying nodes for which bindings + should be inferred from the node content. (Child nodes are not + processed.) Pass none if no nodes should support inferred bindings. + """ # Do this indirection with None in case sys.stderr is # deliberately overridden. We'll only hold on to this file @@ -193,6 +200,7 @@ def __init__(self, dts, bindings_dirs, warn_file=None, self._warn_reg_unit_address_mismatch = warn_reg_unit_address_mismatch self._default_prop_types = default_prop_types self._fixed_partitions_no_bus = support_fixed_partitions_on_any_bus + self._infer_binding_for_paths = set(infer_binding_for_paths or []) self.dts_path = dts self.bindings_dirs = bindings_dirs @@ -957,6 +965,10 @@ def _init_binding(self): # initialized, which is guaranteed by going through the nodes in # node_iter() order. + if self.path in self.edt._infer_binding_for_paths: + self._binding_from_properties() + return + if self.compats: on_bus = self.on_bus @@ -984,6 +996,43 @@ def _init_binding(self): # No binding found self._binding = self.binding_path = self.matching_compat = None + def _binding_from_properties(self): + # Returns a binding synthesized from the properties in the node. + + if self.compats: + _err(f"compatible in node with inferred binding: {self.path}") + + self._binding = OrderedDict() + self.matching_compat = self.path.split('/')[-1] + self.compats = [self.matching_compat] + self.binding_path = None + + properties = OrderedDict() + self._binding["properties"] = properties + for name, prop in self._node.props.items(): + pp = OrderedDict() + properties[name] = pp + if prop.type == TYPE_EMPTY: + pp["type"] = "boolean" + elif prop.type == TYPE_BYTES: + pp["type"] = "uint8-array" + elif prop.type == TYPE_NUM: + pp["type"] = "int" + elif prop.type == TYPE_NUMS: + pp["type"] = "array" + elif prop.type == TYPE_STRING: + pp["type"] = "string" + elif prop.type == TYPE_STRINGS: + pp["type"] = "string-array" + elif prop.type == TYPE_PHANDLE: + pp["type"] = "phandle" + elif prop.type == TYPE_PHANDLES: + pp["type"] = "phandles" + elif prop.type == TYPE_PHANDLES_AND_NUMS: + pp["type"] = "phandle-array" + else: + _err(f"cannot infer binding from property: {prop}") + 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 diff --git a/scripts/dts/gen_defines.py b/scripts/dts/gen_defines.py index be725e4e958d2..3bf91f39dc581 100755 --- a/scripts/dts/gen_defines.py +++ b/scripts/dts/gen_defines.py @@ -39,7 +39,8 @@ def main(): # Suppress this warning if it's suppressed in dtc warn_reg_unit_address_mismatch= "-Wno-simple_bus_reg" not in args.dtc_flags, - default_prop_types=True) + default_prop_types=True, + infer_binding_for_paths=["/zephyr,user"]) except edtlib.EDTError as e: sys.exit(f"devicetree error: {e}") @@ -178,10 +179,16 @@ def write_node_comment(node): """ if node.matching_compat: - s += f""" + if node.binding_path: + s += f""" Binding (compatible = {node.matching_compat}): {relativize(node.binding_path)} """ + else: + s += f""" +Binding (compatible = {node.matching_compat}): + No yaml (bindings inferred from properties) +""" s += f"\nDependency Ordinal: {node.dep_ordinal}\n" diff --git a/scripts/dts/gen_legacy_defines.py b/scripts/dts/gen_legacy_defines.py index a358334e1f01d..e4b45696eda30 100755 --- a/scripts/dts/gen_legacy_defines.py +++ b/scripts/dts/gen_legacy_defines.py @@ -121,9 +121,15 @@ def write_node_comment(node): {node.path} """ if node.matching_compat: - s += f""" + if node.binding_path: + s += f""" Binding (compatible = {node.matching_compat}): {relativize(node.binding_path)} +""" + else: + s += f""" +Binding (compatible = {node.matching_compat}): + No yaml (bindings inferred from properties) """ else: s += "\nNo matching binding.\n" diff --git a/scripts/dts/test.dts b/scripts/dts/test.dts index 695f925cfe2a7..64330d13dd468 100644 --- a/scripts/dts/test.dts +++ b/scripts/dts/test.dts @@ -361,6 +361,22 @@ }; }; + // + // zephyr,user binding inference + // + + zephyr,user { + boolean; + bytes = [81 82 83]; + number = <23>; + numbers = <1>, <2>, <3>; + string = "text"; + strings = "a", "b", "c"; + handle = <&{/props/ctrl-1}>; + phandles = <&{/props/ctrl-1}>, <&{/props/ctrl-2}>; + phandle-array-foos = <&{/props/ctrl-2} 1 2>; + }; + // // For testing that neither 'include: [foo.yaml, bar.yaml]' nor // 'include: [bar.yaml, foo.yaml]' causes errors when one of the files diff --git a/scripts/dts/testedtlib.py b/scripts/dts/testedtlib.py index 1ef332e401cd1..88236ae73ab79 100755 --- a/scripts/dts/testedtlib.py +++ b/scripts/dts/testedtlib.py @@ -215,6 +215,18 @@ def run(): verify_streq(edt.get_node("/defaults").props, r"OrderedDict([('int', ), ('array', ), ('uint8-array', ), ('string', ), ('string-array', ), ('default-not-used', )])") + # + # Test binding inference + # + + verify_streq(edt.get_node("/zephyr,user").props, r"OrderedDict()") + + edt = edtlib.EDT("test.dts", ["test-bindings"], warnings, + infer_binding_for_paths=["/zephyr,user"]) + + verify_streq(edt.get_node("/zephyr,user").props, + r"OrderedDict([('boolean', ), ('bytes', ), ('number', ), ('numbers', ), ('string', ), ('strings', ), ('handle', >), ('phandles', , ]>), ('phandle-array-foos', , data: OrderedDict([('one', 1), ('two', 2)])>]>)])") + # # Test having multiple directories with bindings, with a different .dts file #