1#!/usr/bin/python
2# SPDX-License-Identifier: GPL-2.0+
3#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
7
8from enum import IntEnum
9import struct
10import sys
11
12from dtoc import fdt_util
13import libfdt
14from libfdt import QUIET_NOTFOUND
15from u_boot_pylib import tools
16
17# This deals with a device tree, presenting it as an assortment of Node and
18# Prop objects, representing nodes and properties, respectively. This file
19# contains the base classes and defines the high-level API. You can use
20# FdtScan() as a convenience function to create and scan an Fdt.
21
22# This implementation uses a libfdt Python library to access the device tree,
23# so it is fairly efficient.
24
25# A list of types we support
26class Type(IntEnum):
27    # Types in order from widest to narrowest
28    (BYTE, INT, STRING, BOOL, INT64) = range(5)
29
30    def needs_widening(self, other):
31        """Check if this type needs widening to hold a value from another type
32
33        A wider type is one that can hold a wider array of information than
34        another one, or is less restrictive, so it can hold the information of
35        another type as well as its own. This is similar to the concept of
36        type-widening in C.
37
38        This uses a simple arithmetic comparison, since type values are in order
39        from widest (BYTE) to narrowest (INT64).
40
41        Args:
42            other: Other type to compare against
43
44        Return:
45            True if the other type is wider
46        """
47        return self.value > other.value
48
49def CheckErr(errnum, msg):
50    if errnum:
51        raise ValueError('Error %d: %s: %s' %
52            (errnum, libfdt.fdt_strerror(errnum), msg))
53
54
55def BytesToValue(data):
56    """Converts a string of bytes into a type and value
57
58    Args:
59        A bytes value (which on Python 2 is an alias for str)
60
61    Return:
62        A tuple:
63            Type of data
64            Data, either a single element or a list of elements. Each element
65            is one of:
66                Type.STRING: str/bytes value from the property
67                Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
68                Type.BYTE: a byte stored as a single-byte str/bytes
69    """
70    data = bytes(data)
71    size = len(data)
72    strings = data.split(b'\0')
73    is_string = True
74    count = len(strings) - 1
75    if count > 0 and not len(strings[-1]):
76        for string in strings[:-1]:
77            if not string:
78                is_string = False
79                break
80            for ch in string:
81                if ch < 32 or ch > 127:
82                    is_string = False
83                    break
84    else:
85        is_string = False
86    if is_string:
87        if count == 1:
88            return Type.STRING, strings[0].decode()
89        else:
90            return Type.STRING, [s.decode() for s in strings[:-1]]
91    if size % 4:
92        if size == 1:
93            return Type.BYTE, chr(data[0])
94        else:
95            return Type.BYTE, [chr(ch) for ch in list(data)]
96    val = []
97    for i in range(0, size, 4):
98        val.append(data[i:i + 4])
99    if size == 4:
100        return Type.INT, val[0]
101    else:
102        return Type.INT, val
103
104
105class Prop:
106    """A device tree property
107
108    Properties:
109        node: Node containing this property
110        offset: Offset of the property (None if still to be synced)
111        name: Property name (as per the device tree)
112        value: Property value as a string of bytes, or a list of strings of
113            bytes
114        type: Value type
115    """
116    def __init__(self, node, offset, name, data):
117        self._node = node
118        self._offset = offset
119        self.name = name
120        self.value = None
121        self.bytes = bytes(data)
122        self.dirty = offset is None
123        if not data:
124            self.type = Type.BOOL
125            self.value = True
126            return
127        self.type, self.value = BytesToValue(bytes(data))
128
129    def RefreshOffset(self, poffset):
130        self._offset = poffset
131
132    def Widen(self, newprop):
133        """Figure out which property type is more general
134
135        Given a current property and a new property, this function returns the
136        one that is less specific as to type. The less specific property will
137        be ble to represent the data in the more specific property. This is
138        used for things like:
139
140            node1 {
141                compatible = "fred";
142                value = <1>;
143            };
144            node1 {
145                compatible = "fred";
146                value = <1 2>;
147            };
148
149        He we want to use an int array for 'value'. The first property
150        suggests that a single int is enough, but the second one shows that
151        it is not. Calling this function with these two propertes would
152        update the current property to be like the second, since it is less
153        specific.
154        """
155        if self.type.needs_widening(newprop.type):
156
157            # A boolean has an empty value: if it exists it is True and if not
158            # it is False. So when widening we always start with an empty list
159            # since the only valid integer property would be an empty list of
160            # integers.
161            # e.g. this is a boolean:
162            #    some-prop;
163            # and it would be widened to int list by:
164            #    some-prop = <1 2>;
165            if self.type == Type.BOOL:
166                self.type = Type.INT
167                self.value = [self.GetEmpty(self.type)]
168            if self.type == Type.INT and newprop.type == Type.BYTE:
169                if type(self.value) == list:
170                    new_value = []
171                    for val in self.value:
172                        new_value += [chr(by) for by in val]
173                else:
174                    new_value = [chr(by) for by in self.value]
175                self.value = new_value
176            self.type = newprop.type
177
178        if type(newprop.value) == list:
179            if type(self.value) != list:
180                self.value = [self.value]
181
182            if len(newprop.value) > len(self.value):
183                val = self.GetEmpty(self.type)
184                while len(self.value) < len(newprop.value):
185                    self.value.append(val)
186
187    @classmethod
188    def GetEmpty(self, type):
189        """Get an empty / zero value of the given type
190
191        Returns:
192            A single value of the given type
193        """
194        if type == Type.BYTE:
195            return chr(0)
196        elif type == Type.INT:
197            return struct.pack('>I', 0);
198        elif type == Type.STRING:
199            return ''
200        else:
201            return True
202
203    def GetOffset(self):
204        """Get the offset of a property
205
206        Returns:
207            The offset of the property (struct fdt_property) within the file
208        """
209        self._node._fdt.CheckCache()
210        return self._node._fdt.GetStructOffset(self._offset)
211
212    def SetInt(self, val):
213        """Set the integer value of the property
214
215        The device tree is marked dirty so that the value will be written to
216        the block on the next sync.
217
218        Args:
219            val: Integer value (32-bit, single cell)
220        """
221        self.bytes = struct.pack('>I', val);
222        self.value = self.bytes
223        self.type = Type.INT
224        self.dirty = True
225
226    def SetData(self, bytes):
227        """Set the value of a property as bytes
228
229        Args:
230            bytes: New property value to set
231        """
232        self.bytes = bytes
233        self.type, self.value = BytesToValue(bytes)
234        self.dirty = True
235
236    def Sync(self, auto_resize=False):
237        """Sync property changes back to the device tree
238
239        This updates the device tree blob with any changes to this property
240        since the last sync.
241
242        Args:
243            auto_resize: Resize the device tree automatically if it does not
244                have enough space for the update
245
246        Raises:
247            FdtException if auto_resize is False and there is not enough space
248        """
249        if self.dirty:
250            node = self._node
251            fdt_obj = node._fdt._fdt_obj
252            node_name = fdt_obj.get_name(node._offset)
253            if node_name and node_name != node.name:
254                raise ValueError("Internal error, node '%s' name mismatch '%s'" %
255                                 (node.path, node_name))
256
257            if auto_resize:
258                while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
259                                    (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
260                    fdt_obj.resize(fdt_obj.totalsize() + 1024 +
261                                   len(self.bytes))
262                    fdt_obj.setprop(node.Offset(), self.name, self.bytes)
263            else:
264                fdt_obj.setprop(node.Offset(), self.name, self.bytes)
265            self.dirty = False
266
267
268class Node:
269    """A device tree node
270
271    Properties:
272        parent: Parent Node
273        offset: Integer offset in the device tree (None if to be synced)
274        name: Device tree node tname
275        path: Full path to node, along with the node name itself
276        _fdt: Device tree object
277        subnodes: A list of subnodes for this node, each a Node object
278        props: A dict of properties for this node, each a Prop object.
279            Keyed by property name
280    """
281    def __init__(self, fdt, parent, offset, name, path):
282        self._fdt = fdt
283        self.parent = parent
284        self._offset = offset
285        self.name = name
286        self.path = path
287        self.subnodes = []
288        self.props = {}
289
290    def GetFdt(self):
291        """Get the Fdt object for this node
292
293        Returns:
294            Fdt object
295        """
296        return self._fdt
297
298    def FindNode(self, name):
299        """Find a node given its name
300
301        Args:
302            name: Node name to look for
303        Returns:
304            Node object if found, else None
305        """
306        for subnode in self.subnodes:
307            if subnode.name == name:
308                return subnode
309        return None
310
311    def Offset(self):
312        """Returns the offset of a node, after checking the cache
313
314        This should be used instead of self._offset directly, to ensure that
315        the cache does not contain invalid offsets.
316        """
317        self._fdt.CheckCache()
318        return self._offset
319
320    def Scan(self):
321        """Scan a node's properties and subnodes
322
323        This fills in the props and subnodes properties, recursively
324        searching into subnodes so that the entire tree is built.
325        """
326        fdt_obj = self._fdt._fdt_obj
327        self.props = self._fdt.GetProps(self)
328        phandle = fdt_obj.get_phandle(self.Offset())
329        if phandle:
330            self._fdt.phandle_to_node[phandle] = self
331
332        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
333        while offset >= 0:
334            sep = '' if self.path[-1] == '/' else '/'
335            name = fdt_obj.get_name(offset)
336            path = self.path + sep + name
337            node = Node(self._fdt, self, offset, name, path)
338            self.subnodes.append(node)
339
340            node.Scan()
341            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
342
343    def Refresh(self, my_offset):
344        """Fix up the _offset for each node, recursively
345
346        Note: This does not take account of property offsets - these will not
347        be updated.
348        """
349        fdt_obj = self._fdt._fdt_obj
350        if self._offset != my_offset:
351            self._offset = my_offset
352        name = fdt_obj.get_name(self._offset)
353        if name and self.name != name:
354            raise ValueError("Internal error, node '%s' name mismatch '%s'" %
355                             (self.path, name))
356
357        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
358        for subnode in self.subnodes:
359            if subnode._offset is None:
360                continue
361            if subnode.name != fdt_obj.get_name(offset):
362                raise ValueError('Internal error, node name mismatch %s != %s' %
363                                 (subnode.name, fdt_obj.get_name(offset)))
364            subnode.Refresh(offset)
365            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
366        if offset != -libfdt.FDT_ERR_NOTFOUND:
367            raise ValueError('Internal error, offset == %d' % offset)
368
369        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
370        while poffset >= 0:
371            p = fdt_obj.get_property_by_offset(poffset)
372            prop = self.props.get(p.name)
373            if not prop:
374                raise ValueError("Internal error, node '%s' property '%s' missing, "
375                                 'offset %d' % (self.path, p.name, poffset))
376            prop.RefreshOffset(poffset)
377            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
378
379    def DeleteProp(self, prop_name):
380        """Delete a property of a node
381
382        The property is deleted and the offset cache is invalidated.
383
384        Args:
385            prop_name: Name of the property to delete
386        Raises:
387            ValueError if the property does not exist
388        """
389        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
390                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
391        del self.props[prop_name]
392        self._fdt.Invalidate()
393
394    def AddZeroProp(self, prop_name):
395        """Add a new property to the device tree with an integer value of 0.
396
397        Args:
398            prop_name: Name of property
399        """
400        self.props[prop_name] = Prop(self, None, prop_name,
401                                     tools.get_bytes(0, 4))
402
403    def AddEmptyProp(self, prop_name, len):
404        """Add a property with a fixed data size, for filling in later
405
406        The device tree is marked dirty so that the value will be written to
407        the blob on the next sync.
408
409        Args:
410            prop_name: Name of property
411            len: Length of data in property
412        """
413        value = tools.get_bytes(0, len)
414        self.props[prop_name] = Prop(self, None, prop_name, value)
415
416    def _CheckProp(self, prop_name):
417        """Check if a property is present
418
419        Args:
420            prop_name: Name of property
421
422        Returns:
423            self
424
425        Raises:
426            ValueError if the property is missing
427        """
428        if prop_name not in self.props:
429            raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
430                             (self._fdt._fname, self.path, prop_name))
431        return self
432
433    def SetInt(self, prop_name, val):
434        """Update an integer property int the device tree.
435
436        This is not allowed to change the size of the FDT.
437
438        The device tree is marked dirty so that the value will be written to
439        the blob on the next sync.
440
441        Args:
442            prop_name: Name of property
443            val: Value to set
444        """
445        self._CheckProp(prop_name).props[prop_name].SetInt(val)
446
447    def SetData(self, prop_name, val):
448        """Set the data value of a property
449
450        The device tree is marked dirty so that the value will be written to
451        the blob on the next sync.
452
453        Args:
454            prop_name: Name of property to set
455            val: Data value to set
456        """
457        self._CheckProp(prop_name).props[prop_name].SetData(val)
458
459    def SetString(self, prop_name, val):
460        """Set the string value of a property
461
462        The device tree is marked dirty so that the value will be written to
463        the blob on the next sync.
464
465        Args:
466            prop_name: Name of property to set
467            val: String value to set (will be \0-terminated in DT)
468        """
469        if type(val) == str:
470            val = val.encode('utf-8')
471        self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
472
473    def AddData(self, prop_name, val):
474        """Add a new property to a node
475
476        The device tree is marked dirty so that the value will be written to
477        the blob on the next sync.
478
479        Args:
480            prop_name: Name of property to add
481            val: Bytes value of property
482
483        Returns:
484            Prop added
485        """
486        prop = Prop(self, None, prop_name, val)
487        self.props[prop_name] = prop
488        return prop
489
490    def AddString(self, prop_name, val):
491        """Add a new string property to a node
492
493        The device tree is marked dirty so that the value will be written to
494        the blob on the next sync.
495
496        Args:
497            prop_name: Name of property to add
498            val: String value of property
499
500        Returns:
501            Prop added
502        """
503        val = bytes(val, 'utf-8')
504        return self.AddData(prop_name, val + b'\0')
505
506    def AddStringList(self, prop_name, val):
507        """Add a new string-list property to a node
508
509        The device tree is marked dirty so that the value will be written to
510        the blob on the next sync.
511
512        Args:
513            prop_name: Name of property to add
514            val (list of str): List of strings to add
515
516        Returns:
517            Prop added
518        """
519        out = b'\0'.join(bytes(s, 'utf-8') for s in val) + b'\0' if val else b''
520        return self.AddData(prop_name, out)
521
522    def AddInt(self, prop_name, val):
523        """Add a new integer property to a node
524
525        The device tree is marked dirty so that the value will be written to
526        the blob on the next sync.
527
528        Args:
529            prop_name: Name of property to add
530            val: Integer value of property
531
532        Returns:
533            Prop added
534        """
535        return self.AddData(prop_name, struct.pack('>I', val))
536
537    def AddSubnode(self, name):
538        """Add a new subnode to the node
539
540        Args:
541            name: name of node to add
542
543        Returns:
544            New subnode that was created
545        """
546        path = self.path + '/' + name
547        subnode = Node(self._fdt, self, None, name, path)
548        self.subnodes.append(subnode)
549        return subnode
550
551    def Delete(self):
552        """Delete a node
553
554        The node is deleted and the offset cache is invalidated.
555
556        Args:
557            node (Node): Node to delete
558
559        Raises:
560            ValueError if the node does not exist
561        """
562        CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
563                 "Node '%s': delete" % self.path)
564        parent = self.parent
565        self._fdt.Invalidate()
566        parent.subnodes.remove(self)
567
568    def Sync(self, auto_resize=False):
569        """Sync node changes back to the device tree
570
571        This updates the device tree blob with any changes to this node and its
572        subnodes since the last sync.
573
574        Args:
575            auto_resize: Resize the device tree automatically if it does not
576                have enough space for the update
577
578        Returns:
579            True if the node had to be added, False if it already existed
580
581        Raises:
582            FdtException if auto_resize is False and there is not enough space
583        """
584        added = False
585        if self._offset is None:
586            # The subnode doesn't exist yet, so add it
587            fdt_obj = self._fdt._fdt_obj
588            if auto_resize:
589                while True:
590                    offset = fdt_obj.add_subnode(self.parent._offset, self.name,
591                                                (libfdt.NOSPACE,))
592                    if offset != -libfdt.NOSPACE:
593                        break
594                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
595            else:
596                offset = fdt_obj.add_subnode(self.parent._offset, self.name)
597            self._offset = offset
598            added = True
599
600        # Sync the existing subnodes first, so that we can rely on the offsets
601        # being correct. As soon as we add new subnodes, it pushes all the
602        # existing subnodes up.
603        for node in reversed(self.subnodes):
604            if node._offset is not None:
605                node.Sync(auto_resize)
606
607        # Sync subnodes in reverse so that we get the expected order. Each
608        # new node goes at the start of the subnode list. This avoids an O(n^2)
609        # rescan of node offsets.
610        num_added = 0
611        for node in reversed(self.subnodes):
612            if node.Sync(auto_resize):
613                num_added += 1
614        if num_added:
615            # Reorder our list of nodes to put the new ones first, since that's
616            # what libfdt does
617            old_count = len(self.subnodes) - num_added
618            subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
619            self.subnodes = subnodes
620
621        # Sync properties now, whose offsets should not have been disturbed,
622        # since properties come before subnodes. This is done after all the
623        # subnode processing above, since updating properties can disturb the
624        # offsets of those subnodes.
625        # Properties are synced in reverse order, with new properties added
626        # before existing properties are synced. This ensures that the offsets
627        # of earlier properties are not disturbed.
628        # Note that new properties will have an offset of None here, which
629        # Python cannot sort against int. So use a large value instead so that
630        # new properties are added first.
631        prop_list = sorted(self.props.values(),
632                           key=lambda prop: prop._offset or 1 << 31,
633                           reverse=True)
634        for prop in prop_list:
635            prop.Sync(auto_resize)
636        return added
637
638
639class Fdt:
640    """Provides simple access to a flat device tree blob using libfdts.
641
642    Properties:
643      fname: Filename of fdt
644      _root: Root of device tree (a Node object)
645      name: Helpful name for this Fdt for the user (useful when creating the
646        DT from data rather than a file)
647    """
648    def __init__(self, fname):
649        self._fname = fname
650        self._cached_offsets = False
651        self.phandle_to_node = {}
652        self.name = ''
653        if self._fname:
654            self.name = self._fname
655            self._fname = fdt_util.EnsureCompiled(self._fname)
656
657            with open(self._fname, 'rb') as fd:
658                self._fdt_obj = libfdt.Fdt(fd.read())
659
660    @staticmethod
661    def FromData(data, name=''):
662        """Create a new Fdt object from the given data
663
664        Args:
665            data: Device-tree data blob
666            name: Helpful name for this Fdt for the user
667
668        Returns:
669            Fdt object containing the data
670        """
671        fdt = Fdt(None)
672        fdt._fdt_obj = libfdt.Fdt(bytes(data))
673        fdt.name = name
674        return fdt
675
676    def LookupPhandle(self, phandle):
677        """Look up a phandle
678
679        Args:
680            phandle: Phandle to look up (int)
681
682        Returns:
683            Node object the phandle points to
684        """
685        return self.phandle_to_node.get(phandle)
686
687    def Scan(self, root='/'):
688        """Scan a device tree, building up a tree of Node objects
689
690        This fills in the self._root property
691
692        Args:
693            root: Ignored
694
695        TODO(sjg@chromium.org): Implement the 'root' parameter
696        """
697        self._cached_offsets = True
698        self._root = self.Node(self, None, 0, '/', '/')
699        self._root.Scan()
700
701    def GetRoot(self):
702        """Get the root Node of the device tree
703
704        Returns:
705            The root Node object
706        """
707        return self._root
708
709    def GetNode(self, path):
710        """Look up a node from its path
711
712        Args:
713            path: Path to look up, e.g. '/microcode/update@0'
714        Returns:
715            Node object, or None if not found
716        """
717        node = self._root
718        parts = path.split('/')
719        if len(parts) < 2:
720            return None
721        if len(parts) == 2 and parts[1] == '':
722            return node
723        for part in parts[1:]:
724            node = node.FindNode(part)
725            if not node:
726                return None
727        return node
728
729    def Flush(self):
730        """Flush device tree changes back to the file
731
732        If the device tree has changed in memory, write it back to the file.
733        """
734        with open(self._fname, 'wb') as fd:
735            fd.write(self._fdt_obj.as_bytearray())
736
737    def Sync(self, auto_resize=False):
738        """Make sure any DT changes are written to the blob
739
740        Args:
741            auto_resize: Resize the device tree automatically if it does not
742                have enough space for the update
743
744        Raises:
745            FdtException if auto_resize is False and there is not enough space
746        """
747        self.CheckCache()
748        self._root.Sync(auto_resize)
749        self.Refresh()
750
751    def Pack(self):
752        """Pack the device tree down to its minimum size
753
754        When nodes and properties shrink or are deleted, wasted space can
755        build up in the device tree binary.
756        """
757        CheckErr(self._fdt_obj.pack(), 'pack')
758        self.Refresh()
759
760    def GetContents(self):
761        """Get the contents of the FDT
762
763        Returns:
764            The FDT contents as a string of bytes
765        """
766        return bytes(self._fdt_obj.as_bytearray())
767
768    def GetFdtObj(self):
769        """Get the contents of the FDT
770
771        Returns:
772            The FDT contents as a libfdt.Fdt object
773        """
774        return self._fdt_obj
775
776    def GetProps(self, node):
777        """Get all properties from a node.
778
779        Args:
780            node: Full path to node name to look in.
781
782        Returns:
783            A dictionary containing all the properties, indexed by node name.
784            The entries are Prop objects.
785
786        Raises:
787            ValueError: if the node does not exist.
788        """
789        props_dict = {}
790        poffset = self._fdt_obj.first_property_offset(node._offset,
791                                                      QUIET_NOTFOUND)
792        while poffset >= 0:
793            p = self._fdt_obj.get_property_by_offset(poffset)
794            prop = Prop(node, poffset, p.name, p)
795            props_dict[prop.name] = prop
796
797            poffset = self._fdt_obj.next_property_offset(poffset,
798                                                         QUIET_NOTFOUND)
799        return props_dict
800
801    def Invalidate(self):
802        """Mark our offset cache as invalid"""
803        self._cached_offsets = False
804
805    def CheckCache(self):
806        """Refresh the offset cache if needed"""
807        if self._cached_offsets:
808            return
809        self.Refresh()
810
811    def Refresh(self):
812        """Refresh the offset cache"""
813        self._root.Refresh(0)
814        self._cached_offsets = True
815
816    def GetStructOffset(self, offset):
817        """Get the file offset of a given struct offset
818
819        Args:
820            offset: Offset within the 'struct' region of the device tree
821        Returns:
822            Position of @offset within the device tree binary
823        """
824        return self._fdt_obj.off_dt_struct() + offset
825
826    @classmethod
827    def Node(self, fdt, parent, offset, name, path):
828        """Create a new node
829
830        This is used by Fdt.Scan() to create a new node using the correct
831        class.
832
833        Args:
834            fdt: Fdt object
835            parent: Parent node, or None if this is the root node
836            offset: Offset of node
837            name: Node name
838            path: Full path to node
839        """
840        node = Node(fdt, parent, offset, name, path)
841        return node
842
843    def GetFilename(self):
844        """Get the filename of the device tree
845
846        Returns:
847            String filename
848        """
849        return self._fname
850
851def FdtScan(fname):
852    """Returns a new Fdt object"""
853    dtb = Fdt(fname)
854    dtb.Scan()
855    return dtb
856