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 SetInt(self, prop_name, val):
366 """Update an integer property int the device tree.
368 This is not allowed to change the size of the FDT.
370 The device tree is marked dirty so that the value will be written to
371 the blob on the next sync.
374 prop_name: Name of property
377 self.props[prop_name].SetInt(val)
379 def SetData(self, prop_name, val):
380 """Set the data value of a property
382 The device tree is marked dirty so that the value will be written to
383 the blob on the next sync.
386 prop_name: Name of property to set
387 val: Data value to set
389 self.props[prop_name].SetData(val)
391 def SetString(self, prop_name, val):
392 """Set the string value of a property
394 The device tree is marked dirty so that the value will be written to
395 the blob on the next sync.
398 prop_name: Name of property to set
399 val: String value to set (will be \0-terminated in DT)
401 if sys.version_info[0] >= 3: # pragma: no cover
402 val = bytes(val, 'utf-8')
403 self.props[prop_name].SetData(val + b'\0')
405 def AddString(self, prop_name, val):
406 """Add a new string property to a node
408 The device tree is marked dirty so that the value will be written to
409 the blob on the next sync.
412 prop_name: Name of property to add
413 val: String value of property
415 if sys.version_info[0] >= 3: # pragma: no cover
416 val = bytes(val, 'utf-8')
417 self.props[prop_name] = Prop(self, None, prop_name, val + b'\0')
419 def AddSubnode(self, name):
420 """Add a new subnode to the node
423 name: name of node to add
426 New subnode that was created
428 path = self.path + '/' + name
429 subnode = Node(self._fdt, self, None, name, path)
430 self.subnodes.append(subnode)
433 def Sync(self, auto_resize=False):
434 """Sync node changes back to the device tree
436 This updates the device tree blob with any changes to this node and its
437 subnodes since the last sync.
440 auto_resize: Resize the device tree automatically if it does not
441 have enough space for the update
444 FdtException if auto_resize is False and there is not enough space
446 if self._offset is None:
447 # The subnode doesn't exist yet, so add it
448 fdt_obj = self._fdt._fdt_obj
451 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
453 if offset != -libfdt.NOSPACE:
455 fdt_obj.resize(fdt_obj.totalsize() + 1024)
457 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
458 self._offset = offset
460 # Sync subnodes in reverse so that we don't disturb node offsets for
461 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
463 for node in reversed(self.subnodes):
464 node.Sync(auto_resize)
466 # Sync properties now, whose offsets should not have been disturbed.
467 # We do this after subnodes, since this disturbs the offsets of these
469 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
471 for prop in prop_list:
472 prop.Sync(auto_resize)
476 """Provides simple access to a flat device tree blob using libfdts.
479 fname: Filename of fdt
480 _root: Root of device tree (a Node object)
482 def __init__(self, fname):
484 self._cached_offsets = False
485 self.phandle_to_node = {}
487 self._fname = fdt_util.EnsureCompiled(self._fname)
489 with open(self._fname, 'rb') as fd:
490 self._fdt_obj = libfdt.Fdt(fd.read())
494 """Create a new Fdt object from the given data
497 data: Device-tree data blob
500 Fdt object containing the data
503 fdt._fdt_obj = libfdt.Fdt(bytes(data))
506 def LookupPhandle(self, phandle):
510 phandle: Phandle to look up (int)
513 Node object the phandle points to
515 return self.phandle_to_node.get(phandle)
517 def Scan(self, root='/'):
518 """Scan a device tree, building up a tree of Node objects
520 This fills in the self._root property
525 TODO(sjg@chromium.org): Implement the 'root' parameter
527 self._cached_offsets = True
528 self._root = self.Node(self, None, 0, '/', '/')
532 """Get the root Node of the device tree
539 def GetNode(self, path):
540 """Look up a node from its path
543 path: Path to look up, e.g. '/microcode/update@0'
545 Node object, or None if not found
548 parts = path.split('/')
551 for part in parts[1:]:
552 node = node.FindNode(part)
558 """Flush device tree changes back to the file
560 If the device tree has changed in memory, write it back to the file.
562 with open(self._fname, 'wb') as fd:
563 fd.write(self._fdt_obj.as_bytearray())
565 def Sync(self, auto_resize=False):
566 """Make sure any DT changes are written to the blob
569 auto_resize: Resize the device tree automatically if it does not
570 have enough space for the update
573 FdtException if auto_resize is False and there is not enough space
575 self._root.Sync(auto_resize)
579 """Pack the device tree down to its minimum size
581 When nodes and properties shrink or are deleted, wasted space can
582 build up in the device tree binary.
584 CheckErr(self._fdt_obj.pack(), 'pack')
587 def GetContents(self):
588 """Get the contents of the FDT
591 The FDT contents as a string of bytes
593 return bytes(self._fdt_obj.as_bytearray())
596 """Get the contents of the FDT
599 The FDT contents as a libfdt.Fdt object
603 def GetProps(self, node):
604 """Get all properties from a node.
607 node: Full path to node name to look in.
610 A dictionary containing all the properties, indexed by node name.
611 The entries are Prop objects.
614 ValueError: if the node does not exist.
617 poffset = self._fdt_obj.first_property_offset(node._offset,
620 p = self._fdt_obj.get_property_by_offset(poffset)
621 prop = Prop(node, poffset, p.name, p)
622 props_dict[prop.name] = prop
624 poffset = self._fdt_obj.next_property_offset(poffset,
628 def Invalidate(self):
629 """Mark our offset cache as invalid"""
630 self._cached_offsets = False
632 def CheckCache(self):
633 """Refresh the offset cache if needed"""
634 if self._cached_offsets:
637 self._cached_offsets = True
640 """Refresh the offset cache"""
641 self._root.Refresh(0)
643 def GetStructOffset(self, offset):
644 """Get the file offset of a given struct offset
647 offset: Offset within the 'struct' region of the device tree
649 Position of @offset within the device tree binary
651 return self._fdt_obj.off_dt_struct() + offset
654 def Node(self, fdt, parent, offset, name, path):
657 This is used by Fdt.Scan() to create a new node using the correct
662 parent: Parent node, or None if this is the root node
663 offset: Offset of node
665 path: Full path to node
667 node = Node(fdt, parent, offset, name, path)
671 """Returns a new Fdt object"""