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
15 # This deals with a device tree, presenting it as an assortment of Node and
16 # Prop objects, representing nodes and properties, respectively. This file
17 # contains the base classes and defines the high-level API. You can use
18 # FdtScan() as a convenience function to create and scan an Fdt.
20 # This implementation uses a libfdt Python library to access the device tree,
21 # so it is fairly efficient.
23 # A list of types we support
24 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
26 def CheckErr(errnum, msg):
28 raise ValueError('Error %d: %s: %s' %
29 (errnum, libfdt.fdt_strerror(errnum), msg))
32 """A device tree property
35 name: Property name (as per the device tree)
36 value: Property value as a string of bytes, or a list of strings of
40 def __init__(self, node, offset, name, bytes):
45 self.bytes = str(bytes)
51 self.type, self.value = self.BytesToValue(bytes)
53 def RefreshOffset(self, poffset):
54 self._offset = poffset
56 def Widen(self, newprop):
57 """Figure out which property type is more general
59 Given a current property and a new property, this function returns the
60 one that is less specific as to type. The less specific property will
61 be ble to represent the data in the more specific property. This is
73 He we want to use an int array for 'value'. The first property
74 suggests that a single int is enough, but the second one shows that
75 it is not. Calling this function with these two propertes would
76 update the current property to be like the second, since it is less
79 if newprop.type < self.type:
80 self.type = newprop.type
82 if type(newprop.value) == list and type(self.value) != list:
83 self.value = [self.value]
85 if type(self.value) == list and len(newprop.value) > len(self.value):
86 val = self.GetEmpty(self.type)
87 while len(self.value) < len(newprop.value):
88 self.value.append(val)
90 def BytesToValue(self, bytes):
91 """Converts a string of bytes into a type and value
94 A string containing bytes
99 Data, either a single element or a list of elements. Each element
101 TYPE_STRING: string value from the property
102 TYPE_INT: a byte-swapped integer stored as a 4-byte string
103 TYPE_BYTE: a byte stored as a single-byte string
107 strings = bytes.split('\0')
109 count = len(strings) - 1
110 if count > 0 and not strings[-1]:
111 for string in strings[:-1]:
116 if ch < ' ' or ch > '~':
123 return TYPE_STRING, strings[0]
125 return TYPE_STRING, strings[:-1]
128 return TYPE_BYTE, bytes[0]
130 return TYPE_BYTE, list(bytes)
132 for i in range(0, size, 4):
133 val.append(bytes[i:i + 4])
135 return TYPE_INT, val[0]
140 def GetEmpty(self, type):
141 """Get an empty / zero value of the given type
144 A single value of the given type
146 if type == TYPE_BYTE:
148 elif type == TYPE_INT:
149 return struct.pack('>I', 0);
150 elif type == TYPE_STRING:
156 """Get the offset of a property
159 The offset of the property (struct fdt_property) within the file
161 self._node._fdt.CheckCache()
162 return self._node._fdt.GetStructOffset(self._offset)
164 def SetInt(self, val):
165 """Set the integer value of the property
167 The device tree is marked dirty so that the value will be written to
168 the block on the next sync.
171 val: Integer value (32-bit, single cell)
173 self.bytes = struct.pack('>I', val);
178 def SetData(self, bytes):
179 """Set the value of a property as bytes
182 bytes: New property value to set
184 self.bytes = str(bytes)
185 self.type, self.value = self.BytesToValue(bytes)
188 def Sync(self, auto_resize=False):
189 """Sync property changes back to the device tree
191 This updates the device tree blob with any changes to this property
195 auto_resize: Resize the device tree automatically if it does not
196 have enough space for the update
199 FdtException if auto_resize is False and there is not enough space
201 if self._offset is None or self.dirty:
203 fdt_obj = node._fdt._fdt_obj
205 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
206 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
207 fdt_obj.resize(fdt_obj.totalsize() + 1024)
208 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
210 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
214 """A device tree node
217 offset: Integer offset in the device tree
218 name: Device tree node tname
219 path: Full path to node, along with the node name itself
220 _fdt: Device tree object
221 subnodes: A list of subnodes for this node, each a Node object
222 props: A dict of properties for this node, each a Prop object.
223 Keyed by property name
225 def __init__(self, fdt, parent, offset, name, path):
228 self._offset = offset
235 """Get the Fdt object for this node
242 def FindNode(self, name):
243 """Find a node given its name
246 name: Node name to look for
248 Node object if found, else None
250 for subnode in self.subnodes:
251 if subnode.name == name:
256 """Returns the offset of a node, after checking the cache
258 This should be used instead of self._offset directly, to ensure that
259 the cache does not contain invalid offsets.
261 self._fdt.CheckCache()
265 """Scan a node's properties and subnodes
267 This fills in the props and subnodes properties, recursively
268 searching into subnodes so that the entire tree is built.
270 fdt_obj = self._fdt._fdt_obj
271 self.props = self._fdt.GetProps(self)
272 phandle = fdt_obj.get_phandle(self.Offset())
274 self._fdt.phandle_to_node[phandle] = self
276 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
278 sep = '' if self.path[-1] == '/' else '/'
279 name = fdt_obj.get_name(offset)
280 path = self.path + sep + name
281 node = Node(self._fdt, self, offset, name, path)
282 self.subnodes.append(node)
285 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
287 def Refresh(self, my_offset):
288 """Fix up the _offset for each node, recursively
290 Note: This does not take account of property offsets - these will not
293 fdt_obj = self._fdt._fdt_obj
294 if self._offset != my_offset:
295 self._offset = my_offset
296 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
297 for subnode in self.subnodes:
298 if subnode.name != fdt_obj.get_name(offset):
299 raise ValueError('Internal error, node name mismatch %s != %s' %
300 (subnode.name, fdt_obj.get_name(offset)))
301 subnode.Refresh(offset)
302 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
303 if offset != -libfdt.FDT_ERR_NOTFOUND:
304 raise ValueError('Internal error, offset == %d' % offset)
306 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
308 p = fdt_obj.get_property_by_offset(poffset)
309 prop = self.props.get(p.name)
311 raise ValueError("Internal error, property '%s' missing, "
312 'offset %d' % (p.name, poffset))
313 prop.RefreshOffset(poffset)
314 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
316 def DeleteProp(self, prop_name):
317 """Delete a property of a node
319 The property is deleted and the offset cache is invalidated.
322 prop_name: Name of the property to delete
324 ValueError if the property does not exist
326 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
327 "Node '%s': delete property: '%s'" % (self.path, prop_name))
328 del self.props[prop_name]
329 self._fdt.Invalidate()
331 def AddZeroProp(self, prop_name):
332 """Add a new property to the device tree with an integer value of 0.
335 prop_name: Name of property
337 self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4)
339 def AddEmptyProp(self, prop_name, len):
340 """Add a property with a fixed data size, for filling in later
342 The device tree is marked dirty so that the value will be written to
343 the blob on the next sync.
346 prop_name: Name of property
347 len: Length of data in property
350 self.props[prop_name] = Prop(self, None, prop_name, value)
352 def SetInt(self, prop_name, val):
353 """Update an integer property int the device tree.
355 This is not allowed to change the size of the FDT.
357 The device tree is marked dirty so that the value will be written to
358 the blob on the next sync.
361 prop_name: Name of property
364 self.props[prop_name].SetInt(val)
366 def SetData(self, prop_name, val):
367 """Set the data value of a property
369 The device tree is marked dirty so that the value will be written to
370 the blob on the next sync.
373 prop_name: Name of property to set
374 val: Data value to set
376 self.props[prop_name].SetData(val)
378 def SetString(self, prop_name, val):
379 """Set the string value of a property
381 The device tree is marked dirty so that the value will be written to
382 the blob on the next sync.
385 prop_name: Name of property to set
386 val: String value to set (will be \0-terminated in DT)
388 self.props[prop_name].SetData(val + chr(0))
390 def AddString(self, prop_name, val):
391 """Add a new string property to a node
393 The device tree is marked dirty so that the value will be written to
394 the blob on the next sync.
397 prop_name: Name of property to add
398 val: String value of property
400 self.props[prop_name] = Prop(self, None, prop_name, val + chr(0))
402 def AddSubnode(self, name):
403 """Add a new subnode to the node
406 name: name of node to add
409 New subnode that was created
411 path = self.path + '/' + name
412 subnode = Node(self._fdt, self, None, name, path)
413 self.subnodes.append(subnode)
416 def Sync(self, auto_resize=False):
417 """Sync node changes back to the device tree
419 This updates the device tree blob with any changes to this node and its
420 subnodes since the last sync.
423 auto_resize: Resize the device tree automatically if it does not
424 have enough space for the update
427 FdtException if auto_resize is False and there is not enough space
429 if self._offset is None:
430 # The subnode doesn't exist yet, so add it
431 fdt_obj = self._fdt._fdt_obj
434 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
436 if offset != -libfdt.NOSPACE:
438 fdt_obj.resize(fdt_obj.totalsize() + 1024)
440 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
441 self._offset = offset
443 # Sync subnodes in reverse so that we don't disturb node offsets for
444 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
446 for node in reversed(self.subnodes):
447 node.Sync(auto_resize)
449 # Sync properties now, whose offsets should not have been disturbed.
450 # We do this after subnodes, since this disturbs the offsets of these
452 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
454 for prop in prop_list:
455 prop.Sync(auto_resize)
459 """Provides simple access to a flat device tree blob using libfdts.
462 fname: Filename of fdt
463 _root: Root of device tree (a Node object)
465 def __init__(self, fname):
467 self._cached_offsets = False
468 self.phandle_to_node = {}
470 self._fname = fdt_util.EnsureCompiled(self._fname)
472 with open(self._fname) as fd:
473 self._fdt_obj = libfdt.Fdt(fd.read())
477 """Create a new Fdt object from the given data
480 data: Device-tree data blob
483 Fdt object containing the data
486 fdt._fdt_obj = libfdt.Fdt(bytearray(data))
489 def LookupPhandle(self, phandle):
493 phandle: Phandle to look up (int)
496 Node object the phandle points to
498 return self.phandle_to_node.get(phandle)
500 def Scan(self, root='/'):
501 """Scan a device tree, building up a tree of Node objects
503 This fills in the self._root property
508 TODO(sjg@chromium.org): Implement the 'root' parameter
510 self._cached_offsets = True
511 self._root = self.Node(self, None, 0, '/', '/')
515 """Get the root Node of the device tree
522 def GetNode(self, path):
523 """Look up a node from its path
526 path: Path to look up, e.g. '/microcode/update@0'
528 Node object, or None if not found
531 parts = path.split('/')
534 for part in parts[1:]:
535 node = node.FindNode(part)
541 """Flush device tree changes back to the file
543 If the device tree has changed in memory, write it back to the file.
545 with open(self._fname, 'wb') as fd:
546 fd.write(self._fdt_obj.as_bytearray())
548 def Sync(self, auto_resize=False):
549 """Make sure any DT changes are written to the blob
552 auto_resize: Resize the device tree automatically if it does not
553 have enough space for the update
556 FdtException if auto_resize is False and there is not enough space
558 self._root.Sync(auto_resize)
562 """Pack the device tree down to its minimum size
564 When nodes and properties shrink or are deleted, wasted space can
565 build up in the device tree binary.
567 CheckErr(self._fdt_obj.pack(), 'pack')
570 def GetContents(self):
571 """Get the contents of the FDT
574 The FDT contents as a string of bytes
576 return self._fdt_obj.as_bytearray()
579 """Get the contents of the FDT
582 The FDT contents as a libfdt.Fdt object
586 def GetProps(self, node):
587 """Get all properties from a node.
590 node: Full path to node name to look in.
593 A dictionary containing all the properties, indexed by node name.
594 The entries are Prop objects.
597 ValueError: if the node does not exist.
600 poffset = self._fdt_obj.first_property_offset(node._offset,
603 p = self._fdt_obj.get_property_by_offset(poffset)
604 prop = Prop(node, poffset, p.name, p)
605 props_dict[prop.name] = prop
607 poffset = self._fdt_obj.next_property_offset(poffset,
611 def Invalidate(self):
612 """Mark our offset cache as invalid"""
613 self._cached_offsets = False
615 def CheckCache(self):
616 """Refresh the offset cache if needed"""
617 if self._cached_offsets:
620 self._cached_offsets = True
623 """Refresh the offset cache"""
624 self._root.Refresh(0)
626 def GetStructOffset(self, offset):
627 """Get the file offset of a given struct offset
630 offset: Offset within the 'struct' region of the device tree
632 Position of @offset within the device tree binary
634 return self._fdt_obj.off_dt_struct() + offset
637 def Node(self, fdt, parent, offset, name, path):
640 This is used by Fdt.Scan() to create a new node using the correct
645 parent: Parent node, or None if this is the root node
646 offset: Offset of node
648 path: Full path to node
650 node = Node(fdt, parent, offset, name, path)
654 """Returns a new Fdt object"""