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