Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1 | # Copyright (c) 2019 Nordic Semiconductor ASA |
| 2 | # Copyright (c) 2019 Linaro Limited |
| 3 | # SPDX-License-Identifier: BSD-3-Clause |
| 4 | |
| 5 | # Tip: You can view just the documentation with 'pydoc3 edtlib' |
| 6 | |
| 7 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 8 | Library for working with devicetrees at a higher level compared to dtlib. Like |
| 9 | dtlib, this library presents a tree of devicetree nodes, but the nodes are |
| 10 | augmented with information from bindings and include some interpretation of |
Martí Bolívar | 8640a8d | 2020-10-12 11:33:01 -0700 | [diff] [blame] | 11 | properties. Some of this interpretation is based on conventions established |
| 12 | by the Linux kernel, so the Documentation/devicetree/bindings in the Linux |
| 13 | source code is sometimes good reference material. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 14 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 15 | Bindings are YAML files that describe devicetree nodes. Devicetree |
Martí Bolívar | 8640a8d | 2020-10-12 11:33:01 -0700 | [diff] [blame] | 16 | nodes are usually mapped to bindings via their 'compatible = "..."' property, |
| 17 | but a binding can also come from a 'child-binding:' key in the binding for the |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 18 | parent devicetree node. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 19 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 20 | Each devicetree node (dtlib.Node) gets a corresponding edtlib.Node instance, |
| 21 | which has all the information related to the node. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 22 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 23 | The top-level entry points for the library are the EDT and Binding classes. |
Martí Bolívar | 2c19ccc | 2020-10-19 21:42:53 -0700 | [diff] [blame] | 24 | See their constructor docstrings for details. There is also a |
| 25 | bindings_from_paths() helper function. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 26 | """ |
| 27 | |
Martí Bolívar | a8612f7 | 2020-09-29 17:04:34 -0700 | [diff] [blame] | 28 | # NOTE: testedtlib.py is the test suite for this library. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 29 | |
| 30 | # Implementation notes |
| 31 | # -------------------- |
| 32 | # |
| 33 | # A '_' prefix on an identifier in Python is a convention for marking it private. |
| 34 | # Please do not access private things. Instead, think of what API you need, and |
| 35 | # add it. |
| 36 | # |
Ulf Magnusson | 35166c4 | 2020-02-14 03:27:59 +0100 | [diff] [blame] | 37 | # This module is not meant to have any global state. It should be possible to |
| 38 | # create several EDT objects with independent binding paths and flags. If you |
| 39 | # need to add a configuration parameter or the like, store it in the EDT |
| 40 | # instance, and initialize it e.g. with a constructor argument. |
| 41 | # |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 42 | # This library is layered on top of dtlib, and is not meant to expose it to |
| 43 | # clients. This keeps the header generation script simple. |
| 44 | # |
| 45 | # General biased advice: |
| 46 | # |
| 47 | # - Consider using @property for APIs that don't need parameters. It makes |
| 48 | # functions look like attributes, which is less awkward in clients, and makes |
| 49 | # it easy to switch back and forth between variables and functions. |
| 50 | # |
| 51 | # - Think about the data type of the thing you're exposing. Exposing something |
| 52 | # as e.g. a list or a dictionary is often nicer and more flexible than adding |
| 53 | # a function. |
| 54 | # |
| 55 | # - Avoid get_*() prefixes on functions. Name them after the thing they return |
| 56 | # instead. This often makes the code read more naturally in callers. |
| 57 | # |
| 58 | # Also, consider using @property instead of get_*(). |
| 59 | # |
| 60 | # - Don't expose dtlib stuff directly. |
| 61 | # |
| 62 | # - Add documentation for any new APIs you add. |
| 63 | # |
| 64 | # The convention here is that docstrings (quoted strings) are used for public |
| 65 | # APIs, and "doc comments" for internal functions. |
| 66 | # |
| 67 | # @properties are documented in the class docstring, as if they were |
| 68 | # variables. See the existing @properties for a template. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 69 | |
Ulf Magnusson | 88db84b | 2020-01-28 04:46:12 +0100 | [diff] [blame] | 70 | from collections import OrderedDict, defaultdict |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 71 | import logging |
Ulf Magnusson | d55ed93 | 2019-11-12 18:37:15 +0100 | [diff] [blame] | 72 | import os |
| 73 | import re |
Ulf Magnusson | d55ed93 | 2019-11-12 18:37:15 +0100 | [diff] [blame] | 74 | |
| 75 | import yaml |
| 76 | try: |
| 77 | # Use the C LibYAML parser if available, rather than the Python parser. |
| 78 | # This makes e.g. gen_defines.py more than twice as fast. |
| 79 | from yaml import CLoader as Loader |
| 80 | except ImportError: |
| 81 | from yaml import Loader |
| 82 | |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 83 | from dtlib import DT, DTError, to_num, to_nums, TYPE_EMPTY, TYPE_BYTES, \ |
| 84 | TYPE_NUM, TYPE_NUMS, TYPE_STRING, TYPE_STRINGS, \ |
| 85 | TYPE_PHANDLE, TYPE_PHANDLES, TYPE_PHANDLES_AND_NUMS |
Ulf Magnusson | d55ed93 | 2019-11-12 18:37:15 +0100 | [diff] [blame] | 86 | from grutils import Graph |
| 87 | |
Andrei Gansari | e87fec1 | 2019-11-20 16:31:55 +0200 | [diff] [blame] | 88 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 89 | # |
| 90 | # Public classes |
| 91 | # |
| 92 | |
| 93 | |
| 94 | class EDT: |
| 95 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 96 | Represents a devicetree augmented with information from bindings. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 97 | |
| 98 | These attributes are available on EDT objects: |
| 99 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 100 | nodes: |
| 101 | A list of Node objects for the nodes that appear in the devicetree |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 102 | |
Martí Bolívar | eac56e4 | 2020-05-06 11:22:30 -0700 | [diff] [blame] | 103 | compat2nodes: |
| 104 | A collections.defaultdict that maps each 'compatible' string that appears |
| 105 | on some Node to a list of Nodes with that compatible. |
| 106 | |
| 107 | compat2okay: |
| 108 | Like compat2nodes, but just for nodes with status 'okay'. |
| 109 | |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 110 | label2node: |
| 111 | A collections.OrderedDict that maps a node label to the node with |
| 112 | that label. |
| 113 | |
Peter Bigot | 44394e3 | 2020-08-24 13:35:25 -0700 | [diff] [blame] | 114 | dep_ord2node: |
| 115 | A collections.OrderedDict that maps an ordinal to the node with |
| 116 | that dependency ordinal. |
| 117 | |
Martí Bolívar | 0de9a08 | 2020-03-11 16:10:16 -0700 | [diff] [blame] | 118 | chosen_nodes: |
| 119 | A collections.OrderedDict that maps the properties defined on the |
| 120 | devicetree's /chosen node to their values. 'chosen' is indexed by |
| 121 | property name (a string), and values are converted to Node objects. |
| 122 | Note that properties of the /chosen node which can't be converted |
| 123 | to a Node are not included in the value. |
Ulf Magnusson | 88db84b | 2020-01-28 04:46:12 +0100 | [diff] [blame] | 124 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 125 | dts_path: |
| 126 | The .dts path passed to __init__() |
| 127 | |
Ulf Magnusson | a758af4 | 2020-01-29 06:25:20 +0100 | [diff] [blame] | 128 | dts_source: |
| 129 | The final DTS source code of the loaded devicetree after merging nodes |
| 130 | and processing /delete-node/ and /delete-property/, as a string |
| 131 | |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 132 | bindings_dirs: |
| 133 | The bindings directory paths passed to __init__() |
Martí Bolívar | e76b720 | 2020-07-01 10:37:34 -0700 | [diff] [blame] | 134 | |
Martí Bolívar | b6db201 | 2020-08-24 13:33:53 -0700 | [diff] [blame] | 135 | scc_order: |
| 136 | A list of lists of Nodes. All elements of each list |
| 137 | depend on each other, and the Nodes in any list do not depend |
| 138 | on any Node in a subsequent list. Each list defines a Strongly |
| 139 | Connected Component (SCC) of the graph. |
| 140 | |
| 141 | For an acyclic graph each list will be a singleton. Cycles |
| 142 | will be represented by lists with multiple nodes. Cycles are |
| 143 | not expected to be present in devicetree graphs. |
| 144 | |
Martí Bolívar | e76b720 | 2020-07-01 10:37:34 -0700 | [diff] [blame] | 145 | The standard library's pickle module can be used to marshal and |
| 146 | unmarshal EDT objects. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 147 | """ |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 148 | def __init__(self, dts, bindings_dirs, |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 149 | warn_reg_unit_address_mismatch=True, |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 150 | default_prop_types=True, |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 151 | support_fixed_partitions_on_any_bus=True, |
| 152 | infer_binding_for_paths=None): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 153 | """EDT constructor. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 154 | |
| 155 | dts: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 156 | Path to devicetree .dts file |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 157 | |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 158 | bindings_dirs: |
| 159 | List of paths to directories containing bindings, in YAML format. |
| 160 | These directories are recursively searched for .yaml files. |
Ulf Magnusson | 7c26c17 | 2019-09-28 15:03:45 +0200 | [diff] [blame] | 161 | |
Ulf Magnusson | 35166c4 | 2020-02-14 03:27:59 +0100 | [diff] [blame] | 162 | warn_reg_unit_address_mismatch (default: True): |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 163 | If True, a warning is logged if a node has a 'reg' property where |
Ulf Magnusson | 35166c4 | 2020-02-14 03:27:59 +0100 | [diff] [blame] | 164 | the address of the first entry does not match the unit address of the |
| 165 | node |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 166 | |
| 167 | default_prop_types (default: True): |
| 168 | If True, default property types will be used when a node has no |
| 169 | bindings. |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 170 | |
| 171 | support_fixed_partitions_on_any_bus (default True): |
| 172 | If True, set the Node.bus for 'fixed-partitions' compatible nodes |
| 173 | to None. This allows 'fixed-partitions' binding to match regardless |
| 174 | of the bus the 'fixed-partition' is under. |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 175 | |
| 176 | infer_binding_for_paths (default: None): |
| 177 | An iterable of devicetree paths identifying nodes for which bindings |
| 178 | should be inferred from the node content. (Child nodes are not |
| 179 | processed.) Pass none if no nodes should support inferred bindings. |
| 180 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 181 | """ |
Ulf Magnusson | 35166c4 | 2020-02-14 03:27:59 +0100 | [diff] [blame] | 182 | self._warn_reg_unit_address_mismatch = warn_reg_unit_address_mismatch |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 183 | self._default_prop_types = default_prop_types |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 184 | self._fixed_partitions_no_bus = support_fixed_partitions_on_any_bus |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 185 | self._infer_binding_for_paths = set(infer_binding_for_paths or []) |
Ulf Magnusson | 35166c4 | 2020-02-14 03:27:59 +0100 | [diff] [blame] | 186 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 187 | self.dts_path = dts |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 188 | self.bindings_dirs = bindings_dirs |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 189 | |
| 190 | self._dt = DT(dts) |
Ulf Magnusson | acf276f | 2019-08-07 19:33:45 +0200 | [diff] [blame] | 191 | _check_dt(self._dt) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 192 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 193 | self._init_compat2binding() |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 194 | self._init_nodes() |
Martí Bolívar | b6db201 | 2020-08-24 13:33:53 -0700 | [diff] [blame] | 195 | self._init_graph() |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 196 | self._init_luts() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 197 | |
Martí Bolívar | f673e20 | 2020-12-08 12:55:44 -0800 | [diff] [blame] | 198 | self._check() |
| 199 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 200 | def get_node(self, path): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 201 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 202 | Returns the Node at the DT path or alias 'path'. Raises EDTError if the |
| 203 | path or alias doesn't exist. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 204 | """ |
| 205 | try: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 206 | return self._node2enode[self._dt.get_node(path)] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 207 | except DTError as e: |
| 208 | _err(e) |
| 209 | |
Martí Bolívar | 0de9a08 | 2020-03-11 16:10:16 -0700 | [diff] [blame] | 210 | @property |
| 211 | def chosen_nodes(self): |
| 212 | ret = OrderedDict() |
| 213 | |
| 214 | try: |
| 215 | chosen = self._dt.get_node("/chosen") |
| 216 | except DTError: |
| 217 | return ret |
| 218 | |
| 219 | for name, prop in chosen.props.items(): |
| 220 | try: |
| 221 | node = prop.to_path() |
| 222 | except DTError: |
| 223 | # DTS value is not phandle or string, or path doesn't exist |
| 224 | continue |
| 225 | |
| 226 | ret[name] = self._node2enode[node] |
| 227 | |
| 228 | return ret |
| 229 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 230 | def chosen_node(self, name): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 231 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 232 | Returns the Node pointed at by the property named 'name' in /chosen, or |
| 233 | None if the property is missing |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 234 | """ |
Martí Bolívar | 0de9a08 | 2020-03-11 16:10:16 -0700 | [diff] [blame] | 235 | return self.chosen_nodes.get(name) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 236 | |
Ulf Magnusson | a758af4 | 2020-01-29 06:25:20 +0100 | [diff] [blame] | 237 | @property |
| 238 | def dts_source(self): |
| 239 | return f"{self._dt}" |
| 240 | |
Ulf Magnusson | 7c26c17 | 2019-09-28 15:03:45 +0200 | [diff] [blame] | 241 | def __repr__(self): |
| 242 | return "<EDT for '{}', binding directories '{}'>".format( |
| 243 | self.dts_path, self.bindings_dirs) |
| 244 | |
Martí Bolívar | b6db201 | 2020-08-24 13:33:53 -0700 | [diff] [blame] | 245 | @property |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 246 | def scc_order(self): |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 247 | try: |
| 248 | return self._graph.scc_order() |
| 249 | except Exception as e: |
| 250 | raise EDTError(e) |
| 251 | |
Martí Bolívar | b6db201 | 2020-08-24 13:33:53 -0700 | [diff] [blame] | 252 | def _init_graph(self): |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 253 | # Constructs a graph of dependencies between Node instances, |
Martí Bolívar | b6db201 | 2020-08-24 13:33:53 -0700 | [diff] [blame] | 254 | # which is usable for computing a partial order over the dependencies. |
| 255 | # The algorithm supports detecting dependency loops. |
| 256 | # |
| 257 | # Actually computing the SCC order is lazily deferred to the |
| 258 | # first time the scc_order property is read. |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 259 | |
| 260 | self._graph = Graph() |
| 261 | |
| 262 | for node in self.nodes: |
| 263 | # A Node always depends on its parent. |
| 264 | for child in node.children.values(): |
| 265 | self._graph.add_edge(child, node) |
| 266 | |
| 267 | # A Node depends on any Nodes present in 'phandle', |
| 268 | # 'phandles', or 'phandle-array' property values. |
| 269 | for prop in node.props.values(): |
| 270 | if prop.type == 'phandle': |
| 271 | self._graph.add_edge(node, prop.val) |
| 272 | elif prop.type == 'phandles': |
| 273 | for phandle_node in prop.val: |
| 274 | self._graph.add_edge(node, phandle_node) |
| 275 | elif prop.type == 'phandle-array': |
| 276 | for cd in prop.val: |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 277 | if cd is None: |
| 278 | continue |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 279 | self._graph.add_edge(node, cd.controller) |
| 280 | |
| 281 | # A Node depends on whatever supports the interrupts it |
| 282 | # generates. |
| 283 | for intr in node.interrupts: |
| 284 | self._graph.add_edge(node, intr.controller) |
| 285 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 286 | def _init_compat2binding(self): |
| 287 | # Creates self._compat2binding, a dictionary that maps |
| 288 | # (<compatible>, <bus>) tuples (both strings) to Binding objects. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 289 | # |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 290 | # The Binding objects are created from YAML files discovered |
| 291 | # in self.bindings_dirs as needed. |
| 292 | # |
| 293 | # For example, self._compat2binding["company,dev", "can"] |
| 294 | # contains the Binding for the 'company,dev' device, when it |
| 295 | # appears on the CAN bus. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 296 | # |
| 297 | # For bindings that don't specify a bus, <bus> is None, so that e.g. |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 298 | # self._compat2binding["company,notonbus", None] is the Binding. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 299 | # |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 300 | # Only bindings for 'compatible' strings that appear in the devicetree |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 301 | # are loaded. |
| 302 | |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 303 | dt_compats = _dt_compats(self._dt) |
| 304 | # Searches for any 'compatible' string mentioned in the devicetree |
| 305 | # files, with a regex |
| 306 | dt_compats_search = re.compile( |
| 307 | "|".join(re.escape(compat) for compat in dt_compats) |
| 308 | ).search |
| 309 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 310 | self._binding_paths = _binding_paths(self.bindings_dirs) |
| 311 | self._binding_fname2path = {os.path.basename(path): path |
| 312 | for path in self._binding_paths} |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 313 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 314 | self._compat2binding = {} |
| 315 | for binding_path in self._binding_paths: |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 316 | with open(binding_path, encoding="utf-8") as f: |
| 317 | contents = f.read() |
| 318 | |
| 319 | # As an optimization, skip parsing files that don't contain any of |
Ulf Magnusson | b92ceb7 | 2019-10-29 08:48:05 +0100 | [diff] [blame] | 320 | # the .dts 'compatible' strings, which should be reasonably safe |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 321 | if not dt_compats_search(contents): |
| 322 | continue |
| 323 | |
| 324 | # Load the binding and check that it actually matches one of the |
| 325 | # compatibles. Might get false positives above due to comments and |
| 326 | # stuff. |
| 327 | |
Ulf Magnusson | 4c6ea2d | 2019-09-19 22:54:55 +0200 | [diff] [blame] | 328 | try: |
| 329 | # Parsed PyYAML output (Python lists/dictionaries/strings/etc., |
| 330 | # representing the file) |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 331 | raw = yaml.load(contents, Loader=_BindingLoader) |
Ulf Magnusson | 4c6ea2d | 2019-09-19 22:54:55 +0200 | [diff] [blame] | 332 | except yaml.YAMLError as e: |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 333 | _LOG.warning( |
| 334 | f"'{binding_path}' appears in binding directories " |
| 335 | f"but isn't valid YAML: {e}") |
Ulf Magnusson | 4c6ea2d | 2019-09-19 22:54:55 +0200 | [diff] [blame] | 336 | continue |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 337 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 338 | # Convert the raw data to a Binding object, erroring out |
| 339 | # if necessary. |
| 340 | binding = self._binding(raw, binding_path, dt_compats) |
Kumar Gala | 4e2988d | 2020-06-09 11:18:05 -0500 | [diff] [blame] | 341 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 342 | if binding is None: |
| 343 | # Either the file is not a binding or it's a binding |
| 344 | # whose compatible does not appear in the devicetree |
| 345 | # (picked up via some unrelated text in the binding |
| 346 | # file that happened to match a compatible). |
Ulf Magnusson | 14138c3 | 2019-08-16 14:43:58 +0200 | [diff] [blame] | 347 | continue |
| 348 | |
Ulf Magnusson | e311380 | 2019-10-14 17:45:45 +0200 | [diff] [blame] | 349 | # Do not allow two different bindings to have the same |
Ulf Magnusson | 379145f | 2019-11-26 22:19:55 +0100 | [diff] [blame] | 350 | # 'compatible:'/'on-bus:' combo |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 351 | old_binding = self._compat2binding.get((binding.compatible, |
| 352 | binding.on_bus)) |
Ulf Magnusson | e311380 | 2019-10-14 17:45:45 +0200 | [diff] [blame] | 353 | if old_binding: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 354 | msg = (f"both {old_binding.path} and {binding_path} have " |
| 355 | f"'compatible: {binding.compatible}'") |
| 356 | if binding.on_bus is not None: |
| 357 | msg += f" and 'on-bus: {binding.on_bus}'" |
Ulf Magnusson | e311380 | 2019-10-14 17:45:45 +0200 | [diff] [blame] | 358 | _err(msg) |
| 359 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 360 | # Register the binding. |
| 361 | self._compat2binding[binding.compatible, binding.on_bus] = binding |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 362 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 363 | def _binding(self, raw, binding_path, dt_compats): |
| 364 | # Convert a 'raw' binding from YAML to a Binding object and return it. |
Ulf Magnusson | d834b69 | 2019-08-21 16:41:03 +0200 | [diff] [blame] | 365 | # |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 366 | # Error out if the raw data looks like an invalid binding. |
| 367 | # |
| 368 | # Return None if the file doesn't contain a binding or the |
| 369 | # binding's compatible isn't in dt_compats. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 370 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 371 | # Get the 'compatible:' string. |
| 372 | if raw is None or "compatible" not in raw: |
| 373 | # Empty file, binding fragment, spurious file, etc. |
| 374 | return None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 375 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 376 | compatible = raw["compatible"] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 377 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 378 | if compatible not in dt_compats: |
| 379 | # Not a compatible we care about. |
| 380 | return None |
Kumar Gala | e75ac55 | 2020-04-08 15:25:56 -0500 | [diff] [blame] | 381 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 382 | # Initialize and return the Binding object. |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 383 | return Binding(binding_path, self._binding_fname2path, raw=raw) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 384 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 385 | def _init_nodes(self): |
| 386 | # Creates a list of edtlib.Node objects from the dtlib.Node objects, in |
| 387 | # self.nodes |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 388 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 389 | # Maps each dtlib.Node to its corresponding edtlib.Node |
| 390 | self._node2enode = {} |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 391 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 392 | self.nodes = [] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 393 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 394 | for dt_node in self._dt.node_iter(): |
| 395 | # Warning: We depend on parent Nodes being created before their |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 396 | # children. This is guaranteed by node_iter(). |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 397 | node = Node() |
| 398 | node.edt = self |
| 399 | node._node = dt_node |
Kumar Gala | 06edcb1 | 2020-05-07 15:39:21 -0500 | [diff] [blame] | 400 | if "compatible" in node._node.props: |
| 401 | node.compats = node._node.props["compatible"].to_strings() |
| 402 | else: |
| 403 | node.compats = [] |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 404 | node.bus_node = node._bus_node(self._fixed_partitions_no_bus) |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 405 | node._init_binding() |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 406 | node._init_regs() |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 407 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 408 | self.nodes.append(node) |
| 409 | self._node2enode[dt_node] = node |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 410 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 411 | for node in self.nodes: |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 412 | # These depend on all Node objects having been created, because |
| 413 | # they (either always or sometimes) reference other nodes, so we |
| 414 | # run them separately |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 415 | node._init_props(default_prop_types=self._default_prop_types) |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 416 | node._init_interrupts() |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 417 | node._init_pinctrls() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 418 | |
Martí Bolívar | e05c94e | 2020-07-01 13:25:15 -0700 | [diff] [blame] | 419 | if self._warn_reg_unit_address_mismatch: |
| 420 | # This warning matches the simple_bus_reg warning in dtc |
| 421 | for node in self.nodes: |
| 422 | if node.regs and node.regs[0].addr != node.unit_addr: |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 423 | _LOG.warning("unit address and first address in 'reg' " |
| 424 | f"(0x{node.regs[0].addr:x}) don't match for " |
| 425 | f"{node.path}") |
Martí Bolívar | e05c94e | 2020-07-01 13:25:15 -0700 | [diff] [blame] | 426 | |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 427 | def _init_luts(self): |
Martí Bolívar | eac56e4 | 2020-05-06 11:22:30 -0700 | [diff] [blame] | 428 | # Initialize node lookup tables (LUTs). |
Ulf Magnusson | 88db84b | 2020-01-28 04:46:12 +0100 | [diff] [blame] | 429 | |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 430 | self.label2node = OrderedDict() |
Peter Bigot | 44394e3 | 2020-08-24 13:35:25 -0700 | [diff] [blame] | 431 | self.dep_ord2node = OrderedDict() |
Martí Bolívar | eac56e4 | 2020-05-06 11:22:30 -0700 | [diff] [blame] | 432 | self.compat2nodes = defaultdict(list) |
| 433 | self.compat2okay = defaultdict(list) |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 434 | |
Ulf Magnusson | 88db84b | 2020-01-28 04:46:12 +0100 | [diff] [blame] | 435 | for node in self.nodes: |
Martí Bolívar | 2707d2a | 2020-04-22 18:05:41 -0700 | [diff] [blame] | 436 | for label in node.labels: |
| 437 | self.label2node[label] = node |
| 438 | |
Martí Bolívar | eac56e4 | 2020-05-06 11:22:30 -0700 | [diff] [blame] | 439 | for compat in node.compats: |
| 440 | self.compat2nodes[compat].append(node) |
| 441 | |
Martí Bolívar | eac56e4 | 2020-05-06 11:22:30 -0700 | [diff] [blame] | 442 | if node.status == "okay": |
| 443 | self.compat2okay[compat].append(node) |
| 444 | |
Peter Bigot | 44394e3 | 2020-08-24 13:35:25 -0700 | [diff] [blame] | 445 | for nodeset in self.scc_order: |
| 446 | node = nodeset[0] |
| 447 | self.dep_ord2node[node.dep_ordinal] = node |
| 448 | |
Martí Bolívar | f673e20 | 2020-12-08 12:55:44 -0800 | [diff] [blame] | 449 | def _check(self): |
| 450 | # Tree-wide checks and warnings. |
| 451 | |
| 452 | for binding in self._compat2binding.values(): |
| 453 | for spec in binding.prop2specs.values(): |
| 454 | if not spec.enum or spec.type != 'string': |
| 455 | continue |
| 456 | |
| 457 | if not spec.enum_tokenizable: |
| 458 | _LOG.warning( |
| 459 | f"compatible '{binding.compatible}' " |
| 460 | f"in binding '{binding.path}' has non-tokenizable enum " |
| 461 | f"for property '{spec.name}': " + |
| 462 | ', '.join(repr(x) for x in spec.enum)) |
| 463 | elif not spec.enum_upper_tokenizable: |
| 464 | _LOG.warning( |
| 465 | f"compatible '{binding.compatible}' " |
| 466 | f"in binding '{binding.path}' has enum for property " |
| 467 | f"'{spec.name}' that is only tokenizable " |
| 468 | 'in lowercase: ' + |
| 469 | ', '.join(repr(x) for x in spec.enum)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 470 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 471 | class Node: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 472 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 473 | Represents a devicetree node, augmented with information from bindings, and |
| 474 | with some interpretation of devicetree properties. There's a one-to-one |
| 475 | correspondence between devicetree nodes and Nodes. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 476 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 477 | These attributes are available on Node objects: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 478 | |
| 479 | edt: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 480 | The EDT instance this node is from |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 481 | |
| 482 | name: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 483 | The name of the node |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 484 | |
| 485 | unit_addr: |
| 486 | An integer with the ...@<unit-address> portion of the node name, |
| 487 | translated through any 'ranges' properties on parent nodes, or None if |
| 488 | the node name has no unit-address portion |
| 489 | |
Ulf Magnusson | 72bfa83 | 2019-10-14 17:30:22 +0200 | [diff] [blame] | 490 | description: |
| 491 | The description string from the binding for the node, or None if the node |
Ulf Magnusson | f3f88a8 | 2019-10-28 13:02:21 +0100 | [diff] [blame] | 492 | has no binding. Leading and trailing whitespace (including newlines) is |
| 493 | removed. |
Ulf Magnusson | 72bfa83 | 2019-10-14 17:30:22 +0200 | [diff] [blame] | 494 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 495 | path: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 496 | The devicetree path of the node |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 497 | |
| 498 | label: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 499 | The text from the 'label' property on the node, or None if the node has |
| 500 | no 'label' |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 501 | |
Martí Bolívar | 7846fd1 | 2020-02-12 22:18:39 -0800 | [diff] [blame] | 502 | labels: |
| 503 | A list of all of the devicetree labels for the node, in the same order |
| 504 | as the labels appear, but with duplicates removed. |
| 505 | |
| 506 | This corresponds to the actual devicetree source labels, unlike the |
| 507 | "label" attribute, which is the value of a devicetree property named |
| 508 | "label". |
| 509 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 510 | parent: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 511 | The Node instance for the devicetree parent of the Node, or None if the |
| 512 | node is the root node |
Ulf Magnusson | 110526e | 2019-09-21 04:14:33 +0200 | [diff] [blame] | 513 | |
| 514 | children: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 515 | A dictionary with the Node instances for the devicetree children of the |
| 516 | node, indexed by name |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 517 | |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 518 | dep_ordinal: |
| 519 | A non-negative integer value such that the value for a Node is |
| 520 | less than the value for all Nodes that depend on it. |
| 521 | |
Martí Bolívar | 8165008 | 2020-10-05 20:02:13 -0700 | [diff] [blame] | 522 | The ordinal is defined for all Nodes, and is unique among nodes in its |
| 523 | EDT 'nodes' list. |
Peter A. Bigot | ea956f4 | 2019-09-27 11:31:36 -0500 | [diff] [blame] | 524 | |
Ulf Magnusson | 2e1d288 | 2019-11-06 22:36:50 +0100 | [diff] [blame] | 525 | required_by: |
| 526 | A list with the nodes that directly depend on the node |
| 527 | |
| 528 | depends_on: |
| 529 | A list with the nodes that the node directly depends on |
| 530 | |
Martí Bolívar | bd0ecc8 | 2020-05-05 16:37:26 -0700 | [diff] [blame] | 531 | status: |
| 532 | The node's status property value, as a string, or "okay" if the node |
| 533 | has no status property set. If the node's status property is "ok", |
| 534 | it is converted to "okay" for consistency. |
| 535 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 536 | read_only: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 537 | True if the node has a 'read-only' property, and False otherwise |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 538 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 539 | matching_compat: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 540 | The 'compatible' string for the binding that matched the node, or None if |
| 541 | the node has no binding |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 542 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 543 | binding_path: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 544 | The path to the binding file for the node, or None if the node has no |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 545 | binding |
| 546 | |
| 547 | compats: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 548 | A list of 'compatible' strings for the node, in the same order that |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 549 | they're listed in the .dts file |
| 550 | |
| 551 | regs: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 552 | A list of Register objects for the node's registers |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 553 | |
| 554 | props: |
Ulf Magnusson | 7215885 | 2019-11-12 18:33:00 +0100 | [diff] [blame] | 555 | A collections.OrderedDict that maps property names to Property objects. |
| 556 | Property objects are created for all devicetree properties on the node |
| 557 | that are mentioned in 'properties:' in the binding. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 558 | |
| 559 | aliases: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 560 | A list of aliases for the node. This is fetched from the /aliases node. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 561 | |
| 562 | interrupts: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 563 | A list of ControllerAndData objects for the interrupts generated by the |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 564 | node. The list is empty if the node does not generate interrupts. |
| 565 | |
| 566 | pinctrls: |
| 567 | A list of PinCtrl objects for the pinctrl-<index> properties on the |
| 568 | node, sorted by index. The list is empty if the node does not have any |
| 569 | pinctrl-<index> properties. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 570 | |
| 571 | bus: |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 572 | If the node is a bus node (has a 'bus:' key in its binding), then this |
| 573 | attribute holds the bus type, e.g. "i2c" or "spi". If the node is not a |
| 574 | bus node, then this attribute is None. |
| 575 | |
| 576 | on_bus: |
| 577 | The bus the node appears on, e.g. "i2c" or "spi". The bus is determined |
| 578 | by searching upwards for a parent node whose binding has a 'bus:' key, |
| 579 | returning the value of the first 'bus:' key found. If none of the node's |
| 580 | parents has a 'bus:' key, this attribute is None. |
| 581 | |
| 582 | bus_node: |
| 583 | Like on_bus, but contains the Node for the bus controller, or None if the |
| 584 | node is not on a bus. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 585 | |
| 586 | flash_controller: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 587 | The flash controller for the node. Only meaningful for nodes representing |
| 588 | flash partitions. |
Ulf Magnusson | 8cacb9f | 2020-02-16 08:45:37 +0100 | [diff] [blame] | 589 | |
| 590 | spi_cs_gpio: |
| 591 | The device's SPI GPIO chip select as a ControllerAndData instance, if it |
| 592 | exists, and None otherwise. See |
| 593 | Documentation/devicetree/bindings/spi/spi-controller.yaml in the Linux kernel. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 594 | """ |
| 595 | @property |
| 596 | def name(self): |
| 597 | "See the class docstring" |
| 598 | return self._node.name |
| 599 | |
| 600 | @property |
| 601 | def unit_addr(self): |
| 602 | "See the class docstring" |
| 603 | |
| 604 | # TODO: Return a plain string here later, like dtlib.Node.unit_addr? |
| 605 | |
| 606 | if "@" not in self.name: |
| 607 | return None |
| 608 | |
| 609 | try: |
| 610 | addr = int(self.name.split("@", 1)[1], 16) |
| 611 | except ValueError: |
| 612 | _err("{!r} has non-hex unit address".format(self)) |
| 613 | |
Martí Bolívar | e05c94e | 2020-07-01 13:25:15 -0700 | [diff] [blame] | 614 | return _translate(addr, self._node) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 615 | |
| 616 | @property |
Ulf Magnusson | 72bfa83 | 2019-10-14 17:30:22 +0200 | [diff] [blame] | 617 | def description(self): |
| 618 | "See the class docstring." |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 619 | if self._binding: |
| 620 | return self._binding.description |
Ulf Magnusson | 72bfa83 | 2019-10-14 17:30:22 +0200 | [diff] [blame] | 621 | return None |
| 622 | |
| 623 | @property |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 624 | def path(self): |
| 625 | "See the class docstring" |
| 626 | return self._node.path |
| 627 | |
| 628 | @property |
| 629 | def label(self): |
| 630 | "See the class docstring" |
| 631 | if "label" in self._node.props: |
| 632 | return self._node.props["label"].to_string() |
| 633 | return None |
| 634 | |
| 635 | @property |
Martí Bolívar | 7846fd1 | 2020-02-12 22:18:39 -0800 | [diff] [blame] | 636 | def labels(self): |
| 637 | "See the class docstring" |
| 638 | return self._node.labels |
| 639 | |
| 640 | @property |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 641 | def parent(self): |
| 642 | "See the class docstring" |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 643 | return self.edt._node2enode.get(self._node.parent) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 644 | |
| 645 | @property |
Ulf Magnusson | 110526e | 2019-09-21 04:14:33 +0200 | [diff] [blame] | 646 | def children(self): |
| 647 | "See the class docstring" |
| 648 | # Could be initialized statically too to preserve identity, but not |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 649 | # sure if needed. Parent nodes being initialized before their children |
| 650 | # would need to be kept in mind. |
Ulf Magnusson | 7215885 | 2019-11-12 18:33:00 +0100 | [diff] [blame] | 651 | return OrderedDict((name, self.edt._node2enode[node]) |
| 652 | for name, node in self._node.nodes.items()) |
Ulf Magnusson | 110526e | 2019-09-21 04:14:33 +0200 | [diff] [blame] | 653 | |
| 654 | @property |
Ulf Magnusson | 2e1d288 | 2019-11-06 22:36:50 +0100 | [diff] [blame] | 655 | def required_by(self): |
| 656 | "See the class docstring" |
| 657 | return self.edt._graph.required_by(self) |
| 658 | |
| 659 | @property |
| 660 | def depends_on(self): |
| 661 | "See the class docstring" |
| 662 | return self.edt._graph.depends_on(self) |
| 663 | |
| 664 | @property |
Martí Bolívar | bd0ecc8 | 2020-05-05 16:37:26 -0700 | [diff] [blame] | 665 | def status(self): |
| 666 | "See the class docstring" |
| 667 | status = self._node.props.get("status") |
| 668 | |
| 669 | if status is None: |
| 670 | as_string = "okay" |
| 671 | else: |
| 672 | as_string = status.to_string() |
| 673 | |
| 674 | if as_string == "ok": |
| 675 | as_string = "okay" |
| 676 | |
| 677 | return as_string |
| 678 | |
| 679 | @property |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 680 | def read_only(self): |
| 681 | "See the class docstring" |
| 682 | return "read-only" in self._node.props |
| 683 | |
| 684 | @property |
| 685 | def aliases(self): |
| 686 | "See the class docstring" |
| 687 | return [alias for alias, node in self._node.dt.alias2node.items() |
| 688 | if node is self._node] |
| 689 | |
| 690 | @property |
| 691 | def bus(self): |
| 692 | "See the class docstring" |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 693 | if self._binding: |
| 694 | return self._binding.bus |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 695 | return None |
| 696 | |
| 697 | @property |
| 698 | def on_bus(self): |
| 699 | "See the class docstring" |
| 700 | bus_node = self.bus_node |
| 701 | return bus_node.bus if bus_node else None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 702 | |
| 703 | @property |
| 704 | def flash_controller(self): |
| 705 | "See the class docstring" |
| 706 | |
| 707 | # The node path might be something like |
| 708 | # /flash-controller@4001E000/flash@0/partitions/partition@fc000. We go |
| 709 | # up two levels to get the flash and check its compat. The flash |
| 710 | # controller might be the flash itself (for cases like NOR flashes). |
| 711 | # For the case of 'soc-nv-flash', we assume the controller is the |
| 712 | # parent of the flash node. |
| 713 | |
| 714 | if not self.parent or not self.parent.parent: |
| 715 | _err("flash partition {!r} lacks parent or grandparent node" |
| 716 | .format(self)) |
| 717 | |
| 718 | controller = self.parent.parent |
| 719 | if controller.matching_compat == "soc-nv-flash": |
| 720 | return controller.parent |
| 721 | return controller |
| 722 | |
Ulf Magnusson | 8cacb9f | 2020-02-16 08:45:37 +0100 | [diff] [blame] | 723 | @property |
| 724 | def spi_cs_gpio(self): |
| 725 | "See the class docstring" |
| 726 | |
| 727 | if not (self.on_bus == "spi" and "cs-gpios" in self.bus_node.props): |
| 728 | return None |
| 729 | |
| 730 | if not self.regs: |
| 731 | _err("{!r} needs a 'reg' property, to look up the chip select index " |
| 732 | "for SPI".format(self)) |
| 733 | |
| 734 | parent_cs_lst = self.bus_node.props["cs-gpios"].val |
| 735 | |
| 736 | # cs-gpios is indexed by the unit address |
| 737 | cs_index = self.regs[0].addr |
| 738 | if cs_index >= len(parent_cs_lst): |
| 739 | _err("index from 'regs' in {!r} ({}) is >= number of cs-gpios " |
| 740 | "in {!r} ({})".format( |
| 741 | self, cs_index, self.bus_node, len(parent_cs_lst))) |
| 742 | |
| 743 | return parent_cs_lst[cs_index] |
| 744 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 745 | def __repr__(self): |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 746 | return "<Node {} in '{}', {}>".format( |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 747 | self.path, self.edt.dts_path, |
| 748 | "binding " + self.binding_path if self.binding_path |
| 749 | else "no binding") |
| 750 | |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 751 | def _init_binding(self): |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 752 | # Initializes Node.matching_compat, Node._binding, and |
| 753 | # Node.binding_path. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 754 | # |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 755 | # Node._binding holds the data from the node's binding file, in the |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 756 | # format returned by PyYAML (plain Python lists, dicts, etc.), or None |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 757 | # if the node has no binding. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 758 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 759 | # This relies on the parent of the node having already been |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 760 | # initialized, which is guaranteed by going through the nodes in |
| 761 | # node_iter() order. |
| 762 | |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 763 | if self.path in self.edt._infer_binding_for_paths: |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 764 | self._binding_from_properties() |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 765 | return |
| 766 | |
Kumar Gala | 06edcb1 | 2020-05-07 15:39:21 -0500 | [diff] [blame] | 767 | if self.compats: |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 768 | on_bus = self.on_bus |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 769 | |
| 770 | for compat in self.compats: |
Johan Hedberg | 4ba3878 | 2020-12-21 12:52:32 +0200 | [diff] [blame] | 771 | # When matching, respect the order of the 'compatible' entries, |
| 772 | # and for each one first try to match against an explicitly |
| 773 | # specified bus (if any) and then against any bus. This is so |
| 774 | # that matching against bindings which do not specify a bus |
| 775 | # works the same way in Zephyr as it does elsewhere. |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 776 | if (compat, on_bus) in self.edt._compat2binding: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 777 | binding = self.edt._compat2binding[compat, on_bus] |
Johan Hedberg | 4ba3878 | 2020-12-21 12:52:32 +0200 | [diff] [blame] | 778 | elif (compat, None) in self.edt._compat2binding: |
| 779 | binding = self.edt._compat2binding[compat, None] |
| 780 | else: |
| 781 | continue |
| 782 | |
| 783 | self.binding_path = binding.path |
| 784 | self.matching_compat = compat |
| 785 | self._binding = binding |
| 786 | return |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 787 | else: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 788 | # No 'compatible' property. See if the parent binding has |
| 789 | # a compatible. This can come from one or more levels of |
| 790 | # nesting with 'child-binding:'. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 791 | |
Ulf Magnusson | 0b1ab4a | 2019-09-17 18:24:30 +0200 | [diff] [blame] | 792 | binding_from_parent = self._binding_from_parent() |
| 793 | if binding_from_parent: |
| 794 | self._binding = binding_from_parent |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 795 | self.binding_path = self._binding.path |
| 796 | self.matching_compat = self._binding.compatible |
Ulf Magnusson | 0b1ab4a | 2019-09-17 18:24:30 +0200 | [diff] [blame] | 797 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 798 | return |
| 799 | |
| 800 | # No binding found |
Ulf Magnusson | 72bfa83 | 2019-10-14 17:30:22 +0200 | [diff] [blame] | 801 | self._binding = self.binding_path = self.matching_compat = None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 802 | |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 803 | def _binding_from_properties(self): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 804 | # Sets up a Binding object synthesized from the properties in the node. |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 805 | |
| 806 | if self.compats: |
| 807 | _err(f"compatible in node with inferred binding: {self.path}") |
| 808 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 809 | # Synthesize a 'raw' binding as if it had been parsed from YAML. |
| 810 | raw = { |
| 811 | 'description': 'Inferred binding from properties, via edtlib.', |
| 812 | 'properties': {}, |
| 813 | } |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 814 | for name, prop in self._node.props.items(): |
| 815 | pp = OrderedDict() |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 816 | if prop.type == TYPE_EMPTY: |
| 817 | pp["type"] = "boolean" |
| 818 | elif prop.type == TYPE_BYTES: |
| 819 | pp["type"] = "uint8-array" |
| 820 | elif prop.type == TYPE_NUM: |
| 821 | pp["type"] = "int" |
| 822 | elif prop.type == TYPE_NUMS: |
| 823 | pp["type"] = "array" |
| 824 | elif prop.type == TYPE_STRING: |
| 825 | pp["type"] = "string" |
| 826 | elif prop.type == TYPE_STRINGS: |
| 827 | pp["type"] = "string-array" |
| 828 | elif prop.type == TYPE_PHANDLE: |
| 829 | pp["type"] = "phandle" |
| 830 | elif prop.type == TYPE_PHANDLES: |
| 831 | pp["type"] = "phandles" |
| 832 | elif prop.type == TYPE_PHANDLES_AND_NUMS: |
| 833 | pp["type"] = "phandle-array" |
| 834 | else: |
| 835 | _err(f"cannot infer binding from property: {prop}") |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 836 | raw['properties'][name] = pp |
| 837 | |
| 838 | # Set up Node state. |
| 839 | self.binding_path = None |
| 840 | self.matching_compat = None |
| 841 | self.compats = [] |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 842 | self._binding = Binding(None, {}, raw=raw, require_compatible=False) |
Peter Bigot | 32e6159 | 2020-09-01 10:22:31 -0500 | [diff] [blame] | 843 | |
Ulf Magnusson | 0b1ab4a | 2019-09-17 18:24:30 +0200 | [diff] [blame] | 844 | def _binding_from_parent(self): |
| 845 | # Returns the binding from 'child-binding:' in the parent node's |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 846 | # binding. |
Ulf Magnusson | 0b1ab4a | 2019-09-17 18:24:30 +0200 | [diff] [blame] | 847 | |
| 848 | if not self.parent: |
| 849 | return None |
| 850 | |
| 851 | pbinding = self.parent._binding |
| 852 | if not pbinding: |
| 853 | return None |
| 854 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 855 | if pbinding.child_binding: |
| 856 | return pbinding.child_binding |
Ulf Magnusson | 0b1ab4a | 2019-09-17 18:24:30 +0200 | [diff] [blame] | 857 | |
| 858 | return None |
| 859 | |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 860 | def _bus_node(self, support_fixed_partitions_on_any_bus = True): |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 861 | # Returns the value for self.bus_node. Relies on parent nodes being |
| 862 | # initialized before their children. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 863 | |
| 864 | if not self.parent: |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 865 | # This is the root node |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 866 | return None |
| 867 | |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 868 | # Treat 'fixed-partitions' as if they are not on any bus. The reason is |
Kumar Gala | 058842b | 2020-05-06 22:36:50 -0500 | [diff] [blame] | 869 | # that flash nodes might be on a SPI or controller or SoC bus. Having |
| 870 | # bus be None means we'll always match the binding for fixed-partitions |
| 871 | # also this means want processing the fixed-partitions node we wouldn't |
| 872 | # try to do anything bus specific with it. |
Kumar Gala | 3a68566 | 2020-05-08 05:40:52 -0500 | [diff] [blame] | 873 | if support_fixed_partitions_on_any_bus and "fixed-partitions" in self.compats: |
Kumar Gala | 058842b | 2020-05-06 22:36:50 -0500 | [diff] [blame] | 874 | return None |
| 875 | |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 876 | if self.parent.bus: |
| 877 | # The parent node is a bus node |
| 878 | return self.parent |
Ulf Magnusson | 1ebe945 | 2019-09-16 16:42:18 +0200 | [diff] [blame] | 879 | |
Ulf Magnusson | 5e55eda | 2019-11-27 04:08:36 +0100 | [diff] [blame] | 880 | # Same bus node as parent (possibly None) |
| 881 | return self.parent.bus_node |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 882 | |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 883 | def _init_props(self, default_prop_types=False): |
Ulf Magnusson | b918e25 | 2019-08-30 03:49:43 +0200 | [diff] [blame] | 884 | # Creates self.props. See the class docstring. Also checks that all |
| 885 | # properties on the node are declared in its binding. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 886 | |
Ulf Magnusson | 7215885 | 2019-11-12 18:33:00 +0100 | [diff] [blame] | 887 | self.props = OrderedDict() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 888 | |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 889 | node = self._node |
| 890 | if self._binding: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 891 | prop2specs = self._binding.prop2specs |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 892 | else: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 893 | prop2specs = None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 894 | |
Ulf Magnusson | b918e25 | 2019-08-30 03:49:43 +0200 | [diff] [blame] | 895 | # Initialize self.props |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 896 | if prop2specs: |
| 897 | for prop_spec in prop2specs.values(): |
| 898 | self._init_prop(prop_spec) |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 899 | self._check_undeclared_props() |
| 900 | elif default_prop_types: |
| 901 | for name in node.props: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 902 | if name not in _DEFAULT_PROP_SPECS: |
| 903 | continue |
| 904 | prop_spec = _DEFAULT_PROP_SPECS[name] |
| 905 | val = self._prop_val(name, prop_spec.type, False, False, None) |
| 906 | self.props[name] = Property(prop_spec, val, self) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 907 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 908 | def _init_prop(self, prop_spec): |
| 909 | # _init_props() helper for initializing a single property. |
| 910 | # 'prop_spec' is a PropertySpec object from the node's binding. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 911 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 912 | name = prop_spec.name |
| 913 | prop_type = prop_spec.type |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 914 | if not prop_type: |
| 915 | _err("'{}' in {} lacks 'type'".format(name, self.binding_path)) |
| 916 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 917 | val = self._prop_val(name, prop_type, prop_spec.deprecated, |
| 918 | prop_spec.required, prop_spec.default) |
Ulf Magnusson | 8d317bc | 2019-09-06 12:31:55 +0200 | [diff] [blame] | 919 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 920 | if val is None: |
Ulf Magnusson | fcd665a | 2019-08-28 00:22:01 +0200 | [diff] [blame] | 921 | # 'required: false' property that wasn't there, or a property type |
| 922 | # for which we store no data. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 923 | return |
| 924 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 925 | enum = prop_spec.enum |
Kumar Gala | 62bf267 | 2019-08-09 14:51:58 -0500 | [diff] [blame] | 926 | if enum and val not in enum: |
| 927 | _err("value of property '{}' on {} in {} ({!r}) is not in 'enum' " |
| 928 | "list in {} ({!r})" |
| 929 | .format(name, self.path, self.edt.dts_path, val, |
| 930 | self.binding_path, enum)) |
| 931 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 932 | const = prop_spec.const |
Kumar Gala | 3d14374 | 2019-08-09 16:03:46 -0500 | [diff] [blame] | 933 | if const is not None and val != const: |
| 934 | _err("value of property '{}' on {} in {} ({!r}) is different from " |
| 935 | "the 'const' value specified in {} ({!r})" |
| 936 | .format(name, self.path, self.edt.dts_path, val, |
| 937 | self.binding_path, const)) |
| 938 | |
Kumar Gala | 5dd715b | 2019-08-09 09:49:22 -0500 | [diff] [blame] | 939 | # Skip properties that start with '#', like '#size-cells', and mapping |
| 940 | # properties like 'gpio-map'/'interrupt-map' |
| 941 | if name[0] == "#" or name.endswith("-map"): |
| 942 | return |
| 943 | |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 944 | self.props[name] = Property(prop_spec, val, self) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 945 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 946 | def _prop_val(self, name, prop_type, deprecated, required, default): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 947 | # _init_prop() helper for getting the property's value |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 948 | # |
| 949 | # name: |
| 950 | # Property name from binding |
| 951 | # |
| 952 | # prop_type: |
| 953 | # Property type from binding (a string like "int") |
| 954 | # |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 955 | # deprecated: |
| 956 | # True if the property is deprecated |
| 957 | # |
Kumar Gala | fc8c0c0 | 2020-10-17 11:34:38 -0500 | [diff] [blame] | 958 | # required: |
| 959 | # True if the property is required to exist |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 960 | # |
| 961 | # default: |
| 962 | # Default value to use when the property doesn't exist, or None if |
| 963 | # the binding doesn't give a default value |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 964 | |
| 965 | node = self._node |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 966 | prop = node.props.get(name) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 967 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 968 | if prop and deprecated: |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 969 | _LOG.warning(f"'{name}' is marked as deprecated in 'properties:' " |
| 970 | f"in {self.binding_path} for node {node.path}.") |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 971 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 972 | if not prop: |
Martí Bolívar | 8165008 | 2020-10-05 20:02:13 -0700 | [diff] [blame] | 973 | if required and self.status == "okay": |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 974 | _err("'{}' is marked as required in 'properties:' in {}, but " |
| 975 | "does not appear in {!r}".format( |
| 976 | name, self.binding_path, node)) |
| 977 | |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 978 | if default is not None: |
| 979 | # YAML doesn't have a native format for byte arrays. We need to |
| 980 | # convert those from an array like [0x12, 0x34, ...]. The |
| 981 | # format has already been checked in |
| 982 | # _check_prop_type_and_default(). |
| 983 | if prop_type == "uint8-array": |
| 984 | return bytes(default) |
| 985 | return default |
| 986 | |
Ulf Magnusson | 0c4f4a9 | 2019-09-30 16:42:07 +0200 | [diff] [blame] | 987 | return False if prop_type == "boolean" else None |
| 988 | |
| 989 | if prop_type == "boolean": |
| 990 | if prop.type is not TYPE_EMPTY: |
| 991 | _err("'{0}' in {1!r} is defined with 'type: boolean' in {2}, " |
| 992 | "but is assigned a value ('{3}') instead of being empty " |
| 993 | "('{0};')".format(name, node, self.binding_path, prop)) |
| 994 | return True |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 995 | |
| 996 | if prop_type == "int": |
| 997 | return prop.to_num() |
| 998 | |
| 999 | if prop_type == "array": |
| 1000 | return prop.to_nums() |
| 1001 | |
| 1002 | if prop_type == "uint8-array": |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 1003 | return prop.to_bytes() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1004 | |
| 1005 | if prop_type == "string": |
| 1006 | return prop.to_string() |
| 1007 | |
| 1008 | if prop_type == "string-array": |
| 1009 | return prop.to_strings() |
| 1010 | |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 1011 | if prop_type == "phandle": |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1012 | return self.edt._node2enode[prop.to_node()] |
Ulf Magnusson | 06b746c | 2019-08-09 20:38:17 +0200 | [diff] [blame] | 1013 | |
Ulf Magnusson | c42873f | 2019-08-13 11:52:10 +0200 | [diff] [blame] | 1014 | if prop_type == "phandles": |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1015 | return [self.edt._node2enode[node] for node in prop.to_nodes()] |
Ulf Magnusson | c42873f | 2019-08-13 11:52:10 +0200 | [diff] [blame] | 1016 | |
| 1017 | if prop_type == "phandle-array": |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1018 | # This type is a bit high-level for dtlib as it involves |
| 1019 | # information from bindings and *-names properties, so there's no |
| 1020 | # to_phandle_array() in dtlib. Do the type check ourselves. |
Martí Bolívar | 1ea7bf0 | 2020-10-05 09:34:02 -0700 | [diff] [blame] | 1021 | if prop.type not in (TYPE_PHANDLE, TYPE_PHANDLES, TYPE_PHANDLES_AND_NUMS): |
| 1022 | _err(f"expected property '{name}' in {node.path} in " |
| 1023 | f"{node.dt.filename} to be assigned " |
| 1024 | f"with '{name} = < &foo ... &bar 1 ... &baz 2 3 >' " |
| 1025 | f"(a mix of phandles and numbers), not '{prop}'") |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1026 | |
| 1027 | return self._standard_phandle_val_list(prop) |
Ulf Magnusson | c42873f | 2019-08-13 11:52:10 +0200 | [diff] [blame] | 1028 | |
Ulf Magnusson | 23a5b49 | 2019-12-30 22:52:11 +0100 | [diff] [blame] | 1029 | if prop_type == "path": |
| 1030 | return self.edt._node2enode[prop.to_path()] |
| 1031 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1032 | # prop_type == "compound". Checking that the 'type:' |
| 1033 | # value is valid is done in _check_prop_type_and_default(). |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 1034 | # |
| 1035 | # 'compound' is a dummy type for properties that don't fit any of the |
| 1036 | # patterns above, so that we can require all entries in 'properties:' |
| 1037 | # to have a 'type: ...'. No Property object is created for it. |
| 1038 | return None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1039 | |
Ulf Magnusson | b918e25 | 2019-08-30 03:49:43 +0200 | [diff] [blame] | 1040 | def _check_undeclared_props(self): |
| 1041 | # Checks that all properties are declared in the binding |
| 1042 | |
Ulf Magnusson | b918e25 | 2019-08-30 03:49:43 +0200 | [diff] [blame] | 1043 | for prop_name in self._node.props: |
| 1044 | # Allow a few special properties to not be declared in the binding |
| 1045 | if prop_name.endswith("-controller") or \ |
| 1046 | prop_name.startswith("#") or \ |
| 1047 | prop_name.startswith("pinctrl-") or \ |
| 1048 | prop_name in { |
| 1049 | "compatible", "status", "ranges", "phandle", |
| 1050 | "interrupt-parent", "interrupts-extended", "device_type"}: |
| 1051 | continue |
| 1052 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1053 | if prop_name not in self._binding.prop2specs: |
Ulf Magnusson | b918e25 | 2019-08-30 03:49:43 +0200 | [diff] [blame] | 1054 | _err("'{}' appears in {} in {}, but is not declared in " |
| 1055 | "'properties:' in {}" |
| 1056 | .format(prop_name, self._node.path, self.edt.dts_path, |
| 1057 | self.binding_path)) |
| 1058 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1059 | def _init_regs(self): |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1060 | # Initializes self.regs |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1061 | |
| 1062 | node = self._node |
| 1063 | |
| 1064 | self.regs = [] |
| 1065 | |
| 1066 | if "reg" not in node.props: |
| 1067 | return |
| 1068 | |
| 1069 | address_cells = _address_cells(node) |
| 1070 | size_cells = _size_cells(node) |
| 1071 | |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 1072 | for raw_reg in _slice(node, "reg", 4*(address_cells + size_cells), |
| 1073 | "4*(<#address-cells> (= {}) + <#size-cells> (= {}))" |
| 1074 | .format(address_cells, size_cells)): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1075 | reg = Register() |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1076 | reg.node = self |
Kumar Gala | 8af311a | 2020-03-13 12:43:56 -0500 | [diff] [blame] | 1077 | if address_cells == 0: |
| 1078 | reg.addr = None |
| 1079 | else: |
| 1080 | reg.addr = _translate(to_num(raw_reg[:4*address_cells]), node) |
| 1081 | if size_cells == 0: |
| 1082 | reg.size = None |
| 1083 | else: |
| 1084 | reg.size = to_num(raw_reg[4*address_cells:]) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1085 | if size_cells != 0 and reg.size == 0: |
| 1086 | _err("zero-sized 'reg' in {!r} seems meaningless (maybe you " |
| 1087 | "want a size of one or #size-cells = 0 instead)" |
| 1088 | .format(self._node)) |
| 1089 | |
| 1090 | self.regs.append(reg) |
| 1091 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1092 | _add_names(node, "reg", self.regs) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1093 | |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 1094 | def _init_pinctrls(self): |
| 1095 | # Initializes self.pinctrls from any pinctrl-<index> properties |
| 1096 | |
| 1097 | node = self._node |
| 1098 | |
| 1099 | # pinctrl-<index> properties |
| 1100 | pinctrl_props = [prop for name, prop in node.props.items() |
| 1101 | if re.match("pinctrl-[0-9]+", name)] |
| 1102 | # Sort by index |
| 1103 | pinctrl_props.sort(key=lambda prop: prop.name) |
| 1104 | |
| 1105 | # Check indices |
| 1106 | for i, prop in enumerate(pinctrl_props): |
| 1107 | if prop.name != "pinctrl-" + str(i): |
| 1108 | _err("missing 'pinctrl-{}' property on {!r} - indices should " |
| 1109 | "be contiguous and start from zero".format(i, node)) |
| 1110 | |
| 1111 | self.pinctrls = [] |
| 1112 | for prop in pinctrl_props: |
| 1113 | pinctrl = PinCtrl() |
| 1114 | pinctrl.node = self |
| 1115 | pinctrl.conf_nodes = [ |
| 1116 | self.edt._node2enode[node] for node in prop.to_nodes() |
| 1117 | ] |
| 1118 | self.pinctrls.append(pinctrl) |
| 1119 | |
| 1120 | _add_names(node, "pinctrl", self.pinctrls) |
| 1121 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1122 | def _init_interrupts(self): |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1123 | # Initializes self.interrupts |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1124 | |
| 1125 | node = self._node |
| 1126 | |
| 1127 | self.interrupts = [] |
| 1128 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1129 | for controller_node, data in _interrupts(node): |
| 1130 | interrupt = ControllerAndData() |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1131 | interrupt.node = self |
| 1132 | interrupt.controller = self.edt._node2enode[controller_node] |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1133 | interrupt.data = self._named_cells(interrupt.controller, data, |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 1134 | "interrupt") |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1135 | |
| 1136 | self.interrupts.append(interrupt) |
| 1137 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1138 | _add_names(node, "interrupt", self.interrupts) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1139 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1140 | def _standard_phandle_val_list(self, prop): |
| 1141 | # Parses a property like |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1142 | # |
| 1143 | # <name>s = <phandle value phandle value ...> |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1144 | # (e.g., pwms = <&foo 1 2 &bar 3 4>) |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1145 | # |
| 1146 | # , where each phandle points to a node that has a |
| 1147 | # |
| 1148 | # #<name>-cells = <size> |
| 1149 | # |
| 1150 | # property that gives the number of cells in the value after the |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1151 | # phandle. These values are given names in *-cells in the binding for |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1152 | # the controller. |
| 1153 | # |
| 1154 | # Also parses any |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1155 | # |
| 1156 | # <name>-names = "...", "...", ... |
| 1157 | # |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 1158 | # Returns a list of Optional[ControllerAndData] instances. |
| 1159 | # An index is None if the underlying phandle-array element |
| 1160 | # is unspecified. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1161 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1162 | if prop.name.endswith("gpios"): |
| 1163 | # There's some slight special-casing for *-gpios properties in that |
| 1164 | # e.g. foo-gpios still maps to #gpio-cells rather than |
| 1165 | # #foo-gpio-cells |
| 1166 | basename = "gpio" |
| 1167 | else: |
| 1168 | # Strip -s. We've already checked that the property names end in -s |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1169 | # in _check_prop_type_and_default(). |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1170 | basename = prop.name[:-1] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1171 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1172 | res = [] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1173 | |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 1174 | for item in _phandle_val_list(prop, basename): |
| 1175 | if item is None: |
| 1176 | res.append(None) |
| 1177 | continue |
| 1178 | |
| 1179 | controller_node, data = item |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1180 | mapped_controller, mapped_data = \ |
| 1181 | _map_phandle_array_entry(prop.node, controller_node, data, |
| 1182 | basename) |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1183 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1184 | entry = ControllerAndData() |
| 1185 | entry.node = self |
| 1186 | entry.controller = self.edt._node2enode[mapped_controller] |
| 1187 | entry.data = self._named_cells(entry.controller, mapped_data, |
| 1188 | basename) |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1189 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1190 | res.append(entry) |
| 1191 | |
| 1192 | _add_names(self._node, basename, res) |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 1193 | |
| 1194 | return res |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1195 | |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1196 | def _named_cells(self, controller, data, basename): |
| 1197 | # Returns a dictionary that maps <basename>-cells names given in the |
| 1198 | # binding for 'controller' to cell values. 'data' is the raw data, as a |
| 1199 | # byte array. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1200 | |
| 1201 | if not controller._binding: |
| 1202 | _err("{} controller {!r} for {!r} lacks binding" |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1203 | .format(basename, controller._node, self._node)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1204 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1205 | if basename in controller._binding.specifier2cells: |
| 1206 | cell_names = controller._binding.specifier2cells[basename] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1207 | else: |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1208 | # Treat no *-cells in the binding the same as an empty *-cells, so |
| 1209 | # that bindings don't have to have e.g. an empty 'clock-cells:' for |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1210 | # '#clock-cells = <0>'. |
| 1211 | cell_names = [] |
| 1212 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1213 | data_list = to_nums(data) |
| 1214 | if len(data_list) != len(cell_names): |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1215 | _err("unexpected '{}-cells:' length in binding for {!r} - {} " |
| 1216 | "instead of {}" |
| 1217 | .format(basename, controller._node, len(cell_names), |
| 1218 | len(data_list))) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1219 | |
Ulf Magnusson | 7215885 | 2019-11-12 18:33:00 +0100 | [diff] [blame] | 1220 | return OrderedDict(zip(cell_names, data_list)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1221 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1222 | |
| 1223 | class Register: |
| 1224 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1225 | Represents a register on a node. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1226 | |
| 1227 | These attributes are available on Register objects: |
| 1228 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1229 | node: |
| 1230 | The Node instance this register is from |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1231 | |
| 1232 | name: |
| 1233 | The name of the register as given in the 'reg-names' property, or None if |
| 1234 | there is no 'reg-names' property |
| 1235 | |
| 1236 | addr: |
Kumar Gala | 8af311a | 2020-03-13 12:43:56 -0500 | [diff] [blame] | 1237 | The starting address of the register, in the parent address space, or None |
| 1238 | if #address-cells is zero. Any 'ranges' properties are taken into account. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1239 | |
| 1240 | size: |
| 1241 | The length of the register in bytes |
| 1242 | """ |
| 1243 | def __repr__(self): |
| 1244 | fields = [] |
| 1245 | |
| 1246 | if self.name is not None: |
| 1247 | fields.append("name: " + self.name) |
Kumar Gala | 8af311a | 2020-03-13 12:43:56 -0500 | [diff] [blame] | 1248 | if self.addr is not None: |
| 1249 | fields.append("addr: " + hex(self.addr)) |
| 1250 | if self.size is not None: |
| 1251 | fields.append("size: " + hex(self.size)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1252 | |
| 1253 | return "<Register, {}>".format(", ".join(fields)) |
| 1254 | |
| 1255 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1256 | class ControllerAndData: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1257 | """ |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1258 | Represents an entry in an 'interrupts' or 'type: phandle-array' property |
| 1259 | value, e.g. <&ctrl-1 4 0> in |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1260 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1261 | cs-gpios = <&ctrl-1 4 0 &ctrl-2 3 4>; |
| 1262 | |
| 1263 | These attributes are available on ControllerAndData objects: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1264 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1265 | node: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1266 | The Node instance the property appears on |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1267 | |
| 1268 | controller: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1269 | The Node instance for the controller (e.g. the controller the interrupt |
| 1270 | gets sent to for interrupts) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1271 | |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 1272 | data: |
Ulf Magnusson | 567c348 | 2019-09-26 20:34:13 +0200 | [diff] [blame] | 1273 | A dictionary that maps names from the *-cells key in the binding for the |
| 1274 | controller to data values, e.g. {"pin": 4, "flags": 0} for the example |
| 1275 | above. |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1276 | |
| 1277 | 'interrupts = <1 2>' might give {"irq": 1, "level": 2}. |
| 1278 | |
| 1279 | name: |
| 1280 | The name of the entry as given in |
| 1281 | 'interrupt-names'/'gpio-names'/'pwm-names'/etc., or None if there is no |
| 1282 | *-names property |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1283 | """ |
| 1284 | def __repr__(self): |
| 1285 | fields = [] |
| 1286 | |
| 1287 | if self.name is not None: |
| 1288 | fields.append("name: " + self.name) |
| 1289 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1290 | fields.append("controller: {}".format(self.controller)) |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 1291 | fields.append("data: {}".format(self.data)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1292 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1293 | return "<ControllerAndData, {}>".format(", ".join(fields)) |
Jim Paris | 67f53ba | 2019-08-07 15:05:23 -0400 | [diff] [blame] | 1294 | |
| 1295 | |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 1296 | class PinCtrl: |
| 1297 | """ |
| 1298 | Represents a pin control configuration for a set of pins on a device, |
| 1299 | e.g. pinctrl-0 or pinctrl-1. |
| 1300 | |
| 1301 | These attributes are available on PinCtrl objects: |
| 1302 | |
| 1303 | node: |
| 1304 | The Node instance the pinctrl-* property is on |
| 1305 | |
| 1306 | name: |
| 1307 | The name of the configuration, as given in pinctrl-names, or None if |
| 1308 | there is no pinctrl-names property |
| 1309 | |
| 1310 | conf_nodes: |
| 1311 | A list of Node instances for the pin configuration nodes, e.g. |
| 1312 | the nodes pointed at by &state_1 and &state_2 in |
| 1313 | |
| 1314 | pinctrl-0 = <&state_1 &state_2>; |
| 1315 | """ |
| 1316 | def __repr__(self): |
| 1317 | fields = [] |
| 1318 | |
| 1319 | if self.name is not None: |
| 1320 | fields.append("name: " + self.name) |
| 1321 | |
| 1322 | fields.append("configuration nodes: " + str(self.conf_nodes)) |
| 1323 | |
| 1324 | return "<PinCtrl, {}>".format(", ".join(fields)) |
| 1325 | |
| 1326 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1327 | class Property: |
| 1328 | """ |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1329 | Represents a property on a Node, as set in its DT node and with |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1330 | additional info from the 'properties:' section of the binding. |
| 1331 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1332 | Only properties mentioned in 'properties:' get created. Properties of type |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1333 | 'compound' currently do not get Property instances, as it's not clear |
| 1334 | what to generate for them. |
| 1335 | |
| 1336 | These attributes are available on Property objects. Several are |
| 1337 | just convenience accessors for attributes on the PropertySpec object |
| 1338 | accessible via the 'spec' attribute. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1339 | |
| 1340 | These attributes are available on Property objects: |
| 1341 | |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1342 | node: |
| 1343 | The Node instance the property is on |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1344 | |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1345 | spec: |
| 1346 | The PropertySpec object which specifies this property. |
| 1347 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1348 | name: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1349 | Convenience for spec.name. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1350 | |
| 1351 | description: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1352 | Convenience for spec.name with leading and trailing whitespace |
| 1353 | (including newlines) removed. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1354 | |
Ulf Magnusson | 165dde0 | 2019-08-14 11:13:14 +0200 | [diff] [blame] | 1355 | type: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1356 | Convenience for spec.type. |
Ulf Magnusson | 165dde0 | 2019-08-14 11:13:14 +0200 | [diff] [blame] | 1357 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1358 | val: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1359 | The value of the property, with the format determined by spec.type, |
| 1360 | which comes from the 'type:' string in the binding. |
Ulf Magnusson | c42873f | 2019-08-13 11:52:10 +0200 | [diff] [blame] | 1361 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1362 | - For 'type: int/array/string/string-array', 'val' is what you'd expect |
| 1363 | (a Python integer or string, or a list of them) |
Ulf Magnusson | c42873f | 2019-08-13 11:52:10 +0200 | [diff] [blame] | 1364 | |
Ulf Magnusson | 23a5b49 | 2019-12-30 22:52:11 +0100 | [diff] [blame] | 1365 | - For 'type: phandle' and 'type: path', 'val' is the pointed-to Node |
| 1366 | instance |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 1367 | |
| 1368 | - For 'type: phandles', 'val' is a list of the pointed-to Node |
| 1369 | instances |
| 1370 | |
| 1371 | - For 'type: phandle-array', 'val' is a list of ControllerAndData |
| 1372 | instances. See the documentation for that class. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1373 | |
Martí Bolívar | b6dc0a2 | 2020-12-02 14:03:46 -0800 | [diff] [blame] | 1374 | val_as_token: |
| 1375 | The value of the property as a token, i.e. with non-alphanumeric |
| 1376 | characters replaced with underscores. This is only safe to access |
| 1377 | if self.enum_tokenizable returns True. |
| 1378 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1379 | enum_index: |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1380 | The index of 'val' in 'spec.enum' (which comes from the 'enum:' list |
| 1381 | in the binding), or None if spec.enum is None. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1382 | """ |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1383 | |
| 1384 | def __init__(self, spec, val, node): |
| 1385 | self.val = val |
| 1386 | self.spec = spec |
| 1387 | self.node = node |
| 1388 | |
| 1389 | @property |
| 1390 | def name(self): |
| 1391 | "See the class docstring" |
| 1392 | return self.spec.name |
| 1393 | |
| 1394 | @property |
| 1395 | def description(self): |
| 1396 | "See the class docstring" |
| 1397 | return self.spec.description.strip() |
| 1398 | |
| 1399 | @property |
| 1400 | def type(self): |
| 1401 | "See the class docstring" |
| 1402 | return self.spec.type |
| 1403 | |
| 1404 | @property |
Martí Bolívar | b6dc0a2 | 2020-12-02 14:03:46 -0800 | [diff] [blame] | 1405 | def val_as_token(self): |
| 1406 | "See the class docstring" |
| 1407 | return re.sub(_NOT_ALPHANUM_OR_UNDERSCORE, '_', self.val) |
| 1408 | |
| 1409 | @property |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 1410 | def enum_index(self): |
| 1411 | "See the class docstring" |
| 1412 | enum = self.spec.enum |
| 1413 | return enum.index(self.val) if enum else None |
| 1414 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1415 | def __repr__(self): |
| 1416 | fields = ["name: " + self.name, |
| 1417 | # repr() to deal with lists |
Ulf Magnusson | 165dde0 | 2019-08-14 11:13:14 +0200 | [diff] [blame] | 1418 | "type: " + self.type, |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1419 | "value: " + repr(self.val)] |
| 1420 | |
| 1421 | if self.enum_index is not None: |
| 1422 | fields.append("enum index: {}".format(self.enum_index)) |
| 1423 | |
| 1424 | return "<Property, {}>".format(", ".join(fields)) |
| 1425 | |
| 1426 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1427 | class Binding: |
| 1428 | """ |
| 1429 | Represents a parsed binding. |
| 1430 | |
| 1431 | These attributes are available on Binding objects: |
| 1432 | |
| 1433 | path: |
| 1434 | The absolute path to the file defining the binding. |
| 1435 | |
| 1436 | description: |
| 1437 | The free-form description of the binding. |
| 1438 | |
| 1439 | compatible: |
Martí Bolívar | 0d4dca1 | 2020-11-03 08:34:04 -0800 | [diff] [blame] | 1440 | The compatible string the binding matches. This is None if the Binding is |
| 1441 | inferred from node properties. If the Binding is a child binding, then |
| 1442 | this will be inherited from the parent binding unless the child binding |
| 1443 | explicitly sets its own compatible. |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1444 | |
| 1445 | prop2specs: |
| 1446 | A collections.OrderedDict mapping property names to PropertySpec objects |
| 1447 | describing those properties' values. |
| 1448 | |
| 1449 | specifier2cells: |
| 1450 | A collections.OrderedDict that maps specifier space names (like "gpio", |
| 1451 | "clock", "pwm", etc.) to lists of cell names. |
| 1452 | |
| 1453 | For example, if the binding YAML contains 'pin' and 'flags' cell names |
| 1454 | for the 'gpio' specifier space, like this: |
| 1455 | |
| 1456 | gpio-cells: |
| 1457 | - pin |
| 1458 | - flags |
| 1459 | |
| 1460 | Then the Binding object will have a 'specifier2cells' attribute mapping |
| 1461 | "gpio" to ["pin", "flags"]. A missing key should be interpreted as zero |
| 1462 | cells. |
| 1463 | |
| 1464 | raw: |
| 1465 | The binding as an object parsed from YAML. |
| 1466 | |
| 1467 | bus: |
| 1468 | If nodes with this binding's 'compatible' describe a bus, a string |
| 1469 | describing the bus type (like "i2c"). None otherwise. |
| 1470 | |
| 1471 | on_bus: |
| 1472 | If nodes with this binding's 'compatible' appear on a bus, a string |
| 1473 | describing the bus type (like "i2c"). None otherwise. |
| 1474 | |
| 1475 | child_binding: |
| 1476 | If this binding describes the properties of child nodes, then |
| 1477 | this is a Binding object for those children; it is None otherwise. |
| 1478 | A Binding object's 'child_binding.child_binding' is not None if there |
| 1479 | are multiple levels of 'child-binding' descriptions in the binding. |
| 1480 | """ |
| 1481 | |
| 1482 | def __init__(self, path, fname2path, raw=None, |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 1483 | require_compatible=True, require_description=True): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1484 | """ |
| 1485 | Binding constructor. |
| 1486 | |
| 1487 | path: |
| 1488 | Path to binding YAML file. May be None. |
| 1489 | |
| 1490 | fname2path: |
| 1491 | Map from include files to their absolute paths. Must |
| 1492 | not be None, but may be empty. |
| 1493 | |
| 1494 | raw: |
| 1495 | Optional raw content in the binding. |
| 1496 | This does not have to have any "include:" lines resolved. |
| 1497 | May be left out, in which case 'path' is opened and read. |
| 1498 | This can be used to resolve child bindings, for example. |
| 1499 | |
| 1500 | require_compatible: |
| 1501 | If True, it is an error if the binding does not contain a |
| 1502 | "compatible:" line. If False, a missing "compatible:" is |
| 1503 | not an error. Either way, "compatible:" must be a string |
| 1504 | if it is present in the binding. |
| 1505 | |
| 1506 | require_description: |
| 1507 | If True, it is an error if the binding does not contain a |
| 1508 | "description:" line. If False, a missing "description:" is |
| 1509 | not an error. Either way, "description:" must be a string |
| 1510 | if it is present in the binding. |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1511 | """ |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1512 | self.path = path |
| 1513 | self._fname2path = fname2path |
| 1514 | |
| 1515 | if raw is None: |
| 1516 | with open(path, encoding="utf-8") as f: |
| 1517 | raw = yaml.load(f, Loader=_BindingLoader) |
| 1518 | |
| 1519 | # Merge any included files into self.raw. This also pulls in |
| 1520 | # inherited child binding definitions, so it has to be done |
| 1521 | # before initializing those. |
| 1522 | self.raw = self._merge_includes(raw, self.path) |
| 1523 | |
| 1524 | # Recursively initialize any child bindings. These don't |
| 1525 | # require a 'compatible' or 'description' to be well defined, |
| 1526 | # but they must be dicts. |
| 1527 | if "child-binding" in raw: |
| 1528 | if not isinstance(raw["child-binding"], dict): |
| 1529 | _err(f"malformed 'child-binding:' in {self.path}, " |
| 1530 | "expected a binding (dictionary with keys/values)") |
| 1531 | self.child_binding = Binding(path, fname2path, |
| 1532 | raw=raw["child-binding"], |
| 1533 | require_compatible=False, |
| 1534 | require_description=False) |
| 1535 | else: |
| 1536 | self.child_binding = None |
| 1537 | |
| 1538 | # Make sure this is a well defined object. |
| 1539 | self._check(require_compatible, require_description) |
| 1540 | |
| 1541 | # Initialize look up tables. |
| 1542 | self.prop2specs = OrderedDict() |
| 1543 | for prop_name in self.raw.get("properties", {}).keys(): |
| 1544 | self.prop2specs[prop_name] = PropertySpec(prop_name, self) |
| 1545 | self.specifier2cells = OrderedDict() |
| 1546 | for key, val in self.raw.items(): |
| 1547 | if key.endswith("-cells"): |
| 1548 | self.specifier2cells[key[:-len("-cells")]] = val |
| 1549 | |
Martí Bolívar | 0d4dca1 | 2020-11-03 08:34:04 -0800 | [diff] [blame] | 1550 | # Make child binding compatibles match ours if they are missing. |
| 1551 | if self.compatible is not None: |
| 1552 | child = self.child_binding |
| 1553 | while child is not None: |
| 1554 | if child.compatible is None: |
| 1555 | child.compatible = self.compatible |
| 1556 | child = child.child_binding |
| 1557 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1558 | def __repr__(self): |
| 1559 | if self.compatible: |
| 1560 | compat = f" for compatible '{self.compatible}'" |
| 1561 | else: |
| 1562 | compat = "" |
| 1563 | return f"<Binding {os.path.basename(self.path)}" + compat + ">" |
| 1564 | |
| 1565 | @property |
| 1566 | def description(self): |
| 1567 | "See the class docstring" |
| 1568 | return self.raw['description'] |
| 1569 | |
| 1570 | @property |
| 1571 | def compatible(self): |
| 1572 | "See the class docstring" |
Martí Bolívar | 0d4dca1 | 2020-11-03 08:34:04 -0800 | [diff] [blame] | 1573 | if hasattr(self, '_compatible'): |
| 1574 | return self._compatible |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1575 | return self.raw.get('compatible') |
| 1576 | |
Martí Bolívar | 0d4dca1 | 2020-11-03 08:34:04 -0800 | [diff] [blame] | 1577 | @compatible.setter |
| 1578 | def compatible(self, compatible): |
| 1579 | "See the class docstring" |
| 1580 | self._compatible = compatible |
| 1581 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1582 | @property |
| 1583 | def bus(self): |
| 1584 | "See the class docstring" |
| 1585 | return self.raw.get('bus') |
| 1586 | |
| 1587 | @property |
| 1588 | def on_bus(self): |
| 1589 | "See the class docstring" |
| 1590 | return self.raw.get('on-bus') |
| 1591 | |
| 1592 | def _merge_includes(self, raw, binding_path): |
| 1593 | # Constructor helper. Merges included files in |
| 1594 | # 'raw["include"]' into 'raw' using 'self._include_paths' as a |
| 1595 | # source of include files, removing the "include" key while |
| 1596 | # doing so. |
| 1597 | # |
| 1598 | # This treats 'binding_path' as the binding file being built up |
| 1599 | # and uses it for error messages. |
| 1600 | |
| 1601 | if "include" not in raw: |
| 1602 | return raw |
| 1603 | |
| 1604 | include = raw.pop("include") |
| 1605 | fnames = [] |
| 1606 | if isinstance(include, str): |
| 1607 | fnames.append(include) |
| 1608 | elif isinstance(include, list): |
Martí Bolívar | 876b961 | 2020-10-16 10:19:54 -0700 | [diff] [blame] | 1609 | if not all(isinstance(elem, str) for elem in include): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1610 | _err(f"all elements in 'include:' in {binding_path} " |
| 1611 | "should be strings") |
| 1612 | fnames += include |
| 1613 | else: |
| 1614 | _err(f"'include:' in {binding_path} " |
| 1615 | "should be a string or a list of strings") |
| 1616 | |
| 1617 | # First, merge the included files together. If more than one included |
| 1618 | # file has a 'required:' for a particular property, OR the values |
| 1619 | # together, so that 'required: true' wins. |
| 1620 | |
| 1621 | merged = {} |
| 1622 | for fname in fnames: |
| 1623 | _merge_props(merged, self._load_raw(fname), None, binding_path, |
| 1624 | check_required=False) |
| 1625 | |
| 1626 | # Next, merge the merged included files into 'raw'. Error out if |
| 1627 | # 'raw' has 'required: false' while the merged included files have |
| 1628 | # 'required: true'. |
| 1629 | |
| 1630 | _merge_props(raw, merged, None, binding_path, check_required=True) |
| 1631 | |
| 1632 | return raw |
| 1633 | |
| 1634 | def _load_raw(self, fname): |
| 1635 | # Returns the contents of the binding given by 'fname' after merging |
| 1636 | # any bindings it lists in 'include:' into it. 'fname' is just the |
| 1637 | # basename of the file, so we check that there aren't multiple |
| 1638 | # candidates. |
| 1639 | |
| 1640 | path = self._fname2path.get(fname) |
| 1641 | |
| 1642 | if not path: |
| 1643 | _err(f"'{fname}' not found") |
| 1644 | |
| 1645 | with open(path, encoding="utf-8") as f: |
| 1646 | contents = yaml.load(f, Loader=_BindingLoader) |
| 1647 | |
| 1648 | return self._merge_includes(contents, path) |
| 1649 | |
| 1650 | def _check(self, require_compatible, require_description): |
| 1651 | # Does sanity checking on the binding. |
| 1652 | |
| 1653 | raw = self.raw |
| 1654 | |
| 1655 | if "compatible" in raw: |
| 1656 | compatible = raw["compatible"] |
| 1657 | if not isinstance(compatible, str): |
| 1658 | _err(f"malformed 'compatible: {compatible}' " |
| 1659 | f"field in {self.path} - " |
| 1660 | f"should be a string, not {type(compatible).__name__}") |
| 1661 | elif require_compatible: |
| 1662 | _err(f"missing 'compatible' property in {self.path}") |
| 1663 | |
| 1664 | if "description" not in raw and require_description: |
| 1665 | _err(f"missing 'description' property in {self.path}") |
| 1666 | |
| 1667 | for prop in "title", "description": |
| 1668 | if prop in raw and (not isinstance(raw[prop], str) or |
| 1669 | not raw[prop]): |
| 1670 | _err(f"malformed or empty '{prop}' in {self.path}") |
| 1671 | |
| 1672 | ok_top = {"title", "description", "compatible", "properties", |
| 1673 | "bus", "on-bus", "parent-bus", "child-bus", "parent", "child", |
| 1674 | "child-binding", "sub-node"} |
| 1675 | |
| 1676 | for prop in raw: |
| 1677 | if prop == "#cells": # clean error for users of legacy syntax |
| 1678 | _err(f"malformed '{prop}:' in {self.path}, " |
| 1679 | "expected *-cells syntax") |
| 1680 | if prop not in ok_top and not prop.endswith("-cells"): |
| 1681 | _err(f"unknown key '{prop}' in {self.path}, " |
| 1682 | "expected one of {', '.join(ok_top)}, or *-cells") |
| 1683 | |
| 1684 | for bus_key in "bus", "on-bus": |
| 1685 | if bus_key in raw and \ |
| 1686 | not isinstance(raw[bus_key], str): |
| 1687 | _err(f"malformed '{bus_key}:' value in {self.path}, " |
| 1688 | "expected string") |
| 1689 | |
| 1690 | self._check_properties() |
| 1691 | |
| 1692 | for key, val in raw.items(): |
| 1693 | if key.endswith("-cells"): |
| 1694 | if not isinstance(val, list) or \ |
Martí Bolívar | 876b961 | 2020-10-16 10:19:54 -0700 | [diff] [blame] | 1695 | not all(isinstance(elem, str) for elem in val): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1696 | _err(f"malformed '{key}:' in {self.path}, " |
| 1697 | "expected a list of strings") |
| 1698 | |
| 1699 | def _check_properties(self): |
| 1700 | # _check() helper for checking the contents of 'properties:'. |
| 1701 | |
| 1702 | raw = self.raw |
| 1703 | |
| 1704 | if "properties" not in raw: |
| 1705 | return |
| 1706 | |
Kumar Gala | c7baf2f | 2020-10-17 11:12:32 -0500 | [diff] [blame] | 1707 | ok_prop_keys = {"description", "type", "required", |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 1708 | "enum", "const", "default", "deprecated"} |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1709 | |
| 1710 | for prop_name, options in raw["properties"].items(): |
| 1711 | for key in options: |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1712 | if key not in ok_prop_keys: |
| 1713 | _err(f"unknown setting '{key}' in " |
| 1714 | f"'properties: {prop_name}: ...' in {self.path}, " |
| 1715 | f"expected one of {', '.join(ok_prop_keys)}") |
| 1716 | |
| 1717 | _check_prop_type_and_default( |
| 1718 | prop_name, options.get("type"), |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1719 | options.get("default"), |
| 1720 | self.path) |
| 1721 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 1722 | for true_false_opt in ["required", "deprecated"]: |
| 1723 | if true_false_opt in options: |
| 1724 | option = options[true_false_opt] |
| 1725 | if not isinstance(option, bool): |
| 1726 | _err(f"malformed '{true_false_opt}:' setting '{option}' " |
| 1727 | f"for '{prop_name}' in 'properties' in {self.path}, " |
| 1728 | "expected true/false") |
| 1729 | |
| 1730 | if options.get("deprecated") and options.get("required"): |
| 1731 | _err(f"'{prop_name}' in 'properties' in {self.path} should not " |
| 1732 | "have both 'deprecated' and 'required' set") |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1733 | |
| 1734 | if "description" in options and \ |
| 1735 | not isinstance(options["description"], str): |
| 1736 | _err("missing, malformed, or empty 'description' for " |
| 1737 | f"'{prop_name}' in 'properties' in {self.path}") |
| 1738 | |
| 1739 | if "enum" in options and not isinstance(options["enum"], list): |
| 1740 | _err(f"enum in {self.path} for property '{prop_name}' " |
| 1741 | "is not a list") |
| 1742 | |
| 1743 | if "const" in options and not isinstance(options["const"], |
| 1744 | (int, str)): |
| 1745 | _err(f"const in {self.path} for property '{prop_name}' " |
| 1746 | "is not a scalar") |
| 1747 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1748 | |
Martí Bolívar | 2c19ccc | 2020-10-19 21:42:53 -0700 | [diff] [blame] | 1749 | def bindings_from_paths(yaml_paths, ignore_errors=False): |
| 1750 | """ |
| 1751 | Get a list of Binding objects from the yaml files 'yaml_paths'. |
| 1752 | |
| 1753 | If 'ignore_errors' is True, YAML files that cause an EDTError when |
| 1754 | loaded are ignored. (No other exception types are silenced.) |
| 1755 | """ |
| 1756 | |
| 1757 | ret = [] |
| 1758 | fname2path = {os.path.basename(path): path for path in yaml_paths} |
| 1759 | for path in yaml_paths: |
| 1760 | try: |
| 1761 | ret.append(Binding(path, fname2path)) |
| 1762 | except EDTError: |
| 1763 | if ignore_errors: |
| 1764 | continue |
| 1765 | raise |
| 1766 | |
| 1767 | return ret |
| 1768 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1769 | class PropertySpec: |
| 1770 | """ |
| 1771 | Represents a "property specification", i.e. the description of a |
| 1772 | property provided by a binding file, like its type and description. |
| 1773 | |
| 1774 | These attributes are available on PropertySpec objects: |
| 1775 | |
| 1776 | binding: |
| 1777 | The Binding object which defined this property. |
| 1778 | |
| 1779 | name: |
| 1780 | The property's name. |
| 1781 | |
| 1782 | path: |
| 1783 | The file where this property was defined. In case a binding includes |
| 1784 | other bindings, this is the file where the property was last modified. |
| 1785 | |
| 1786 | type: |
| 1787 | The type of the property as a string, as given in the binding. |
| 1788 | |
| 1789 | description: |
| 1790 | The free-form description of the property as a string, or None. |
| 1791 | |
| 1792 | enum: |
| 1793 | A list of values the property may take as given in the binding, or None. |
| 1794 | |
Martí Bolívar | b6dc0a2 | 2020-12-02 14:03:46 -0800 | [diff] [blame] | 1795 | enum_tokenizable: |
| 1796 | True if enum is not None and all the values in it are tokenizable; |
| 1797 | False otherwise. |
| 1798 | |
| 1799 | A property must have string type and an "enum:" in its binding to be |
| 1800 | tokenizable. Additionally, the "enum:" values must be unique after |
| 1801 | converting all non-alphanumeric characters to underscores (so "foo bar" |
| 1802 | and "foo_bar" in the same "enum:" would not be tokenizable). |
| 1803 | |
| 1804 | enum_upper_tokenizable: |
| 1805 | Like 'enum_tokenizable', with the additional restriction that the |
| 1806 | "enum:" values must be unique after uppercasing and converting |
| 1807 | non-alphanumeric characters to underscores. |
| 1808 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1809 | const: |
| 1810 | The property's constant value as given in the binding, or None. |
| 1811 | |
| 1812 | default: |
| 1813 | The property's default value as given in the binding, or None. |
| 1814 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 1815 | deprecated: |
| 1816 | True if the property is deprecated; False otherwise. |
| 1817 | |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1818 | required: |
| 1819 | True if the property is marked required; False otherwise. |
| 1820 | """ |
| 1821 | |
| 1822 | def __init__(self, name, binding): |
| 1823 | self.binding = binding |
| 1824 | self.name = name |
| 1825 | self._raw = self.binding.raw["properties"][name] |
| 1826 | |
| 1827 | def __repr__(self): |
| 1828 | return f"<PropertySpec {self.name} type '{self.type}'>" |
| 1829 | |
| 1830 | @property |
| 1831 | def path(self): |
| 1832 | "See the class docstring" |
| 1833 | return self.binding.path |
| 1834 | |
| 1835 | @property |
| 1836 | def type(self): |
| 1837 | "See the class docstring" |
| 1838 | return self._raw["type"] |
| 1839 | |
| 1840 | @property |
| 1841 | def description(self): |
| 1842 | "See the class docstring" |
| 1843 | return self._raw.get("description") |
| 1844 | |
| 1845 | @property |
| 1846 | def enum(self): |
| 1847 | "See the class docstring" |
| 1848 | return self._raw.get("enum") |
| 1849 | |
| 1850 | @property |
Martí Bolívar | b6dc0a2 | 2020-12-02 14:03:46 -0800 | [diff] [blame] | 1851 | def enum_tokenizable(self): |
| 1852 | "See the class docstring" |
| 1853 | if not hasattr(self, '_enum_tokenizable'): |
| 1854 | if self.type != 'string' or self.enum is None: |
| 1855 | self._enum_tokenizable = False |
| 1856 | else: |
| 1857 | # Saving _as_tokens here lets us reuse it in |
| 1858 | # enum_upper_tokenizable. |
| 1859 | self._as_tokens = [re.sub(_NOT_ALPHANUM_OR_UNDERSCORE, |
| 1860 | '_', value) |
| 1861 | for value in self.enum] |
| 1862 | self._enum_tokenizable = (len(self._as_tokens) == |
| 1863 | len(set(self._as_tokens))) |
| 1864 | |
| 1865 | return self._enum_tokenizable |
| 1866 | |
| 1867 | @property |
| 1868 | def enum_upper_tokenizable(self): |
| 1869 | "See the class docstring" |
| 1870 | if not hasattr(self, '_enum_upper_tokenizable'): |
| 1871 | if not self.enum_tokenizable: |
| 1872 | self._enum_upper_tokenizable = False |
| 1873 | else: |
| 1874 | self._enum_upper_tokenizable = \ |
| 1875 | (len(self._as_tokens) == |
| 1876 | len(set(x.upper() for x in self._as_tokens))) |
| 1877 | return self._enum_upper_tokenizable |
| 1878 | |
| 1879 | @property |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1880 | def const(self): |
| 1881 | "See the class docstring" |
| 1882 | return self._raw.get("const") |
| 1883 | |
| 1884 | @property |
| 1885 | def default(self): |
| 1886 | "See the class docstring" |
| 1887 | return self._raw.get("default") |
| 1888 | |
| 1889 | @property |
| 1890 | def required(self): |
| 1891 | "See the class docstring" |
| 1892 | return self._raw.get("required", False) |
| 1893 | |
Kumar Gala | 33db7b5 | 2020-10-17 11:46:03 -0500 | [diff] [blame] | 1894 | @property |
| 1895 | def deprecated(self): |
| 1896 | "See the class docstring" |
| 1897 | return self._raw.get("deprecated", False) |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1898 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1899 | class EDTError(Exception): |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1900 | "Exception raised for devicetree- and binding-related errors" |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1901 | |
| 1902 | |
| 1903 | # |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1904 | # Private global functions |
| 1905 | # |
| 1906 | |
| 1907 | |
| 1908 | def _dt_compats(dt): |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 1909 | # Returns a set() with all 'compatible' strings in the devicetree |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1910 | # represented by dt (a dtlib.DT instance) |
| 1911 | |
| 1912 | return {compat |
| 1913 | for node in dt.node_iter() |
| 1914 | if "compatible" in node.props |
| 1915 | for compat in node.props["compatible"].to_strings()} |
| 1916 | |
| 1917 | |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 1918 | def _binding_paths(bindings_dirs): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1919 | # Returns a list with the paths to all bindings (.yaml files) in |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 1920 | # 'bindings_dirs' |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1921 | |
Michael Scott | b890943 | 2019-08-02 11:42:06 -0700 | [diff] [blame] | 1922 | binding_paths = [] |
| 1923 | |
| 1924 | for bindings_dir in bindings_dirs: |
| 1925 | for root, _, filenames in os.walk(bindings_dir): |
| 1926 | for filename in filenames: |
| 1927 | if filename.endswith(".yaml"): |
| 1928 | binding_paths.append(os.path.join(root, filename)) |
| 1929 | |
| 1930 | return binding_paths |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1931 | |
| 1932 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1933 | def _binding_inc_error(msg): |
| 1934 | # Helper for reporting errors in the !include implementation |
| 1935 | |
| 1936 | raise yaml.constructor.ConstructorError(None, None, "error: " + msg) |
| 1937 | |
| 1938 | |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1939 | def _merge_props(to_dict, from_dict, parent, binding_path, check_required): |
Ulf Magnusson | d834b69 | 2019-08-21 16:41:03 +0200 | [diff] [blame] | 1940 | # Recursively merges 'from_dict' into 'to_dict', to implement 'include:'. |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1941 | # |
| 1942 | # If 'from_dict' and 'to_dict' contain a 'required:' key for the same |
| 1943 | # property, then the values are ORed together. |
| 1944 | # |
| 1945 | # If 'check_required' is True, then an error is raised if 'from_dict' has |
| 1946 | # 'required: true' while 'to_dict' has 'required: false'. This prevents |
| 1947 | # bindings from "downgrading" requirements from bindings they include, |
| 1948 | # which might help keep bindings well-organized. |
| 1949 | # |
| 1950 | # It's an error for most other keys to appear in both 'from_dict' and |
| 1951 | # 'to_dict'. When it's not an error, the value in 'to_dict' takes |
| 1952 | # precedence. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1953 | # |
| 1954 | # 'parent' is the name of the parent key containing 'to_dict' and |
| 1955 | # 'from_dict', and 'binding_path' is the path to the top-level binding. |
| 1956 | # These are used to generate errors for sketchy property overwrites. |
| 1957 | |
| 1958 | for prop in from_dict: |
| 1959 | if isinstance(to_dict.get(prop), dict) and \ |
| 1960 | isinstance(from_dict[prop], dict): |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1961 | _merge_props(to_dict[prop], from_dict[prop], prop, binding_path, |
| 1962 | check_required) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1963 | elif prop not in to_dict: |
| 1964 | to_dict[prop] = from_dict[prop] |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1965 | elif _bad_overwrite(to_dict, from_dict, prop, check_required): |
Ulf Magnusson | d834b69 | 2019-08-21 16:41:03 +0200 | [diff] [blame] | 1966 | _err("{} (in '{}'): '{}' from included file overwritten " |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1967 | "('{}' replaced with '{}')".format( |
| 1968 | binding_path, parent, prop, from_dict[prop], |
| 1969 | to_dict[prop])) |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1970 | elif prop == "required": |
| 1971 | # Need a separate check here, because this code runs before |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 1972 | # Binding._check() |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1973 | if not (isinstance(from_dict["required"], bool) and |
| 1974 | isinstance(to_dict["required"], bool)): |
| 1975 | _err("malformed 'required:' setting for '{}' in 'properties' " |
| 1976 | "in {}, expected true/false".format(parent, binding_path)) |
| 1977 | |
| 1978 | # 'required: true' takes precedence |
| 1979 | to_dict["required"] = to_dict["required"] or from_dict["required"] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1980 | |
| 1981 | |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1982 | def _bad_overwrite(to_dict, from_dict, prop, check_required): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1983 | # _merge_props() helper. Returns True in cases where it's bad that |
| 1984 | # to_dict[prop] takes precedence over from_dict[prop]. |
| 1985 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1986 | if to_dict[prop] == from_dict[prop]: |
| 1987 | return False |
| 1988 | |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1989 | # These are overridden deliberately |
| 1990 | if prop in {"title", "description", "compatible"}: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1991 | return False |
| 1992 | |
Ulf Magnusson | 2845d8f | 2019-09-10 19:17:29 +0200 | [diff] [blame] | 1993 | if prop == "required": |
| 1994 | if not check_required: |
| 1995 | return False |
| 1996 | return from_dict[prop] and not to_dict[prop] |
| 1997 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 1998 | return True |
| 1999 | |
| 2000 | |
Ulf Magnusson | 8f22529 | 2019-09-05 22:31:09 +0200 | [diff] [blame] | 2001 | def _binding_include(loader, node): |
| 2002 | # Implements !include, for backwards compatibility. '!include [foo, bar]' |
| 2003 | # just becomes [foo, bar]. |
| 2004 | |
| 2005 | if isinstance(node, yaml.ScalarNode): |
| 2006 | # !include foo.yaml |
| 2007 | return [loader.construct_scalar(node)] |
| 2008 | |
| 2009 | if isinstance(node, yaml.SequenceNode): |
| 2010 | # !include [foo.yaml, bar.yaml] |
| 2011 | return loader.construct_sequence(node) |
| 2012 | |
| 2013 | _binding_inc_error("unrecognised node type in !include statement") |
| 2014 | |
| 2015 | |
Kumar Gala | 83be5cb | 2020-10-17 11:23:22 -0500 | [diff] [blame] | 2016 | def _check_prop_type_and_default(prop_name, prop_type, default, binding_path): |
Martí Bolívar | 7165b77 | 2020-10-12 12:32:25 -0700 | [diff] [blame] | 2017 | # Binding._check_properties() helper. Checks 'type:' and 'default:' for the |
| 2018 | # property named 'prop_name' |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 2019 | |
| 2020 | if prop_type is None: |
| 2021 | _err("missing 'type:' for '{}' in 'properties' in {}" |
| 2022 | .format(prop_name, binding_path)) |
| 2023 | |
| 2024 | ok_types = {"boolean", "int", "array", "uint8-array", "string", |
| 2025 | "string-array", "phandle", "phandles", "phandle-array", |
Ulf Magnusson | 23a5b49 | 2019-12-30 22:52:11 +0100 | [diff] [blame] | 2026 | "path", "compound"} |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 2027 | |
| 2028 | if prop_type not in ok_types: |
| 2029 | _err("'{}' in 'properties:' in {} has unknown type '{}', expected one " |
| 2030 | "of {}".format(prop_name, binding_path, prop_type, |
| 2031 | ", ".join(ok_types))) |
| 2032 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2033 | if prop_type == "phandle-array" and not prop_name.endswith("s"): |
| 2034 | _err("'{}' in 'properties:' in {} is 'type: phandle-array', but its " |
| 2035 | "name does not end in -s. This is required since property names " |
| 2036 | "like '#pwm-cells' and 'pwm-names' get derived from 'pwms', for " |
| 2037 | "example.".format(prop_name, binding_path)) |
| 2038 | |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 2039 | # Check default |
| 2040 | |
| 2041 | if default is None: |
| 2042 | return |
| 2043 | |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 2044 | if prop_type in {"boolean", "compound", "phandle", "phandles", |
Ulf Magnusson | 23a5b49 | 2019-12-30 22:52:11 +0100 | [diff] [blame] | 2045 | "phandle-array", "path"}: |
Ulf Magnusson | ff1f752 | 2019-08-29 22:21:33 +0200 | [diff] [blame] | 2046 | _err("'default:' can't be combined with 'type: {}' for '{}' in " |
| 2047 | "'properties:' in {}".format(prop_type, prop_name, binding_path)) |
| 2048 | |
| 2049 | def ok_default(): |
| 2050 | # Returns True if 'default' is an okay default for the property's type |
| 2051 | |
| 2052 | if prop_type == "int" and isinstance(default, int) or \ |
| 2053 | prop_type == "string" and isinstance(default, str): |
| 2054 | return True |
| 2055 | |
| 2056 | # array, uint8-array, or string-array |
| 2057 | |
| 2058 | if not isinstance(default, list): |
| 2059 | return False |
| 2060 | |
| 2061 | if prop_type == "array" and \ |
| 2062 | all(isinstance(val, int) for val in default): |
| 2063 | return True |
| 2064 | |
| 2065 | if prop_type == "uint8-array" and \ |
| 2066 | all(isinstance(val, int) and 0 <= val <= 255 for val in default): |
| 2067 | return True |
| 2068 | |
| 2069 | # string-array |
| 2070 | return all(isinstance(val, str) for val in default) |
| 2071 | |
| 2072 | if not ok_default(): |
| 2073 | _err("'default: {}' is invalid for '{}' in 'properties:' in {}, which " |
| 2074 | "has type {}".format(default, prop_name, binding_path, prop_type)) |
| 2075 | |
| 2076 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2077 | def _translate(addr, node): |
| 2078 | # Recursively translates 'addr' on 'node' to the address space(s) of its |
| 2079 | # parent(s), by looking at 'ranges' properties. Returns the translated |
| 2080 | # address. |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 2081 | # |
| 2082 | # node: |
| 2083 | # dtlib.Node instance |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2084 | |
| 2085 | if not node.parent or "ranges" not in node.parent.props: |
| 2086 | # No translation |
| 2087 | return addr |
| 2088 | |
| 2089 | if not node.parent.props["ranges"].value: |
| 2090 | # DT spec.: "If the property is defined with an <empty> value, it |
| 2091 | # specifies that the parent and child address space is identical, and |
| 2092 | # no address translation is required." |
| 2093 | # |
| 2094 | # Treat this the same as a 'range' that explicitly does a one-to-one |
| 2095 | # mapping, as opposed to there not being any translation. |
| 2096 | return _translate(addr, node.parent) |
| 2097 | |
| 2098 | # Gives the size of each component in a translation 3-tuple in 'ranges' |
| 2099 | child_address_cells = _address_cells(node) |
| 2100 | parent_address_cells = _address_cells(node.parent) |
| 2101 | child_size_cells = _size_cells(node) |
| 2102 | |
| 2103 | # Number of cells for one translation 3-tuple in 'ranges' |
| 2104 | entry_cells = child_address_cells + parent_address_cells + child_size_cells |
| 2105 | |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 2106 | for raw_range in _slice(node.parent, "ranges", 4*entry_cells, |
| 2107 | "4*(<#address-cells> (= {}) + " |
| 2108 | "<#address-cells for parent> (= {}) + " |
| 2109 | "<#size-cells> (= {}))" |
| 2110 | .format(child_address_cells, parent_address_cells, |
| 2111 | child_size_cells)): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2112 | child_addr = to_num(raw_range[:4*child_address_cells]) |
| 2113 | raw_range = raw_range[4*child_address_cells:] |
| 2114 | |
| 2115 | parent_addr = to_num(raw_range[:4*parent_address_cells]) |
| 2116 | raw_range = raw_range[4*parent_address_cells:] |
| 2117 | |
| 2118 | child_len = to_num(raw_range) |
| 2119 | |
| 2120 | if child_addr <= addr < child_addr + child_len: |
| 2121 | # 'addr' is within range of a translation in 'ranges'. Recursively |
| 2122 | # translate it and return the result. |
| 2123 | return _translate(parent_addr + addr - child_addr, node.parent) |
| 2124 | |
| 2125 | # 'addr' is not within range of any translation in 'ranges' |
| 2126 | return addr |
| 2127 | |
| 2128 | |
| 2129 | def _add_names(node, names_ident, objs): |
| 2130 | # Helper for registering names from <foo>-names properties. |
| 2131 | # |
| 2132 | # node: |
Ulf Magnusson | 73ac146 | 2019-09-23 05:14:18 +0200 | [diff] [blame] | 2133 | # edtlib.Node instance |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2134 | # |
| 2135 | # names-ident: |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2136 | # The <foo> part of <foo>-names, e.g. "reg" for "reg-names" |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2137 | # |
| 2138 | # objs: |
| 2139 | # list of objects whose .name field should be set |
| 2140 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2141 | full_names_ident = names_ident + "-names" |
| 2142 | |
| 2143 | if full_names_ident in node.props: |
| 2144 | names = node.props[full_names_ident].to_strings() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2145 | if len(names) != len(objs): |
Ulf Magnusson | eef8d19 | 2019-10-30 16:05:30 +0100 | [diff] [blame] | 2146 | _err("{} property in {} in {} has {} strings, expected {} strings" |
| 2147 | .format(full_names_ident, node.path, node.dt.filename, |
| 2148 | len(names), len(objs))) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2149 | |
| 2150 | for obj, name in zip(objs, names): |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 2151 | if obj is None: |
| 2152 | continue |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2153 | obj.name = name |
| 2154 | else: |
| 2155 | for obj in objs: |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 2156 | if obj is not None: |
| 2157 | obj.name = None |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2158 | |
| 2159 | |
| 2160 | def _interrupt_parent(node): |
| 2161 | # Returns the node pointed at by the closest 'interrupt-parent', searching |
| 2162 | # the parents of 'node'. As of writing, this behavior isn't specified in |
| 2163 | # the DT spec., but seems to match what some .dts files except. |
| 2164 | |
| 2165 | while node: |
| 2166 | if "interrupt-parent" in node.props: |
| 2167 | return node.props["interrupt-parent"].to_node() |
| 2168 | node = node.parent |
| 2169 | |
| 2170 | _err("{!r} has an 'interrupts' property, but neither the node nor any " |
| 2171 | "of its parents has an 'interrupt-parent' property".format(node)) |
| 2172 | |
| 2173 | |
| 2174 | def _interrupts(node): |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2175 | # Returns a list of (<controller>, <data>) tuples, with one tuple per |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2176 | # interrupt generated by 'node'. <controller> is the destination of the |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2177 | # interrupt (possibly after mapping through an 'interrupt-map'), and <data> |
| 2178 | # the data associated with the interrupt (as a 'bytes' object). |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2179 | |
| 2180 | # Takes precedence over 'interrupts' if both are present |
| 2181 | if "interrupts-extended" in node.props: |
| 2182 | prop = node.props["interrupts-extended"] |
| 2183 | return [_map_interrupt(node, iparent, spec) |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2184 | for iparent, spec in _phandle_val_list(prop, "interrupt")] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2185 | |
| 2186 | if "interrupts" in node.props: |
| 2187 | # Treat 'interrupts' as a special case of 'interrupts-extended', with |
| 2188 | # the same interrupt parent for all interrupts |
| 2189 | |
| 2190 | iparent = _interrupt_parent(node) |
| 2191 | interrupt_cells = _interrupt_cells(iparent) |
| 2192 | |
| 2193 | return [_map_interrupt(node, iparent, raw) |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 2194 | for raw in _slice(node, "interrupts", 4*interrupt_cells, |
| 2195 | "4*<#interrupt-cells>")] |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2196 | |
| 2197 | return [] |
| 2198 | |
| 2199 | |
| 2200 | def _map_interrupt(child, parent, child_spec): |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2201 | # Translates an interrupt headed from 'child' to 'parent' with data |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2202 | # 'child_spec' through any 'interrupt-map' properties. Returns a |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2203 | # (<controller>, <data>) tuple with the final destination after mapping. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2204 | |
| 2205 | if "interrupt-controller" in parent.props: |
| 2206 | return (parent, child_spec) |
| 2207 | |
| 2208 | def own_address_cells(node): |
| 2209 | # Used for parents pointed at by 'interrupt-map'. We can't use |
| 2210 | # _address_cells(), because it's the #address-cells property on 'node' |
| 2211 | # itself that matters. |
| 2212 | |
| 2213 | address_cells = node.props.get("#address-cells") |
| 2214 | if not address_cells: |
| 2215 | _err("missing #address-cells on {!r} (while handling interrupt-map)" |
| 2216 | .format(node)) |
| 2217 | return address_cells.to_num() |
| 2218 | |
| 2219 | def spec_len_fn(node): |
| 2220 | # Can't use _address_cells() here, because it's the #address-cells |
| 2221 | # property on 'node' itself that matters |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2222 | return own_address_cells(node) + _interrupt_cells(node) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2223 | |
| 2224 | parent, raw_spec = _map( |
| 2225 | "interrupt", child, parent, _raw_unit_addr(child) + child_spec, |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2226 | spec_len_fn, require_controller=True) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2227 | |
| 2228 | # Strip the parent unit address part, if any |
| 2229 | return (parent, raw_spec[4*own_address_cells(parent):]) |
| 2230 | |
| 2231 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2232 | def _map_phandle_array_entry(child, parent, child_spec, basename): |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2233 | # Returns a (<controller>, <data>) tuple with the final destination after |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2234 | # mapping through any '<basename>-map' (e.g. gpio-map) properties. See |
| 2235 | # _map_interrupt(). |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2236 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2237 | def spec_len_fn(node): |
| 2238 | prop_name = "#{}-cells".format(basename) |
| 2239 | if prop_name not in node.props: |
| 2240 | _err("expected '{}' property on {!r} (referenced by {!r})" |
| 2241 | .format(prop_name, node, child)) |
| 2242 | return node.props[prop_name].to_num() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2243 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2244 | # Do not require <prefix>-controller for anything but interrupts for now |
| 2245 | return _map(basename, child, parent, child_spec, spec_len_fn, |
| 2246 | require_controller=False) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2247 | |
| 2248 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2249 | def _map(prefix, child, parent, child_spec, spec_len_fn, require_controller): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2250 | # Common code for mapping through <prefix>-map properties, e.g. |
| 2251 | # interrupt-map and gpio-map. |
| 2252 | # |
| 2253 | # prefix: |
| 2254 | # The prefix, e.g. "interrupt" or "gpio" |
| 2255 | # |
| 2256 | # child: |
| 2257 | # The "sender", e.g. the node with 'interrupts = <...>' |
| 2258 | # |
| 2259 | # parent: |
| 2260 | # The "receiver", e.g. a node with 'interrupt-map = <...>' or |
| 2261 | # 'interrupt-controller' (no mapping) |
| 2262 | # |
| 2263 | # child_spec: |
| 2264 | # The data associated with the interrupt/GPIO/etc., as a 'bytes' object, |
| 2265 | # e.g. <1 2> for 'foo-gpios = <&gpio1 1 2>'. |
| 2266 | # |
| 2267 | # spec_len_fn: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2268 | # Function called on a parent specified in a *-map property to get the |
| 2269 | # length of the parent specifier (data after phandle in *-map), in cells |
| 2270 | # |
| 2271 | # require_controller: |
| 2272 | # If True, the final controller node after mapping is required to have |
| 2273 | # to have a <prefix>-controller property. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2274 | |
| 2275 | map_prop = parent.props.get(prefix + "-map") |
| 2276 | if not map_prop: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2277 | if require_controller and prefix + "-controller" not in parent.props: |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2278 | _err("expected '{}-controller' property on {!r} " |
| 2279 | "(referenced by {!r})".format(prefix, parent, child)) |
| 2280 | |
| 2281 | # No mapping |
| 2282 | return (parent, child_spec) |
| 2283 | |
| 2284 | masked_child_spec = _mask(prefix, child, parent, child_spec) |
| 2285 | |
| 2286 | raw = map_prop.value |
| 2287 | while raw: |
| 2288 | if len(raw) < len(child_spec): |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2289 | _err("bad value for {!r}, missing/truncated child data" |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2290 | .format(map_prop)) |
| 2291 | child_spec_entry = raw[:len(child_spec)] |
| 2292 | raw = raw[len(child_spec):] |
| 2293 | |
| 2294 | if len(raw) < 4: |
| 2295 | _err("bad value for {!r}, missing/truncated phandle" |
| 2296 | .format(map_prop)) |
| 2297 | phandle = to_num(raw[:4]) |
| 2298 | raw = raw[4:] |
| 2299 | |
| 2300 | # Parent specified in *-map |
| 2301 | map_parent = parent.dt.phandle2node.get(phandle) |
| 2302 | if not map_parent: |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2303 | _err("bad phandle ({}) in {!r}".format(phandle, map_prop)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2304 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2305 | map_parent_spec_len = 4*spec_len_fn(map_parent) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2306 | if len(raw) < map_parent_spec_len: |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2307 | _err("bad value for {!r}, missing/truncated parent data" |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2308 | .format(map_prop)) |
| 2309 | parent_spec = raw[:map_parent_spec_len] |
| 2310 | raw = raw[map_parent_spec_len:] |
| 2311 | |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2312 | # Got one *-map row. Check if it matches the child data. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2313 | if child_spec_entry == masked_child_spec: |
| 2314 | # Handle *-map-pass-thru |
| 2315 | parent_spec = _pass_thru( |
| 2316 | prefix, child, parent, child_spec, parent_spec) |
| 2317 | |
| 2318 | # Found match. Recursively map and return it. |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2319 | return _map(prefix, parent, map_parent, parent_spec, spec_len_fn, |
| 2320 | require_controller) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2321 | |
Ulf Magnusson | b97ed9e | 2019-09-23 09:10:22 +0200 | [diff] [blame] | 2322 | _err("child specifier for {!r} ({}) does not appear in {!r}" |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2323 | .format(child, child_spec, map_prop)) |
| 2324 | |
| 2325 | |
| 2326 | def _mask(prefix, child, parent, child_spec): |
| 2327 | # Common code for handling <prefix>-mask properties, e.g. interrupt-mask. |
| 2328 | # See _map() for the parameters. |
| 2329 | |
| 2330 | mask_prop = parent.props.get(prefix + "-map-mask") |
| 2331 | if not mask_prop: |
| 2332 | # No mask |
| 2333 | return child_spec |
| 2334 | |
| 2335 | mask = mask_prop.value |
| 2336 | if len(mask) != len(child_spec): |
| 2337 | _err("{!r}: expected '{}-mask' in {!r} to be {} bytes, is {} bytes" |
| 2338 | .format(child, prefix, parent, len(child_spec), len(mask))) |
| 2339 | |
| 2340 | return _and(child_spec, mask) |
| 2341 | |
| 2342 | |
| 2343 | def _pass_thru(prefix, child, parent, child_spec, parent_spec): |
| 2344 | # Common code for handling <prefix>-map-thru properties, e.g. |
| 2345 | # interrupt-pass-thru. |
| 2346 | # |
| 2347 | # parent_spec: |
Ulf Magnusson | 6b87504 | 2019-09-23 07:40:16 +0200 | [diff] [blame] | 2348 | # The parent data from the matched entry in the <prefix>-map property |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2349 | # |
| 2350 | # See _map() for the other parameters. |
| 2351 | |
| 2352 | pass_thru_prop = parent.props.get(prefix + "-map-pass-thru") |
| 2353 | if not pass_thru_prop: |
| 2354 | # No pass-thru |
| 2355 | return parent_spec |
| 2356 | |
| 2357 | pass_thru = pass_thru_prop.value |
| 2358 | if len(pass_thru) != len(child_spec): |
| 2359 | _err("{!r}: expected '{}-map-pass-thru' in {!r} to be {} bytes, is {} bytes" |
| 2360 | .format(child, prefix, parent, len(child_spec), len(pass_thru))) |
| 2361 | |
| 2362 | res = _or(_and(child_spec, pass_thru), |
| 2363 | _and(parent_spec, _not(pass_thru))) |
| 2364 | |
| 2365 | # Truncate to length of parent spec. |
| 2366 | return res[-len(parent_spec):] |
| 2367 | |
| 2368 | |
| 2369 | def _raw_unit_addr(node): |
| 2370 | # _map_interrupt() helper. Returns the unit address (derived from 'reg' and |
| 2371 | # #address-cells) as a raw 'bytes' |
| 2372 | |
| 2373 | if 'reg' not in node.props: |
| 2374 | _err("{!r} lacks 'reg' property (needed for 'interrupt-map' unit " |
| 2375 | "address lookup)".format(node)) |
| 2376 | |
| 2377 | addr_len = 4*_address_cells(node) |
| 2378 | |
| 2379 | if len(node.props['reg'].value) < addr_len: |
| 2380 | _err("{!r} has too short 'reg' property (while doing 'interrupt-map' " |
| 2381 | "unit address lookup)".format(node)) |
| 2382 | |
| 2383 | return node.props['reg'].value[:addr_len] |
| 2384 | |
| 2385 | |
| 2386 | def _and(b1, b2): |
| 2387 | # Returns the bitwise AND of the two 'bytes' objects b1 and b2. Pads |
| 2388 | # with ones on the left if the lengths are not equal. |
| 2389 | |
| 2390 | # Pad on the left, to equal length |
| 2391 | maxlen = max(len(b1), len(b2)) |
| 2392 | return bytes(x & y for x, y in zip(b1.rjust(maxlen, b'\xff'), |
| 2393 | b2.rjust(maxlen, b'\xff'))) |
| 2394 | |
| 2395 | |
| 2396 | def _or(b1, b2): |
| 2397 | # Returns the bitwise OR of the two 'bytes' objects b1 and b2. Pads with |
| 2398 | # zeros on the left if the lengths are not equal. |
| 2399 | |
| 2400 | # Pad on the left, to equal length |
| 2401 | maxlen = max(len(b1), len(b2)) |
| 2402 | return bytes(x | y for x, y in zip(b1.rjust(maxlen, b'\x00'), |
| 2403 | b2.rjust(maxlen, b'\x00'))) |
| 2404 | |
| 2405 | |
| 2406 | def _not(b): |
| 2407 | # Returns the bitwise not of the 'bytes' object 'b' |
| 2408 | |
| 2409 | # ANDing with 0xFF avoids negative numbers |
| 2410 | return bytes(~x & 0xFF for x in b) |
| 2411 | |
| 2412 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2413 | def _phandle_val_list(prop, n_cells_name): |
| 2414 | # Parses a '<phandle> <value> <phandle> <value> ...' value. The number of |
| 2415 | # cells that make up each <value> is derived from the node pointed at by |
| 2416 | # the preceding <phandle>. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2417 | # |
| 2418 | # prop: |
| 2419 | # dtlib.Property with value to parse |
| 2420 | # |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2421 | # n_cells_name: |
| 2422 | # The <name> part of the #<name>-cells property to look for on the nodes |
| 2423 | # the phandles point to, e.g. "gpio" for #gpio-cells. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2424 | # |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 2425 | # Returns a list[Optional[tuple]]. |
| 2426 | # |
| 2427 | # Each tuple in the list is a (<node>, <value>) pair, where <node> |
| 2428 | # is the node pointed at by <phandle>. If <phandle> does not refer |
| 2429 | # to a node, the entire list element is None. |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2430 | |
| 2431 | full_n_cells_name = "#{}-cells".format(n_cells_name) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2432 | |
| 2433 | res = [] |
| 2434 | |
| 2435 | raw = prop.value |
| 2436 | while raw: |
| 2437 | if len(raw) < 4: |
| 2438 | # Not enough room for phandle |
| 2439 | _err("bad value for " + repr(prop)) |
| 2440 | phandle = to_num(raw[:4]) |
| 2441 | raw = raw[4:] |
| 2442 | |
| 2443 | node = prop.node.dt.phandle2node.get(phandle) |
| 2444 | if not node: |
Martí Bolívar | 38ede5a | 2020-12-17 14:12:01 -0800 | [diff] [blame] | 2445 | # Unspecified phandle-array element. This is valid; a 0 |
| 2446 | # phandle value followed by no cells is an empty element. |
| 2447 | res.append(None) |
| 2448 | continue |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2449 | |
Ulf Magnusson | 95deec1 | 2019-08-06 21:51:06 +0200 | [diff] [blame] | 2450 | if full_n_cells_name not in node.props: |
| 2451 | _err("{!r} lacks {}".format(node, full_n_cells_name)) |
| 2452 | |
| 2453 | n_cells = node.props[full_n_cells_name].to_num() |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2454 | if len(raw) < 4*n_cells: |
| 2455 | _err("missing data after phandle in " + repr(prop)) |
| 2456 | |
| 2457 | res.append((node, raw[:4*n_cells])) |
| 2458 | raw = raw[4*n_cells:] |
| 2459 | |
| 2460 | return res |
| 2461 | |
| 2462 | |
| 2463 | def _address_cells(node): |
| 2464 | # Returns the #address-cells setting for 'node', giving the number of <u32> |
| 2465 | # cells used to encode the address in the 'reg' property |
| 2466 | |
| 2467 | if "#address-cells" in node.parent.props: |
| 2468 | return node.parent.props["#address-cells"].to_num() |
| 2469 | return 2 # Default value per DT spec. |
| 2470 | |
| 2471 | |
| 2472 | def _size_cells(node): |
| 2473 | # Returns the #size-cells setting for 'node', giving the number of <u32> |
| 2474 | # cells used to encode the size in the 'reg' property |
| 2475 | |
| 2476 | if "#size-cells" in node.parent.props: |
| 2477 | return node.parent.props["#size-cells"].to_num() |
| 2478 | return 1 # Default value per DT spec. |
| 2479 | |
| 2480 | |
| 2481 | def _interrupt_cells(node): |
| 2482 | # Returns the #interrupt-cells property value on 'node', erroring out if |
| 2483 | # 'node' has no #interrupt-cells property |
| 2484 | |
| 2485 | if "#interrupt-cells" not in node.props: |
| 2486 | _err("{!r} lacks #interrupt-cells".format(node)) |
| 2487 | return node.props["#interrupt-cells"].to_num() |
| 2488 | |
| 2489 | |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 2490 | def _slice(node, prop_name, size, size_hint): |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2491 | # Splits node.props[prop_name].value into 'size'-sized chunks, returning a |
| 2492 | # list of chunks. Raises EDTError if the length of the property is not |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 2493 | # evenly divisible by 'size'. 'size_hint' is a string shown on errors that |
| 2494 | # gives a hint on how 'size' was calculated. |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2495 | |
| 2496 | raw = node.props[prop_name].value |
| 2497 | if len(raw) % size: |
| 2498 | _err("'{}' property in {!r} has length {}, which is not evenly " |
Ulf Magnusson | 57b2d27 | 2019-12-28 17:11:32 +0100 | [diff] [blame] | 2499 | "divisible by {} (= {}). Note that #*-cells " |
| 2500 | "properties come either from the parent node or from the " |
| 2501 | "controller (in the case of 'interrupts')." |
| 2502 | .format(prop_name, node, len(raw), size, size_hint)) |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2503 | |
| 2504 | return [raw[i:i + size] for i in range(0, len(raw), size)] |
| 2505 | |
| 2506 | |
Ulf Magnusson | acf276f | 2019-08-07 19:33:45 +0200 | [diff] [blame] | 2507 | def _check_dt(dt): |
| 2508 | # Does devicetree sanity checks. dtlib is meant to be general and |
| 2509 | # anything-goes except for very special properties like phandle, but in |
| 2510 | # edtlib we can be pickier. |
| 2511 | |
| 2512 | # Check that 'status' has one of the values given in the devicetree spec. |
| 2513 | |
Ulf Magnusson | fc5cd77 | 2019-09-26 13:47:58 +0200 | [diff] [blame] | 2514 | # Accept "ok" for backwards compatibility |
| 2515 | ok_status = {"ok", "okay", "disabled", "reserved", "fail", "fail-sss"} |
Ulf Magnusson | acf276f | 2019-08-07 19:33:45 +0200 | [diff] [blame] | 2516 | |
| 2517 | for node in dt.node_iter(): |
| 2518 | if "status" in node.props: |
| 2519 | try: |
| 2520 | status_val = node.props["status"].to_string() |
| 2521 | except DTError as e: |
| 2522 | # The error message gives the path |
| 2523 | _err(str(e)) |
| 2524 | |
| 2525 | if status_val not in ok_status: |
| 2526 | _err("unknown 'status' value \"{}\" in {} in {}, expected one " |
| 2527 | "of {} (see the devicetree specification)" |
| 2528 | .format(status_val, node.path, node.dt.filename, |
| 2529 | ", ".join(ok_status))) |
| 2530 | |
Ulf Magnusson | 378254a | 2019-08-14 18:44:35 +0200 | [diff] [blame] | 2531 | ranges_prop = node.props.get("ranges") |
| 2532 | if ranges_prop: |
| 2533 | if ranges_prop.type not in (TYPE_EMPTY, TYPE_NUMS): |
| 2534 | _err("expected 'ranges = < ... >;' in {} in {}, not '{}' " |
| 2535 | "(see the devicetree specification)" |
| 2536 | .format(node.path, node.dt.filename, ranges_prop)) |
| 2537 | |
Ulf Magnusson | acf276f | 2019-08-07 19:33:45 +0200 | [diff] [blame] | 2538 | |
Ulf Magnusson | 62d5741 | 2018-12-17 20:09:47 +0100 | [diff] [blame] | 2539 | def _err(msg): |
| 2540 | raise EDTError(msg) |
Ulf Magnusson | 0e23994 | 2019-11-12 18:21:02 +0100 | [diff] [blame] | 2541 | |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 2542 | # Logging object |
| 2543 | _LOG = logging.getLogger(__name__) |
| 2544 | |
Martí Bolívar | b6dc0a2 | 2020-12-02 14:03:46 -0800 | [diff] [blame] | 2545 | # Regular expression for non-alphanumeric-or-underscore characters. |
| 2546 | _NOT_ALPHANUM_OR_UNDERSCORE = re.compile(r'\W', re.ASCII) |
Ulf Magnusson | 0e23994 | 2019-11-12 18:21:02 +0100 | [diff] [blame] | 2547 | |
| 2548 | # Custom PyYAML binding loader class to avoid modifying yaml.Loader directly, |
| 2549 | # which could interfere with YAML loading in clients |
| 2550 | class _BindingLoader(Loader): |
| 2551 | pass |
| 2552 | |
| 2553 | |
| 2554 | # Add legacy '!include foo.yaml' handling |
| 2555 | _BindingLoader.add_constructor("!include", _binding_include) |
Ulf Magnusson | 7215885 | 2019-11-12 18:33:00 +0100 | [diff] [blame] | 2556 | |
| 2557 | # Use OrderedDict instead of plain dict for YAML mappings, to preserve |
| 2558 | # insertion order on Python 3.5 and earlier (plain dicts only preserve |
| 2559 | # insertion order on Python 3.6+). This makes testing easier and avoids |
| 2560 | # surprises. |
| 2561 | # |
| 2562 | # Adapted from |
| 2563 | # https://stackoverflow.com/questions/5121931/in-python-how-can-you-load-yaml-mappings-as-ordereddicts. |
| 2564 | # Hopefully this API stays stable. |
| 2565 | _BindingLoader.add_constructor( |
| 2566 | yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, |
| 2567 | lambda loader, node: OrderedDict(loader.construct_pairs(node))) |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 2568 | |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 2569 | # |
| 2570 | # "Default" binding for properties which are defined by the spec. |
| 2571 | # |
| 2572 | # Zephyr: do not change the _DEFAULT_PROP_TYPES keys without |
| 2573 | # updating the documentation for the DT_PROP() macro in |
| 2574 | # include/devicetree.h. |
| 2575 | # |
| 2576 | |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 2577 | _DEFAULT_PROP_TYPES = { |
| 2578 | "compatible": "string-array", |
| 2579 | "status": "string", |
| 2580 | "reg": "array", |
| 2581 | "reg-names": "string-array", |
| 2582 | "label": "string", |
Martí Bolívar | 414ed86 | 2020-05-19 14:13:46 -0700 | [diff] [blame] | 2583 | "interrupts": "array", |
Kumar Gala | bc48f1c | 2020-05-01 12:33:00 -0500 | [diff] [blame] | 2584 | "interrupts-extended": "compound", |
| 2585 | "interrupt-names": "string-array", |
| 2586 | "interrupt-controller": "boolean", |
| 2587 | } |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 2588 | |
| 2589 | _STATUS_ENUM = "ok okay disabled reserved fail fail-sss".split() |
| 2590 | |
| 2591 | def _raw_default_property_for(name): |
| 2592 | ret = { |
| 2593 | 'type': _DEFAULT_PROP_TYPES[name], |
| 2594 | 'required': False, |
| 2595 | } |
| 2596 | if name == 'status': |
| 2597 | ret['enum'] = _STATUS_ENUM |
| 2598 | return ret |
| 2599 | |
| 2600 | _DEFAULT_PROP_BINDING = Binding( |
| 2601 | None, {}, |
| 2602 | raw={ |
| 2603 | 'properties': { |
| 2604 | name: _raw_default_property_for(name) |
| 2605 | for name in _DEFAULT_PROP_TYPES |
| 2606 | }, |
| 2607 | }, |
Martí Bolívar | 0985849 | 2020-12-08 09:41:49 -0800 | [diff] [blame] | 2608 | require_compatible=False, require_description=False, |
Martí Bolívar | 88094ec | 2020-12-02 13:52:43 -0800 | [diff] [blame] | 2609 | ) |
| 2610 | |
| 2611 | _DEFAULT_PROP_SPECS = { |
| 2612 | name: PropertySpec(name, _DEFAULT_PROP_BINDING) |
| 2613 | for name in _DEFAULT_PROP_TYPES |
| 2614 | } |