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