2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2016 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
13 from libfdt import QUIET_NOTFOUND
16 # This deals with a device tree, presenting it as an assortment of Node and
17 # Prop objects, representing nodes and properties, respectively. This file
18 # contains the base classes and defines the high-level API. You can use
19 # FdtScan() as a convenience function to create and scan an Fdt.
21 # This implementation uses a libfdt Python library to access the device tree,
22 # so it is fairly efficient.
24 # A list of types we support
25 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
27 def CheckErr(errnum, msg):
29 raise ValueError('Error %d: %s: %s' %
30 (errnum, libfdt.fdt_strerror(errnum), msg))
33 def BytesToValue(data):
34 """Converts a string of bytes into a type and value
37 A bytes value (which on Python 2 is an alias for str)
42 Data, either a single element or a list of elements. Each element
44 TYPE_STRING: str/bytes value from the property
45 TYPE_INT: a byte-swapped integer stored as a 4-byte str/bytes
46 TYPE_BYTE: a byte stored as a single-byte str/bytes
50 strings = data.split(b'\0')
52 count = len(strings) - 1
53 if count > 0 and not len(strings[-1]):
54 for string in strings[:-1]:
59 # Handle Python 2 treating bytes as str
62 if ch < 32 or ch > 127:
69 if sys.version_info[0] >= 3: # pragma: no cover
70 return TYPE_STRING, strings[0].decode()
72 return TYPE_STRING, strings[0]
74 if sys.version_info[0] >= 3: # pragma: no cover
75 return TYPE_STRING, [s.decode() for s in strings[:-1]]
77 return TYPE_STRING, strings[:-1]
80 return TYPE_BYTE, tools.ToChar(data[0])
82 return TYPE_BYTE, [tools.ToChar(ch) for ch in list(data)]
84 for i in range(0, size, 4):
85 val.append(data[i:i + 4])
87 return TYPE_INT, val[0]
93 """A device tree property
96 name: Property name (as per the device tree)
97 value: Property value as a string of bytes, or a list of strings of
101 def __init__(self, node, offset, name, data):
103 self._offset = offset
106 self.bytes = bytes(data)
109 self.type = TYPE_BOOL
112 self.type, self.value = BytesToValue(bytes(data))
114 def RefreshOffset(self, poffset):
115 self._offset = poffset
117 def Widen(self, newprop):
118 """Figure out which property type is more general
120 Given a current property and a new property, this function returns the
121 one that is less specific as to type. The less specific property will
122 be ble to represent the data in the more specific property. This is
123 used for things like:
134 He we want to use an int array for 'value'. The first property
135 suggests that a single int is enough, but the second one shows that
136 it is not. Calling this function with these two propertes would
137 update the current property to be like the second, since it is less
140 if newprop.type < self.type:
141 self.type = newprop.type
143 if type(newprop.value) == list and type(self.value) != list:
144 self.value = [self.value]
146 if type(self.value) == list and len(newprop.value) > len(self.value):
147 val = self.GetEmpty(self.type)
148 while len(self.value) < len(newprop.value):
149 self.value.append(val)
152 def GetEmpty(self, type):
153 """Get an empty / zero value of the given type
156 A single value of the given type
158 if type == TYPE_BYTE:
160 elif type == TYPE_INT:
161 return struct.pack('>I', 0);
162 elif type == TYPE_STRING:
168 """Get the offset of a property
171 The offset of the property (struct fdt_property) within the file
173 self._node._fdt.CheckCache()
174 return self._node._fdt.GetStructOffset(self._offset)
176 def SetInt(self, val):
177 """Set the integer value of the property
179 The device tree is marked dirty so that the value will be written to
180 the block on the next sync.
183 val: Integer value (32-bit, single cell)
185 self.bytes = struct.pack('>I', val);
186 self.value = self.bytes
190 def SetData(self, bytes):
191 """Set the value of a property as bytes
194 bytes: New property value to set
197 self.type, self.value = BytesToValue(bytes)
200 def Sync(self, auto_resize=False):
201 """Sync property changes back to the device tree
203 This updates the device tree blob with any changes to this property
207 auto_resize: Resize the device tree automatically if it does not
208 have enough space for the update
211 FdtException if auto_resize is False and there is not enough space
213 if self._offset is None or self.dirty:
215 fdt_obj = node._fdt._fdt_obj
217 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
218 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
219 fdt_obj.resize(fdt_obj.totalsize() + 1024)
220 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
222 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
226 """A device tree node
229 offset: Integer offset in the device tree
230 name: Device tree node tname
231 path: Full path to node, along with the node name itself
232 _fdt: Device tree object
233 subnodes: A list of subnodes for this node, each a Node object
234 props: A dict of properties for this node, each a Prop object.
235 Keyed by property name
237 def __init__(self, fdt, parent, offset, name, path):
240 self._offset = offset
247 """Get the Fdt object for this node
254 def FindNode(self, name):
255 """Find a node given its name
258 name: Node name to look for
260 Node object if found, else None
262 for subnode in self.subnodes:
263 if subnode.name == name:
268 """Returns the offset of a node, after checking the cache
270 This should be used instead of self._offset directly, to ensure that
271 the cache does not contain invalid offsets.
273 self._fdt.CheckCache()
277 """Scan a node's properties and subnodes
279 This fills in the props and subnodes properties, recursively
280 searching into subnodes so that the entire tree is built.
282 fdt_obj = self._fdt._fdt_obj
283 self.props = self._fdt.GetProps(self)
284 phandle = fdt_obj.get_phandle(self.Offset())
286 self._fdt.phandle_to_node[phandle] = self
288 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
290 sep = '' if self.path[-1] == '/' else '/'
291 name = fdt_obj.get_name(offset)
292 path = self.path + sep + name
293 node = Node(self._fdt, self, offset, name, path)
294 self.subnodes.append(node)
297 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
299 def Refresh(self, my_offset):
300 """Fix up the _offset for each node, recursively
302 Note: This does not take account of property offsets - these will not
305 fdt_obj = self._fdt._fdt_obj
306 if self._offset != my_offset:
307 self._offset = my_offset
308 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
309 for subnode in self.subnodes:
310 if subnode.name != fdt_obj.get_name(offset):
311 raise ValueError('Internal error, node name mismatch %s != %s' %
312 (subnode.name, fdt_obj.get_name(offset)))
313 subnode.Refresh(offset)
314 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
315 if offset != -libfdt.FDT_ERR_NOTFOUND:
316 raise ValueError('Internal error, offset == %d' % offset)
318 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
320 p = fdt_obj.get_property_by_offset(poffset)
321 prop = self.props.get(p.name)
323 raise ValueError("Internal error, property '%s' missing, "
324 'offset %d' % (p.name, poffset))
325 prop.RefreshOffset(poffset)
326 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
328 def DeleteProp(self, prop_name):
329 """Delete a property of a node
331 The property is deleted and the offset cache is invalidated.
334 prop_name: Name of the property to delete
336 ValueError if the property does not exist
338 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
339 "Node '%s': delete property: '%s'" % (self.path, prop_name))
340 del self.props[prop_name]
341 self._fdt.Invalidate()
343 def AddZeroProp(self, prop_name):
344 """Add a new property to the device tree with an integer value of 0.
347 prop_name: Name of property
349 self.props[prop_name] = Prop(self, None, prop_name,
350 tools.GetBytes(0, 4))
352 def AddEmptyProp(self, prop_name, len):
353 """Add a property with a fixed data size, for filling in later
355 The device tree is marked dirty so that the value will be written to
356 the blob on the next sync.
359 prop_name: Name of property
360 len: Length of data in property
362 value = tools.GetBytes(0, len)
363 self.props[prop_name] = Prop(self, None, prop_name, value)
365 def _CheckProp(self, prop_name):
366 """Check if a property is present
369 prop_name: Name of property
375 ValueError if the property is missing
377 if prop_name not in self.props:
378 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
379 (self._fdt._fname, self.path, prop_name))
382 def SetInt(self, prop_name, val):
383 """Update an integer property int the device tree.
385 This is not allowed to change the size of the FDT.
387 The device tree is marked dirty so that the value will be written to
388 the blob on the next sync.
391 prop_name: Name of property
394 self._CheckProp(prop_name).props[prop_name].SetInt(val)
396 def SetData(self, prop_name, val):
397 """Set the data value of a property
399 The device tree is marked dirty so that the value will be written to
400 the blob on the next sync.
403 prop_name: Name of property to set
404 val: Data value to set
406 self._CheckProp(prop_name).props[prop_name].SetData(val)
408 def SetString(self, prop_name, val):
409 """Set the string value of a property
411 The device tree is marked dirty so that the value will be written to
412 the blob on the next sync.
415 prop_name: Name of property to set
416 val: String value to set (will be \0-terminated in DT)
418 if sys.version_info[0] >= 3: # pragma: no cover
419 val = bytes(val, 'utf-8')
420 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
422 def AddString(self, prop_name, val):
423 """Add a new string property to a node
425 The device tree is marked dirty so that the value will be written to
426 the blob on the next sync.
429 prop_name: Name of property to add
430 val: String value of property
432 if sys.version_info[0] >= 3: # pragma: no cover
433 val = bytes(val, 'utf-8')
434 self.props[prop_name] = Prop(self, None, prop_name, val + b'\0')
436 def AddSubnode(self, name):
437 """Add a new subnode to the node
440 name: name of node to add
443 New subnode that was created
445 path = self.path + '/' + name
446 subnode = Node(self._fdt, self, None, name, path)
447 self.subnodes.append(subnode)
450 def Sync(self, auto_resize=False):
451 """Sync node changes back to the device tree
453 This updates the device tree blob with any changes to this node and its
454 subnodes since the last sync.
457 auto_resize: Resize the device tree automatically if it does not
458 have enough space for the update
461 FdtException if auto_resize is False and there is not enough space
463 if self._offset is None:
464 # The subnode doesn't exist yet, so add it
465 fdt_obj = self._fdt._fdt_obj
468 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
470 if offset != -libfdt.NOSPACE:
472 fdt_obj.resize(fdt_obj.totalsize() + 1024)
474 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
475 self._offset = offset
477 # Sync subnodes in reverse so that we don't disturb node offsets for
478 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
480 for node in reversed(self.subnodes):
481 node.Sync(auto_resize)
483 # Sync properties now, whose offsets should not have been disturbed.
484 # We do this after subnodes, since this disturbs the offsets of these
485 # properties. Note that new properties will have an offset of None here,
486 # which Python 3 cannot sort against int. So use a large value instead
487 # to ensure that the new properties are added first.
488 prop_list = sorted(self.props.values(),
489 key=lambda prop: prop._offset or 1 << 31,
491 for prop in prop_list:
492 prop.Sync(auto_resize)
496 """Provides simple access to a flat device tree blob using libfdts.
499 fname: Filename of fdt
500 _root: Root of device tree (a Node object)
501 name: Helpful name for this Fdt for the user (useful when creating the
502 DT from data rather than a file)
504 def __init__(self, fname):
506 self._cached_offsets = False
507 self.phandle_to_node = {}
510 self.name = self._fname
511 self._fname = fdt_util.EnsureCompiled(self._fname)
513 with open(self._fname, 'rb') as fd:
514 self._fdt_obj = libfdt.Fdt(fd.read())
517 def FromData(data, name=''):
518 """Create a new Fdt object from the given data
521 data: Device-tree data blob
522 name: Helpful name for this Fdt for the user
525 Fdt object containing the data
528 fdt._fdt_obj = libfdt.Fdt(bytes(data))
532 def LookupPhandle(self, phandle):
536 phandle: Phandle to look up (int)
539 Node object the phandle points to
541 return self.phandle_to_node.get(phandle)
543 def Scan(self, root='/'):
544 """Scan a device tree, building up a tree of Node objects
546 This fills in the self._root property
551 TODO(sjg@chromium.org): Implement the 'root' parameter
553 self._cached_offsets = True
554 self._root = self.Node(self, None, 0, '/', '/')
558 """Get the root Node of the device tree
565 def GetNode(self, path):
566 """Look up a node from its path
569 path: Path to look up, e.g. '/microcode/update@0'
571 Node object, or None if not found
574 parts = path.split('/')
577 if len(parts) == 2 and parts[1] == '':
579 for part in parts[1:]:
580 node = node.FindNode(part)
586 """Flush device tree changes back to the file
588 If the device tree has changed in memory, write it back to the file.
590 with open(self._fname, 'wb') as fd:
591 fd.write(self._fdt_obj.as_bytearray())
593 def Sync(self, auto_resize=False):
594 """Make sure any DT changes are written to the blob
597 auto_resize: Resize the device tree automatically if it does not
598 have enough space for the update
601 FdtException if auto_resize is False and there is not enough space
603 self._root.Sync(auto_resize)
607 """Pack the device tree down to its minimum size
609 When nodes and properties shrink or are deleted, wasted space can
610 build up in the device tree binary.
612 CheckErr(self._fdt_obj.pack(), 'pack')
615 def GetContents(self):
616 """Get the contents of the FDT
619 The FDT contents as a string of bytes
621 return bytes(self._fdt_obj.as_bytearray())
624 """Get the contents of the FDT
627 The FDT contents as a libfdt.Fdt object
631 def GetProps(self, node):
632 """Get all properties from a node.
635 node: Full path to node name to look in.
638 A dictionary containing all the properties, indexed by node name.
639 The entries are Prop objects.
642 ValueError: if the node does not exist.
645 poffset = self._fdt_obj.first_property_offset(node._offset,
648 p = self._fdt_obj.get_property_by_offset(poffset)
649 prop = Prop(node, poffset, p.name, p)
650 props_dict[prop.name] = prop
652 poffset = self._fdt_obj.next_property_offset(poffset,
656 def Invalidate(self):
657 """Mark our offset cache as invalid"""
658 self._cached_offsets = False
660 def CheckCache(self):
661 """Refresh the offset cache if needed"""
662 if self._cached_offsets:
665 self._cached_offsets = True
668 """Refresh the offset cache"""
669 self._root.Refresh(0)
671 def GetStructOffset(self, offset):
672 """Get the file offset of a given struct offset
675 offset: Offset within the 'struct' region of the device tree
677 Position of @offset within the device tree binary
679 return self._fdt_obj.off_dt_struct() + offset
682 def Node(self, fdt, parent, offset, name, path):
685 This is used by Fdt.Scan() to create a new node using the correct
690 parent: Parent node, or None if this is the root node
691 offset: Offset of node
693 path: Full path to node
695 node = Node(fdt, parent, offset, name, path)
698 def GetFilename(self):
699 """Get the filename of the device tree
707 """Returns a new Fdt object"""