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 """A device tree property
36 name: Property name (as per the device tree)
37 value: Property value as a string of bytes, or a list of strings of
41 def __init__(self, node, offset, name, bytes):
46 self.bytes = str(bytes)
52 self.type, self.value = self.BytesToValue(bytes)
54 def RefreshOffset(self, poffset):
55 self._offset = poffset
57 def Widen(self, newprop):
58 """Figure out which property type is more general
60 Given a current property and a new property, this function returns the
61 one that is less specific as to type. The less specific property will
62 be ble to represent the data in the more specific property. This is
74 He we want to use an int array for 'value'. The first property
75 suggests that a single int is enough, but the second one shows that
76 it is not. Calling this function with these two propertes would
77 update the current property to be like the second, since it is less
80 if newprop.type < self.type:
81 self.type = newprop.type
83 if type(newprop.value) == list and type(self.value) != list:
84 self.value = [self.value]
86 if type(self.value) == list and len(newprop.value) > len(self.value):
87 val = self.GetEmpty(self.type)
88 while len(self.value) < len(newprop.value):
89 self.value.append(val)
91 def BytesToValue(self, bytes):
92 """Converts a string of bytes into a type and value
95 A string containing bytes
100 Data, either a single element or a list of elements. Each element
102 TYPE_STRING: string value from the property
103 TYPE_INT: a byte-swapped integer stored as a 4-byte string
104 TYPE_BYTE: a byte stored as a single-byte string
108 strings = bytes.split('\0')
110 count = len(strings) - 1
111 if count > 0 and not strings[-1]:
112 for string in strings[:-1]:
117 if ch < ' ' or ch > '~':
124 return TYPE_STRING, strings[0]
126 return TYPE_STRING, strings[:-1]
129 return TYPE_BYTE, bytes[0]
131 return TYPE_BYTE, list(bytes)
133 for i in range(0, size, 4):
134 val.append(bytes[i:i + 4])
136 return TYPE_INT, val[0]
141 def GetEmpty(self, type):
142 """Get an empty / zero value of the given type
145 A single value of the given type
147 if type == TYPE_BYTE:
149 elif type == TYPE_INT:
150 return struct.pack('>I', 0);
151 elif type == TYPE_STRING:
157 """Get the offset of a property
160 The offset of the property (struct fdt_property) within the file
162 self._node._fdt.CheckCache()
163 return self._node._fdt.GetStructOffset(self._offset)
165 def SetInt(self, val):
166 """Set the integer value of the property
168 The device tree is marked dirty so that the value will be written to
169 the block on the next sync.
172 val: Integer value (32-bit, single cell)
174 self.bytes = struct.pack('>I', val);
175 self.value = self.bytes
179 def SetData(self, bytes):
180 """Set the value of a property as bytes
183 bytes: New property value to set
185 self.bytes = str(bytes)
186 self.type, self.value = self.BytesToValue(bytes)
189 def Sync(self, auto_resize=False):
190 """Sync property changes back to the device tree
192 This updates the device tree blob with any changes to this property
196 auto_resize: Resize the device tree automatically if it does not
197 have enough space for the update
200 FdtException if auto_resize is False and there is not enough space
202 if self._offset is None or self.dirty:
204 fdt_obj = node._fdt._fdt_obj
206 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
207 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
208 fdt_obj.resize(fdt_obj.totalsize() + 1024)
209 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
211 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
215 """A device tree node
218 offset: Integer offset in the device tree
219 name: Device tree node tname
220 path: Full path to node, along with the node name itself
221 _fdt: Device tree object
222 subnodes: A list of subnodes for this node, each a Node object
223 props: A dict of properties for this node, each a Prop object.
224 Keyed by property name
226 def __init__(self, fdt, parent, offset, name, path):
229 self._offset = offset
236 """Get the Fdt object for this node
243 def FindNode(self, name):
244 """Find a node given its name
247 name: Node name to look for
249 Node object if found, else None
251 for subnode in self.subnodes:
252 if subnode.name == name:
257 """Returns the offset of a node, after checking the cache
259 This should be used instead of self._offset directly, to ensure that
260 the cache does not contain invalid offsets.
262 self._fdt.CheckCache()
266 """Scan a node's properties and subnodes
268 This fills in the props and subnodes properties, recursively
269 searching into subnodes so that the entire tree is built.
271 fdt_obj = self._fdt._fdt_obj
272 self.props = self._fdt.GetProps(self)
273 phandle = fdt_obj.get_phandle(self.Offset())
275 self._fdt.phandle_to_node[phandle] = self
277 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
279 sep = '' if self.path[-1] == '/' else '/'
280 name = fdt_obj.get_name(offset)
281 path = self.path + sep + name
282 node = Node(self._fdt, self, offset, name, path)
283 self.subnodes.append(node)
286 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
288 def Refresh(self, my_offset):
289 """Fix up the _offset for each node, recursively
291 Note: This does not take account of property offsets - these will not
294 fdt_obj = self._fdt._fdt_obj
295 if self._offset != my_offset:
296 self._offset = my_offset
297 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
298 for subnode in self.subnodes:
299 if subnode.name != fdt_obj.get_name(offset):
300 raise ValueError('Internal error, node name mismatch %s != %s' %
301 (subnode.name, fdt_obj.get_name(offset)))
302 subnode.Refresh(offset)
303 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
304 if offset != -libfdt.FDT_ERR_NOTFOUND:
305 raise ValueError('Internal error, offset == %d' % offset)
307 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
309 p = fdt_obj.get_property_by_offset(poffset)
310 prop = self.props.get(p.name)
312 raise ValueError("Internal error, property '%s' missing, "
313 'offset %d' % (p.name, poffset))
314 prop.RefreshOffset(poffset)
315 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
317 def DeleteProp(self, prop_name):
318 """Delete a property of a node
320 The property is deleted and the offset cache is invalidated.
323 prop_name: Name of the property to delete
325 ValueError if the property does not exist
327 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
328 "Node '%s': delete property: '%s'" % (self.path, prop_name))
329 del self.props[prop_name]
330 self._fdt.Invalidate()
332 def AddZeroProp(self, prop_name):
333 """Add a new property to the device tree with an integer value of 0.
336 prop_name: Name of property
338 self.props[prop_name] = Prop(self, None, prop_name,
339 tools.GetBytes(0, 4))
341 def AddEmptyProp(self, prop_name, len):
342 """Add a property with a fixed data size, for filling in later
344 The device tree is marked dirty so that the value will be written to
345 the blob on the next sync.
348 prop_name: Name of property
349 len: Length of data in property
351 value = tools.GetBytes(0, len)
352 self.props[prop_name] = Prop(self, None, prop_name, value)
354 def SetInt(self, prop_name, val):
355 """Update an integer property int the device tree.
357 This is not allowed to change the size of the FDT.
359 The device tree is marked dirty so that the value will be written to
360 the blob on the next sync.
363 prop_name: Name of property
366 self.props[prop_name].SetInt(val)
368 def SetData(self, prop_name, val):
369 """Set the data value of a property
371 The device tree is marked dirty so that the value will be written to
372 the blob on the next sync.
375 prop_name: Name of property to set
376 val: Data value to set
378 self.props[prop_name].SetData(val)
380 def SetString(self, prop_name, val):
381 """Set the string value of a property
383 The device tree is marked dirty so that the value will be written to
384 the blob on the next sync.
387 prop_name: Name of property to set
388 val: String value to set (will be \0-terminated in DT)
390 self.props[prop_name].SetData(val + chr(0))
392 def AddString(self, prop_name, val):
393 """Add a new string property to a node
395 The device tree is marked dirty so that the value will be written to
396 the blob on the next sync.
399 prop_name: Name of property to add
400 val: String value of property
402 self.props[prop_name] = Prop(self, None, prop_name, val + chr(0))
404 def AddSubnode(self, name):
405 """Add a new subnode to the node
408 name: name of node to add
411 New subnode that was created
413 path = self.path + '/' + name
414 subnode = Node(self._fdt, self, None, name, path)
415 self.subnodes.append(subnode)
418 def Sync(self, auto_resize=False):
419 """Sync node changes back to the device tree
421 This updates the device tree blob with any changes to this node and its
422 subnodes since the last sync.
425 auto_resize: Resize the device tree automatically if it does not
426 have enough space for the update
429 FdtException if auto_resize is False and there is not enough space
431 if self._offset is None:
432 # The subnode doesn't exist yet, so add it
433 fdt_obj = self._fdt._fdt_obj
436 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
438 if offset != -libfdt.NOSPACE:
440 fdt_obj.resize(fdt_obj.totalsize() + 1024)
442 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
443 self._offset = offset
445 # Sync subnodes in reverse so that we don't disturb node offsets for
446 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
448 for node in reversed(self.subnodes):
449 node.Sync(auto_resize)
451 # Sync properties now, whose offsets should not have been disturbed.
452 # We do this after subnodes, since this disturbs the offsets of these
454 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
456 for prop in prop_list:
457 prop.Sync(auto_resize)
461 """Provides simple access to a flat device tree blob using libfdts.
464 fname: Filename of fdt
465 _root: Root of device tree (a Node object)
467 def __init__(self, fname):
469 self._cached_offsets = False
470 self.phandle_to_node = {}
472 self._fname = fdt_util.EnsureCompiled(self._fname)
474 with open(self._fname, 'rb') as fd:
475 self._fdt_obj = libfdt.Fdt(fd.read())
479 """Create a new Fdt object from the given data
482 data: Device-tree data blob
485 Fdt object containing the data
488 fdt._fdt_obj = libfdt.Fdt(bytearray(data))
491 def LookupPhandle(self, phandle):
495 phandle: Phandle to look up (int)
498 Node object the phandle points to
500 return self.phandle_to_node.get(phandle)
502 def Scan(self, root='/'):
503 """Scan a device tree, building up a tree of Node objects
505 This fills in the self._root property
510 TODO(sjg@chromium.org): Implement the 'root' parameter
512 self._cached_offsets = True
513 self._root = self.Node(self, None, 0, '/', '/')
517 """Get the root Node of the device tree
524 def GetNode(self, path):
525 """Look up a node from its path
528 path: Path to look up, e.g. '/microcode/update@0'
530 Node object, or None if not found
533 parts = path.split('/')
536 for part in parts[1:]:
537 node = node.FindNode(part)
543 """Flush device tree changes back to the file
545 If the device tree has changed in memory, write it back to the file.
547 with open(self._fname, 'wb') as fd:
548 fd.write(self._fdt_obj.as_bytearray())
550 def Sync(self, auto_resize=False):
551 """Make sure any DT changes are written to the blob
554 auto_resize: Resize the device tree automatically if it does not
555 have enough space for the update
558 FdtException if auto_resize is False and there is not enough space
560 self._root.Sync(auto_resize)
564 """Pack the device tree down to its minimum size
566 When nodes and properties shrink or are deleted, wasted space can
567 build up in the device tree binary.
569 CheckErr(self._fdt_obj.pack(), 'pack')
572 def GetContents(self):
573 """Get the contents of the FDT
576 The FDT contents as a string of bytes
578 return self._fdt_obj.as_bytearray()
581 """Get the contents of the FDT
584 The FDT contents as a libfdt.Fdt object
588 def GetProps(self, node):
589 """Get all properties from a node.
592 node: Full path to node name to look in.
595 A dictionary containing all the properties, indexed by node name.
596 The entries are Prop objects.
599 ValueError: if the node does not exist.
602 poffset = self._fdt_obj.first_property_offset(node._offset,
605 p = self._fdt_obj.get_property_by_offset(poffset)
606 prop = Prop(node, poffset, p.name, p)
607 props_dict[prop.name] = prop
609 poffset = self._fdt_obj.next_property_offset(poffset,
613 def Invalidate(self):
614 """Mark our offset cache as invalid"""
615 self._cached_offsets = False
617 def CheckCache(self):
618 """Refresh the offset cache if needed"""
619 if self._cached_offsets:
622 self._cached_offsets = True
625 """Refresh the offset cache"""
626 self._root.Refresh(0)
628 def GetStructOffset(self, offset):
629 """Get the file offset of a given struct offset
632 offset: Offset within the 'struct' region of the device tree
634 Position of @offset within the device tree binary
636 return self._fdt_obj.off_dt_struct() + offset
639 def Node(self, fdt, parent, offset, name, path):
642 This is used by Fdt.Scan() to create a new node using the correct
647 parent: Parent node, or None if this is the root node
648 offset: Offset of node
650 path: Full path to node
652 node = Node(fdt, parent, offset, name, path)
656 """Returns a new Fdt object"""