1#!/usr/bin/env python3 2# SPDX-License-Identifier: GPL-2.0+ 3 4""" 5Tests for the Fdt module 6Copyright (c) 2018 Google, Inc 7Written by Simon Glass <sjg@chromium.org> 8""" 9 10from argparse import ArgumentParser 11import os 12import shutil 13import sys 14import tempfile 15import unittest 16 17# Bring in the patman libraries 18our_path = os.path.dirname(os.path.realpath(__file__)) 19sys.path.insert(1, os.path.join(our_path, '..')) 20 21# Bring in the libfdt module 22sys.path.insert(2, 'scripts/dtc/pylibfdt') 23sys.path.insert(2, os.path.join(our_path, '../../scripts/dtc/pylibfdt')) 24sys.path.insert(2, os.path.join(our_path, 25 '../../build-sandbox_spl/scripts/dtc/pylibfdt')) 26 27#pylint: disable=wrong-import-position 28from dtoc import fdt 29from dtoc import fdt_util 30from dtoc.fdt_util import fdt32_to_cpu, fdt64_to_cpu 31from dtoc.fdt import Type, BytesToValue 32import libfdt 33from u_boot_pylib import test_util 34from u_boot_pylib import tools 35from u_boot_pylib import tout 36 37#pylint: disable=protected-access 38 39def _get_property_value(dtb, node, prop_name): 40 """Low-level function to get the property value based on its offset 41 42 This looks directly in the device tree at the property's offset to find 43 its value. It is useful as a check that the property is in the correct 44 place. 45 46 Args: 47 node: Node to look in 48 prop_name: Property name to find 49 50 Returns: 51 Tuple: 52 Prop object found 53 Value of property as a string (found using property offset) 54 """ 55 prop = node.props[prop_name] 56 57 # Add 12, which is sizeof(struct fdt_property), to get to start of data 58 offset = prop.GetOffset() + 12 59 data = dtb.GetContents()[offset:offset + len(prop.value)] 60 return prop, [chr(x) for x in data] 61 62def find_dtb_file(dts_fname): 63 """Locate a test file in the test/ directory 64 65 Args: 66 dts_fname (str): Filename to find, e.g. 'dtoc_test_simple.dts] 67 68 Returns: 69 str: Path to the test filename 70 """ 71 return os.path.join('tools/dtoc/test', dts_fname) 72 73 74class TestFdt(unittest.TestCase): 75 """Tests for the Fdt module 76 77 This includes unit tests for some functions and functional tests for the fdt 78 module. 79 """ 80 @classmethod 81 def setUpClass(cls): 82 tools.prepare_output_dir(None) 83 84 @classmethod 85 def tearDownClass(cls): 86 tools.finalise_output_dir() 87 88 def setUp(self): 89 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) 90 91 def test_fdt(self): 92 """Test that we can open an Fdt""" 93 self.dtb.Scan() 94 root = self.dtb.GetRoot() 95 self.assertTrue(isinstance(root, fdt.Node)) 96 97 def test_get_node(self): 98 """Test the GetNode() method""" 99 node = self.dtb.GetNode('/spl-test') 100 self.assertTrue(isinstance(node, fdt.Node)) 101 102 node = self.dtb.GetNode('/i2c@0/pmic@9') 103 self.assertTrue(isinstance(node, fdt.Node)) 104 self.assertEqual('pmic@9', node.name) 105 self.assertIsNone(self.dtb.GetNode('/i2c@0/pmic@9/missing')) 106 107 node = self.dtb.GetNode('/') 108 self.assertTrue(isinstance(node, fdt.Node)) 109 self.assertEqual(0, node.Offset()) 110 111 def test_flush(self): 112 """Check that we can flush the device tree out to its file""" 113 fname = self.dtb._fname 114 with open(fname, 'rb') as inf: 115 inf.read() 116 os.remove(fname) 117 with self.assertRaises(IOError): 118 with open(fname, 'rb'): 119 pass 120 self.dtb.Flush() 121 with open(fname, 'rb') as inf: 122 inf.read() 123 124 def test_pack(self): 125 """Test that packing a device tree works""" 126 self.dtb.Pack() 127 128 def test_get_fdt_raw(self): 129 """Tetst that we can access the raw device-tree data""" 130 self.assertTrue(isinstance(self.dtb.GetContents(), bytes)) 131 132 def test_get_props(self): 133 """Tests obtaining a list of properties""" 134 node = self.dtb.GetNode('/spl-test') 135 props = self.dtb.GetProps(node) 136 self.assertEqual(['boolval', 'bootph-all', 'bytearray', 'byteval', 137 'compatible', 'int64val', 'intarray', 'intval', 138 'longbytearray', 'maybe-empty-int', 'notstring', 139 'stringarray', 'stringval', ], 140 sorted(props.keys())) 141 142 def test_check_error(self): 143 """Tests the ChecKError() function""" 144 with self.assertRaises(ValueError) as exc: 145 fdt.CheckErr(-libfdt.NOTFOUND, 'hello') 146 self.assertIn('FDT_ERR_NOTFOUND: hello', str(exc.exception)) 147 148 def test_get_fdt(self): 149 """Test getting an Fdt object from a node""" 150 node = self.dtb.GetNode('/spl-test') 151 self.assertEqual(self.dtb, node.GetFdt()) 152 153 def test_bytes_to_value(self): 154 """Test converting a string list into Python""" 155 self.assertEqual(BytesToValue(b'this\0is\0'), 156 (Type.STRING, ['this', 'is'])) 157 158class TestNode(unittest.TestCase): 159 """Test operation of the Node class""" 160 161 @classmethod 162 def setUpClass(cls): 163 tools.prepare_output_dir(None) 164 165 @classmethod 166 def tearDownClass(cls): 167 tools.finalise_output_dir() 168 169 def setUp(self): 170 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) 171 self.node = self.dtb.GetNode('/spl-test') 172 self.fdt = self.dtb.GetFdtObj() 173 174 def test_offset(self): 175 """Tests that we can obtain the offset of a node""" 176 self.assertTrue(self.node.Offset() > 0) 177 178 def test_delete(self): 179 """Tests that we can delete a property""" 180 node2 = self.dtb.GetNode('/spl-test2') 181 offset1 = node2.Offset() 182 self.node.DeleteProp('intval') 183 offset2 = node2.Offset() 184 self.assertTrue(offset2 < offset1) 185 self.node.DeleteProp('intarray') 186 offset3 = node2.Offset() 187 self.assertTrue(offset3 < offset2) 188 with self.assertRaises(libfdt.FdtException): 189 self.node.DeleteProp('missing') 190 191 def test_delete_get_offset(self): 192 """Test that property offset update when properties are deleted""" 193 self.node.DeleteProp('intval') 194 prop, value = _get_property_value(self.dtb, self.node, 'longbytearray') 195 self.assertEqual(prop.value, value) 196 197 def test_find_node(self): 198 """Tests that we can find a node using the FindNode() functoin""" 199 node = self.dtb.GetRoot().FindNode('i2c@0') 200 self.assertEqual('i2c@0', node.name) 201 subnode = node.FindNode('pmic@9') 202 self.assertEqual('pmic@9', subnode.name) 203 self.assertEqual(None, node.FindNode('missing')) 204 205 def test_refresh_missing_node(self): 206 """Test refreshing offsets when an extra node is present in dtb""" 207 # Delete it from our tables, not the device tree 208 del self.dtb._root.subnodes[-1] 209 with self.assertRaises(ValueError) as exc: 210 self.dtb.Refresh() 211 self.assertIn('Internal error, offset', str(exc.exception)) 212 213 def test_refresh_extra_node(self): 214 """Test refreshing offsets when an expected node is missing""" 215 # Delete it from the device tre, not our tables 216 self.fdt.del_node(self.node.Offset()) 217 with self.assertRaises(ValueError) as exc: 218 self.dtb.Refresh() 219 self.assertIn('Internal error, node name mismatch ' 220 'spl-test != spl-test2', str(exc.exception)) 221 222 def test_refresh_missing_prop(self): 223 """Test refreshing offsets when an extra property is present in dtb""" 224 # Delete it from our tables, not the device tree 225 del self.node.props['notstring'] 226 with self.assertRaises(ValueError) as exc: 227 self.dtb.Refresh() 228 self.assertIn("Internal error, node '/spl-test' property 'notstring' missing, offset ", 229 str(exc.exception)) 230 231 def test_lookup_phandle(self): 232 """Test looking up a single phandle""" 233 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts')) 234 node = dtb.GetNode('/phandle-source2') 235 prop = node.props['clocks'] 236 target = dtb.GetNode('/phandle-target') 237 self.assertEqual(target, dtb.LookupPhandle(fdt32_to_cpu(prop.value))) 238 239 def test_add_node_space(self): 240 """Test adding a single node when out of space""" 241 self.fdt.pack() 242 self.node.AddSubnode('subnode') 243 with self.assertRaises(libfdt.FdtException) as exc: 244 self.dtb.Sync(auto_resize=False) 245 self.assertIn('FDT_ERR_NOSPACE', str(exc.exception)) 246 247 self.dtb.Sync(auto_resize=True) 248 offset = self.fdt.path_offset('/spl-test/subnode') 249 self.assertTrue(offset > 0) 250 251 def test_add_nodes(self): 252 """Test adding various subnode and properies""" 253 node = self.dtb.GetNode('/i2c@0') 254 255 # Add one more node next to the pmic one 256 sn1 = node.AddSubnode('node-one') 257 sn1.AddInt('integer-a', 12) 258 sn1.AddInt('integer-b', 23) 259 260 # Sync so that everything is clean 261 self.dtb.Sync(auto_resize=True) 262 263 # Add two subnodes next to pmic and node-one 264 sn2 = node.AddSubnode('node-two') 265 sn2.AddInt('integer-2a', 34) 266 sn2.AddInt('integer-2b', 45) 267 268 sn3 = node.AddSubnode('node-three') 269 sn3.AddInt('integer-3', 123) 270 271 # Add a property to the node after i2c@0 to check that this is not 272 # disturbed by adding a subnode to i2c@0 273 orig_node = self.dtb.GetNode('/orig-node') 274 orig_node.AddInt('integer-4', 456) 275 276 # Add a property to the pmic node to check that pmic properties are not 277 # disturbed 278 pmic = self.dtb.GetNode('/i2c@0/pmic@9') 279 pmic.AddInt('integer-5', 567) 280 281 self.dtb.Sync(auto_resize=True) 282 283 def test_add_one_node(self): 284 """Testing deleting and adding a subnode before syncing""" 285 subnode = self.node.AddSubnode('subnode') 286 self.node.AddSubnode('subnode2') 287 self.dtb.Sync(auto_resize=True) 288 289 # Delete a node and add a new one 290 subnode.Delete() 291 self.node.AddSubnode('subnode3') 292 self.dtb.Sync() 293 294 def test_refresh_name_mismatch(self): 295 """Test name mismatch when syncing nodes and properties""" 296 self.node.AddInt('integer-a', 12) 297 298 wrong_offset = self.dtb.GetNode('/i2c@0')._offset 299 self.node._offset = wrong_offset 300 with self.assertRaises(ValueError) as exc: 301 self.dtb.Sync() 302 self.assertIn("Internal error, node '/spl-test' name mismatch 'i2c@0'", 303 str(exc.exception)) 304 305 with self.assertRaises(ValueError) as exc: 306 self.node.Refresh(wrong_offset) 307 self.assertIn("Internal error, node '/spl-test' name mismatch 'i2c@0'", 308 str(exc.exception)) 309 310 def test_copy_node(self): 311 """Test copy_node() function""" 312 def do_copy_checks(dtb, dst, second1_ph_val, expect_none): 313 self.assertEqual( 314 ['/dest/base', '/dest/first@0', '/dest/existing'], 315 [n.path for n in dst.subnodes]) 316 317 chk = dtb.GetNode('/dest/base') 318 self.assertTrue(chk) 319 self.assertEqual( 320 {'compatible', 'bootph-all', '#address-cells', '#size-cells'}, 321 chk.props.keys()) 322 323 # Check the first property 324 prop = chk.props['bootph-all'] 325 self.assertEqual('bootph-all', prop.name) 326 self.assertEqual(True, prop.value) 327 self.assertEqual(chk.path, prop._node.path) 328 329 # Check the second property 330 prop2 = chk.props['compatible'] 331 self.assertEqual('compatible', prop2.name) 332 self.assertEqual('sandbox,i2c', prop2.value) 333 self.assertEqual(chk.path, prop2._node.path) 334 335 base = chk.FindNode('base') 336 self.assertTrue(chk) 337 338 first = dtb.GetNode('/dest/base/first@0') 339 self.assertTrue(first) 340 over = dtb.GetNode('/dest/base/over') 341 self.assertTrue(over) 342 343 # Make sure that the phandle for 'over' is copied 344 self.assertIn('phandle', over.props.keys()) 345 346 second = dtb.GetNode('/dest/base/second') 347 self.assertTrue(second) 348 self.assertEqual([over.name, first.name, second.name], 349 [n.name for n in chk.subnodes]) 350 self.assertEqual(chk, over.parent) 351 self.assertEqual( 352 {'bootph-all', 'compatible', 'reg', 'low-power', 'phandle'}, 353 over.props.keys()) 354 355 if expect_none: 356 self.assertIsNone(prop._offset) 357 self.assertIsNone(prop2._offset) 358 self.assertIsNone(over._offset) 359 else: 360 self.assertTrue(prop._offset) 361 self.assertTrue(prop2._offset) 362 self.assertTrue(over._offset) 363 364 # Now check ordering of the subnodes 365 self.assertEqual( 366 ['second1', 'second2', 'second3', 'second4'], 367 [n.name for n in second.subnodes]) 368 369 # Check the 'second_1_bad' phandle is not copied over 370 second1 = second.FindNode('second1') 371 self.assertTrue(second1) 372 sph = second1.props.get('phandle') 373 self.assertTrue(sph) 374 self.assertEqual(second1_ph_val, sph.bytes) 375 376 377 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_copy.dts')) 378 tmpl = dtb.GetNode('/base') 379 dst = dtb.GetNode('/dest') 380 second1_ph_val = (dtb.GetNode('/dest/base/second/second1'). 381 props['phandle'].bytes) 382 dst.copy_node(tmpl) 383 384 do_copy_checks(dtb, dst, second1_ph_val, expect_none=True) 385 386 dtb.Sync(auto_resize=True) 387 388 # Now check the resulting FDT. It should have duplicate phandles since 389 # 'over' has been copied to 'dest/base/over' but still exists in its old 390 # place 391 new_dtb = fdt.Fdt.FromData(dtb.GetContents()) 392 with self.assertRaises(ValueError) as exc: 393 new_dtb.Scan() 394 self.assertIn( 395 'Duplicate phandle 1 in nodes /dest/base/over and /base/over', 396 str(exc.exception)) 397 398 # Remove the source nodes for the copy 399 new_dtb.GetNode('/base').Delete() 400 401 # Now it should scan OK 402 new_dtb.Scan() 403 404 dst = new_dtb.GetNode('/dest') 405 do_copy_checks(new_dtb, dst, second1_ph_val, expect_none=False) 406 407 def test_copy_subnodes_from_phandles(self): 408 """Test copy_node() function""" 409 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_copy.dts')) 410 411 orig = dtb.GetNode('/') 412 node_list = fdt_util.GetPhandleList(orig, 'copy-list') 413 414 dst = dtb.GetNode('/dest') 415 dst.copy_subnodes_from_phandles(node_list) 416 417 pmic = dtb.GetNode('/dest/over') 418 self.assertTrue(pmic) 419 420 subn = dtb.GetNode('/dest/first@0') 421 self.assertTrue(subn) 422 self.assertEqual({'a-prop', 'b-prop', 'reg'}, subn.props.keys()) 423 424 self.assertEqual( 425 ['/dest/earlier', '/dest/later', '/dest/over', '/dest/first@0', 426 '/dest/second', '/dest/existing', '/dest/base'], 427 [n.path for n in dst.subnodes]) 428 429 # Make sure that the phandle for 'over' is not copied 430 over = dst.FindNode('over') 431 tout.debug(f'keys: {over.props.keys()}') 432 self.assertNotIn('phandle', over.props.keys()) 433 434 # Check the merged properties, first the base ones in '/dest' 435 expect = {'bootph-all', 'compatible', 'stringarray', 'longbytearray', 436 'maybe-empty-int'} 437 438 # Properties from 'base' 439 expect.update({'#address-cells', '#size-cells'}) 440 441 # Properties from 'another' 442 expect.add('new-prop') 443 444 self.assertEqual(expect, set(dst.props.keys())) 445 446 447class TestProp(unittest.TestCase): 448 """Test operation of the Prop class""" 449 450 @classmethod 451 def setUpClass(cls): 452 tools.prepare_output_dir(None) 453 454 @classmethod 455 def tearDownClass(cls): 456 tools.finalise_output_dir() 457 458 def setUp(self): 459 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) 460 self.node = self.dtb.GetNode('/spl-test') 461 self.fdt = self.dtb.GetFdtObj() 462 463 def test_missing_node(self): 464 """Test GetNode() when the node is missing""" 465 self.assertEqual(None, self.dtb.GetNode('missing')) 466 467 def test_phandle(self): 468 """Test GetNode() on a phandle""" 469 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts')) 470 node = dtb.GetNode('/phandle-source2') 471 prop = node.props['clocks'] 472 self.assertTrue(fdt32_to_cpu(prop.value) > 0) 473 474 def _convert_prop(self, prop_name): 475 """Helper function to look up a property in self.node and return it 476 477 Args: 478 str: Property name to find 479 480 Returns: 481 fdt.Prop: object for this property 482 """ 483 prop = self.fdt.getprop(self.node.Offset(), prop_name) 484 return fdt.Prop(self.node, -1, prop_name, prop) 485 486 def test_make_prop(self): 487 """Test we can convert all the the types that are supported""" 488 prop = self._convert_prop('boolval') 489 self.assertEqual(Type.BOOL, prop.type) 490 self.assertEqual(True, prop.value) 491 492 prop = self._convert_prop('intval') 493 self.assertEqual(Type.INT, prop.type) 494 self.assertEqual(1, fdt32_to_cpu(prop.value)) 495 496 prop = self._convert_prop('int64val') 497 self.assertEqual(Type.INT, prop.type) 498 self.assertEqual(0x123456789abcdef0, fdt64_to_cpu(prop.value)) 499 500 prop = self._convert_prop('intarray') 501 self.assertEqual(Type.INT, prop.type) 502 val = [fdt32_to_cpu(val) for val in prop.value] 503 self.assertEqual([2, 3, 4], val) 504 505 prop = self._convert_prop('byteval') 506 self.assertEqual(Type.BYTE, prop.type) 507 self.assertEqual(5, ord(prop.value)) 508 509 prop = self._convert_prop('longbytearray') 510 self.assertEqual(Type.BYTE, prop.type) 511 val = [ord(val) for val in prop.value] 512 self.assertEqual([9, 10, 11, 12, 13, 14, 15, 16, 17], val) 513 514 prop = self._convert_prop('stringval') 515 self.assertEqual(Type.STRING, prop.type) 516 self.assertEqual('message', prop.value) 517 518 prop = self._convert_prop('stringarray') 519 self.assertEqual(Type.STRING, prop.type) 520 self.assertEqual(['multi-word', 'message'], prop.value) 521 522 prop = self._convert_prop('notstring') 523 self.assertEqual(Type.BYTE, prop.type) 524 val = [ord(val) for val in prop.value] 525 self.assertEqual([0x20, 0x21, 0x22, 0x10, 0], val) 526 527 def test_get_empty(self): 528 """Tests the GetEmpty() function for the various supported types""" 529 self.assertEqual(True, fdt.Prop.GetEmpty(Type.BOOL)) 530 self.assertEqual(chr(0), fdt.Prop.GetEmpty(Type.BYTE)) 531 self.assertEqual(tools.get_bytes(0, 4), fdt.Prop.GetEmpty(Type.INT)) 532 self.assertEqual('', fdt.Prop.GetEmpty(Type.STRING)) 533 534 def test_get_offset(self): 535 """Test we can get the offset of a property""" 536 prop, value = _get_property_value(self.dtb, self.node, 'longbytearray') 537 self.assertEqual(prop.value, value) 538 539 def test_widen(self): 540 """Test widening of values""" 541 node2 = self.dtb.GetNode('/spl-test2') 542 node3 = self.dtb.GetNode('/spl-test3') 543 prop = self.node.props['intval'] 544 545 # No action 546 prop2 = node2.props['intval'] 547 prop.Widen(prop2) 548 self.assertEqual(Type.INT, prop.type) 549 self.assertEqual(1, fdt32_to_cpu(prop.value)) 550 551 # Convert single value to array 552 prop2 = self.node.props['intarray'] 553 prop.Widen(prop2) 554 self.assertEqual(Type.INT, prop.type) 555 self.assertTrue(isinstance(prop.value, list)) 556 557 # A 4-byte array looks like a single integer. When widened by a longer 558 # byte array, it should turn into an array. 559 prop = self.node.props['longbytearray'] 560 prop2 = node2.props['longbytearray'] 561 prop3 = node3.props['longbytearray'] 562 self.assertFalse(isinstance(prop2.value, list)) 563 self.assertEqual(4, len(prop2.value)) 564 self.assertEqual(b'\x09\x0a\x0b\x0c', prop2.value) 565 prop2.Widen(prop) 566 self.assertTrue(isinstance(prop2.value, list)) 567 self.assertEqual(9, len(prop2.value)) 568 self.assertEqual(['\x09', '\x0a', '\x0b', '\x0c', '\0', 569 '\0', '\0', '\0', '\0'], prop2.value) 570 prop3.Widen(prop) 571 self.assertTrue(isinstance(prop3.value, list)) 572 self.assertEqual(9, len(prop3.value)) 573 self.assertEqual(['\x09', '\x0a', '\x0b', '\x0c', '\x0d', 574 '\x0e', '\x0f', '\x10', '\0'], prop3.value) 575 576 def test_widen_more(self): 577 """More tests of widening values""" 578 node2 = self.dtb.GetNode('/spl-test2') 579 node3 = self.dtb.GetNode('/spl-test3') 580 prop = self.node.props['intval'] 581 582 # Test widening a single string into a string array 583 prop = self.node.props['stringval'] 584 prop2 = node2.props['stringarray'] 585 self.assertFalse(isinstance(prop.value, list)) 586 self.assertEqual(7, len(prop.value)) 587 prop.Widen(prop2) 588 self.assertTrue(isinstance(prop.value, list)) 589 self.assertEqual(3, len(prop.value)) 590 591 # Enlarging an existing array 592 prop = self.node.props['stringarray'] 593 prop2 = node2.props['stringarray'] 594 self.assertTrue(isinstance(prop.value, list)) 595 self.assertEqual(2, len(prop.value)) 596 prop.Widen(prop2) 597 self.assertTrue(isinstance(prop.value, list)) 598 self.assertEqual(3, len(prop.value)) 599 600 # Widen an array of ints with an int (should do nothing) 601 prop = self.node.props['intarray'] 602 prop2 = node2.props['intval'] 603 self.assertEqual(Type.INT, prop.type) 604 self.assertEqual(3, len(prop.value)) 605 prop.Widen(prop2) 606 self.assertEqual(Type.INT, prop.type) 607 self.assertEqual(3, len(prop.value)) 608 609 # Widen an empty bool to an int 610 prop = self.node.props['maybe-empty-int'] 611 prop3 = node3.props['maybe-empty-int'] 612 self.assertEqual(Type.BOOL, prop.type) 613 self.assertEqual(True, prop.value) 614 self.assertEqual(Type.INT, prop3.type) 615 self.assertFalse(isinstance(prop.value, list)) 616 self.assertEqual(4, len(prop3.value)) 617 prop.Widen(prop3) 618 self.assertEqual(Type.INT, prop.type) 619 self.assertTrue(isinstance(prop.value, list)) 620 self.assertEqual(1, len(prop.value)) 621 622 def test_add(self): 623 """Test adding properties""" 624 self.fdt.pack() 625 # This function should automatically expand the device tree 626 self.node.AddZeroProp('one') 627 self.node.AddZeroProp('two') 628 self.node.AddZeroProp('three') 629 self.dtb.Sync(auto_resize=True) 630 631 # Updating existing properties should be OK, since the device-tree size 632 # does not change 633 self.fdt.pack() 634 self.node.SetInt('one', 1) 635 self.node.SetInt('two', 2) 636 self.node.SetInt('three', 3) 637 self.dtb.Sync(auto_resize=False) 638 639 # This should fail since it would need to increase the device-tree size 640 self.node.AddZeroProp('four') 641 with self.assertRaises(libfdt.FdtException) as exc: 642 self.dtb.Sync(auto_resize=False) 643 self.assertIn('FDT_ERR_NOSPACE', str(exc.exception)) 644 self.dtb.Sync(auto_resize=True) 645 646 def test_add_more(self): 647 """Test various other methods for adding and setting properties""" 648 self.node.AddZeroProp('one') 649 self.dtb.Sync(auto_resize=True) 650 data = self.fdt.getprop(self.node.Offset(), 'one') 651 self.assertEqual(0, fdt32_to_cpu(data)) 652 653 self.node.SetInt('one', 1) 654 self.dtb.Sync(auto_resize=False) 655 data = self.fdt.getprop(self.node.Offset(), 'one') 656 self.assertEqual(1, fdt32_to_cpu(data)) 657 658 val = 1234 659 self.node.AddInt('integer', val) 660 self.dtb.Sync(auto_resize=True) 661 data = self.fdt.getprop(self.node.Offset(), 'integer') 662 self.assertEqual(val, fdt32_to_cpu(data)) 663 664 val = '123' + chr(0) + '456' 665 self.node.AddString('string', val) 666 self.dtb.Sync(auto_resize=True) 667 data = self.fdt.getprop(self.node.Offset(), 'string') 668 self.assertEqual(tools.to_bytes(val) + b'\0', data) 669 670 self.fdt.pack() 671 self.node.SetString('string', val + 'x') 672 with self.assertRaises(libfdt.FdtException) as exc: 673 self.dtb.Sync(auto_resize=False) 674 self.assertIn('FDT_ERR_NOSPACE', str(exc.exception)) 675 self.node.SetString('string', val[:-1]) 676 677 prop = self.node.props['string'] 678 prop.SetData(tools.to_bytes(val)) 679 self.dtb.Sync(auto_resize=False) 680 data = self.fdt.getprop(self.node.Offset(), 'string') 681 self.assertEqual(tools.to_bytes(val), data) 682 683 self.node.AddEmptyProp('empty', 5) 684 self.dtb.Sync(auto_resize=True) 685 prop = self.node.props['empty'] 686 prop.SetData(tools.to_bytes(val)) 687 self.dtb.Sync(auto_resize=False) 688 data = self.fdt.getprop(self.node.Offset(), 'empty') 689 self.assertEqual(tools.to_bytes(val), data) 690 691 self.node.SetData('empty', b'123') 692 self.assertEqual(b'123', prop.bytes) 693 694 # Trying adding a lot of data at once 695 self.node.AddData('data', tools.get_bytes(65, 20000)) 696 self.dtb.Sync(auto_resize=True) 697 698 def test_string_list(self): 699 """Test adding string-list property to a node""" 700 val = ['123', '456'] 701 self.node.AddStringList('stringlist', val) 702 self.dtb.Sync(auto_resize=True) 703 data = self.fdt.getprop(self.node.Offset(), 'stringlist') 704 self.assertEqual(b'123\x00456\0', data) 705 706 val = [] 707 self.node.AddStringList('stringlist', val) 708 self.dtb.Sync(auto_resize=True) 709 data = self.fdt.getprop(self.node.Offset(), 'stringlist') 710 self.assertEqual(b'', data) 711 712 def test_delete_node(self): 713 """Test deleting a node""" 714 old_offset = self.fdt.path_offset('/spl-test') 715 self.assertGreater(old_offset, 0) 716 self.node.Delete() 717 self.dtb.Sync() 718 new_offset = self.fdt.path_offset('/spl-test', libfdt.QUIET_NOTFOUND) 719 self.assertEqual(-libfdt.NOTFOUND, new_offset) 720 721 def test_from_data(self): 722 """Test creating an FDT from data""" 723 dtb2 = fdt.Fdt.FromData(self.dtb.GetContents()) 724 self.assertEqual(dtb2.GetContents(), self.dtb.GetContents()) 725 726 self.node.AddEmptyProp('empty', 5) 727 self.dtb.Sync(auto_resize=True) 728 self.assertTrue(dtb2.GetContents() != self.dtb.GetContents()) 729 730 def test_missing_set_int(self): 731 """Test handling of a missing property with SetInt""" 732 with self.assertRaises(ValueError) as exc: 733 self.node.SetInt('one', 1) 734 self.assertIn("node '/spl-test': Missing property 'one'", 735 str(exc.exception)) 736 737 def test_missing_set_data(self): 738 """Test handling of a missing property with SetData""" 739 with self.assertRaises(ValueError) as exc: 740 self.node.SetData('one', b'data') 741 self.assertIn("node '/spl-test': Missing property 'one'", 742 str(exc.exception)) 743 744 def test_missing_set_string(self): 745 """Test handling of a missing property with SetString""" 746 with self.assertRaises(ValueError) as exc: 747 self.node.SetString('one', 1) 748 self.assertIn("node '/spl-test': Missing property 'one'", 749 str(exc.exception)) 750 751 def test_get_filename(self): 752 """Test the dtb filename can be provided""" 753 self.assertEqual(tools.get_output_filename('source.dtb'), 754 self.dtb.GetFilename()) 755 756 757class TestFdtUtil(unittest.TestCase): 758 """Tests for the fdt_util module 759 760 This module will likely be mostly replaced at some point, once upstream 761 libfdt has better Python support. For now, this provides tests for current 762 functionality. 763 """ 764 @classmethod 765 def setUpClass(cls): 766 tools.prepare_output_dir(None) 767 768 @classmethod 769 def tearDownClass(cls): 770 tools.finalise_output_dir() 771 772 def setUp(self): 773 self.dtb = fdt.FdtScan(find_dtb_file('dtoc_test_simple.dts')) 774 self.node = self.dtb.GetNode('/spl-test') 775 776 def test_get_int(self): 777 """Test getting an int from a node""" 778 self.assertEqual(1, fdt_util.GetInt(self.node, 'intval')) 779 self.assertEqual(3, fdt_util.GetInt(self.node, 'missing', 3)) 780 781 with self.assertRaises(ValueError) as exc: 782 fdt_util.GetInt(self.node, 'intarray') 783 self.assertIn("property 'intarray' has list value: expecting a single " 784 'integer', str(exc.exception)) 785 786 def test_get_int64(self): 787 """Test getting a 64-bit int from a node""" 788 self.assertEqual(0x123456789abcdef0, 789 fdt_util.GetInt64(self.node, 'int64val')) 790 self.assertEqual(3, fdt_util.GetInt64(self.node, 'missing', 3)) 791 792 with self.assertRaises(ValueError) as exc: 793 fdt_util.GetInt64(self.node, 'intarray') 794 self.assertIn( 795 "property 'intarray' should be a list with 2 items for 64-bit values", 796 str(exc.exception)) 797 798 def test_get_string(self): 799 """Test getting a string from a node""" 800 self.assertEqual('message', fdt_util.GetString(self.node, 'stringval')) 801 self.assertEqual('test', fdt_util.GetString(self.node, 'missing', 802 'test')) 803 self.assertEqual('', fdt_util.GetString(self.node, 'boolval')) 804 805 with self.assertRaises(ValueError) as exc: 806 self.assertEqual(3, fdt_util.GetString(self.node, 'stringarray')) 807 self.assertIn("property 'stringarray' has list value: expecting a " 808 'single string', str(exc.exception)) 809 810 def test_get_string_list(self): 811 """Test getting a string list from a node""" 812 self.assertEqual(['message'], 813 fdt_util.GetStringList(self.node, 'stringval')) 814 self.assertEqual( 815 ['multi-word', 'message'], 816 fdt_util.GetStringList(self.node, 'stringarray')) 817 self.assertEqual(['test'], 818 fdt_util.GetStringList(self.node, 'missing', ['test'])) 819 self.assertEqual([], fdt_util.GetStringList(self.node, 'boolval')) 820 821 def test_get_args(self): 822 """Test getting arguments from a node""" 823 node = self.dtb.GetNode('/orig-node') 824 self.assertEqual(['message'], fdt_util.GetArgs(self.node, 'stringval')) 825 self.assertEqual( 826 ['multi-word', 'message'], 827 fdt_util.GetArgs(self.node, 'stringarray')) 828 self.assertEqual([], fdt_util.GetArgs(self.node, 'boolval')) 829 self.assertEqual(['-n first', 'second', '-p', '123,456', '-x'], 830 fdt_util.GetArgs(node, 'args')) 831 self.assertEqual(['a space', 'there'], 832 fdt_util.GetArgs(node, 'args2')) 833 self.assertEqual(['-n', 'first', 'second', '-p', '123,456', '-x'], 834 fdt_util.GetArgs(node, 'args3')) 835 with self.assertRaises(ValueError) as exc: 836 fdt_util.GetArgs(self.node, 'missing') 837 self.assertIn( 838 "Node '/spl-test': Expected property 'missing'", 839 str(exc.exception)) 840 841 def test_get_bool(self): 842 """Test getting a bool from a node""" 843 self.assertEqual(True, fdt_util.GetBool(self.node, 'boolval')) 844 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing')) 845 self.assertEqual(True, fdt_util.GetBool(self.node, 'missing', True)) 846 self.assertEqual(False, fdt_util.GetBool(self.node, 'missing', False)) 847 848 def test_get_byte(self): 849 """Test getting a byte from a node""" 850 self.assertEqual(5, fdt_util.GetByte(self.node, 'byteval')) 851 self.assertEqual(3, fdt_util.GetByte(self.node, 'missing', 3)) 852 853 with self.assertRaises(ValueError) as exc: 854 fdt_util.GetByte(self.node, 'longbytearray') 855 self.assertIn("property 'longbytearray' has list value: expecting a " 856 'single byte', str(exc.exception)) 857 858 with self.assertRaises(ValueError) as exc: 859 fdt_util.GetByte(self.node, 'intval') 860 self.assertIn("property 'intval' has length 4, expecting 1", 861 str(exc.exception)) 862 863 def test_get_bytes(self): 864 """Test getting multiple bytes from a node""" 865 self.assertEqual(bytes([5]), fdt_util.GetBytes(self.node, 'byteval', 1)) 866 self.assertEqual(None, fdt_util.GetBytes(self.node, 'missing', 3)) 867 self.assertEqual( 868 bytes([3]), fdt_util.GetBytes(self.node, 'missing', 3, bytes([3]))) 869 870 with self.assertRaises(ValueError) as exc: 871 fdt_util.GetBytes(self.node, 'longbytearray', 7) 872 self.assertIn( 873 "Node 'spl-test' property 'longbytearray' has length 9, expecting 7", 874 str(exc.exception)) 875 876 self.assertEqual( 877 bytes([0, 0, 0, 1]), fdt_util.GetBytes(self.node, 'intval', 4)) 878 self.assertEqual( 879 bytes([3]), fdt_util.GetBytes(self.node, 'missing', 3, bytes([3]))) 880 881 def test_get_phandle_list(self): 882 """Test getting a list of phandles from a node""" 883 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts')) 884 node = dtb.GetNode('/phandle-source2') 885 self.assertEqual([1], fdt_util.GetPhandleList(node, 'clocks')) 886 node = dtb.GetNode('/phandle-source') 887 self.assertEqual([1, 2, 11, 3, 12, 13, 1], 888 fdt_util.GetPhandleList(node, 'clocks')) 889 self.assertEqual(None, fdt_util.GetPhandleList(node, 'missing')) 890 891 def test_get_data_type(self): 892 """Test getting a value of a particular type from a node""" 893 self.assertEqual(1, fdt_util.GetDatatype(self.node, 'intval', int)) 894 self.assertEqual('message', fdt_util.GetDatatype(self.node, 'stringval', 895 str)) 896 with self.assertRaises(ValueError): 897 self.assertEqual(3, fdt_util.GetDatatype(self.node, 'boolval', 898 bool)) 899 def test_fdt_cells_to_cpu(self): 900 """Test getting cells with the correct endianness""" 901 val = self.node.props['intarray'].value 902 self.assertEqual(0, fdt_util.fdt_cells_to_cpu(val, 0)) 903 self.assertEqual(2, fdt_util.fdt_cells_to_cpu(val, 1)) 904 905 dtb2 = fdt.FdtScan(find_dtb_file('dtoc_test_addr64.dts')) 906 node1 = dtb2.GetNode('/test1') 907 val = node1.props['reg'].value 908 self.assertEqual(0x1234, fdt_util.fdt_cells_to_cpu(val, 2)) 909 910 node2 = dtb2.GetNode('/test2') 911 val = node2.props['reg'].value 912 self.assertEqual(0x1234567890123456, fdt_util.fdt_cells_to_cpu(val, 2)) 913 self.assertEqual(0x9876543210987654, fdt_util.fdt_cells_to_cpu(val[2:], 914 2)) 915 self.assertEqual(0x12345678, fdt_util.fdt_cells_to_cpu(val, 1)) 916 917 def test_ensure_compiled(self): 918 """Test a degenerate case of this function (file already compiled)""" 919 dtb = fdt_util.EnsureCompiled(find_dtb_file('dtoc_test_simple.dts')) 920 self.assertEqual(dtb, fdt_util.EnsureCompiled(dtb)) 921 922 def test_ensure_compiled_tmpdir(self): 923 """Test providing a temporary directory""" 924 old_outdir = tools.outdir 925 try: 926 tools.outdir= None 927 tmpdir = tempfile.mkdtemp(prefix='test_fdt.') 928 dtb = fdt_util.EnsureCompiled(find_dtb_file('dtoc_test_simple.dts'), 929 tmpdir) 930 self.assertEqual(tmpdir, os.path.dirname(dtb)) 931 shutil.rmtree(tmpdir) 932 finally: 933 tools.outdir = old_outdir 934 935 def test_get_phandle_name_offset(self): 936 val = fdt_util.GetPhandleNameOffset(self.node, 'missing') 937 self.assertIsNone(val) 938 939 dtb = fdt.FdtScan(find_dtb_file('dtoc_test_phandle.dts')) 940 node = dtb.GetNode('/phandle-source') 941 node, name, offset = fdt_util.GetPhandleNameOffset(node, 942 'phandle-name-offset') 943 self.assertEqual('phandle3-target', node.name) 944 self.assertEqual('fred', name) 945 self.assertEqual(123, offset) 946 947def run_test_coverage(build_dir): 948 """Run the tests and check that we get 100% coverage 949 950 Args: 951 build_dir (str): Directory containing the build output 952 """ 953 test_util.run_test_coverage('tools/dtoc/test_fdt.py', None, 954 ['tools/patman/*.py', 'tools/u_boot_pylib/*', '*test_fdt.py'], 955 build_dir) 956 957 958def run_tests(names, processes): 959 """Run all the test we have for the fdt model 960 961 Args: 962 names (list of str): List of test names provided. Only the first is used 963 processes (int): Number of processes to use (None means as many as there 964 are CPUs on the system. This must be set to 1 when running under 965 the python3-coverage tool 966 967 Returns: 968 int: Return code, 0 on success 969 """ 970 test_name = names[0] if names else None 971 result = test_util.run_test_suites( 972 'test_fdt', False, False, False, processes, test_name, None, 973 [TestFdt, TestNode, TestProp, TestFdtUtil]) 974 975 return (0 if result.wasSuccessful() else 1) 976 977 978def main(): 979 """Main program for this tool""" 980 parser = ArgumentParser() 981 parser.add_argument('-B', '--build-dir', type=str, default='b', 982 help='Directory containing the build output') 983 parser.add_argument('-P', '--processes', type=int, 984 help='set number of processes to use for running tests') 985 parser.add_argument('-t', '--test', action='store_true', dest='test', 986 default=False, help='run tests') 987 parser.add_argument('-T', '--test-coverage', action='store_true', 988 default=False, 989 help='run tests and check for 100% coverage') 990 parser.add_argument('name', nargs='*') 991 args = parser.parse_args() 992 993 # Run our meagre tests 994 if args.test: 995 ret_code = run_tests(args.name, args.processes) 996 return ret_code 997 if args.test_coverage: 998 run_test_coverage(args.build_dir) 999 return 0 1000 1001if __name__ == '__main__': 1002 sys.exit(main()) 1003