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 patman 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.name != fdt_obj.get_name(offset):
360                raise ValueError('Internal error, node name mismatch %s != %s' %
361                                 (subnode.name, fdt_obj.get_name(offset)))
362            subnode.Refresh(offset)
363            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
364        if offset != -libfdt.FDT_ERR_NOTFOUND:
365            raise ValueError('Internal error, offset == %d' % offset)
366
367        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
368        while poffset >= 0:
369            p = fdt_obj.get_property_by_offset(poffset)
370            prop = self.props.get(p.name)
371            if not prop:
372                raise ValueError("Internal error, node '%s' property '%s' missing, "
373                                 'offset %d' % (self.path, p.name, poffset))
374            prop.RefreshOffset(poffset)
375            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
376
377    def DeleteProp(self, prop_name):
378        """Delete a property of a node
379
380        The property is deleted and the offset cache is invalidated.
381
382        Args:
383            prop_name: Name of the property to delete
384        Raises:
385            ValueError if the property does not exist
386        """
387        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
388                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
389        del self.props[prop_name]
390        self._fdt.Invalidate()
391
392    def AddZeroProp(self, prop_name):
393        """Add a new property to the device tree with an integer value of 0.
394
395        Args:
396            prop_name: Name of property
397        """
398        self.props[prop_name] = Prop(self, None, prop_name,
399                                     tools.GetBytes(0, 4))
400
401    def AddEmptyProp(self, prop_name, len):
402        """Add a property with a fixed data size, for filling in later
403
404        The device tree is marked dirty so that the value will be written to
405        the blob on the next sync.
406
407        Args:
408            prop_name: Name of property
409            len: Length of data in property
410        """
411        value = tools.GetBytes(0, len)
412        self.props[prop_name] = Prop(self, None, prop_name, value)
413
414    def _CheckProp(self, prop_name):
415        """Check if a property is present
416
417        Args:
418            prop_name: Name of property
419
420        Returns:
421            self
422
423        Raises:
424            ValueError if the property is missing
425        """
426        if prop_name not in self.props:
427            raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
428                             (self._fdt._fname, self.path, prop_name))
429        return self
430
431    def SetInt(self, prop_name, val):
432        """Update an integer property int the device tree.
433
434        This is not allowed to change the size of the FDT.
435
436        The device tree is marked dirty so that the value will be written to
437        the blob on the next sync.
438
439        Args:
440            prop_name: Name of property
441            val: Value to set
442        """
443        self._CheckProp(prop_name).props[prop_name].SetInt(val)
444
445    def SetData(self, prop_name, val):
446        """Set the data value of a property
447
448        The device tree is marked dirty so that the value will be written to
449        the blob on the next sync.
450
451        Args:
452            prop_name: Name of property to set
453            val: Data value to set
454        """
455        self._CheckProp(prop_name).props[prop_name].SetData(val)
456
457    def SetString(self, prop_name, val):
458        """Set the string value of a property
459
460        The device tree is marked dirty so that the value will be written to
461        the blob on the next sync.
462
463        Args:
464            prop_name: Name of property to set
465            val: String value to set (will be \0-terminated in DT)
466        """
467        if type(val) == str:
468            val = val.encode('utf-8')
469        self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
470
471    def AddData(self, prop_name, val):
472        """Add a new property to a node
473
474        The device tree is marked dirty so that the value will be written to
475        the blob on the next sync.
476
477        Args:
478            prop_name: Name of property to add
479            val: Bytes value of property
480
481        Returns:
482            Prop added
483        """
484        prop = Prop(self, None, prop_name, val)
485        self.props[prop_name] = prop
486        return prop
487
488    def AddString(self, prop_name, val):
489        """Add a new string property to a node
490
491        The device tree is marked dirty so that the value will be written to
492        the blob on the next sync.
493
494        Args:
495            prop_name: Name of property to add
496            val: String value of property
497
498        Returns:
499            Prop added
500        """
501        val = bytes(val, 'utf-8')
502        return self.AddData(prop_name, val + b'\0')
503
504    def AddInt(self, prop_name, val):
505        """Add a new integer property to a node
506
507        The device tree is marked dirty so that the value will be written to
508        the blob on the next sync.
509
510        Args:
511            prop_name: Name of property to add
512            val: Integer value of property
513
514        Returns:
515            Prop added
516        """
517        return self.AddData(prop_name, struct.pack('>I', val))
518
519    def AddSubnode(self, name):
520        """Add a new subnode to the node
521
522        Args:
523            name: name of node to add
524
525        Returns:
526            New subnode that was created
527        """
528        path = self.path + '/' + name
529        subnode = Node(self._fdt, self, None, name, path)
530        self.subnodes.append(subnode)
531        return subnode
532
533    def Sync(self, auto_resize=False):
534        """Sync node changes back to the device tree
535
536        This updates the device tree blob with any changes to this node and its
537        subnodes since the last sync.
538
539        Args:
540            auto_resize: Resize the device tree automatically if it does not
541                have enough space for the update
542
543        Returns:
544            True if the node had to be added, False if it already existed
545
546        Raises:
547            FdtException if auto_resize is False and there is not enough space
548        """
549        added = False
550        if self._offset is None:
551            # The subnode doesn't exist yet, so add it
552            fdt_obj = self._fdt._fdt_obj
553            if auto_resize:
554                while True:
555                    offset = fdt_obj.add_subnode(self.parent._offset, self.name,
556                                                (libfdt.NOSPACE,))
557                    if offset != -libfdt.NOSPACE:
558                        break
559                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
560            else:
561                offset = fdt_obj.add_subnode(self.parent._offset, self.name)
562            self._offset = offset
563            added = True
564
565        # Sync the existing subnodes first, so that we can rely on the offsets
566        # being correct. As soon as we add new subnodes, it pushes all the
567        # existing subnodes up.
568        for node in reversed(self.subnodes):
569            if node._offset is not None:
570                node.Sync(auto_resize)
571
572        # Sync subnodes in reverse so that we get the expected order. Each
573        # new node goes at the start of the subnode list. This avoids an O(n^2)
574        # rescan of node offsets.
575        num_added = 0
576        for node in reversed(self.subnodes):
577            if node.Sync(auto_resize):
578                num_added += 1
579        if num_added:
580            # Reorder our list of nodes to put the new ones first, since that's
581            # what libfdt does
582            old_count = len(self.subnodes) - num_added
583            subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
584            self.subnodes = subnodes
585
586        # Sync properties now, whose offsets should not have been disturbed,
587        # since properties come before subnodes. This is done after all the
588        # subnode processing above, since updating properties can disturb the
589        # offsets of those subnodes.
590        # Properties are synced in reverse order, with new properties added
591        # before existing properties are synced. This ensures that the offsets
592        # of earlier properties are not disturbed.
593        # Note that new properties will have an offset of None here, which
594        # Python cannot sort against int. So use a large value instead so that
595        # new properties are added first.
596        prop_list = sorted(self.props.values(),
597                           key=lambda prop: prop._offset or 1 << 31,
598                           reverse=True)
599        for prop in prop_list:
600            prop.Sync(auto_resize)
601        return added
602
603
604class Fdt:
605    """Provides simple access to a flat device tree blob using libfdts.
606
607    Properties:
608      fname: Filename of fdt
609      _root: Root of device tree (a Node object)
610      name: Helpful name for this Fdt for the user (useful when creating the
611        DT from data rather than a file)
612    """
613    def __init__(self, fname):
614        self._fname = fname
615        self._cached_offsets = False
616        self.phandle_to_node = {}
617        self.name = ''
618        if self._fname:
619            self.name = self._fname
620            self._fname = fdt_util.EnsureCompiled(self._fname)
621
622            with open(self._fname, 'rb') as fd:
623                self._fdt_obj = libfdt.Fdt(fd.read())
624
625    @staticmethod
626    def FromData(data, name=''):
627        """Create a new Fdt object from the given data
628
629        Args:
630            data: Device-tree data blob
631            name: Helpful name for this Fdt for the user
632
633        Returns:
634            Fdt object containing the data
635        """
636        fdt = Fdt(None)
637        fdt._fdt_obj = libfdt.Fdt(bytes(data))
638        fdt.name = name
639        return fdt
640
641    def LookupPhandle(self, phandle):
642        """Look up a phandle
643
644        Args:
645            phandle: Phandle to look up (int)
646
647        Returns:
648            Node object the phandle points to
649        """
650        return self.phandle_to_node.get(phandle)
651
652    def Scan(self, root='/'):
653        """Scan a device tree, building up a tree of Node objects
654
655        This fills in the self._root property
656
657        Args:
658            root: Ignored
659
660        TODO(sjg@chromium.org): Implement the 'root' parameter
661        """
662        self._cached_offsets = True
663        self._root = self.Node(self, None, 0, '/', '/')
664        self._root.Scan()
665
666    def GetRoot(self):
667        """Get the root Node of the device tree
668
669        Returns:
670            The root Node object
671        """
672        return self._root
673
674    def GetNode(self, path):
675        """Look up a node from its path
676
677        Args:
678            path: Path to look up, e.g. '/microcode/update@0'
679        Returns:
680            Node object, or None if not found
681        """
682        node = self._root
683        parts = path.split('/')
684        if len(parts) < 2:
685            return None
686        if len(parts) == 2 and parts[1] == '':
687            return node
688        for part in parts[1:]:
689            node = node.FindNode(part)
690            if not node:
691                return None
692        return node
693
694    def Flush(self):
695        """Flush device tree changes back to the file
696
697        If the device tree has changed in memory, write it back to the file.
698        """
699        with open(self._fname, 'wb') as fd:
700            fd.write(self._fdt_obj.as_bytearray())
701
702    def Sync(self, auto_resize=False):
703        """Make sure any DT changes are written to the blob
704
705        Args:
706            auto_resize: Resize the device tree automatically if it does not
707                have enough space for the update
708
709        Raises:
710            FdtException if auto_resize is False and there is not enough space
711        """
712        self.CheckCache()
713        self._root.Sync(auto_resize)
714        self.Refresh()
715
716    def Pack(self):
717        """Pack the device tree down to its minimum size
718
719        When nodes and properties shrink or are deleted, wasted space can
720        build up in the device tree binary.
721        """
722        CheckErr(self._fdt_obj.pack(), 'pack')
723        self.Refresh()
724
725    def GetContents(self):
726        """Get the contents of the FDT
727
728        Returns:
729            The FDT contents as a string of bytes
730        """
731        return bytes(self._fdt_obj.as_bytearray())
732
733    def GetFdtObj(self):
734        """Get the contents of the FDT
735
736        Returns:
737            The FDT contents as a libfdt.Fdt object
738        """
739        return self._fdt_obj
740
741    def GetProps(self, node):
742        """Get all properties from a node.
743
744        Args:
745            node: Full path to node name to look in.
746
747        Returns:
748            A dictionary containing all the properties, indexed by node name.
749            The entries are Prop objects.
750
751        Raises:
752            ValueError: if the node does not exist.
753        """
754        props_dict = {}
755        poffset = self._fdt_obj.first_property_offset(node._offset,
756                                                      QUIET_NOTFOUND)
757        while poffset >= 0:
758            p = self._fdt_obj.get_property_by_offset(poffset)
759            prop = Prop(node, poffset, p.name, p)
760            props_dict[prop.name] = prop
761
762            poffset = self._fdt_obj.next_property_offset(poffset,
763                                                         QUIET_NOTFOUND)
764        return props_dict
765
766    def Invalidate(self):
767        """Mark our offset cache as invalid"""
768        self._cached_offsets = False
769
770    def CheckCache(self):
771        """Refresh the offset cache if needed"""
772        if self._cached_offsets:
773            return
774        self.Refresh()
775
776    def Refresh(self):
777        """Refresh the offset cache"""
778        self._root.Refresh(0)
779        self._cached_offsets = True
780
781    def GetStructOffset(self, offset):
782        """Get the file offset of a given struct offset
783
784        Args:
785            offset: Offset within the 'struct' region of the device tree
786        Returns:
787            Position of @offset within the device tree binary
788        """
789        return self._fdt_obj.off_dt_struct() + offset
790
791    @classmethod
792    def Node(self, fdt, parent, offset, name, path):
793        """Create a new node
794
795        This is used by Fdt.Scan() to create a new node using the correct
796        class.
797
798        Args:
799            fdt: Fdt object
800            parent: Parent node, or None if this is the root node
801            offset: Offset of node
802            name: Node name
803            path: Full path to node
804        """
805        node = Node(fdt, parent, offset, name, path)
806        return node
807
808    def GetFilename(self):
809        """Get the filename of the device tree
810
811        Returns:
812            String filename
813        """
814        return self._fname
815
816def FdtScan(fname):
817    """Returns a new Fdt object"""
818    dtb = Fdt(fname)
819    dtb.Scan()
820    return dtb
821