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):
157 # A boolean has an empty value: if it exists it is True and if not
158 # it is False. So when widening we always start with an empty list
159 # since the only valid integer property would be an empty list of
161 # e.g. this is a boolean:
163 # and it would be widened to int list by:
165 if self.type == Type.BOOL:
167 self.value = [self.GetEmpty(self.type)]
168 if self.type == Type.INT and newprop.type == Type.BYTE:
169 if type(self.value) == list:
171 for val in self.value:
172 new_value += [chr(by) for by in val]
174 new_value = [chr(by) for by in self.value]
175 self.value = new_value
176 self.type = newprop.type
178 if type(newprop.value) == list:
179 if type(self.value) != list:
180 self.value = [self.value]
182 if len(newprop.value) > len(self.value):
183 val = self.GetEmpty(self.type)
184 while len(self.value) < len(newprop.value):
185 self.value.append(val)
188 def GetEmpty(self, type):
189 """Get an empty / zero value of the given type
192 A single value of the given type
194 if type == Type.BYTE:
196 elif type == Type.INT:
197 return struct.pack('>I', 0);
198 elif type == Type.STRING:
204 """Get the offset of a property
207 The offset of the property (struct fdt_property) within the file
209 self._node._fdt.CheckCache()
210 return self._node._fdt.GetStructOffset(self._offset)
212 def SetInt(self, val):
213 """Set the integer value of the property
215 The device tree is marked dirty so that the value will be written to
216 the block on the next sync.
219 val: Integer value (32-bit, single cell)
221 self.bytes = struct.pack('>I', val);
222 self.value = self.bytes
226 def SetData(self, bytes):
227 """Set the value of a property as bytes
230 bytes: New property value to set
233 self.type, self.value = BytesToValue(bytes)
236 def Sync(self, auto_resize=False):
237 """Sync property changes back to the device tree
239 This updates the device tree blob with any changes to this property
243 auto_resize: Resize the device tree automatically if it does not
244 have enough space for the update
247 FdtException if auto_resize is False and there is not enough space
251 fdt_obj = node._fdt._fdt_obj
252 node_name = fdt_obj.get_name(node._offset)
253 if node_name and node_name != node.name:
254 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
255 (node.path, node_name))
258 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
259 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
260 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
262 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
264 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
269 """A device tree node
273 offset: Integer offset in the device tree (None if to be synced)
274 name: Device tree node tname
275 path: Full path to node, along with the node name itself
276 _fdt: Device tree object
277 subnodes: A list of subnodes for this node, each a Node object
278 props: A dict of properties for this node, each a Prop object.
279 Keyed by property name
281 def __init__(self, fdt, parent, offset, name, path):
284 self._offset = offset
291 """Get the Fdt object for this node
298 def FindNode(self, name):
299 """Find a node given its name
302 name: Node name to look for
304 Node object if found, else None
306 for subnode in self.subnodes:
307 if subnode.name == name:
312 """Returns the offset of a node, after checking the cache
314 This should be used instead of self._offset directly, to ensure that
315 the cache does not contain invalid offsets.
317 self._fdt.CheckCache()
321 """Scan a node's properties and subnodes
323 This fills in the props and subnodes properties, recursively
324 searching into subnodes so that the entire tree is built.
326 fdt_obj = self._fdt._fdt_obj
327 self.props = self._fdt.GetProps(self)
328 phandle = fdt_obj.get_phandle(self.Offset())
330 self._fdt.phandle_to_node[phandle] = self
332 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
334 sep = '' if self.path[-1] == '/' else '/'
335 name = fdt_obj.get_name(offset)
336 path = self.path + sep + name
337 node = Node(self._fdt, self, offset, name, path)
338 self.subnodes.append(node)
341 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
343 def Refresh(self, my_offset):
344 """Fix up the _offset for each node, recursively
346 Note: This does not take account of property offsets - these will not
349 fdt_obj = self._fdt._fdt_obj
350 if self._offset != my_offset:
351 self._offset = my_offset
352 name = fdt_obj.get_name(self._offset)
353 if name and self.name != name:
354 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
357 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
358 for subnode in self.subnodes:
359 if subnode.name != fdt_obj.get_name(offset):
360 raise ValueError('Internal error, node name mismatch %s != %s' %
361 (subnode.name, fdt_obj.get_name(offset)))
362 subnode.Refresh(offset)
363 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
364 if offset != -libfdt.FDT_ERR_NOTFOUND:
365 raise ValueError('Internal error, offset == %d' % offset)
367 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
369 p = fdt_obj.get_property_by_offset(poffset)
370 prop = self.props.get(p.name)
372 raise ValueError("Internal error, node '%s' property '%s' missing, "
373 'offset %d' % (self.path, p.name, poffset))
374 prop.RefreshOffset(poffset)
375 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
377 def DeleteProp(self, prop_name):
378 """Delete a property of a node
380 The property is deleted and the offset cache is invalidated.
383 prop_name: Name of the property to delete
385 ValueError if the property does not exist
387 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
388 "Node '%s': delete property: '%s'" % (self.path, prop_name))
389 del self.props[prop_name]
390 self._fdt.Invalidate()
392 def AddZeroProp(self, prop_name):
393 """Add a new property to the device tree with an integer value of 0.
396 prop_name: Name of property
398 self.props[prop_name] = Prop(self, None, prop_name,
399 tools.get_bytes(0, 4))
401 def AddEmptyProp(self, prop_name, len):
402 """Add a property with a fixed data size, for filling in later
404 The device tree is marked dirty so that the value will be written to
405 the blob on the next sync.
408 prop_name: Name of property
409 len: Length of data in property
411 value = tools.get_bytes(0, len)
412 self.props[prop_name] = Prop(self, None, prop_name, value)
414 def _CheckProp(self, prop_name):
415 """Check if a property is present
418 prop_name: Name of property
424 ValueError if the property is missing
426 if prop_name not in self.props:
427 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
428 (self._fdt._fname, self.path, prop_name))
431 def SetInt(self, prop_name, val):
432 """Update an integer property int the device tree.
434 This is not allowed to change the size of the FDT.
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
443 self._CheckProp(prop_name).props[prop_name].SetInt(val)
445 def SetData(self, prop_name, val):
446 """Set the data 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: Data value to set
455 self._CheckProp(prop_name).props[prop_name].SetData(val)
457 def SetString(self, prop_name, val):
458 """Set the string value of a property
460 The device tree is marked dirty so that the value will be written to
461 the blob on the next sync.
464 prop_name: Name of property to set
465 val: String value to set (will be \0-terminated in DT)
468 val = val.encode('utf-8')
469 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
471 def AddData(self, prop_name, val):
472 """Add a new property to a node
474 The device tree is marked dirty so that the value will be written to
475 the blob on the next sync.
478 prop_name: Name of property to add
479 val: Bytes value of property
484 prop = Prop(self, None, prop_name, val)
485 self.props[prop_name] = prop
488 def AddString(self, prop_name, val):
489 """Add a new string 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: String value of property
501 val = bytes(val, 'utf-8')
502 return self.AddData(prop_name, val + b'\0')
504 def AddStringList(self, prop_name, val):
505 """Add a new string-list property to a node
507 The device tree is marked dirty so that the value will be written to
508 the blob on the next sync.
511 prop_name: Name of property to add
512 val (list of str): List of strings to add
519 out += bytes(string, 'utf-8') + b'\0'
520 return self.AddData(prop_name, out)
522 def AddInt(self, prop_name, val):
523 """Add a new integer property to a node
525 The device tree is marked dirty so that the value will be written to
526 the blob on the next sync.
529 prop_name: Name of property to add
530 val: Integer value of property
535 return self.AddData(prop_name, struct.pack('>I', val))
537 def AddSubnode(self, name):
538 """Add a new subnode to the node
541 name: name of node to add
544 New subnode that was created
546 path = self.path + '/' + name
547 subnode = Node(self._fdt, self, None, name, path)
548 self.subnodes.append(subnode)
554 The node is deleted and the offset cache is invalidated.
557 node (Node): Node to delete
560 ValueError if the node does not exist
562 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
563 "Node '%s': delete" % self.path)
565 self._fdt.Invalidate()
566 parent.subnodes.remove(self)
568 def Sync(self, auto_resize=False):
569 """Sync node changes back to the device tree
571 This updates the device tree blob with any changes to this node and its
572 subnodes since the last sync.
575 auto_resize: Resize the device tree automatically if it does not
576 have enough space for the update
579 True if the node had to be added, False if it already existed
582 FdtException if auto_resize is False and there is not enough space
585 if self._offset is None:
586 # The subnode doesn't exist yet, so add it
587 fdt_obj = self._fdt._fdt_obj
590 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
592 if offset != -libfdt.NOSPACE:
594 fdt_obj.resize(fdt_obj.totalsize() + 1024)
596 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
597 self._offset = offset
600 # Sync the existing subnodes first, so that we can rely on the offsets
601 # being correct. As soon as we add new subnodes, it pushes all the
602 # existing subnodes up.
603 for node in reversed(self.subnodes):
604 if node._offset is not None:
605 node.Sync(auto_resize)
607 # Sync subnodes in reverse so that we get the expected order. Each
608 # new node goes at the start of the subnode list. This avoids an O(n^2)
609 # rescan of node offsets.
611 for node in reversed(self.subnodes):
612 if node.Sync(auto_resize):
615 # Reorder our list of nodes to put the new ones first, since that's
617 old_count = len(self.subnodes) - num_added
618 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
619 self.subnodes = subnodes
621 # Sync properties now, whose offsets should not have been disturbed,
622 # since properties come before subnodes. This is done after all the
623 # subnode processing above, since updating properties can disturb the
624 # offsets of those subnodes.
625 # Properties are synced in reverse order, with new properties added
626 # before existing properties are synced. This ensures that the offsets
627 # of earlier properties are not disturbed.
628 # Note that new properties will have an offset of None here, which
629 # Python cannot sort against int. So use a large value instead so that
630 # new properties are added first.
631 prop_list = sorted(self.props.values(),
632 key=lambda prop: prop._offset or 1 << 31,
634 for prop in prop_list:
635 prop.Sync(auto_resize)
640 """Provides simple access to a flat device tree blob using libfdts.
643 fname: Filename of fdt
644 _root: Root of device tree (a Node object)
645 name: Helpful name for this Fdt for the user (useful when creating the
646 DT from data rather than a file)
648 def __init__(self, fname):
650 self._cached_offsets = False
651 self.phandle_to_node = {}
654 self.name = self._fname
655 self._fname = fdt_util.EnsureCompiled(self._fname)
657 with open(self._fname, 'rb') as fd:
658 self._fdt_obj = libfdt.Fdt(fd.read())
661 def FromData(data, name=''):
662 """Create a new Fdt object from the given data
665 data: Device-tree data blob
666 name: Helpful name for this Fdt for the user
669 Fdt object containing the data
672 fdt._fdt_obj = libfdt.Fdt(bytes(data))
676 def LookupPhandle(self, phandle):
680 phandle: Phandle to look up (int)
683 Node object the phandle points to
685 return self.phandle_to_node.get(phandle)
687 def Scan(self, root='/'):
688 """Scan a device tree, building up a tree of Node objects
690 This fills in the self._root property
695 TODO(sjg@chromium.org): Implement the 'root' parameter
697 self._cached_offsets = True
698 self._root = self.Node(self, None, 0, '/', '/')
702 """Get the root Node of the device tree
709 def GetNode(self, path):
710 """Look up a node from its path
713 path: Path to look up, e.g. '/microcode/update@0'
715 Node object, or None if not found
718 parts = path.split('/')
721 if len(parts) == 2 and parts[1] == '':
723 for part in parts[1:]:
724 node = node.FindNode(part)
730 """Flush device tree changes back to the file
732 If the device tree has changed in memory, write it back to the file.
734 with open(self._fname, 'wb') as fd:
735 fd.write(self._fdt_obj.as_bytearray())
737 def Sync(self, auto_resize=False):
738 """Make sure any DT changes are written to the blob
741 auto_resize: Resize the device tree automatically if it does not
742 have enough space for the update
745 FdtException if auto_resize is False and there is not enough space
748 self._root.Sync(auto_resize)
752 """Pack the device tree down to its minimum size
754 When nodes and properties shrink or are deleted, wasted space can
755 build up in the device tree binary.
757 CheckErr(self._fdt_obj.pack(), 'pack')
760 def GetContents(self):
761 """Get the contents of the FDT
764 The FDT contents as a string of bytes
766 return bytes(self._fdt_obj.as_bytearray())
769 """Get the contents of the FDT
772 The FDT contents as a libfdt.Fdt object
776 def GetProps(self, node):
777 """Get all properties from a node.
780 node: Full path to node name to look in.
783 A dictionary containing all the properties, indexed by node name.
784 The entries are Prop objects.
787 ValueError: if the node does not exist.
790 poffset = self._fdt_obj.first_property_offset(node._offset,
793 p = self._fdt_obj.get_property_by_offset(poffset)
794 prop = Prop(node, poffset, p.name, p)
795 props_dict[prop.name] = prop
797 poffset = self._fdt_obj.next_property_offset(poffset,
801 def Invalidate(self):
802 """Mark our offset cache as invalid"""
803 self._cached_offsets = False
805 def CheckCache(self):
806 """Refresh the offset cache if needed"""
807 if self._cached_offsets:
812 """Refresh the offset cache"""
813 self._root.Refresh(0)
814 self._cached_offsets = True
816 def GetStructOffset(self, offset):
817 """Get the file offset of a given struct offset
820 offset: Offset within the 'struct' region of the device tree
822 Position of @offset within the device tree binary
824 return self._fdt_obj.off_dt_struct() + offset
827 def Node(self, fdt, parent, offset, name, path):
830 This is used by Fdt.Scan() to create a new node using the correct
835 parent: Parent node, or None if this is the root node
836 offset: Offset of node
838 path: Full path to node
840 node = Node(fdt, parent, offset, name, path)
843 def GetFilename(self):
844 """Get the filename of the device tree
852 """Returns a new Fdt object"""