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 (BYTE, INT, STRING, BOOL, INT64) = range(5)
29 def is_wider_than(self, other):
30 """Check if another type is 'wider' than this one
32 A wider type is one that holds more information than an earlier one,
33 similar to the concept of type-widening in C.
35 This uses a simple arithmetic comparison, since type values are in order
36 from narrowest (BYTE) to widest (INT64).
39 other: Other type to compare against
42 True if the other type is wider
44 return self.value > other.value
46 def CheckErr(errnum, msg):
48 raise ValueError('Error %d: %s: %s' %
49 (errnum, libfdt.fdt_strerror(errnum), msg))
52 def BytesToValue(data):
53 """Converts a string of bytes into a type and value
56 A bytes value (which on Python 2 is an alias for str)
61 Data, either a single element or a list of elements. Each element
63 Type.STRING: str/bytes value from the property
64 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
65 Type.BYTE: a byte stored as a single-byte str/bytes
69 strings = data.split(b'\0')
71 count = len(strings) - 1
72 if count > 0 and not len(strings[-1]):
73 for string in strings[:-1]:
78 if ch < 32 or ch > 127:
85 return Type.STRING, strings[0].decode()
87 return Type.STRING, [s.decode() for s in strings[:-1]]
90 return Type.BYTE, chr(data[0])
92 return Type.BYTE, [chr(ch) for ch in list(data)]
94 for i in range(0, size, 4):
95 val.append(data[i:i + 4])
97 return Type.INT, val[0]
103 """A device tree property
106 node: Node containing this property
107 offset: Offset of the property (None if still to be synced)
108 name: Property name (as per the device tree)
109 value: Property value as a string of bytes, or a list of strings of
113 def __init__(self, node, offset, name, data):
115 self._offset = offset
118 self.bytes = bytes(data)
119 self.dirty = offset is None
121 self.type = Type.BOOL
124 self.type, self.value = BytesToValue(bytes(data))
126 def RefreshOffset(self, poffset):
127 self._offset = poffset
129 def Widen(self, newprop):
130 """Figure out which property type is more general
132 Given a current property and a new property, this function returns the
133 one that is less specific as to type. The less specific property will
134 be ble to represent the data in the more specific property. This is
135 used for things like:
146 He we want to use an int array for 'value'. The first property
147 suggests that a single int is enough, but the second one shows that
148 it is not. Calling this function with these two propertes would
149 update the current property to be like the second, since it is less
152 if self.type.is_wider_than(newprop.type):
153 if self.type == Type.INT and newprop.type == Type.BYTE:
154 if type(self.value) == list:
156 for val in self.value:
157 new_value += [chr(by) for by in val]
159 new_value = [chr(by) for by in self.value]
160 self.value = new_value
161 self.type = newprop.type
163 if type(newprop.value) == list and type(self.value) != list:
164 self.value = [self.value]
166 if type(self.value) == list and len(newprop.value) > len(self.value):
167 val = self.GetEmpty(self.type)
168 while len(self.value) < len(newprop.value):
169 self.value.append(val)
172 def GetEmpty(self, type):
173 """Get an empty / zero value of the given type
176 A single value of the given type
178 if type == Type.BYTE:
180 elif type == Type.INT:
181 return struct.pack('>I', 0);
182 elif type == Type.STRING:
188 """Get the offset of a property
191 The offset of the property (struct fdt_property) within the file
193 self._node._fdt.CheckCache()
194 return self._node._fdt.GetStructOffset(self._offset)
196 def SetInt(self, val):
197 """Set the integer value of the property
199 The device tree is marked dirty so that the value will be written to
200 the block on the next sync.
203 val: Integer value (32-bit, single cell)
205 self.bytes = struct.pack('>I', val);
206 self.value = self.bytes
210 def SetData(self, bytes):
211 """Set the value of a property as bytes
214 bytes: New property value to set
217 self.type, self.value = BytesToValue(bytes)
220 def Sync(self, auto_resize=False):
221 """Sync property changes back to the device tree
223 This updates the device tree blob with any changes to this property
227 auto_resize: Resize the device tree automatically if it does not
228 have enough space for the update
231 FdtException if auto_resize is False and there is not enough space
235 fdt_obj = node._fdt._fdt_obj
236 node_name = fdt_obj.get_name(node._offset)
237 if node_name and node_name != node.name:
238 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
239 (node.path, node_name))
242 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
243 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
244 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
246 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
248 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
253 """A device tree node
257 offset: Integer offset in the device tree (None if to be synced)
258 name: Device tree node tname
259 path: Full path to node, along with the node name itself
260 _fdt: Device tree object
261 subnodes: A list of subnodes for this node, each a Node object
262 props: A dict of properties for this node, each a Prop object.
263 Keyed by property name
265 def __init__(self, fdt, parent, offset, name, path):
268 self._offset = offset
275 """Get the Fdt object for this node
282 def FindNode(self, name):
283 """Find a node given its name
286 name: Node name to look for
288 Node object if found, else None
290 for subnode in self.subnodes:
291 if subnode.name == name:
296 """Returns the offset of a node, after checking the cache
298 This should be used instead of self._offset directly, to ensure that
299 the cache does not contain invalid offsets.
301 self._fdt.CheckCache()
305 """Scan a node's properties and subnodes
307 This fills in the props and subnodes properties, recursively
308 searching into subnodes so that the entire tree is built.
310 fdt_obj = self._fdt._fdt_obj
311 self.props = self._fdt.GetProps(self)
312 phandle = fdt_obj.get_phandle(self.Offset())
314 self._fdt.phandle_to_node[phandle] = self
316 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
318 sep = '' if self.path[-1] == '/' else '/'
319 name = fdt_obj.get_name(offset)
320 path = self.path + sep + name
321 node = Node(self._fdt, self, offset, name, path)
322 self.subnodes.append(node)
325 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
327 def Refresh(self, my_offset):
328 """Fix up the _offset for each node, recursively
330 Note: This does not take account of property offsets - these will not
333 fdt_obj = self._fdt._fdt_obj
334 if self._offset != my_offset:
335 self._offset = my_offset
336 name = fdt_obj.get_name(self._offset)
337 if name and self.name != name:
338 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
341 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
342 for subnode in self.subnodes:
343 if subnode.name != fdt_obj.get_name(offset):
344 raise ValueError('Internal error, node name mismatch %s != %s' %
345 (subnode.name, fdt_obj.get_name(offset)))
346 subnode.Refresh(offset)
347 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
348 if offset != -libfdt.FDT_ERR_NOTFOUND:
349 raise ValueError('Internal error, offset == %d' % offset)
351 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
353 p = fdt_obj.get_property_by_offset(poffset)
354 prop = self.props.get(p.name)
356 raise ValueError("Internal error, node '%s' property '%s' missing, "
357 'offset %d' % (self.path, p.name, poffset))
358 prop.RefreshOffset(poffset)
359 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
361 def DeleteProp(self, prop_name):
362 """Delete a property of a node
364 The property is deleted and the offset cache is invalidated.
367 prop_name: Name of the property to delete
369 ValueError if the property does not exist
371 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
372 "Node '%s': delete property: '%s'" % (self.path, prop_name))
373 del self.props[prop_name]
374 self._fdt.Invalidate()
376 def AddZeroProp(self, prop_name):
377 """Add a new property to the device tree with an integer value of 0.
380 prop_name: Name of property
382 self.props[prop_name] = Prop(self, None, prop_name,
383 tools.GetBytes(0, 4))
385 def AddEmptyProp(self, prop_name, len):
386 """Add a property with a fixed data size, for filling in later
388 The device tree is marked dirty so that the value will be written to
389 the blob on the next sync.
392 prop_name: Name of property
393 len: Length of data in property
395 value = tools.GetBytes(0, len)
396 self.props[prop_name] = Prop(self, None, prop_name, value)
398 def _CheckProp(self, prop_name):
399 """Check if a property is present
402 prop_name: Name of property
408 ValueError if the property is missing
410 if prop_name not in self.props:
411 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
412 (self._fdt._fname, self.path, prop_name))
415 def SetInt(self, prop_name, val):
416 """Update an integer property int the device tree.
418 This is not allowed to change the size of the FDT.
420 The device tree is marked dirty so that the value will be written to
421 the blob on the next sync.
424 prop_name: Name of property
427 self._CheckProp(prop_name).props[prop_name].SetInt(val)
429 def SetData(self, prop_name, val):
430 """Set the data value of a property
432 The device tree is marked dirty so that the value will be written to
433 the blob on the next sync.
436 prop_name: Name of property to set
437 val: Data value to set
439 self._CheckProp(prop_name).props[prop_name].SetData(val)
441 def SetString(self, prop_name, val):
442 """Set the string value of a property
444 The device tree is marked dirty so that the value will be written to
445 the blob on the next sync.
448 prop_name: Name of property to set
449 val: String value to set (will be \0-terminated in DT)
452 val = val.encode('utf-8')
453 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
455 def AddData(self, prop_name, val):
456 """Add a new property to a node
458 The device tree is marked dirty so that the value will be written to
459 the blob on the next sync.
462 prop_name: Name of property to add
463 val: Bytes value of property
468 prop = Prop(self, None, prop_name, val)
469 self.props[prop_name] = prop
472 def AddString(self, prop_name, val):
473 """Add a new string property to a node
475 The device tree is marked dirty so that the value will be written to
476 the blob on the next sync.
479 prop_name: Name of property to add
480 val: String value of property
485 val = bytes(val, 'utf-8')
486 return self.AddData(prop_name, val + b'\0')
488 def AddInt(self, prop_name, val):
489 """Add a new integer 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: Integer value of property
501 return self.AddData(prop_name, struct.pack('>I', val))
503 def AddSubnode(self, name):
504 """Add a new subnode to the node
507 name: name of node to add
510 New subnode that was created
512 path = self.path + '/' + name
513 subnode = Node(self._fdt, self, None, name, path)
514 self.subnodes.append(subnode)
517 def Sync(self, auto_resize=False):
518 """Sync node changes back to the device tree
520 This updates the device tree blob with any changes to this node and its
521 subnodes since the last sync.
524 auto_resize: Resize the device tree automatically if it does not
525 have enough space for the update
528 True if the node had to be added, False if it already existed
531 FdtException if auto_resize is False and there is not enough space
534 if self._offset is None:
535 # The subnode doesn't exist yet, so add it
536 fdt_obj = self._fdt._fdt_obj
539 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
541 if offset != -libfdt.NOSPACE:
543 fdt_obj.resize(fdt_obj.totalsize() + 1024)
545 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
546 self._offset = offset
549 # Sync the existing subnodes first, so that we can rely on the offsets
550 # being correct. As soon as we add new subnodes, it pushes all the
551 # existing subnodes up.
552 for node in reversed(self.subnodes):
553 if node._offset is not None:
554 node.Sync(auto_resize)
556 # Sync subnodes in reverse so that we get the expected order. Each
557 # new node goes at the start of the subnode list. This avoids an O(n^2)
558 # rescan of node offsets.
560 for node in reversed(self.subnodes):
561 if node.Sync(auto_resize):
564 # Reorder our list of nodes to put the new ones first, since that's
566 old_count = len(self.subnodes) - num_added
567 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
568 self.subnodes = subnodes
570 # Sync properties now, whose offsets should not have been disturbed,
571 # since properties come before subnodes. This is done after all the
572 # subnode processing above, since updating properties can disturb the
573 # offsets of those subnodes.
574 # Properties are synced in reverse order, with new properties added
575 # before existing properties are synced. This ensures that the offsets
576 # of earlier properties are not disturbed.
577 # Note that new properties will have an offset of None here, which
578 # Python cannot sort against int. So use a large value instead so that
579 # new properties are added first.
580 prop_list = sorted(self.props.values(),
581 key=lambda prop: prop._offset or 1 << 31,
583 for prop in prop_list:
584 prop.Sync(auto_resize)
589 """Provides simple access to a flat device tree blob using libfdts.
592 fname: Filename of fdt
593 _root: Root of device tree (a Node object)
594 name: Helpful name for this Fdt for the user (useful when creating the
595 DT from data rather than a file)
597 def __init__(self, fname):
599 self._cached_offsets = False
600 self.phandle_to_node = {}
603 self.name = self._fname
604 self._fname = fdt_util.EnsureCompiled(self._fname)
606 with open(self._fname, 'rb') as fd:
607 self._fdt_obj = libfdt.Fdt(fd.read())
610 def FromData(data, name=''):
611 """Create a new Fdt object from the given data
614 data: Device-tree data blob
615 name: Helpful name for this Fdt for the user
618 Fdt object containing the data
621 fdt._fdt_obj = libfdt.Fdt(bytes(data))
625 def LookupPhandle(self, phandle):
629 phandle: Phandle to look up (int)
632 Node object the phandle points to
634 return self.phandle_to_node.get(phandle)
636 def Scan(self, root='/'):
637 """Scan a device tree, building up a tree of Node objects
639 This fills in the self._root property
644 TODO(sjg@chromium.org): Implement the 'root' parameter
646 self._cached_offsets = True
647 self._root = self.Node(self, None, 0, '/', '/')
651 """Get the root Node of the device tree
658 def GetNode(self, path):
659 """Look up a node from its path
662 path: Path to look up, e.g. '/microcode/update@0'
664 Node object, or None if not found
667 parts = path.split('/')
670 if len(parts) == 2 and parts[1] == '':
672 for part in parts[1:]:
673 node = node.FindNode(part)
679 """Flush device tree changes back to the file
681 If the device tree has changed in memory, write it back to the file.
683 with open(self._fname, 'wb') as fd:
684 fd.write(self._fdt_obj.as_bytearray())
686 def Sync(self, auto_resize=False):
687 """Make sure any DT changes are written to the blob
690 auto_resize: Resize the device tree automatically if it does not
691 have enough space for the update
694 FdtException if auto_resize is False and there is not enough space
697 self._root.Sync(auto_resize)
701 """Pack the device tree down to its minimum size
703 When nodes and properties shrink or are deleted, wasted space can
704 build up in the device tree binary.
706 CheckErr(self._fdt_obj.pack(), 'pack')
709 def GetContents(self):
710 """Get the contents of the FDT
713 The FDT contents as a string of bytes
715 return bytes(self._fdt_obj.as_bytearray())
718 """Get the contents of the FDT
721 The FDT contents as a libfdt.Fdt object
725 def GetProps(self, node):
726 """Get all properties from a node.
729 node: Full path to node name to look in.
732 A dictionary containing all the properties, indexed by node name.
733 The entries are Prop objects.
736 ValueError: if the node does not exist.
739 poffset = self._fdt_obj.first_property_offset(node._offset,
742 p = self._fdt_obj.get_property_by_offset(poffset)
743 prop = Prop(node, poffset, p.name, p)
744 props_dict[prop.name] = prop
746 poffset = self._fdt_obj.next_property_offset(poffset,
750 def Invalidate(self):
751 """Mark our offset cache as invalid"""
752 self._cached_offsets = False
754 def CheckCache(self):
755 """Refresh the offset cache if needed"""
756 if self._cached_offsets:
761 """Refresh the offset cache"""
762 self._root.Refresh(0)
763 self._cached_offsets = True
765 def GetStructOffset(self, offset):
766 """Get the file offset of a given struct offset
769 offset: Offset within the 'struct' region of the device tree
771 Position of @offset within the device tree binary
773 return self._fdt_obj.off_dt_struct() + offset
776 def Node(self, fdt, parent, offset, name, path):
779 This is used by Fdt.Scan() to create a new node using the correct
784 parent: Parent node, or None if this is the root node
785 offset: Offset of node
787 path: Full path to node
789 node = Node(fdt, parent, offset, name, path)
792 def GetFilename(self):
793 """Get the filename of the device tree
801 """Returns a new Fdt object"""