1# Copyright (c) 2019 Nordic Semiconductor ASA 2# SPDX-License-Identifier: BSD-3-Clause 3 4import contextlib 5from copy import deepcopy 6import io 7from logging import WARNING 8import os 9from pathlib import Path 10import textwrap 11 12import pytest 13 14from devicetree import edtlib 15 16# Test suite for edtlib.py. 17# 18# Run it using pytest (https://docs.pytest.org/en/stable/usage.html): 19# 20# $ pytest testedtlib.py 21# 22# See the comment near the top of testdtlib.py for additional pytest advice. 23# 24# test.dts is the main test file. test-bindings/ and test-bindings-2/ has 25# bindings. The tests mostly use string comparisons via the various __repr__() 26# methods. 27 28HERE = os.path.dirname(__file__) 29 30@contextlib.contextmanager 31def from_here(): 32 # Convenience hack to minimize diff from zephyr. 33 cwd = os.getcwd() 34 try: 35 os.chdir(HERE) 36 yield 37 finally: 38 os.chdir(cwd) 39 40def hpath(filename): 41 '''Convert 'filename' to the host path syntax.''' 42 return os.fspath(Path(filename)) 43 44def test_warnings(caplog): 45 '''Tests for situations that should cause warnings.''' 46 47 with from_here(): edtlib.EDT("test.dts", ["test-bindings"]) 48 49 enums_hpath = hpath('test-bindings/enums.yaml') 50 expected_warnings = [ 51 f"'oldprop' is marked as deprecated in 'properties:' in {hpath('test-bindings/deprecated.yaml')} for node /test-deprecated.", 52 "unit address and first address in 'reg' (0x1) don't match for /reg-zero-size-cells/node", 53 "unit address and first address in 'reg' (0x5) don't match for /reg-ranges/parent/node", 54 "unit address and first address in 'reg' (0x30000000200000001) don't match for /reg-nested-ranges/grandparent/parent/node", 55 f"compatible 'enums' in binding '{enums_hpath}' has non-tokenizable enum for property 'string-enum': 'foo bar', 'foo_bar'", 56 f"compatible 'enums' in binding '{enums_hpath}' has enum for property 'tokenizable-lower-enum' that is only tokenizable in lowercase: 'bar', 'BAR'", 57 ] 58 assert caplog.record_tuples == [('devicetree.edtlib', WARNING, warning_message) 59 for warning_message in expected_warnings] 60 61def test_interrupts(): 62 '''Tests for the interrupts property.''' 63 with from_here(): 64 edt = edtlib.EDT("test.dts", ["test-bindings"]) 65 66 node = edt.get_node("/interrupt-parent-test/node") 67 controller = edt.get_node('/interrupt-parent-test/controller') 68 assert node.interrupts == [ 69 edtlib.ControllerAndData(node=node, controller=controller, data={'one': 1, 'two': 2, 'three': 3}, name='foo', basename=None), 70 edtlib.ControllerAndData(node=node, controller=controller, data={'one': 4, 'two': 5, 'three': 6}, name='bar', basename=None) 71 ] 72 73 node = edt.get_node("/interrupts-extended-test/node") 74 controller_0 = edt.get_node('/interrupts-extended-test/controller-0') 75 controller_1 = edt.get_node('/interrupts-extended-test/controller-1') 76 controller_2 = edt.get_node('/interrupts-extended-test/controller-2') 77 assert node.interrupts == [ 78 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 1}, name=None, basename=None), 79 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 2, 'two': 3}, name=None, basename=None), 80 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 4, 'two': 5, 'three': 6}, name=None, basename=None) 81 ] 82 83 node = edt.get_node("/interrupt-map-test/node@0") 84 controller_0 = edt.get_node('/interrupt-map-test/controller-0') 85 controller_1 = edt.get_node('/interrupt-map-test/controller-1') 86 controller_2 = edt.get_node('/interrupt-map-test/controller-2') 87 88 assert node.interrupts == [ 89 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None), 90 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None), 91 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None) 92 ] 93 94 node = edt.get_node("/interrupt-map-test/node@1") 95 assert node.interrupts == [ 96 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None), 97 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None), 98 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None) 99 ] 100 101 node = edt.get_node("/interrupt-map-test/node@2") 102 assert node.interrupts == [ 103 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None), 104 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None), 105 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None) 106 ] 107 108 node = edt.get_node("/interrupt-map-test/node@3") 109 assert node.interrupts == [ 110 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 0}, name=None, basename=None), 111 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 1}, name=None, basename=None), 112 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 2}, name=None, basename=None) 113 ] 114 115 node = edt.get_node("/interrupt-map-test/node@4") 116 assert node.interrupts == [ 117 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None), 118 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None), 119 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None) 120 ] 121 122 node = edt.get_node("/interrupt-map-test/node@100000004") 123 assert node.interrupts == [ 124 edtlib.ControllerAndData(node=node, controller=controller_0, data={'one': 3}, name=None, basename=None), 125 edtlib.ControllerAndData(node=node, controller=controller_1, data={'one': 0, 'two': 4}, name=None, basename=None), 126 edtlib.ControllerAndData(node=node, controller=controller_2, data={'one': 0, 'two': 0, 'three': 5}, name=None, basename=None) 127 ] 128 129 node = edt.get_node("/interrupt-map-bitops-test/node@70000000E") 130 assert node.interrupts == [ 131 edtlib.ControllerAndData(node=node, controller=edt.get_node('/interrupt-map-bitops-test/controller'), data={'one': 3, 'two': 2}, name=None, basename=None) 132 ] 133 134def test_ranges(): 135 '''Tests for the ranges property''' 136 with from_here(): 137 edt = edtlib.EDT("test.dts", ["test-bindings"]) 138 139 node = edt.get_node("/reg-ranges/parent") 140 assert node.ranges == [ 141 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1, parent_bus_cells=0x2, parent_bus_addr=0xa0000000b, length_cells=0x1, length=0x1), 142 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2, parent_bus_cells=0x2, parent_bus_addr=0xc0000000d, length_cells=0x1, length=0x2), 143 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x4, parent_bus_cells=0x2, parent_bus_addr=0xe0000000f, length_cells=0x1, length=0x1) 144 ] 145 146 node = edt.get_node("/reg-nested-ranges/grandparent") 147 assert node.ranges == [ 148 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x0, parent_bus_cells=0x3, parent_bus_addr=0x30000000000000000, length_cells=0x2, length=0x200000002) 149 ] 150 151 node = edt.get_node("/reg-nested-ranges/grandparent/parent") 152 assert node.ranges == [ 153 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x0, parent_bus_cells=0x2, parent_bus_addr=0x200000000, length_cells=0x1, length=0x2) 154 ] 155 156 assert edt.get_node("/ranges-zero-cells/node").ranges == [] 157 158 node = edt.get_node("/ranges-zero-parent-cells/node") 159 assert node.ranges == [ 160 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None), 161 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None), 162 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x0, length=None) 163 ] 164 165 node = edt.get_node("/ranges-one-address-cells/node") 166 assert node.ranges == [ 167 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0xb), 168 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x1b), 169 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x1, length=0x2b) 170 ] 171 172 node = edt.get_node("/ranges-one-address-two-size-cells/node") 173 assert node.ranges == [ 174 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0xa, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0xb0000000c), 175 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x1a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x1b0000001c), 176 edtlib.Range(node=node, child_bus_cells=0x1, child_bus_addr=0x2a, parent_bus_cells=0x0, parent_bus_addr=None, length_cells=0x2, length=0x2b0000002c) 177 ] 178 179 node = edt.get_node("/ranges-two-address-cells/node@1") 180 assert node.ranges == [ 181 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x1, length=0xd), 182 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x1, length=0x1d), 183 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x1, length=0x2d) 184 ] 185 186 node = edt.get_node("/ranges-two-address-two-size-cells/node@1") 187 assert node.ranges == [ 188 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0xa0000000b, parent_bus_cells=0x1, parent_bus_addr=0xc, length_cells=0x2, length=0xd0000000e), 189 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x1a0000001b, parent_bus_cells=0x1, parent_bus_addr=0x1c, length_cells=0x2, length=0x1d0000001e), 190 edtlib.Range(node=node, child_bus_cells=0x2, child_bus_addr=0x2a0000002b, parent_bus_cells=0x1, parent_bus_addr=0x2c, length_cells=0x2, length=0x2d0000001d) 191 ] 192 193 node = edt.get_node("/ranges-three-address-cells/node@1") 194 assert node.ranges == [ 195 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x1, length=0xf), 196 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x1, length=0x1f), 197 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x1, length=0x2f) 198 ] 199 200 node = edt.get_node("/ranges-three-address-two-size-cells/node@1") 201 assert node.ranges == [ 202 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0xa0000000b0000000c, parent_bus_cells=0x2, parent_bus_addr=0xd0000000e, length_cells=0x2, length=0xf00000010), 203 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x1a0000001b0000001c, parent_bus_cells=0x2, parent_bus_addr=0x1d0000001e, length_cells=0x2, length=0x1f00000110), 204 edtlib.Range(node=node, child_bus_cells=0x3, child_bus_addr=0x2a0000002b0000002c, parent_bus_cells=0x2, parent_bus_addr=0x2d0000002e, length_cells=0x2, length=0x2f00000210) 205 ] 206 207def test_reg(): 208 '''Tests for the regs property''' 209 with from_here(): 210 edt = edtlib.EDT("test.dts", ["test-bindings"]) 211 212 def verify_regs(node, expected_tuples): 213 regs = node.regs 214 assert len(regs) == len(expected_tuples) 215 for reg, expected_tuple in zip(regs, expected_tuples): 216 name, addr, size = expected_tuple 217 assert reg.node is node 218 assert reg.name == name 219 assert reg.addr == addr 220 assert reg.size == size 221 222 verify_regs(edt.get_node("/reg-zero-address-cells/node"), 223 [('foo', None, 0x1), 224 ('bar', None, 0x2)]) 225 226 verify_regs(edt.get_node("/reg-zero-size-cells/node"), 227 [(None, 0x1, None), 228 (None, 0x2, None)]) 229 230 verify_regs(edt.get_node("/reg-ranges/parent/node"), 231 [(None, 0x5, 0x1), 232 (None, 0xe0000000f, 0x1), 233 (None, 0xc0000000e, 0x1), 234 (None, 0xc0000000d, 0x1), 235 (None, 0xa0000000b, 0x1), 236 (None, 0x0, 0x1)]) 237 238 verify_regs(edt.get_node("/reg-nested-ranges/grandparent/parent/node"), 239 [(None, 0x30000000200000001, 0x1)]) 240 241def test_pinctrl(): 242 '''Test 'pinctrl-<index>'.''' 243 with from_here(): 244 edt = edtlib.EDT("test.dts", ["test-bindings"]) 245 246 node = edt.get_node("/pinctrl/dev") 247 state_1 = edt.get_node('/pinctrl/pincontroller/state-1') 248 state_2 = edt.get_node('/pinctrl/pincontroller/state-2') 249 assert node.pinctrls == [ 250 edtlib.PinCtrl(node=node, name='zero', conf_nodes=[]), 251 edtlib.PinCtrl(node=node, name='one', conf_nodes=[state_1]), 252 edtlib.PinCtrl(node=node, name='two', conf_nodes=[state_1, state_2]) 253 ] 254 255def test_hierarchy(): 256 '''Test Node.parent and Node.children''' 257 with from_here(): 258 edt = edtlib.EDT("test.dts", ["test-bindings"]) 259 260 assert edt.get_node("/").parent is None 261 262 assert str(edt.get_node("/parent/child-1").parent) == \ 263 "<Node /parent in 'test.dts', no binding>" 264 265 assert str(edt.get_node("/parent/child-2/grandchild").parent) == \ 266 "<Node /parent/child-2 in 'test.dts', no binding>" 267 268 assert str(edt.get_node("/parent").children) == \ 269 "{'child-1': <Node /parent/child-1 in 'test.dts', no binding>, 'child-2': <Node /parent/child-2 in 'test.dts', no binding>}" 270 271 assert edt.get_node("/parent/child-1").children == {} 272 273def test_child_index(): 274 '''Test Node.child_index.''' 275 with from_here(): 276 edt = edtlib.EDT("test.dts", ["test-bindings"]) 277 278 parent, child_1, child_2 = [edt.get_node(path) for path in 279 ("/parent", 280 "/parent/child-1", 281 "/parent/child-2")] 282 assert parent.child_index(child_1) == 0 283 assert parent.child_index(child_2) == 1 284 with pytest.raises(KeyError): 285 parent.child_index(parent) 286 287def test_include(): 288 '''Test 'include:' and the legacy 'inherits: !include ...' in bindings''' 289 with from_here(): 290 edt = edtlib.EDT("test.dts", ["test-bindings"]) 291 292 binding_include = edt.get_node("/binding-include") 293 294 assert binding_include.description == "Parent binding" 295 296 verify_props(binding_include, 297 ['foo', 'bar', 'baz', 'qaz'], 298 ['int', 'int', 'int', 'int'], 299 [0, 1, 2, 3]) 300 301 verify_props(edt.get_node("/binding-include/child"), 302 ['foo', 'bar', 'baz', 'qaz'], 303 ['int', 'int', 'int', 'int'], 304 [0, 1, 2, 3]) 305 306def test_include_filters(): 307 '''Test property-allowlist and property-blocklist in an include.''' 308 309 fname2path = {'include.yaml': 'test-bindings-include/include.yaml', 310 'include-2.yaml': 'test-bindings-include/include-2.yaml'} 311 312 with pytest.raises(edtlib.EDTError) as e: 313 with from_here(): 314 edtlib.Binding("test-bindings-include/allow-and-blocklist.yaml", fname2path) 315 assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'" 316 in str(e.value)) 317 318 with pytest.raises(edtlib.EDTError) as e: 319 with from_here(): 320 edtlib.Binding("test-bindings-include/allow-and-blocklist-child.yaml", fname2path) 321 assert ("should not specify both 'property-allowlist:' and 'property-blocklist:'" 322 in str(e.value)) 323 324 with pytest.raises(edtlib.EDTError) as e: 325 with from_here(): 326 edtlib.Binding("test-bindings-include/allow-not-list.yaml", fname2path) 327 value_str = str(e.value) 328 assert value_str.startswith("'property-allowlist' value") 329 assert value_str.endswith("should be a list") 330 331 with pytest.raises(edtlib.EDTError) as e: 332 with from_here(): 333 edtlib.Binding("test-bindings-include/block-not-list.yaml", fname2path) 334 value_str = str(e.value) 335 assert value_str.startswith("'property-blocklist' value") 336 assert value_str.endswith("should be a list") 337 338 with pytest.raises(edtlib.EDTError) as e: 339 with from_here(): 340 binding = edtlib.Binding("test-bindings-include/include-invalid-keys.yaml", fname2path) 341 value_str = str(e.value) 342 assert value_str.startswith( 343 "'include:' in test-bindings-include/include-invalid-keys.yaml should not have these " 344 "unexpected contents: ") 345 assert 'bad-key-1' in value_str 346 assert 'bad-key-2' in value_str 347 348 with pytest.raises(edtlib.EDTError) as e: 349 with from_here(): 350 binding = edtlib.Binding("test-bindings-include/include-invalid-type.yaml", fname2path) 351 value_str = str(e.value) 352 assert value_str.startswith( 353 "'include:' in test-bindings-include/include-invalid-type.yaml " 354 "should be a string or list, but has type ") 355 356 with pytest.raises(edtlib.EDTError) as e: 357 with from_here(): 358 binding = edtlib.Binding("test-bindings-include/include-no-name.yaml", fname2path) 359 value_str = str(e.value) 360 assert value_str.startswith("'include:' element") 361 assert value_str.endswith( 362 "in test-bindings-include/include-no-name.yaml should have a 'name' key") 363 364 with from_here(): 365 binding = edtlib.Binding("test-bindings-include/allowlist.yaml", fname2path) 366 assert set(binding.prop2specs.keys()) == {'x'} # 'x' is allowed 367 368 binding = edtlib.Binding("test-bindings-include/empty-allowlist.yaml", fname2path) 369 assert set(binding.prop2specs.keys()) == set() # nothing is allowed 370 371 binding = edtlib.Binding("test-bindings-include/blocklist.yaml", fname2path) 372 assert set(binding.prop2specs.keys()) == {'y', 'z'} # 'x' is blocked 373 374 binding = edtlib.Binding("test-bindings-include/empty-blocklist.yaml", fname2path) 375 assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'} # nothing is blocked 376 377 binding = edtlib.Binding("test-bindings-include/intermixed.yaml", fname2path) 378 assert set(binding.prop2specs.keys()) == {'x', 'a'} 379 380 binding = edtlib.Binding("test-bindings-include/include-no-list.yaml", fname2path) 381 assert set(binding.prop2specs.keys()) == {'x', 'y', 'z'} 382 383 binding = edtlib.Binding("test-bindings-include/filter-child-bindings.yaml", fname2path) 384 child = binding.child_binding 385 grandchild = child.child_binding 386 assert set(binding.prop2specs.keys()) == {'x'} 387 assert set(child.prop2specs.keys()) == {'child-prop-2'} 388 assert set(grandchild.prop2specs.keys()) == {'grandchild-prop-1'} 389 390 binding = edtlib.Binding("test-bindings-include/allow-and-blocklist-multilevel.yaml", 391 fname2path) 392 assert set(binding.prop2specs.keys()) == {'x'} # 'x' is allowed 393 child = binding.child_binding 394 assert set(child.prop2specs.keys()) == {'child-prop-1', 'child-prop-2', 395 'x', 'z'} # root level 'y' is blocked 396 397def test_include_filters_inherited_bindings() -> None: 398 '''Test the basics of filtering properties inherited via an intermediary binding file. 399 400 Use-case "B includes I includes X": 401 - X is a base binding file, specifying common properties 402 - I is an intermediary binding file, which includes X without modification 403 nor filter 404 - B includes I, filtering the properties it chooses to inherit 405 with an allowlist or a blocklist 406 407 Checks that the properties inherited from X via I are actually filtered 408 as B intends to. 409 ''' 410 fname2path = { 411 # Base binding file, specifies a few properties up to the grandchild-binding level. 412 "simple.yaml": "test-bindings-include/simple.yaml", 413 # 'include:'s the base file above, without modification nor filter 414 "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml", 415 } 416 with from_here(): 417 binding = edtlib.Binding( 418 # Filters inherited specifications with an allowlist. 419 "test-bindings-include/simple_filter_allowlist.yaml", 420 fname2path, 421 require_compatible=False, 422 require_description=False, 423 ) 424 # Only property allowed. 425 assert {"prop-1"} == set(binding.prop2specs.keys()) 426 427 with from_here(): 428 binding = edtlib.Binding( 429 # Filters inherited specifications with a blocklist. 430 "test-bindings-include/simple_filter_blocklist.yaml", 431 fname2path, 432 require_compatible=False, 433 require_description=False, 434 ) 435 # Only non blocked property. 436 assert {"prop-1"} == set(binding.prop2specs.keys()) 437 438def test_include_filters_inherited_child_bindings() -> None: 439 '''Test the basics of filtering properties inherited via an intermediary binding file 440 (child-binding level). 441 442 See also: test_include_filters_inherited_bindings() 443 ''' 444 fname2path = { 445 "simple.yaml": "test-bindings-include/simple.yaml", 446 "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml", 447 } 448 with from_here(): 449 binding = edtlib.Binding( 450 "test-bindings-include/simple_filter_allowlist.yaml", 451 fname2path, 452 require_compatible=False, 453 require_description=False, 454 ) 455 assert binding.child_binding 456 child_binding = binding.child_binding 457 # Only property allowed. 458 assert {"child-prop-1"} == set(child_binding.prop2specs.keys()) 459 460 with from_here(): 461 binding = edtlib.Binding( 462 "test-bindings-include/simple_filter_blocklist.yaml", 463 fname2path, 464 require_compatible=False, 465 require_description=False, 466 ) 467 # Only non blocked property. 468 assert binding.child_binding 469 child_binding = binding.child_binding 470 assert {"child-prop-1"} == set(child_binding.prop2specs.keys()) 471 472def test_include_filters_inherited_grandchild_bindings() -> None: 473 '''Test the basics of filtering properties inherited via an intermediary binding file 474 (grandchild-binding level). 475 476 See also: test_include_filters_inherited_bindings() 477 ''' 478 fname2path = { 479 "simple.yaml": "test-bindings-include/simple.yaml", 480 "simple_inherit.yaml": "test-bindings-include/simple_inherit.yaml", 481 } 482 with from_here(): 483 binding = edtlib.Binding( 484 "test-bindings-include/simple_filter_allowlist.yaml", 485 fname2path, 486 require_compatible=False, 487 require_description=False, 488 ) 489 assert binding.child_binding 490 child_binding = binding.child_binding 491 assert child_binding.child_binding 492 grandchild_binding = child_binding.child_binding 493 # Only property allowed. 494 assert {"grandchild-prop-1"} == set(grandchild_binding.prop2specs.keys()) 495 496 with from_here(): 497 binding = edtlib.Binding( 498 "test-bindings-include/simple_filter_blocklist.yaml", 499 fname2path, 500 require_compatible=False, 501 require_description=False, 502 ) 503 assert binding.child_binding 504 child_binding = binding.child_binding 505 assert child_binding.child_binding 506 grandchild_binding = child_binding.child_binding 507 # Only non blocked property. 508 assert {"grandchild-prop-1"} == set(grandchild_binding.prop2specs.keys()) 509 510def test_bus(): 511 '''Test 'bus:' and 'on-bus:' in bindings''' 512 with from_here(): 513 edt = edtlib.EDT("test.dts", ["test-bindings"]) 514 515 assert isinstance(edt.get_node("/buses/foo-bus").buses, list) 516 assert "foo" in edt.get_node("/buses/foo-bus").buses 517 518 # foo-bus does not itself appear on a bus 519 assert isinstance(edt.get_node("/buses/foo-bus").on_buses, list) 520 assert not edt.get_node("/buses/foo-bus").on_buses 521 assert edt.get_node("/buses/foo-bus").bus_node is None 522 523 # foo-bus/node1 is not a bus node... 524 assert isinstance(edt.get_node("/buses/foo-bus/node1").buses, list) 525 assert not edt.get_node("/buses/foo-bus/node1").buses 526 # ...but is on a bus 527 assert isinstance(edt.get_node("/buses/foo-bus/node1").on_buses, list) 528 assert "foo" in edt.get_node("/buses/foo-bus/node1").on_buses 529 assert edt.get_node("/buses/foo-bus/node1").bus_node.path == \ 530 "/buses/foo-bus" 531 532 # foo-bus/node2 is not a bus node... 533 assert isinstance(edt.get_node("/buses/foo-bus/node2").buses, list) 534 assert not edt.get_node("/buses/foo-bus/node2").buses 535 # ...but is on a bus 536 assert isinstance(edt.get_node("/buses/foo-bus/node2").on_buses, list) 537 assert "foo" in edt.get_node("/buses/foo-bus/node2").on_buses 538 539 # no-bus-node is not a bus node... 540 assert isinstance(edt.get_node("/buses/no-bus-node").buses, list) 541 assert not edt.get_node("/buses/no-bus-node").buses 542 # ... and is not on a bus 543 assert isinstance(edt.get_node("/buses/no-bus-node").on_buses, list) 544 assert not edt.get_node("/buses/no-bus-node").on_buses 545 546 # Same compatible string, but different bindings from being on different 547 # buses 548 assert str(edt.get_node("/buses/foo-bus/node1").binding_path) == \ 549 hpath("test-bindings/device-on-foo-bus.yaml") 550 assert str(edt.get_node("/buses/foo-bus/node2").binding_path) == \ 551 hpath("test-bindings/device-on-any-bus.yaml") 552 assert str(edt.get_node("/buses/bar-bus/node").binding_path) == \ 553 hpath("test-bindings/device-on-bar-bus.yaml") 554 assert str(edt.get_node("/buses/no-bus-node").binding_path) == \ 555 hpath("test-bindings/device-on-any-bus.yaml") 556 557 # foo-bus/node/nested also appears on the foo-bus bus 558 assert isinstance(edt.get_node("/buses/foo-bus/node1/nested").on_buses, list) 559 assert "foo" in edt.get_node("/buses/foo-bus/node1/nested").on_buses 560 assert str(edt.get_node("/buses/foo-bus/node1/nested").binding_path) == \ 561 hpath("test-bindings/device-on-foo-bus.yaml") 562 563def test_binding_top_key(): 564 fname2path = {'include.yaml': 'test-bindings-include/include.yaml', 565 'include-2.yaml': 'test-bindings-include/include-2.yaml'} 566 567 with from_here(): 568 binding = edtlib.Binding("test-bindings/defaults.yaml", fname2path) 569 title = binding.title 570 description = binding.description 571 compatible = binding.compatible 572 examples = binding.examples[0] 573 574 assert title == "Test binding" 575 assert description == "Property default value test" 576 assert compatible == "defaults" 577 assert examples == textwrap.dedent("""\ 578 / { 579 leds { 580 compatible = "gpio-leds"; 581 582 uled: led { 583 gpios = <&gpioe 12 GPIO_ACTIVE_HIGH>; 584 }; 585 }; 586 587 aliases { 588 led0 = &uled; 589 }; 590 }; 591 """) 592 593def test_child_binding(): 594 '''Test 'child-binding:' in bindings''' 595 with from_here(): 596 edt = edtlib.EDT("test.dts", ["test-bindings"]) 597 child1 = edt.get_node("/child-binding/child-1") 598 child2 = edt.get_node("/child-binding/child-2") 599 grandchild = edt.get_node("/child-binding/child-1/grandchild") 600 601 assert str(child1.binding_path) == hpath("test-bindings/child-binding.yaml") 602 assert str(child1.description) == "child node" 603 verify_props(child1, ['child-prop'], ['int'], [1]) 604 605 assert str(child2.binding_path) == hpath("test-bindings/child-binding.yaml") 606 assert str(child2.description) == "child node" 607 verify_props(child2, ['child-prop'], ['int'], [3]) 608 609 assert str(grandchild.binding_path) == hpath("test-bindings/child-binding.yaml") 610 assert str(grandchild.description) == "grandchild node" 611 verify_props(grandchild, ['grandchild-prop'], ['int'], [2]) 612 613 with from_here(): 614 binding_file = Path("test-bindings/child-binding.yaml").resolve() 615 top = edtlib.Binding(binding_file, {}) 616 child = top.child_binding 617 assert Path(top.path) == binding_file 618 assert Path(child.path) == binding_file 619 assert top.compatible == 'top-binding' 620 assert child.compatible is None 621 622 with from_here(): 623 binding_file = Path("test-bindings/child-binding-with-compat.yaml").resolve() 624 top = edtlib.Binding(binding_file, {}) 625 child = top.child_binding 626 assert Path(top.path) == binding_file 627 assert Path(child.path) == binding_file 628 assert top.compatible == 'top-binding-with-compat' 629 assert child.compatible == 'child-compat' 630 631def test_props(): 632 '''Test Node.props (derived from DT and 'properties:' in the binding)''' 633 with from_here(): 634 edt = edtlib.EDT("test.dts", ["test-bindings"]) 635 636 props_node = edt.get_node('/props') 637 ctrl_1, ctrl_2 = [edt.get_node(path) for path in ['/ctrl-1', '/ctrl-2']] 638 639 verify_props(props_node, 640 ['int', 641 'existent-boolean', 'nonexistent-boolean', 642 'array', 'uint8-array', 643 'string', 'string-array', 644 'phandle-ref', 'phandle-refs', 645 'path'], 646 ['int', 647 'boolean', 'boolean', 648 'array', 'uint8-array', 649 'string', 'string-array', 650 'phandle', 'phandles', 651 'path'], 652 [1, 653 True, False, 654 [1,2,3], b'\x124', 655 'foo', ['foo','bar','baz'], 656 ctrl_1, [ctrl_1,ctrl_2], 657 ctrl_1]) 658 659 verify_phandle_array_prop(props_node, 660 'phandle-array-foos', 661 [(ctrl_1, {'one': 1}), 662 (ctrl_2, {'one': 2, 'two': 3})]) 663 664 verify_phandle_array_prop(edt.get_node("/props-2"), 665 "phandle-array-foos", 666 [(edt.get_node('/ctrl-0-1'), {}), 667 None, 668 (edt.get_node('/ctrl-0-2'), {})]) 669 670 verify_phandle_array_prop(props_node, 671 'foo-gpios', 672 [(ctrl_1, {'gpio-one': 1})]) 673 674def test_nexus(): 675 '''Test <prefix>-map via gpio-map (the most common case).''' 676 with from_here(): 677 edt = edtlib.EDT("test.dts", ["test-bindings"]) 678 679 source = edt.get_node("/gpio-map/source") 680 destination = edt.get_node('/gpio-map/destination') 681 verify_phandle_array_prop(source, 682 'foo-gpios', 683 [(destination, {'val': 6}), 684 (destination, {'val': 5})]) 685 686 assert source.props["foo-gpios"].val[0].basename == f"gpio" 687 688def test_prop_defaults(): 689 '''Test property default values given in bindings''' 690 with from_here(): 691 edt = edtlib.EDT("test.dts", ["test-bindings"]) 692 693 verify_props(edt.get_node("/defaults"), 694 ['int', 695 'array', 'uint8-array', 696 'string', 'string-array', 697 'default-not-used'], 698 ['int', 699 'array', 'uint8-array', 700 'string', 'string-array', 701 'int'], 702 [123, 703 [1,2,3], b'\x89\xab\xcd', 704 'hello', ['hello','there'], 705 234]) 706 707def test_prop_enums(): 708 '''test properties with enum: in the binding''' 709 710 with from_here(): 711 edt = edtlib.EDT("test.dts", ["test-bindings"]) 712 props = edt.get_node('/enums').props 713 int_enum = props['int-enum'] 714 string_enum = props['string-enum'] 715 tokenizable_enum = props['tokenizable-enum'] 716 tokenizable_lower_enum = props['tokenizable-lower-enum'] 717 array_enum = props['array-enum'] 718 string_array_enum = props['string-array-enum'] 719 no_enum = props['no-enum'] 720 721 assert int_enum.val == 1 722 assert int_enum.enum_indices[0] == 0 723 assert not int_enum.spec.enum_tokenizable 724 assert not int_enum.spec.enum_upper_tokenizable 725 726 assert string_enum.val == 'foo_bar' 727 assert string_enum.enum_indices[0] == 1 728 assert not string_enum.spec.enum_tokenizable 729 assert not string_enum.spec.enum_upper_tokenizable 730 731 assert tokenizable_enum.val == '123 is ok' 732 assert tokenizable_enum.val_as_tokens[0] == '123_is_ok' 733 assert tokenizable_enum.enum_indices[0] == 2 734 assert tokenizable_enum.spec.enum_tokenizable 735 assert tokenizable_enum.spec.enum_upper_tokenizable 736 737 assert tokenizable_lower_enum.val == 'bar' 738 assert tokenizable_lower_enum.val_as_tokens[0] == 'bar' 739 assert tokenizable_lower_enum.enum_indices[0] == 0 740 assert tokenizable_lower_enum.spec.enum_tokenizable 741 assert not tokenizable_lower_enum.spec.enum_upper_tokenizable 742 743 assert array_enum.val == [0, 40, 40, 10] 744 assert array_enum.enum_indices == [0, 4, 4, 1] 745 assert not array_enum.spec.enum_tokenizable 746 assert not array_enum.spec.enum_upper_tokenizable 747 748 assert string_array_enum.val == ["foo", "bar"] 749 assert string_array_enum.val_as_tokens == ["foo", "bar"] 750 assert string_array_enum.enum_indices == [1, 0] 751 assert string_array_enum.spec.enum_tokenizable 752 assert string_array_enum.spec.enum_upper_tokenizable 753 754 assert no_enum.enum_indices is None 755 assert not no_enum.spec.enum_tokenizable 756 assert not no_enum.spec.enum_upper_tokenizable 757 758def test_binding_inference(): 759 '''Test inferred bindings for special zephyr-specific nodes.''' 760 warnings = io.StringIO() 761 with from_here(): 762 edt = edtlib.EDT("test.dts", ["test-bindings"], warnings) 763 764 assert str(edt.get_node("/zephyr,user").props) == '{}' 765 766 with from_here(): 767 edt = edtlib.EDT("test.dts", ["test-bindings"], warnings, 768 infer_binding_for_paths=["/zephyr,user"]) 769 ctrl_1 = edt.get_node('/ctrl-1') 770 ctrl_2 = edt.get_node('/ctrl-2') 771 zephyr_user = edt.get_node("/zephyr,user") 772 773 verify_props(zephyr_user, 774 ['boolean', 'bytes', 'number', 775 'numbers', 'string', 'strings'], 776 ['boolean', 'uint8-array', 'int', 777 'array', 'string', 'string-array'], 778 [True, b'\x81\x82\x83', 23, 779 [1,2,3], 'text', ['a','b','c']]) 780 781 assert zephyr_user.props['handle'].val is ctrl_1 782 783 phandles = zephyr_user.props['phandles'] 784 val = phandles.val 785 assert len(val) == 2 786 assert val[0] is ctrl_1 787 assert val[1] is ctrl_2 788 789 verify_phandle_array_prop(zephyr_user, 790 'phandle-array-foos', 791 [(edt.get_node('/ctrl-2'), {'one': 1, 'two': 2})]) 792 793def test_multi_bindings(): 794 '''Test having multiple directories with bindings''' 795 with from_here(): 796 edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"]) 797 798 assert str(edt.get_node("/in-dir-1").binding_path) == \ 799 hpath("test-bindings/multidir.yaml") 800 801 assert str(edt.get_node("/in-dir-2").binding_path) == \ 802 hpath("test-bindings-2/multidir.yaml") 803 804def test_dependencies(): 805 ''''Test dependency relations''' 806 with from_here(): 807 edt = edtlib.EDT("test-multidir.dts", ["test-bindings", "test-bindings-2"]) 808 809 assert edt.get_node("/").dep_ordinal == 0 810 assert edt.get_node("/in-dir-1").dep_ordinal == 1 811 assert edt.get_node("/") in edt.get_node("/in-dir-1").depends_on 812 assert edt.get_node("/in-dir-1") in edt.get_node("/").required_by 813 814def test_child_dependencies(): 815 '''Test dependencies relashionship with child nodes propagated to parent''' 816 with from_here(): 817 edt = edtlib.EDT("test.dts", ["test-bindings"]) 818 819 dep_node = edt.get_node("/child-binding-dep") 820 821 assert dep_node in edt.get_node("/child-binding").depends_on 822 assert dep_node in edt.get_node("/child-binding/child-1/grandchild").depends_on 823 assert dep_node in edt.get_node("/child-binding/child-2").depends_on 824 assert edt.get_node("/child-binding") in dep_node.required_by 825 assert edt.get_node("/child-binding/child-1/grandchild") in dep_node.required_by 826 assert edt.get_node("/child-binding/child-2") in dep_node.required_by 827 828def test_slice_errs(tmp_path): 829 '''Test error messages from the internal _slice() helper''' 830 831 dts_file = tmp_path / "error.dts" 832 833 verify_error(""" 834/dts-v1/; 835 836/ { 837 #address-cells = <1>; 838 #size-cells = <2>; 839 840 sub { 841 reg = <3>; 842 }; 843}; 844""", 845 dts_file, 846 f"'reg' property in <Node /sub in {dts_file}:8> has length 4, which is not evenly divisible by 12 (= 4*(<#address-cells> (= 1) + <#size-cells> (= 2))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').") 847 848 verify_error(""" 849/dts-v1/; 850 851/ { 852 sub { 853 interrupts = <1>; 854 interrupt-parent = < &{/controller} >; 855 }; 856 controller { 857 interrupt-controller; 858 #interrupt-cells = <2>; 859 }; 860}; 861""", 862 dts_file, 863 f"'interrupts' property in <Node /sub in {dts_file}:5> has length 4, which is not evenly divisible by 8 (= 4*<#interrupt-cells>). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').") 864 865 verify_error(""" 866/dts-v1/; 867 868/ { 869 #address-cells = <1>; 870 871 sub-1 { 872 #address-cells = <2>; 873 #size-cells = <3>; 874 ranges = <4 5>; 875 876 sub-2 { 877 reg = <1 2 3 4 5>; 878 }; 879 }; 880}; 881""", 882 dts_file, 883 f"'ranges' property in <Node /sub-1 in {dts_file}:7> has length 8, which is not evenly divisible by 24 (= 4*(<#address-cells> (= 2) + <#address-cells for parent> (= 1) + <#size-cells> (= 3))). Note that #*-cells properties come either from the parent node or from the controller (in the case of 'interrupts').") 884 885def test_bad_compatible(tmp_path): 886 # An invalid compatible should cause an error, even on a node with 887 # no binding. 888 889 dts_file = tmp_path / "error.dts" 890 891 verify_error(""" 892/dts-v1/; 893 894/ { 895 foo { 896 compatible = "no, whitespace"; 897 }; 898}; 899""", 900 dts_file, 901 r"node '/foo' compatible 'no, whitespace' must match this regular expression: '^[a-zA-Z][a-zA-Z0-9,+\-._]+$'") 902 903def test_wrong_props(): 904 '''Test Node.wrong_props (derived from DT and 'properties:' in the binding)''' 905 906 with from_here(): 907 with pytest.raises(edtlib.EDTError) as e: 908 edtlib.Binding("test-wrong-bindings/wrong-specifier-space-type.yaml", None) 909 assert ("'specifier-space' in 'properties: wrong-type-for-specifier-space' has type 'phandle', expected 'phandle-array'" 910 in str(e.value)) 911 912 with pytest.raises(edtlib.EDTError) as e: 913 edtlib.Binding("test-wrong-bindings/wrong-phandle-array-name.yaml", None) 914 value_str = str(e.value) 915 assert value_str.startswith("'wrong-phandle-array-name' in 'properties:'") 916 assert value_str.endswith("but no 'specifier-space' was provided.") 917 918 919def test_deepcopy(): 920 with from_here(): 921 # We intentionally use different kwarg values than the 922 # defaults to make sure they're getting copied. This implies 923 # we have to set werror=True, so we can't use test.dts, since 924 # that generates warnings on purpose. 925 edt = edtlib.EDT("test-multidir.dts", 926 ["test-bindings", "test-bindings-2"], 927 warn_reg_unit_address_mismatch=False, 928 default_prop_types=False, 929 support_fixed_partitions_on_any_bus=False, 930 infer_binding_for_paths=['/test-node'], 931 vendor_prefixes={'test-vnd': 'A test vendor'}, 932 werror=True) 933 edt_copy = deepcopy(edt) 934 935 def equal_paths(list1, list2): 936 assert len(list1) == len(list2) 937 return all(elt1.path == elt2.path for elt1, elt2 in zip(list1, list2)) 938 939 def equal_key2path(key2node1, key2node2): 940 assert len(key2node1) == len(key2node2) 941 return (all(key1 == key2 for (key1, key2) in 942 zip(key2node1, key2node2)) and 943 all(node1.path == node2.path for (node1, node2) in 944 zip(key2node1.values(), key2node2.values()))) 945 946 def equal_key2paths(key2nodes1, key2nodes2): 947 assert len(key2nodes1) == len(key2nodes2) 948 return (all(key1 == key2 for (key1, key2) in 949 zip(key2nodes1, key2nodes2)) and 950 all(equal_paths(nodes1, nodes2) for (nodes1, nodes2) in 951 zip(key2nodes1.values(), key2nodes2.values()))) 952 953 def test_equal_but_not_same(attribute, equal=None): 954 if equal is None: 955 equal = lambda a, b: a == b 956 copy = getattr(edt_copy, attribute) 957 original = getattr(edt, attribute) 958 assert equal(copy, original) 959 assert copy is not original 960 961 test_equal_but_not_same("nodes", equal_paths) 962 test_equal_but_not_same("compat2nodes", equal_key2paths) 963 test_equal_but_not_same("compat2okay", equal_key2paths) 964 test_equal_but_not_same("compat2vendor") 965 test_equal_but_not_same("compat2model") 966 test_equal_but_not_same("label2node", equal_key2path) 967 test_equal_but_not_same("dep_ord2node", equal_key2path) 968 assert edt_copy.dts_path == "test-multidir.dts" 969 assert edt_copy.bindings_dirs == ["test-bindings", "test-bindings-2"] 970 assert edt_copy.bindings_dirs is not edt.bindings_dirs 971 assert not edt_copy._warn_reg_unit_address_mismatch 972 assert not edt_copy._default_prop_types 973 assert not edt_copy._fixed_partitions_no_bus 974 assert edt_copy._infer_binding_for_paths == set(["/test-node"]) 975 assert edt_copy._infer_binding_for_paths is not edt._infer_binding_for_paths 976 assert edt_copy._vendor_prefixes == {"test-vnd": "A test vendor"} 977 assert edt_copy._vendor_prefixes is not edt._vendor_prefixes 978 assert edt_copy._werror 979 test_equal_but_not_same("_compat2binding", equal_key2path) 980 test_equal_but_not_same("_binding_paths") 981 test_equal_but_not_same("_binding_fname2path") 982 assert len(edt_copy._node2enode) == len(edt._node2enode) 983 for node1, node2 in zip(edt_copy._node2enode, edt._node2enode): 984 enode1 = edt_copy._node2enode[node1] 985 enode2 = edt._node2enode[node2] 986 assert node1.path == node2.path 987 assert enode1.path == enode2.path 988 assert node1 is not node2 989 assert enode1 is not enode2 990 assert edt_copy._dt is not edt._dt 991 992 993def verify_error(dts, dts_file, expected_err): 994 # Verifies that parsing a file 'dts_file' with the contents 'dts' 995 # (a string) raises an EDTError with the message 'expected_err'. 996 # 997 # The path 'dts_file' is written with the string 'dts' before the 998 # test is run. 999 1000 with open(dts_file, "w", encoding="utf-8") as f: 1001 f.write(dts) 1002 f.flush() # Can't have unbuffered text IO, so flush() instead 1003 1004 with pytest.raises(edtlib.EDTError) as e: 1005 edtlib.EDT(dts_file, []) 1006 1007 assert str(e.value) == expected_err 1008 1009 1010def verify_props(node, names, types, values): 1011 # Verifies that each property in 'names' has the expected 1012 # value in 'values'. Property lookup is done in Node 'node'. 1013 1014 for name, type, value in zip(names, types, values): 1015 prop = node.props[name] 1016 assert prop.name == name 1017 assert prop.type == type 1018 assert prop.val == value 1019 assert prop.node is node 1020 1021def verify_phandle_array_prop(node, name, values): 1022 # Verifies 'node.props[name]' is a phandle-array, and has the 1023 # expected controller/data values in 'values'. Elements 1024 # of 'values' may be None. 1025 1026 prop = node.props[name] 1027 assert prop.type == 'phandle-array' 1028 assert prop.name == name 1029 val = prop.val 1030 assert isinstance(val, list) 1031 assert len(val) == len(values) 1032 for actual, expected in zip(val, values): 1033 if expected is not None: 1034 controller, data = expected 1035 assert isinstance(actual, edtlib.ControllerAndData) 1036 assert actual.controller is controller 1037 assert actual.data == data 1038 else: 1039 assert actual is None 1040