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 and type(self.value) != list:
167 self.value = [self.value]
169 if type(self.value) == list and len(newprop.value) > len(self.value):
170 val = self.GetEmpty(self.type)
171 while len(self.value) < len(newprop.value):
172 self.value.append(val)
175 def GetEmpty(self, type):
176 """Get an empty / zero value of the given type
179 A single value of the given type
181 if type == Type.BYTE:
183 elif type == Type.INT:
184 return struct.pack('>I', 0);
185 elif type == Type.STRING:
191 """Get the offset of a property
194 The offset of the property (struct fdt_property) within the file
196 self._node._fdt.CheckCache()
197 return self._node._fdt.GetStructOffset(self._offset)
199 def SetInt(self, val):
200 """Set the integer value of the property
202 The device tree is marked dirty so that the value will be written to
203 the block on the next sync.
206 val: Integer value (32-bit, single cell)
208 self.bytes = struct.pack('>I', val);
209 self.value = self.bytes
213 def SetData(self, bytes):
214 """Set the value of a property as bytes
217 bytes: New property value to set
220 self.type, self.value = BytesToValue(bytes)
223 def Sync(self, auto_resize=False):
224 """Sync property changes back to the device tree
226 This updates the device tree blob with any changes to this property
230 auto_resize: Resize the device tree automatically if it does not
231 have enough space for the update
234 FdtException if auto_resize is False and there is not enough space
238 fdt_obj = node._fdt._fdt_obj
239 node_name = fdt_obj.get_name(node._offset)
240 if node_name and node_name != node.name:
241 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
242 (node.path, node_name))
245 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
246 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
247 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
249 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
251 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
256 """A device tree node
260 offset: Integer offset in the device tree (None if to be synced)
261 name: Device tree node tname
262 path: Full path to node, along with the node name itself
263 _fdt: Device tree object
264 subnodes: A list of subnodes for this node, each a Node object
265 props: A dict of properties for this node, each a Prop object.
266 Keyed by property name
268 def __init__(self, fdt, parent, offset, name, path):
271 self._offset = offset
278 """Get the Fdt object for this node
285 def FindNode(self, name):
286 """Find a node given its name
289 name: Node name to look for
291 Node object if found, else None
293 for subnode in self.subnodes:
294 if subnode.name == name:
299 """Returns the offset of a node, after checking the cache
301 This should be used instead of self._offset directly, to ensure that
302 the cache does not contain invalid offsets.
304 self._fdt.CheckCache()
308 """Scan a node's properties and subnodes
310 This fills in the props and subnodes properties, recursively
311 searching into subnodes so that the entire tree is built.
313 fdt_obj = self._fdt._fdt_obj
314 self.props = self._fdt.GetProps(self)
315 phandle = fdt_obj.get_phandle(self.Offset())
317 self._fdt.phandle_to_node[phandle] = self
319 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
321 sep = '' if self.path[-1] == '/' else '/'
322 name = fdt_obj.get_name(offset)
323 path = self.path + sep + name
324 node = Node(self._fdt, self, offset, name, path)
325 self.subnodes.append(node)
328 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
330 def Refresh(self, my_offset):
331 """Fix up the _offset for each node, recursively
333 Note: This does not take account of property offsets - these will not
336 fdt_obj = self._fdt._fdt_obj
337 if self._offset != my_offset:
338 self._offset = my_offset
339 name = fdt_obj.get_name(self._offset)
340 if name and self.name != name:
341 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
344 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
345 for subnode in self.subnodes:
346 if subnode.name != fdt_obj.get_name(offset):
347 raise ValueError('Internal error, node name mismatch %s != %s' %
348 (subnode.name, fdt_obj.get_name(offset)))
349 subnode.Refresh(offset)
350 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
351 if offset != -libfdt.FDT_ERR_NOTFOUND:
352 raise ValueError('Internal error, offset == %d' % offset)
354 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
356 p = fdt_obj.get_property_by_offset(poffset)
357 prop = self.props.get(p.name)
359 raise ValueError("Internal error, node '%s' property '%s' missing, "
360 'offset %d' % (self.path, p.name, poffset))
361 prop.RefreshOffset(poffset)
362 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
364 def DeleteProp(self, prop_name):
365 """Delete a property of a node
367 The property is deleted and the offset cache is invalidated.
370 prop_name: Name of the property to delete
372 ValueError if the property does not exist
374 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
375 "Node '%s': delete property: '%s'" % (self.path, prop_name))
376 del self.props[prop_name]
377 self._fdt.Invalidate()
379 def AddZeroProp(self, prop_name):
380 """Add a new property to the device tree with an integer value of 0.
383 prop_name: Name of property
385 self.props[prop_name] = Prop(self, None, prop_name,
386 tools.GetBytes(0, 4))
388 def AddEmptyProp(self, prop_name, len):
389 """Add a property with a fixed data size, for filling in later
391 The device tree is marked dirty so that the value will be written to
392 the blob on the next sync.
395 prop_name: Name of property
396 len: Length of data in property
398 value = tools.GetBytes(0, len)
399 self.props[prop_name] = Prop(self, None, prop_name, value)
401 def _CheckProp(self, prop_name):
402 """Check if a property is present
405 prop_name: Name of property
411 ValueError if the property is missing
413 if prop_name not in self.props:
414 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
415 (self._fdt._fname, self.path, prop_name))
418 def SetInt(self, prop_name, val):
419 """Update an integer property int the device tree.
421 This is not allowed to change the size of the FDT.
423 The device tree is marked dirty so that the value will be written to
424 the blob on the next sync.
427 prop_name: Name of property
430 self._CheckProp(prop_name).props[prop_name].SetInt(val)
432 def SetData(self, prop_name, val):
433 """Set the data value of a property
435 The device tree is marked dirty so that the value will be written to
436 the blob on the next sync.
439 prop_name: Name of property to set
440 val: Data value to set
442 self._CheckProp(prop_name).props[prop_name].SetData(val)
444 def SetString(self, prop_name, val):
445 """Set the string value of a property
447 The device tree is marked dirty so that the value will be written to
448 the blob on the next sync.
451 prop_name: Name of property to set
452 val: String value to set (will be \0-terminated in DT)
455 val = val.encode('utf-8')
456 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
458 def AddData(self, prop_name, val):
459 """Add a new property to a node
461 The device tree is marked dirty so that the value will be written to
462 the blob on the next sync.
465 prop_name: Name of property to add
466 val: Bytes value of property
471 prop = Prop(self, None, prop_name, val)
472 self.props[prop_name] = prop
475 def AddString(self, prop_name, val):
476 """Add a new string property to a node
478 The device tree is marked dirty so that the value will be written to
479 the blob on the next sync.
482 prop_name: Name of property to add
483 val: String value of property
488 val = bytes(val, 'utf-8')
489 return self.AddData(prop_name, val + b'\0')
491 def AddInt(self, prop_name, val):
492 """Add a new integer property to a node
494 The device tree is marked dirty so that the value will be written to
495 the blob on the next sync.
498 prop_name: Name of property to add
499 val: Integer value of property
504 return self.AddData(prop_name, struct.pack('>I', val))
506 def AddSubnode(self, name):
507 """Add a new subnode to the node
510 name: name of node to add
513 New subnode that was created
515 path = self.path + '/' + name
516 subnode = Node(self._fdt, self, None, name, path)
517 self.subnodes.append(subnode)
520 def Sync(self, auto_resize=False):
521 """Sync node changes back to the device tree
523 This updates the device tree blob with any changes to this node and its
524 subnodes since the last sync.
527 auto_resize: Resize the device tree automatically if it does not
528 have enough space for the update
531 True if the node had to be added, False if it already existed
534 FdtException if auto_resize is False and there is not enough space
537 if self._offset is None:
538 # The subnode doesn't exist yet, so add it
539 fdt_obj = self._fdt._fdt_obj
542 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
544 if offset != -libfdt.NOSPACE:
546 fdt_obj.resize(fdt_obj.totalsize() + 1024)
548 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
549 self._offset = offset
552 # Sync the existing subnodes first, so that we can rely on the offsets
553 # being correct. As soon as we add new subnodes, it pushes all the
554 # existing subnodes up.
555 for node in reversed(self.subnodes):
556 if node._offset is not None:
557 node.Sync(auto_resize)
559 # Sync subnodes in reverse so that we get the expected order. Each
560 # new node goes at the start of the subnode list. This avoids an O(n^2)
561 # rescan of node offsets.
563 for node in reversed(self.subnodes):
564 if node.Sync(auto_resize):
567 # Reorder our list of nodes to put the new ones first, since that's
569 old_count = len(self.subnodes) - num_added
570 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
571 self.subnodes = subnodes
573 # Sync properties now, whose offsets should not have been disturbed,
574 # since properties come before subnodes. This is done after all the
575 # subnode processing above, since updating properties can disturb the
576 # offsets of those subnodes.
577 # Properties are synced in reverse order, with new properties added
578 # before existing properties are synced. This ensures that the offsets
579 # of earlier properties are not disturbed.
580 # Note that new properties will have an offset of None here, which
581 # Python cannot sort against int. So use a large value instead so that
582 # new properties are added first.
583 prop_list = sorted(self.props.values(),
584 key=lambda prop: prop._offset or 1 << 31,
586 for prop in prop_list:
587 prop.Sync(auto_resize)
592 """Provides simple access to a flat device tree blob using libfdts.
595 fname: Filename of fdt
596 _root: Root of device tree (a Node object)
597 name: Helpful name for this Fdt for the user (useful when creating the
598 DT from data rather than a file)
600 def __init__(self, fname):
602 self._cached_offsets = False
603 self.phandle_to_node = {}
606 self.name = self._fname
607 self._fname = fdt_util.EnsureCompiled(self._fname)
609 with open(self._fname, 'rb') as fd:
610 self._fdt_obj = libfdt.Fdt(fd.read())
613 def FromData(data, name=''):
614 """Create a new Fdt object from the given data
617 data: Device-tree data blob
618 name: Helpful name for this Fdt for the user
621 Fdt object containing the data
624 fdt._fdt_obj = libfdt.Fdt(bytes(data))
628 def LookupPhandle(self, phandle):
632 phandle: Phandle to look up (int)
635 Node object the phandle points to
637 return self.phandle_to_node.get(phandle)
639 def Scan(self, root='/'):
640 """Scan a device tree, building up a tree of Node objects
642 This fills in the self._root property
647 TODO(sjg@chromium.org): Implement the 'root' parameter
649 self._cached_offsets = True
650 self._root = self.Node(self, None, 0, '/', '/')
654 """Get the root Node of the device tree
661 def GetNode(self, path):
662 """Look up a node from its path
665 path: Path to look up, e.g. '/microcode/update@0'
667 Node object, or None if not found
670 parts = path.split('/')
673 if len(parts) == 2 and parts[1] == '':
675 for part in parts[1:]:
676 node = node.FindNode(part)
682 """Flush device tree changes back to the file
684 If the device tree has changed in memory, write it back to the file.
686 with open(self._fname, 'wb') as fd:
687 fd.write(self._fdt_obj.as_bytearray())
689 def Sync(self, auto_resize=False):
690 """Make sure any DT changes are written to the blob
693 auto_resize: Resize the device tree automatically if it does not
694 have enough space for the update
697 FdtException if auto_resize is False and there is not enough space
700 self._root.Sync(auto_resize)
704 """Pack the device tree down to its minimum size
706 When nodes and properties shrink or are deleted, wasted space can
707 build up in the device tree binary.
709 CheckErr(self._fdt_obj.pack(), 'pack')
712 def GetContents(self):
713 """Get the contents of the FDT
716 The FDT contents as a string of bytes
718 return bytes(self._fdt_obj.as_bytearray())
721 """Get the contents of the FDT
724 The FDT contents as a libfdt.Fdt object
728 def GetProps(self, node):
729 """Get all properties from a node.
732 node: Full path to node name to look in.
735 A dictionary containing all the properties, indexed by node name.
736 The entries are Prop objects.
739 ValueError: if the node does not exist.
742 poffset = self._fdt_obj.first_property_offset(node._offset,
745 p = self._fdt_obj.get_property_by_offset(poffset)
746 prop = Prop(node, poffset, p.name, p)
747 props_dict[prop.name] = prop
749 poffset = self._fdt_obj.next_property_offset(poffset,
753 def Invalidate(self):
754 """Mark our offset cache as invalid"""
755 self._cached_offsets = False
757 def CheckCache(self):
758 """Refresh the offset cache if needed"""
759 if self._cached_offsets:
764 """Refresh the offset cache"""
765 self._root.Refresh(0)
766 self._cached_offsets = True
768 def GetStructOffset(self, offset):
769 """Get the file offset of a given struct offset
772 offset: Offset within the 'struct' region of the device tree
774 Position of @offset within the device tree binary
776 return self._fdt_obj.off_dt_struct() + offset
779 def Node(self, fdt, parent, offset, name, path):
782 This is used by Fdt.Scan() to create a new node using the correct
787 parent: Parent node, or None if this is the root node
788 offset: Offset of node
790 path: Full path to node
792 node = Node(fdt, parent, offset, name, path)
795 def GetFilename(self):
796 """Get the filename of the device tree
804 """Returns a new Fdt object"""