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 name: Property name (as per the device tree)
107 value: Property value as a string of bytes, or a list of strings of
111 def __init__(self, node, offset, name, data):
113 self._offset = offset
116 self.bytes = bytes(data)
119 self.type = Type.BOOL
122 self.type, self.value = BytesToValue(bytes(data))
124 def RefreshOffset(self, poffset):
125 self._offset = poffset
127 def Widen(self, newprop):
128 """Figure out which property type is more general
130 Given a current property and a new property, this function returns the
131 one that is less specific as to type. The less specific property will
132 be ble to represent the data in the more specific property. This is
133 used for things like:
144 He we want to use an int array for 'value'. The first property
145 suggests that a single int is enough, but the second one shows that
146 it is not. Calling this function with these two propertes would
147 update the current property to be like the second, since it is less
150 if self.type.is_wider_than(newprop.type):
151 if self.type == Type.INT and newprop.type == Type.BYTE:
152 if type(self.value) == list:
154 for val in self.value:
155 new_value += [chr(by) for by in val]
157 new_value = [chr(by) for by in self.value]
158 self.value = new_value
159 self.type = newprop.type
161 if type(newprop.value) == list and type(self.value) != list:
162 self.value = [self.value]
164 if type(self.value) == list and len(newprop.value) > len(self.value):
165 val = self.GetEmpty(self.type)
166 while len(self.value) < len(newprop.value):
167 self.value.append(val)
170 def GetEmpty(self, type):
171 """Get an empty / zero value of the given type
174 A single value of the given type
176 if type == Type.BYTE:
178 elif type == Type.INT:
179 return struct.pack('>I', 0);
180 elif type == Type.STRING:
186 """Get the offset of a property
189 The offset of the property (struct fdt_property) within the file
191 self._node._fdt.CheckCache()
192 return self._node._fdt.GetStructOffset(self._offset)
194 def SetInt(self, val):
195 """Set the integer value of the property
197 The device tree is marked dirty so that the value will be written to
198 the block on the next sync.
201 val: Integer value (32-bit, single cell)
203 self.bytes = struct.pack('>I', val);
204 self.value = self.bytes
208 def SetData(self, bytes):
209 """Set the value of a property as bytes
212 bytes: New property value to set
215 self.type, self.value = BytesToValue(bytes)
218 def Sync(self, auto_resize=False):
219 """Sync property changes back to the device tree
221 This updates the device tree blob with any changes to this property
225 auto_resize: Resize the device tree automatically if it does not
226 have enough space for the update
229 FdtException if auto_resize is False and there is not enough space
231 if self._offset is None or self.dirty:
233 fdt_obj = node._fdt._fdt_obj
235 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
236 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
237 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
239 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
241 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
245 """A device tree node
248 offset: Integer offset in the device tree
249 name: Device tree node tname
250 path: Full path to node, along with the node name itself
251 _fdt: Device tree object
252 subnodes: A list of subnodes for this node, each a Node object
253 props: A dict of properties for this node, each a Prop object.
254 Keyed by property name
256 def __init__(self, fdt, parent, offset, name, path):
259 self._offset = offset
266 """Get the Fdt object for this node
273 def FindNode(self, name):
274 """Find a node given its name
277 name: Node name to look for
279 Node object if found, else None
281 for subnode in self.subnodes:
282 if subnode.name == name:
287 """Returns the offset of a node, after checking the cache
289 This should be used instead of self._offset directly, to ensure that
290 the cache does not contain invalid offsets.
292 self._fdt.CheckCache()
296 """Scan a node's properties and subnodes
298 This fills in the props and subnodes properties, recursively
299 searching into subnodes so that the entire tree is built.
301 fdt_obj = self._fdt._fdt_obj
302 self.props = self._fdt.GetProps(self)
303 phandle = fdt_obj.get_phandle(self.Offset())
305 self._fdt.phandle_to_node[phandle] = self
307 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
309 sep = '' if self.path[-1] == '/' else '/'
310 name = fdt_obj.get_name(offset)
311 path = self.path + sep + name
312 node = Node(self._fdt, self, offset, name, path)
313 self.subnodes.append(node)
316 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
318 def Refresh(self, my_offset):
319 """Fix up the _offset for each node, recursively
321 Note: This does not take account of property offsets - these will not
324 fdt_obj = self._fdt._fdt_obj
325 if self._offset != my_offset:
326 self._offset = my_offset
327 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
328 for subnode in self.subnodes:
329 if subnode.name != fdt_obj.get_name(offset):
330 raise ValueError('Internal error, node name mismatch %s != %s' %
331 (subnode.name, fdt_obj.get_name(offset)))
332 subnode.Refresh(offset)
333 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
334 if offset != -libfdt.FDT_ERR_NOTFOUND:
335 raise ValueError('Internal error, offset == %d' % offset)
337 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
339 p = fdt_obj.get_property_by_offset(poffset)
340 prop = self.props.get(p.name)
342 raise ValueError("Internal error, node '%s' property '%s' missing, "
343 'offset %d' % (self.path, p.name, poffset))
344 prop.RefreshOffset(poffset)
345 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
347 def DeleteProp(self, prop_name):
348 """Delete a property of a node
350 The property is deleted and the offset cache is invalidated.
353 prop_name: Name of the property to delete
355 ValueError if the property does not exist
357 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
358 "Node '%s': delete property: '%s'" % (self.path, prop_name))
359 del self.props[prop_name]
360 self._fdt.Invalidate()
362 def AddZeroProp(self, prop_name):
363 """Add a new property to the device tree with an integer value of 0.
366 prop_name: Name of property
368 self.props[prop_name] = Prop(self, None, prop_name,
369 tools.GetBytes(0, 4))
371 def AddEmptyProp(self, prop_name, len):
372 """Add a property with a fixed data size, for filling in later
374 The device tree is marked dirty so that the value will be written to
375 the blob on the next sync.
378 prop_name: Name of property
379 len: Length of data in property
381 value = tools.GetBytes(0, len)
382 self.props[prop_name] = Prop(self, None, prop_name, value)
384 def _CheckProp(self, prop_name):
385 """Check if a property is present
388 prop_name: Name of property
394 ValueError if the property is missing
396 if prop_name not in self.props:
397 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
398 (self._fdt._fname, self.path, prop_name))
401 def SetInt(self, prop_name, val):
402 """Update an integer property int the device tree.
404 This is not allowed to change the size of the FDT.
406 The device tree is marked dirty so that the value will be written to
407 the blob on the next sync.
410 prop_name: Name of property
413 self._CheckProp(prop_name).props[prop_name].SetInt(val)
415 def SetData(self, prop_name, val):
416 """Set the data value of a property
418 The device tree is marked dirty so that the value will be written to
419 the blob on the next sync.
422 prop_name: Name of property to set
423 val: Data value to set
425 self._CheckProp(prop_name).props[prop_name].SetData(val)
427 def SetString(self, prop_name, val):
428 """Set the string value of a property
430 The device tree is marked dirty so that the value will be written to
431 the blob on the next sync.
434 prop_name: Name of property to set
435 val: String value to set (will be \0-terminated in DT)
438 val = val.encode('utf-8')
439 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
441 def AddData(self, prop_name, val):
442 """Add a new property to a node
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 add
449 val: Bytes value of property
451 self.props[prop_name] = Prop(self, None, prop_name, val)
453 def AddString(self, prop_name, val):
454 """Add a new string property to a node
456 The device tree is marked dirty so that the value will be written to
457 the blob on the next sync.
460 prop_name: Name of property to add
461 val: String value of property
463 val = bytes(val, 'utf-8')
464 self.AddData(prop_name, val + b'\0')
466 def AddInt(self, prop_name, val):
467 """Add a new integer property to a node
469 The device tree is marked dirty so that the value will be written to
470 the blob on the next sync.
473 prop_name: Name of property to add
474 val: Integer value of property
476 self.AddData(prop_name, struct.pack('>I', val))
478 def AddSubnode(self, name):
479 """Add a new subnode to the node
482 name: name of node to add
485 New subnode that was created
487 path = self.path + '/' + name
488 subnode = Node(self._fdt, self, None, name, path)
489 self.subnodes.append(subnode)
492 def Sync(self, auto_resize=False):
493 """Sync node changes back to the device tree
495 This updates the device tree blob with any changes to this node and its
496 subnodes since the last sync.
499 auto_resize: Resize the device tree automatically if it does not
500 have enough space for the update
503 FdtException if auto_resize is False and there is not enough space
505 if self._offset is None:
506 # The subnode doesn't exist yet, so add it
507 fdt_obj = self._fdt._fdt_obj
510 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
512 if offset != -libfdt.NOSPACE:
514 fdt_obj.resize(fdt_obj.totalsize() + 1024)
516 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
517 self._offset = offset
519 # Sync subnodes in reverse so that we don't disturb node offsets for
520 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
522 for node in reversed(self.subnodes):
523 node.Sync(auto_resize)
525 # Sync properties now, whose offsets should not have been disturbed.
526 # We do this after subnodes, since this disturbs the offsets of these
527 # properties. Note that new properties will have an offset of None here,
528 # which Python 3 cannot sort against int. So use a large value instead
529 # to ensure that the new properties are added first.
530 prop_list = sorted(self.props.values(),
531 key=lambda prop: prop._offset or 1 << 31,
533 for prop in prop_list:
534 prop.Sync(auto_resize)
538 """Provides simple access to a flat device tree blob using libfdts.
541 fname: Filename of fdt
542 _root: Root of device tree (a Node object)
543 name: Helpful name for this Fdt for the user (useful when creating the
544 DT from data rather than a file)
546 def __init__(self, fname):
548 self._cached_offsets = False
549 self.phandle_to_node = {}
552 self.name = self._fname
553 self._fname = fdt_util.EnsureCompiled(self._fname)
555 with open(self._fname, 'rb') as fd:
556 self._fdt_obj = libfdt.Fdt(fd.read())
559 def FromData(data, name=''):
560 """Create a new Fdt object from the given data
563 data: Device-tree data blob
564 name: Helpful name for this Fdt for the user
567 Fdt object containing the data
570 fdt._fdt_obj = libfdt.Fdt(bytes(data))
574 def LookupPhandle(self, phandle):
578 phandle: Phandle to look up (int)
581 Node object the phandle points to
583 return self.phandle_to_node.get(phandle)
585 def Scan(self, root='/'):
586 """Scan a device tree, building up a tree of Node objects
588 This fills in the self._root property
593 TODO(sjg@chromium.org): Implement the 'root' parameter
595 self._cached_offsets = True
596 self._root = self.Node(self, None, 0, '/', '/')
600 """Get the root Node of the device tree
607 def GetNode(self, path):
608 """Look up a node from its path
611 path: Path to look up, e.g. '/microcode/update@0'
613 Node object, or None if not found
616 parts = path.split('/')
619 if len(parts) == 2 and parts[1] == '':
621 for part in parts[1:]:
622 node = node.FindNode(part)
628 """Flush device tree changes back to the file
630 If the device tree has changed in memory, write it back to the file.
632 with open(self._fname, 'wb') as fd:
633 fd.write(self._fdt_obj.as_bytearray())
635 def Sync(self, auto_resize=False):
636 """Make sure any DT changes are written to the blob
639 auto_resize: Resize the device tree automatically if it does not
640 have enough space for the update
643 FdtException if auto_resize is False and there is not enough space
645 self._root.Sync(auto_resize)
649 """Pack the device tree down to its minimum size
651 When nodes and properties shrink or are deleted, wasted space can
652 build up in the device tree binary.
654 CheckErr(self._fdt_obj.pack(), 'pack')
657 def GetContents(self):
658 """Get the contents of the FDT
661 The FDT contents as a string of bytes
663 return bytes(self._fdt_obj.as_bytearray())
666 """Get the contents of the FDT
669 The FDT contents as a libfdt.Fdt object
673 def GetProps(self, node):
674 """Get all properties from a node.
677 node: Full path to node name to look in.
680 A dictionary containing all the properties, indexed by node name.
681 The entries are Prop objects.
684 ValueError: if the node does not exist.
687 poffset = self._fdt_obj.first_property_offset(node._offset,
690 p = self._fdt_obj.get_property_by_offset(poffset)
691 prop = Prop(node, poffset, p.name, p)
692 props_dict[prop.name] = prop
694 poffset = self._fdt_obj.next_property_offset(poffset,
698 def Invalidate(self):
699 """Mark our offset cache as invalid"""
700 self._cached_offsets = False
702 def CheckCache(self):
703 """Refresh the offset cache if needed"""
704 if self._cached_offsets:
707 self._cached_offsets = True
710 """Refresh the offset cache"""
711 self._root.Refresh(0)
713 def GetStructOffset(self, offset):
714 """Get the file offset of a given struct offset
717 offset: Offset within the 'struct' region of the device tree
719 Position of @offset within the device tree binary
721 return self._fdt_obj.off_dt_struct() + offset
724 def Node(self, fdt, parent, offset, name, path):
727 This is used by Fdt.Scan() to create a new node using the correct
732 parent: Parent node, or None if this is the root node
733 offset: Offset of node
735 path: Full path to node
737 node = Node(fdt, parent, offset, name, path)
740 def GetFilename(self):
741 """Get the filename of the device tree
749 """Returns a new Fdt object"""