2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2016 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
8 from enum import IntEnum
12 from dtoc import fdt_util
14 from libfdt import QUIET_NOTFOUND
15 from u_boot_pylib import tools
16 from u_boot_pylib import tout
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.
23 # This implementation uses a libfdt Python library to access the device tree,
24 # so it is fairly efficient.
26 # A list of types we support
28 # Types in order from widest to narrowest
29 (BYTE, INT, STRING, BOOL, INT64) = range(5)
31 def needs_widening(self, other):
32 """Check if this type needs widening to hold a value from another type
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
39 This uses a simple arithmetic comparison, since type values are in order
40 from widest (BYTE) to narrowest (INT64).
43 other: Other type to compare against
46 True if the other type is wider
48 return self.value > other.value
50 def CheckErr(errnum, msg):
52 raise ValueError('Error %d: %s: %s' %
53 (errnum, libfdt.fdt_strerror(errnum), msg))
56 def BytesToValue(data):
57 """Converts a string of bytes into a type and value
60 A bytes value (which on Python 2 is an alias for str)
65 Data, either a single element or a list of elements. Each element
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
73 strings = data.split(b'\0')
75 count = len(strings) - 1
76 if count > 0 and not len(strings[-1]):
77 for string in strings[:-1]:
82 if ch < 32 or ch > 127:
89 return Type.STRING, strings[0].decode()
91 return Type.STRING, [s.decode() for s in strings[:-1]]
94 return Type.BYTE, chr(data[0])
96 return Type.BYTE, [chr(ch) for ch in list(data)]
98 for i in range(0, size, 4):
99 val.append(data[i:i + 4])
101 return Type.INT, val[0]
107 """A device tree property
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
117 def __init__(self, node, offset, name, data):
119 self._offset = offset
122 self.bytes = bytes(data)
123 self.dirty = offset is None
125 self.type = Type.BOOL
128 self.type, self.value = BytesToValue(bytes(data))
130 def RefreshOffset(self, poffset):
131 self._offset = poffset
133 def Widen(self, newprop):
134 """Figure out which property type is more general
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:
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
156 if self.type.needs_widening(newprop.type):
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
162 # e.g. this is a boolean:
164 # and it would be widened to int list by:
166 if self.type == Type.BOOL:
168 self.value = [self.GetEmpty(self.type)]
169 if self.type == Type.INT and newprop.type == Type.BYTE:
170 if type(self.value) == list:
172 for val in self.value:
173 new_value += [chr(by) for by in val]
175 new_value = [chr(by) for by in self.value]
176 self.value = new_value
177 self.type = newprop.type
179 if type(newprop.value) == list:
180 if type(self.value) != list:
181 self.value = [self.value]
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)
189 def GetEmpty(self, type):
190 """Get an empty / zero value of the given type
193 A single value of the given type
195 if type == Type.BYTE:
197 elif type == Type.INT:
198 return struct.pack('>I', 0);
199 elif type == Type.STRING:
205 """Get the offset of a property
208 The offset of the property (struct fdt_property) within the file
210 self._node._fdt.CheckCache()
211 return self._node._fdt.GetStructOffset(self._offset)
213 def SetInt(self, val):
214 """Set the integer value of the property
216 The device tree is marked dirty so that the value will be written to
217 the block on the next sync.
220 val: Integer value (32-bit, single cell)
222 self.bytes = struct.pack('>I', val);
223 self.value = self.bytes
227 def SetData(self, bytes):
228 """Set the value of a property as bytes
231 bytes: New property value to set
234 self.type, self.value = BytesToValue(bytes)
237 def Sync(self, auto_resize=False):
238 """Sync property changes back to the device tree
240 This updates the device tree blob with any changes to this property
244 auto_resize: Resize the device tree automatically if it does not
245 have enough space for the update
248 FdtException if auto_resize is False and there is not enough space
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))
260 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
261 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
262 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
264 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
266 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
270 """Set a property offset to None
272 The property remains in the tree structure and will be recreated when
279 """A device tree 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
291 def __init__(self, fdt, parent, offset, name, path):
294 self._offset = offset
301 """Get the Fdt object for this node
308 def FindNode(self, name):
309 """Find a node given its name
312 name: Node name to look for
314 Node object if found, else None
316 for subnode in self.subnodes:
317 if subnode.name == name:
322 """Returns the offset of a node, after checking the cache
324 This should be used instead of self._offset directly, to ensure that
325 the cache does not contain invalid offsets.
327 self._fdt.CheckCache()
331 """Scan a node's properties and subnodes
333 This fills in the props and subnodes properties, recursively
334 searching into subnodes so that the entire tree is built.
336 fdt_obj = self._fdt._fdt_obj
337 self.props = self._fdt.GetProps(self)
338 phandle = fdt_obj.get_phandle(self.Offset())
340 dup = self._fdt.phandle_to_node.get(phandle)
343 f'Duplicate phandle {phandle} in nodes {dup.path} and {self.path}')
345 self._fdt.phandle_to_node[phandle] = self
347 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
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)
356 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
358 def Refresh(self, my_offset):
359 """Fix up the _offset for each node, recursively
361 Note: This does not take account of property offsets - these will not
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'" %
372 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
373 for subnode in self.subnodes:
374 if subnode._offset is None:
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)
384 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
386 p = fdt_obj.get_property_by_offset(poffset)
387 prop = self.props.get(p.name)
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)
394 def DeleteProp(self, prop_name):
395 """Delete a property of a node
397 The property is deleted and the offset cache is invalidated.
400 prop_name: Name of the property to delete
402 ValueError if the property does not exist
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()
409 def AddZeroProp(self, prop_name):
410 """Add a new property to the device tree with an integer value of 0.
413 prop_name: Name of property
415 self.props[prop_name] = Prop(self, None, prop_name,
416 tools.get_bytes(0, 4))
418 def AddEmptyProp(self, prop_name, len):
419 """Add a property with a fixed data size, for filling in later
421 The device tree is marked dirty so that the value will be written to
422 the blob on the next sync.
425 prop_name: Name of property
426 len: Length of data in property
428 value = tools.get_bytes(0, len)
429 self.props[prop_name] = Prop(self, None, prop_name, value)
431 def _CheckProp(self, prop_name):
432 """Check if a property is present
435 prop_name: Name of property
441 ValueError if the property is missing
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))
448 def SetInt(self, prop_name, val):
449 """Update an integer property int the device tree.
451 This is not allowed to change the size of the FDT.
453 The device tree is marked dirty so that the value will be written to
454 the blob on the next sync.
457 prop_name: Name of property
460 self._CheckProp(prop_name).props[prop_name].SetInt(val)
462 def SetData(self, prop_name, val):
463 """Set the data value of a property
465 The device tree is marked dirty so that the value will be written to
466 the blob on the next sync.
469 prop_name: Name of property to set
470 val: Data value to set
472 self._CheckProp(prop_name).props[prop_name].SetData(val)
474 def SetString(self, prop_name, val):
475 """Set the string value of a property
477 The device tree is marked dirty so that the value will be written to
478 the blob on the next sync.
481 prop_name: Name of property to set
482 val: String value to set (will be \0-terminated in DT)
485 val = val.encode('utf-8')
486 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
488 def AddData(self, prop_name, val):
489 """Add a new property to a node
491 The device tree is marked dirty so that the value will be written to
492 the blob on the next sync.
495 prop_name: Name of property to add
496 val: Bytes value of property
501 prop = Prop(self, None, prop_name, val)
502 self.props[prop_name] = prop
505 def AddString(self, prop_name, val):
506 """Add a new string property to a node
508 The device tree is marked dirty so that the value will be written to
509 the blob on the next sync.
512 prop_name: Name of property to add
513 val: String value of property
518 val = bytes(val, 'utf-8')
519 return self.AddData(prop_name, val + b'\0')
521 def AddStringList(self, prop_name, val):
522 """Add a new string-list property to a node
524 The device tree is marked dirty so that the value will be written to
525 the blob on the next sync.
528 prop_name: Name of property to add
529 val (list of str): List of strings to add
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)
537 def AddInt(self, prop_name, val):
538 """Add a new integer property to a node
540 The device tree is marked dirty so that the value will be written to
541 the blob on the next sync.
544 prop_name: Name of property to add
545 val: Integer value of property
550 return self.AddData(prop_name, struct.pack('>I', val))
552 def Subnode(self, name):
553 """Create new subnode for the node
556 name: name of node to add
559 New subnode that was created
561 path = self.path + '/' + name
562 return Node(self._fdt, self, None, name, path)
564 def AddSubnode(self, name):
565 """Add a new subnode to the node, after all other subnodes
568 name: name of node to add
571 New subnode that was created
573 subnode = self.Subnode(name)
574 self.subnodes.append(subnode)
577 def insert_subnode(self, name):
578 """Add a new subnode to the node, before all other subnodes
580 This deletes other subnodes and sets their offset to None, so that they
581 will be recreated after this one.
584 name: name of node to add
587 New subnode that was created
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
592 for subnode in reversed(self.subnodes):
594 subnode = self.Subnode(name)
595 self.subnodes.insert(0, subnode)
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:
602 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
603 "Node '%s': delete" % self.path)
605 self._fdt.Invalidate()
607 for prop in self.props.values():
610 for subnode in self.subnodes:
613 def move_to_first(self):
614 """Move the current node to first in its parent's node list"""
616 if parent.subnodes and parent.subnodes[0] == self:
618 for subnode in reversed(parent.subnodes):
621 new_subnodes = [self]
622 for subnode in parent.subnodes:
623 #subnode.purge(False)
625 new_subnodes.append(subnode)
626 parent.subnodes = new_subnodes
631 The node is deleted and the offset cache is invalidated.
634 node (Node): Node to delete
637 ValueError if the node does not exist
639 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
640 "Node '%s': delete" % self.path)
642 self._fdt.Invalidate()
643 parent.subnodes.remove(self)
645 def Sync(self, auto_resize=False):
646 """Sync node changes back to the device tree
648 This updates the device tree blob with any changes to this node and its
649 subnodes since the last sync.
652 auto_resize: Resize the device tree automatically if it does not
653 have enough space for the update
656 True if the node had to be added, False if it already existed
659 FdtException if auto_resize is False and there is not enough space
662 if self._offset is None:
663 # The subnode doesn't exist yet, so add it
664 fdt_obj = self._fdt._fdt_obj
667 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
669 if offset != -libfdt.NOSPACE:
671 fdt_obj.resize(fdt_obj.totalsize() + 1024)
673 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
674 self._offset = offset
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)
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.
688 for node in reversed(self.subnodes):
689 if node.Sync(auto_resize):
692 # Reorder our list of nodes to put the new ones first, since that's
694 old_count = len(self.subnodes) - num_added
695 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
696 self.subnodes = subnodes
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,
711 for prop in prop_list:
712 prop.Sync(auto_resize)
715 def merge_props(self, src, copy_phandles):
716 """Copy missing properties (except 'phandle') from another node
719 src (Node): Node containing properties to copy
720 copy_phandles (bool): True to copy phandle properties in nodes
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.
726 tout.debug(f'copy to {self.path}: {src.path}')
727 for name, src_prop in src.props.items():
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)
733 tout.debug(f" {name}{'' if done else ' - ignored'}")
735 def copy_node(self, src, copy_phandles=False):
736 """Copy a node and all its subnodes into this node
739 src (Node): Node to copy
740 copy_phandles (bool): True to copy phandle properties in nodes
743 Node: Resulting destination node
745 This works recursively, with copy_phandles being set to True for the
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.
753 dst = self.FindNode(src.name)
757 dst = self.insert_subnode(src.name)
758 dst.merge_props(src, copy_phandles)
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)
766 def copy_subnodes_from_phandles(self, phandle_list):
767 """Copy subnodes of a list of nodes into another node
770 phandle_list (list of int): List of phandles of nodes to copy
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
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
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)
785 tout.debug(f'merge props from {parent.path} to {dst.path}')
786 self.merge_props(parent, False)
790 """Provides simple access to a flat device tree blob using libfdts.
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)
798 def __init__(self, fname):
800 self._cached_offsets = False
801 self.phandle_to_node = {}
804 self.name = self._fname
805 self._fname = fdt_util.EnsureCompiled(self._fname)
807 with open(self._fname, 'rb') as fd:
808 self._fdt_obj = libfdt.Fdt(fd.read())
811 def FromData(data, name=''):
812 """Create a new Fdt object from the given data
815 data: Device-tree data blob
816 name: Helpful name for this Fdt for the user
819 Fdt object containing the data
822 fdt._fdt_obj = libfdt.Fdt(bytes(data))
826 def LookupPhandle(self, phandle):
830 phandle: Phandle to look up (int)
833 Node object the phandle points to
835 return self.phandle_to_node.get(phandle)
837 def Scan(self, root='/'):
838 """Scan a device tree, building up a tree of Node objects
840 This fills in the self._root property
845 TODO(sjg@chromium.org): Implement the 'root' parameter
847 self.phandle_to_node = {}
848 self._cached_offsets = True
849 self._root = self.Node(self, None, 0, '/', '/')
853 """Get the root Node of the device tree
860 def GetNode(self, path):
861 """Look up a node from its path
864 path: Path to look up, e.g. '/microcode/update@0'
866 Node object, or None if not found
869 parts = path.split('/')
872 if len(parts) == 2 and parts[1] == '':
874 for part in parts[1:]:
875 node = node.FindNode(part)
881 """Flush device tree changes back to the file
883 If the device tree has changed in memory, write it back to the file.
885 with open(self._fname, 'wb') as fd:
886 fd.write(self._fdt_obj.as_bytearray())
888 def Sync(self, auto_resize=False):
889 """Make sure any DT changes are written to the blob
892 auto_resize: Resize the device tree automatically if it does not
893 have enough space for the update
896 FdtException if auto_resize is False and there is not enough space
899 self._root.Sync(auto_resize)
903 """Pack the device tree down to its minimum size
905 When nodes and properties shrink or are deleted, wasted space can
906 build up in the device tree binary.
908 CheckErr(self._fdt_obj.pack(), 'pack')
911 def GetContents(self):
912 """Get the contents of the FDT
915 The FDT contents as a string of bytes
917 return bytes(self._fdt_obj.as_bytearray())
920 """Get the contents of the FDT
923 The FDT contents as a libfdt.Fdt object
927 def GetProps(self, node):
928 """Get all properties from a node.
931 node: Full path to node name to look in.
934 A dictionary containing all the properties, indexed by node name.
935 The entries are Prop objects.
938 ValueError: if the node does not exist.
941 poffset = self._fdt_obj.first_property_offset(node._offset,
944 p = self._fdt_obj.get_property_by_offset(poffset)
945 prop = Prop(node, poffset, p.name, p)
946 props_dict[prop.name] = prop
948 poffset = self._fdt_obj.next_property_offset(poffset,
952 def Invalidate(self):
953 """Mark our offset cache as invalid"""
954 self._cached_offsets = False
956 def CheckCache(self):
957 """Refresh the offset cache if needed"""
958 if self._cached_offsets:
963 """Refresh the offset cache"""
964 self._root.Refresh(0)
965 self._cached_offsets = True
967 def GetStructOffset(self, offset):
968 """Get the file offset of a given struct offset
971 offset: Offset within the 'struct' region of the device tree
973 Position of @offset within the device tree binary
975 return self._fdt_obj.off_dt_struct() + offset
978 def Node(self, fdt, parent, offset, name, path):
981 This is used by Fdt.Scan() to create a new node using the correct
986 parent: Parent node, or None if this is the root node
987 offset: Offset of node
989 path: Full path to node
991 node = Node(fdt, parent, offset, name, path)
994 def GetFilename(self):
995 """Get the filename of the device tree
1003 """Returns a new Fdt object"""