Prepare v2023.10
[platform/kernel/u-boot.git] / tools / dtoc / fdt.py
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
8 from enum import IntEnum
9 import struct
10 import sys
11
12 from dtoc import fdt_util
13 import libfdt
14 from libfdt import QUIET_NOTFOUND
15 from u_boot_pylib import tools
16 from 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
27 class 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
50 def CheckErr(errnum, msg):
51     if errnum:
52         raise ValueError('Error %d: %s: %s' %
53             (errnum, libfdt.fdt_strerror(errnum), msg))
54
55
56 def 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
106 class 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
278 class 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 {dst.path}')
786             self.merge_props(parent, False)
787
788
789 class 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
1002 def FdtScan(fname):
1003     """Returns a new Fdt object"""
1004     dtb = Fdt(fname)
1005     dtb.Scan()
1006     return dtb