Prepare v2023.10
[platform/kernel/u-boot.git] / tools / dtoc / test_fdt.py
1 #!/usr/bin/env python3
2 # SPDX-License-Identifier: GPL-2.0+
3
4 """
5 Tests for the Fdt module
6 Copyright (c) 2018 Google, Inc
7 Written by Simon Glass <sjg@chromium.org>
8 """
9
10 from argparse import ArgumentParser
11 import os
12 import shutil
13 import sys
14 import tempfile
15 import unittest
16
17 # Bring in the patman libraries
18 our_path = os.path.dirname(os.path.realpath(__file__))
19 sys.path.insert(1, os.path.join(our_path, '..'))
20
21 # Bring in the libfdt module
22 sys.path.insert(2, 'scripts/dtc/pylibfdt')
23 sys.path.insert(2, os.path.join(our_path, '../../scripts/dtc/pylibfdt'))
24 sys.path.insert(2, os.path.join(our_path,
25                 '../../build-sandbox_spl/scripts/dtc/pylibfdt'))
26
27 #pylint: disable=wrong-import-position
28 from dtoc import fdt
29 from dtoc import fdt_util
30 from dtoc.fdt_util import fdt32_to_cpu, fdt64_to_cpu
31 from dtoc.fdt import Type, BytesToValue
32 import libfdt
33 from u_boot_pylib import test_util
34 from u_boot_pylib import tools
35 from u_boot_pylib import tout
36
37 #pylint: disable=protected-access
38
39 def _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
62 def 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
74 class 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
158 class 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
447 class 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
757 class 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
947 def 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
958 def 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
978 def 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
1001 if __name__ == '__main__':
1002     sys.exit(main())