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
237 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
238 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
239 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
241 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
243 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
248 """A device tree node
252 offset: Integer offset in the device tree (None if to be synced)
253 name: Device tree node tname
254 path: Full path to node, along with the node name itself
255 _fdt: Device tree object
256 subnodes: A list of subnodes for this node, each a Node object
257 props: A dict of properties for this node, each a Prop object.
258 Keyed by property name
260 def __init__(self, fdt, parent, offset, name, path):
263 self._offset = offset
270 """Get the Fdt object for this node
277 def FindNode(self, name):
278 """Find a node given its name
281 name: Node name to look for
283 Node object if found, else None
285 for subnode in self.subnodes:
286 if subnode.name == name:
291 """Returns the offset of a node, after checking the cache
293 This should be used instead of self._offset directly, to ensure that
294 the cache does not contain invalid offsets.
296 self._fdt.CheckCache()
300 """Scan a node's properties and subnodes
302 This fills in the props and subnodes properties, recursively
303 searching into subnodes so that the entire tree is built.
305 fdt_obj = self._fdt._fdt_obj
306 self.props = self._fdt.GetProps(self)
307 phandle = fdt_obj.get_phandle(self.Offset())
309 self._fdt.phandle_to_node[phandle] = self
311 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
313 sep = '' if self.path[-1] == '/' else '/'
314 name = fdt_obj.get_name(offset)
315 path = self.path + sep + name
316 node = Node(self._fdt, self, offset, name, path)
317 self.subnodes.append(node)
320 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
322 def Refresh(self, my_offset):
323 """Fix up the _offset for each node, recursively
325 Note: This does not take account of property offsets - these will not
328 fdt_obj = self._fdt._fdt_obj
329 if self._offset != my_offset:
330 self._offset = my_offset
331 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
332 for subnode in self.subnodes:
333 if subnode.name != fdt_obj.get_name(offset):
334 raise ValueError('Internal error, node name mismatch %s != %s' %
335 (subnode.name, fdt_obj.get_name(offset)))
336 subnode.Refresh(offset)
337 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
338 if offset != -libfdt.FDT_ERR_NOTFOUND:
339 raise ValueError('Internal error, offset == %d' % offset)
341 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
343 p = fdt_obj.get_property_by_offset(poffset)
344 prop = self.props.get(p.name)
346 raise ValueError("Internal error, node '%s' property '%s' missing, "
347 'offset %d' % (self.path, p.name, poffset))
348 prop.RefreshOffset(poffset)
349 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
351 def DeleteProp(self, prop_name):
352 """Delete a property of a node
354 The property is deleted and the offset cache is invalidated.
357 prop_name: Name of the property to delete
359 ValueError if the property does not exist
361 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
362 "Node '%s': delete property: '%s'" % (self.path, prop_name))
363 del self.props[prop_name]
364 self._fdt.Invalidate()
366 def AddZeroProp(self, prop_name):
367 """Add a new property to the device tree with an integer value of 0.
370 prop_name: Name of property
372 self.props[prop_name] = Prop(self, None, prop_name,
373 tools.GetBytes(0, 4))
375 def AddEmptyProp(self, prop_name, len):
376 """Add a property with a fixed data size, for filling in later
378 The device tree is marked dirty so that the value will be written to
379 the blob on the next sync.
382 prop_name: Name of property
383 len: Length of data in property
385 value = tools.GetBytes(0, len)
386 self.props[prop_name] = Prop(self, None, prop_name, value)
388 def _CheckProp(self, prop_name):
389 """Check if a property is present
392 prop_name: Name of property
398 ValueError if the property is missing
400 if prop_name not in self.props:
401 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
402 (self._fdt._fname, self.path, prop_name))
405 def SetInt(self, prop_name, val):
406 """Update an integer property int the device tree.
408 This is not allowed to change the size of the FDT.
410 The device tree is marked dirty so that the value will be written to
411 the blob on the next sync.
414 prop_name: Name of property
417 self._CheckProp(prop_name).props[prop_name].SetInt(val)
419 def SetData(self, prop_name, val):
420 """Set the data value of a property
422 The device tree is marked dirty so that the value will be written to
423 the blob on the next sync.
426 prop_name: Name of property to set
427 val: Data value to set
429 self._CheckProp(prop_name).props[prop_name].SetData(val)
431 def SetString(self, prop_name, val):
432 """Set the string value of a property
434 The device tree is marked dirty so that the value will be written to
435 the blob on the next sync.
438 prop_name: Name of property to set
439 val: String value to set (will be \0-terminated in DT)
442 val = val.encode('utf-8')
443 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
445 def AddData(self, prop_name, val):
446 """Add a new property to a node
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 add
453 val: Bytes value of property
455 self.props[prop_name] = Prop(self, None, prop_name, val)
457 def AddString(self, prop_name, val):
458 """Add a new string property to a node
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 add
465 val: String value of property
467 val = bytes(val, 'utf-8')
468 self.AddData(prop_name, val + b'\0')
470 def AddInt(self, prop_name, val):
471 """Add a new integer property to a node
473 The device tree is marked dirty so that the value will be written to
474 the blob on the next sync.
477 prop_name: Name of property to add
478 val: Integer value of property
480 self.AddData(prop_name, struct.pack('>I', val))
482 def AddSubnode(self, name):
483 """Add a new subnode to the node
486 name: name of node to add
489 New subnode that was created
491 path = self.path + '/' + name
492 subnode = Node(self._fdt, self, None, name, path)
493 self.subnodes.append(subnode)
496 def Sync(self, auto_resize=False):
497 """Sync node changes back to the device tree
499 This updates the device tree blob with any changes to this node and its
500 subnodes since the last sync.
503 auto_resize: Resize the device tree automatically if it does not
504 have enough space for the update
507 FdtException if auto_resize is False and there is not enough space
509 if self._offset is None:
510 # The subnode doesn't exist yet, so add it
511 fdt_obj = self._fdt._fdt_obj
514 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
516 if offset != -libfdt.NOSPACE:
518 fdt_obj.resize(fdt_obj.totalsize() + 1024)
520 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
521 self._offset = offset
523 # Sync subnodes in reverse so that we don't disturb node offsets for
524 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
526 for node in reversed(self.subnodes):
527 node.Sync(auto_resize)
529 # Sync properties now, whose offsets should not have been disturbed.
530 # We do this after subnodes, since this disturbs the offsets of these
531 # properties. Note that new properties will have an offset of None here,
532 # which Python 3 cannot sort against int. So use a large value instead
533 # to ensure that the new properties are added first.
534 prop_list = sorted(self.props.values(),
535 key=lambda prop: prop._offset or 1 << 31,
537 for prop in prop_list:
538 prop.Sync(auto_resize)
542 """Provides simple access to a flat device tree blob using libfdts.
545 fname: Filename of fdt
546 _root: Root of device tree (a Node object)
547 name: Helpful name for this Fdt for the user (useful when creating the
548 DT from data rather than a file)
550 def __init__(self, fname):
552 self._cached_offsets = False
553 self.phandle_to_node = {}
556 self.name = self._fname
557 self._fname = fdt_util.EnsureCompiled(self._fname)
559 with open(self._fname, 'rb') as fd:
560 self._fdt_obj = libfdt.Fdt(fd.read())
563 def FromData(data, name=''):
564 """Create a new Fdt object from the given data
567 data: Device-tree data blob
568 name: Helpful name for this Fdt for the user
571 Fdt object containing the data
574 fdt._fdt_obj = libfdt.Fdt(bytes(data))
578 def LookupPhandle(self, phandle):
582 phandle: Phandle to look up (int)
585 Node object the phandle points to
587 return self.phandle_to_node.get(phandle)
589 def Scan(self, root='/'):
590 """Scan a device tree, building up a tree of Node objects
592 This fills in the self._root property
597 TODO(sjg@chromium.org): Implement the 'root' parameter
599 self._cached_offsets = True
600 self._root = self.Node(self, None, 0, '/', '/')
604 """Get the root Node of the device tree
611 def GetNode(self, path):
612 """Look up a node from its path
615 path: Path to look up, e.g. '/microcode/update@0'
617 Node object, or None if not found
620 parts = path.split('/')
623 if len(parts) == 2 and parts[1] == '':
625 for part in parts[1:]:
626 node = node.FindNode(part)
632 """Flush device tree changes back to the file
634 If the device tree has changed in memory, write it back to the file.
636 with open(self._fname, 'wb') as fd:
637 fd.write(self._fdt_obj.as_bytearray())
639 def Sync(self, auto_resize=False):
640 """Make sure any DT changes are written to the blob
643 auto_resize: Resize the device tree automatically if it does not
644 have enough space for the update
647 FdtException if auto_resize is False and there is not enough space
650 self._root.Sync(auto_resize)
654 """Pack the device tree down to its minimum size
656 When nodes and properties shrink or are deleted, wasted space can
657 build up in the device tree binary.
659 CheckErr(self._fdt_obj.pack(), 'pack')
662 def GetContents(self):
663 """Get the contents of the FDT
666 The FDT contents as a string of bytes
668 return bytes(self._fdt_obj.as_bytearray())
671 """Get the contents of the FDT
674 The FDT contents as a libfdt.Fdt object
678 def GetProps(self, node):
679 """Get all properties from a node.
682 node: Full path to node name to look in.
685 A dictionary containing all the properties, indexed by node name.
686 The entries are Prop objects.
689 ValueError: if the node does not exist.
692 poffset = self._fdt_obj.first_property_offset(node._offset,
695 p = self._fdt_obj.get_property_by_offset(poffset)
696 prop = Prop(node, poffset, p.name, p)
697 props_dict[prop.name] = prop
699 poffset = self._fdt_obj.next_property_offset(poffset,
703 def Invalidate(self):
704 """Mark our offset cache as invalid"""
705 self._cached_offsets = False
707 def CheckCache(self):
708 """Refresh the offset cache if needed"""
709 if self._cached_offsets:
714 """Refresh the offset cache"""
715 self._root.Refresh(0)
716 self._cached_offsets = True
718 def GetStructOffset(self, offset):
719 """Get the file offset of a given struct offset
722 offset: Offset within the 'struct' region of the device tree
724 Position of @offset within the device tree binary
726 return self._fdt_obj.off_dt_struct() + offset
729 def Node(self, fdt, parent, offset, name, path):
732 This is used by Fdt.Scan() to create a new node using the correct
737 parent: Parent node, or None if this is the root node
738 offset: Offset of node
740 path: Full path to node
742 node = Node(fdt, parent, offset, name, path)
745 def GetFilename(self):
746 """Get the filename of the device tree
754 """Returns a new Fdt object"""