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