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