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 patman import tools
17 # This deals with a device tree, presenting it as an assortment of Node and
18 # Prop objects, representing nodes and properties, respectively. This file
19 # contains the base classes and defines the high-level API. You can use
20 # FdtScan() as a convenience function to create and scan an Fdt.
22 # This implementation uses a libfdt Python library to access the device tree,
23 # so it is fairly efficient.
25 # A list of types we support
27 # Types in order from widest to narrowest
28 (BYTE, INT, STRING, BOOL, INT64) = range(5)
30 def needs_widening(self, other):
31 """Check if this type needs widening to hold a value from another type
33 A wider type is one that can hold a wider array of information than
34 another one, or is less restrictive, so it can hold the information of
35 another type as well as its own. This is similar to the concept of
38 This uses a simple arithmetic comparison, since type values are in order
39 from widest (BYTE) to narrowest (INT64).
42 other: Other type to compare against
45 True if the other type is wider
47 return self.value > other.value
49 def CheckErr(errnum, msg):
51 raise ValueError('Error %d: %s: %s' %
52 (errnum, libfdt.fdt_strerror(errnum), msg))
55 def BytesToValue(data):
56 """Converts a string of bytes into a type and value
59 A bytes value (which on Python 2 is an alias for str)
64 Data, either a single element or a list of elements. Each element
66 Type.STRING: str/bytes value from the property
67 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
68 Type.BYTE: a byte stored as a single-byte str/bytes
72 strings = data.split(b'\0')
74 count = len(strings) - 1
75 if count > 0 and not len(strings[-1]):
76 for string in strings[:-1]:
81 if ch < 32 or ch > 127:
88 return Type.STRING, strings[0].decode()
90 return Type.STRING, [s.decode() for s in strings[:-1]]
93 return Type.BYTE, chr(data[0])
95 return Type.BYTE, [chr(ch) for ch in list(data)]
97 for i in range(0, size, 4):
98 val.append(data[i:i + 4])
100 return Type.INT, val[0]
106 """A device tree property
109 node: Node containing this property
110 offset: Offset of the property (None if still to be synced)
111 name: Property name (as per the device tree)
112 value: Property value as a string of bytes, or a list of strings of
116 def __init__(self, node, offset, name, data):
118 self._offset = offset
121 self.bytes = bytes(data)
122 self.dirty = offset is None
124 self.type = Type.BOOL
127 self.type, self.value = BytesToValue(bytes(data))
129 def RefreshOffset(self, poffset):
130 self._offset = poffset
132 def Widen(self, newprop):
133 """Figure out which property type is more general
135 Given a current property and a new property, this function returns the
136 one that is less specific as to type. The less specific property will
137 be ble to represent the data in the more specific property. This is
138 used for things like:
149 He we want to use an int array for 'value'. The first property
150 suggests that a single int is enough, but the second one shows that
151 it is not. Calling this function with these two propertes would
152 update the current property to be like the second, since it is less
155 if self.type.needs_widening(newprop.type):
156 if self.type == Type.INT and newprop.type == Type.BYTE:
157 if type(self.value) == list:
159 for val in self.value:
160 new_value += [chr(by) for by in val]
162 new_value = [chr(by) for by in self.value]
163 self.value = new_value
164 self.type = newprop.type
166 if type(newprop.value) == list:
167 if type(self.value) != list:
168 self.value = [self.value]
170 if len(newprop.value) > len(self.value):
171 val = self.GetEmpty(self.type)
172 while len(self.value) < len(newprop.value):
173 self.value.append(val)
176 def GetEmpty(self, type):
177 """Get an empty / zero value of the given type
180 A single value of the given type
182 if type == Type.BYTE:
184 elif type == Type.INT:
185 return struct.pack('>I', 0);
186 elif type == Type.STRING:
192 """Get the offset of a property
195 The offset of the property (struct fdt_property) within the file
197 self._node._fdt.CheckCache()
198 return self._node._fdt.GetStructOffset(self._offset)
200 def SetInt(self, val):
201 """Set the integer value of the property
203 The device tree is marked dirty so that the value will be written to
204 the block on the next sync.
207 val: Integer value (32-bit, single cell)
209 self.bytes = struct.pack('>I', val);
210 self.value = self.bytes
214 def SetData(self, bytes):
215 """Set the value of a property as bytes
218 bytes: New property value to set
221 self.type, self.value = BytesToValue(bytes)
224 def Sync(self, auto_resize=False):
225 """Sync property changes back to the device tree
227 This updates the device tree blob with any changes to this property
231 auto_resize: Resize the device tree automatically if it does not
232 have enough space for the update
235 FdtException if auto_resize is False and there is not enough space
239 fdt_obj = node._fdt._fdt_obj
240 node_name = fdt_obj.get_name(node._offset)
241 if node_name and node_name != node.name:
242 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
243 (node.path, node_name))
246 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
247 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
248 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
250 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
252 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
257 """A device tree node
261 offset: Integer offset in the device tree (None if to be synced)
262 name: Device tree node tname
263 path: Full path to node, along with the node name itself
264 _fdt: Device tree object
265 subnodes: A list of subnodes for this node, each a Node object
266 props: A dict of properties for this node, each a Prop object.
267 Keyed by property name
269 def __init__(self, fdt, parent, offset, name, path):
272 self._offset = offset
279 """Get the Fdt object for this node
286 def FindNode(self, name):
287 """Find a node given its name
290 name: Node name to look for
292 Node object if found, else None
294 for subnode in self.subnodes:
295 if subnode.name == name:
300 """Returns the offset of a node, after checking the cache
302 This should be used instead of self._offset directly, to ensure that
303 the cache does not contain invalid offsets.
305 self._fdt.CheckCache()
309 """Scan a node's properties and subnodes
311 This fills in the props and subnodes properties, recursively
312 searching into subnodes so that the entire tree is built.
314 fdt_obj = self._fdt._fdt_obj
315 self.props = self._fdt.GetProps(self)
316 phandle = fdt_obj.get_phandle(self.Offset())
318 self._fdt.phandle_to_node[phandle] = self
320 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
322 sep = '' if self.path[-1] == '/' else '/'
323 name = fdt_obj.get_name(offset)
324 path = self.path + sep + name
325 node = Node(self._fdt, self, offset, name, path)
326 self.subnodes.append(node)
329 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
331 def Refresh(self, my_offset):
332 """Fix up the _offset for each node, recursively
334 Note: This does not take account of property offsets - these will not
337 fdt_obj = self._fdt._fdt_obj
338 if self._offset != my_offset:
339 self._offset = my_offset
340 name = fdt_obj.get_name(self._offset)
341 if name and self.name != name:
342 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
345 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
346 for subnode in self.subnodes:
347 if subnode.name != fdt_obj.get_name(offset):
348 raise ValueError('Internal error, node name mismatch %s != %s' %
349 (subnode.name, fdt_obj.get_name(offset)))
350 subnode.Refresh(offset)
351 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
352 if offset != -libfdt.FDT_ERR_NOTFOUND:
353 raise ValueError('Internal error, offset == %d' % offset)
355 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
357 p = fdt_obj.get_property_by_offset(poffset)
358 prop = self.props.get(p.name)
360 raise ValueError("Internal error, node '%s' property '%s' missing, "
361 'offset %d' % (self.path, p.name, poffset))
362 prop.RefreshOffset(poffset)
363 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
365 def DeleteProp(self, prop_name):
366 """Delete a property of a node
368 The property is deleted and the offset cache is invalidated.
371 prop_name: Name of the property to delete
373 ValueError if the property does not exist
375 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
376 "Node '%s': delete property: '%s'" % (self.path, prop_name))
377 del self.props[prop_name]
378 self._fdt.Invalidate()
380 def AddZeroProp(self, prop_name):
381 """Add a new property to the device tree with an integer value of 0.
384 prop_name: Name of property
386 self.props[prop_name] = Prop(self, None, prop_name,
387 tools.GetBytes(0, 4))
389 def AddEmptyProp(self, prop_name, len):
390 """Add a property with a fixed data size, for filling in later
392 The device tree is marked dirty so that the value will be written to
393 the blob on the next sync.
396 prop_name: Name of property
397 len: Length of data in property
399 value = tools.GetBytes(0, len)
400 self.props[prop_name] = Prop(self, None, prop_name, value)
402 def _CheckProp(self, prop_name):
403 """Check if a property is present
406 prop_name: Name of property
412 ValueError if the property is missing
414 if prop_name not in self.props:
415 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
416 (self._fdt._fname, self.path, prop_name))
419 def SetInt(self, prop_name, val):
420 """Update an integer property int the device tree.
422 This is not allowed to change the size of the FDT.
424 The device tree is marked dirty so that the value will be written to
425 the blob on the next sync.
428 prop_name: Name of property
431 self._CheckProp(prop_name).props[prop_name].SetInt(val)
433 def SetData(self, prop_name, val):
434 """Set the data value of a property
436 The device tree is marked dirty so that the value will be written to
437 the blob on the next sync.
440 prop_name: Name of property to set
441 val: Data value to set
443 self._CheckProp(prop_name).props[prop_name].SetData(val)
445 def SetString(self, prop_name, val):
446 """Set the string value of a property
448 The device tree is marked dirty so that the value will be written to
449 the blob on the next sync.
452 prop_name: Name of property to set
453 val: String value to set (will be \0-terminated in DT)
456 val = val.encode('utf-8')
457 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
459 def AddData(self, prop_name, val):
460 """Add a new property to a node
462 The device tree is marked dirty so that the value will be written to
463 the blob on the next sync.
466 prop_name: Name of property to add
467 val: Bytes value of property
472 prop = Prop(self, None, prop_name, val)
473 self.props[prop_name] = prop
476 def AddString(self, prop_name, val):
477 """Add a new string property to a node
479 The device tree is marked dirty so that the value will be written to
480 the blob on the next sync.
483 prop_name: Name of property to add
484 val: String value of property
489 val = bytes(val, 'utf-8')
490 return self.AddData(prop_name, val + b'\0')
492 def AddInt(self, prop_name, val):
493 """Add a new integer property to a node
495 The device tree is marked dirty so that the value will be written to
496 the blob on the next sync.
499 prop_name: Name of property to add
500 val: Integer value of property
505 return self.AddData(prop_name, struct.pack('>I', val))
507 def AddSubnode(self, name):
508 """Add a new subnode to the node
511 name: name of node to add
514 New subnode that was created
516 path = self.path + '/' + name
517 subnode = Node(self._fdt, self, None, name, path)
518 self.subnodes.append(subnode)
521 def Sync(self, auto_resize=False):
522 """Sync node changes back to the device tree
524 This updates the device tree blob with any changes to this node and its
525 subnodes since the last sync.
528 auto_resize: Resize the device tree automatically if it does not
529 have enough space for the update
532 True if the node had to be added, False if it already existed
535 FdtException if auto_resize is False and there is not enough space
538 if self._offset is None:
539 # The subnode doesn't exist yet, so add it
540 fdt_obj = self._fdt._fdt_obj
543 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
545 if offset != -libfdt.NOSPACE:
547 fdt_obj.resize(fdt_obj.totalsize() + 1024)
549 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
550 self._offset = offset
553 # Sync the existing subnodes first, so that we can rely on the offsets
554 # being correct. As soon as we add new subnodes, it pushes all the
555 # existing subnodes up.
556 for node in reversed(self.subnodes):
557 if node._offset is not None:
558 node.Sync(auto_resize)
560 # Sync subnodes in reverse so that we get the expected order. Each
561 # new node goes at the start of the subnode list. This avoids an O(n^2)
562 # rescan of node offsets.
564 for node in reversed(self.subnodes):
565 if node.Sync(auto_resize):
568 # Reorder our list of nodes to put the new ones first, since that's
570 old_count = len(self.subnodes) - num_added
571 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
572 self.subnodes = subnodes
574 # Sync properties now, whose offsets should not have been disturbed,
575 # since properties come before subnodes. This is done after all the
576 # subnode processing above, since updating properties can disturb the
577 # offsets of those subnodes.
578 # Properties are synced in reverse order, with new properties added
579 # before existing properties are synced. This ensures that the offsets
580 # of earlier properties are not disturbed.
581 # Note that new properties will have an offset of None here, which
582 # Python cannot sort against int. So use a large value instead so that
583 # new properties are added first.
584 prop_list = sorted(self.props.values(),
585 key=lambda prop: prop._offset or 1 << 31,
587 for prop in prop_list:
588 prop.Sync(auto_resize)
593 """Provides simple access to a flat device tree blob using libfdts.
596 fname: Filename of fdt
597 _root: Root of device tree (a Node object)
598 name: Helpful name for this Fdt for the user (useful when creating the
599 DT from data rather than a file)
601 def __init__(self, fname):
603 self._cached_offsets = False
604 self.phandle_to_node = {}
607 self.name = self._fname
608 self._fname = fdt_util.EnsureCompiled(self._fname)
610 with open(self._fname, 'rb') as fd:
611 self._fdt_obj = libfdt.Fdt(fd.read())
614 def FromData(data, name=''):
615 """Create a new Fdt object from the given data
618 data: Device-tree data blob
619 name: Helpful name for this Fdt for the user
622 Fdt object containing the data
625 fdt._fdt_obj = libfdt.Fdt(bytes(data))
629 def LookupPhandle(self, phandle):
633 phandle: Phandle to look up (int)
636 Node object the phandle points to
638 return self.phandle_to_node.get(phandle)
640 def Scan(self, root='/'):
641 """Scan a device tree, building up a tree of Node objects
643 This fills in the self._root property
648 TODO(sjg@chromium.org): Implement the 'root' parameter
650 self._cached_offsets = True
651 self._root = self.Node(self, None, 0, '/', '/')
655 """Get the root Node of the device tree
662 def GetNode(self, path):
663 """Look up a node from its path
666 path: Path to look up, e.g. '/microcode/update@0'
668 Node object, or None if not found
671 parts = path.split('/')
674 if len(parts) == 2 and parts[1] == '':
676 for part in parts[1:]:
677 node = node.FindNode(part)
683 """Flush device tree changes back to the file
685 If the device tree has changed in memory, write it back to the file.
687 with open(self._fname, 'wb') as fd:
688 fd.write(self._fdt_obj.as_bytearray())
690 def Sync(self, auto_resize=False):
691 """Make sure any DT changes are written to the blob
694 auto_resize: Resize the device tree automatically if it does not
695 have enough space for the update
698 FdtException if auto_resize is False and there is not enough space
701 self._root.Sync(auto_resize)
705 """Pack the device tree down to its minimum size
707 When nodes and properties shrink or are deleted, wasted space can
708 build up in the device tree binary.
710 CheckErr(self._fdt_obj.pack(), 'pack')
713 def GetContents(self):
714 """Get the contents of the FDT
717 The FDT contents as a string of bytes
719 return bytes(self._fdt_obj.as_bytearray())
722 """Get the contents of the FDT
725 The FDT contents as a libfdt.Fdt object
729 def GetProps(self, node):
730 """Get all properties from a node.
733 node: Full path to node name to look in.
736 A dictionary containing all the properties, indexed by node name.
737 The entries are Prop objects.
740 ValueError: if the node does not exist.
743 poffset = self._fdt_obj.first_property_offset(node._offset,
746 p = self._fdt_obj.get_property_by_offset(poffset)
747 prop = Prop(node, poffset, p.name, p)
748 props_dict[prop.name] = prop
750 poffset = self._fdt_obj.next_property_offset(poffset,
754 def Invalidate(self):
755 """Mark our offset cache as invalid"""
756 self._cached_offsets = False
758 def CheckCache(self):
759 """Refresh the offset cache if needed"""
760 if self._cached_offsets:
765 """Refresh the offset cache"""
766 self._root.Refresh(0)
767 self._cached_offsets = True
769 def GetStructOffset(self, offset):
770 """Get the file offset of a given struct offset
773 offset: Offset within the 'struct' region of the device tree
775 Position of @offset within the device tree binary
777 return self._fdt_obj.off_dt_struct() + offset
780 def Node(self, fdt, parent, offset, name, path):
783 This is used by Fdt.Scan() to create a new node using the correct
788 parent: Parent node, or None if this is the root node
789 offset: Offset of node
791 path: Full path to node
793 node = Node(fdt, parent, offset, name, path)
796 def GetFilename(self):
797 """Get the filename of the device tree
805 """Returns a new Fdt object"""