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(bytes):
34 """Converts a string of bytes into a type and value
37 A string containing bytes
42 Data, either a single element or a list of elements. Each element
44 TYPE_STRING: string value from the property
45 TYPE_INT: a byte-swapped integer stored as a 4-byte string
46 TYPE_BYTE: a byte stored as a single-byte string
50 strings = bytes.split('\0')
52 count = len(strings) - 1
53 if count > 0 and not strings[-1]:
54 for string in strings[:-1]:
59 if ch < ' ' or ch > '~':
66 return TYPE_STRING, strings[0]
68 return TYPE_STRING, strings[:-1]
71 return TYPE_BYTE, bytes[0]
73 return TYPE_BYTE, list(bytes)
75 for i in range(0, size, 4):
76 val.append(bytes[i:i + 4])
78 return TYPE_INT, val[0]
84 """A device tree property
87 name: Property name (as per the device tree)
88 value: Property value as a string of bytes, or a list of strings of
92 def __init__(self, node, offset, name, bytes):
97 self.bytes = str(bytes)
100 self.type = TYPE_BOOL
103 self.type, self.value = BytesToValue(bytes)
105 def RefreshOffset(self, poffset):
106 self._offset = poffset
108 def Widen(self, newprop):
109 """Figure out which property type is more general
111 Given a current property and a new property, this function returns the
112 one that is less specific as to type. The less specific property will
113 be ble to represent the data in the more specific property. This is
114 used for things like:
125 He we want to use an int array for 'value'. The first property
126 suggests that a single int is enough, but the second one shows that
127 it is not. Calling this function with these two propertes would
128 update the current property to be like the second, since it is less
131 if newprop.type < self.type:
132 self.type = newprop.type
134 if type(newprop.value) == list and type(self.value) != list:
135 self.value = [self.value]
137 if type(self.value) == list and len(newprop.value) > len(self.value):
138 val = self.GetEmpty(self.type)
139 while len(self.value) < len(newprop.value):
140 self.value.append(val)
143 def GetEmpty(self, type):
144 """Get an empty / zero value of the given type
147 A single value of the given type
149 if type == TYPE_BYTE:
151 elif type == TYPE_INT:
152 return struct.pack('>I', 0);
153 elif type == TYPE_STRING:
159 """Get the offset of a property
162 The offset of the property (struct fdt_property) within the file
164 self._node._fdt.CheckCache()
165 return self._node._fdt.GetStructOffset(self._offset)
167 def SetInt(self, val):
168 """Set the integer value of the property
170 The device tree is marked dirty so that the value will be written to
171 the block on the next sync.
174 val: Integer value (32-bit, single cell)
176 self.bytes = struct.pack('>I', val);
177 self.value = self.bytes
181 def SetData(self, bytes):
182 """Set the value of a property as bytes
185 bytes: New property value to set
187 self.bytes = str(bytes)
188 self.type, self.value = BytesToValue(bytes)
191 def Sync(self, auto_resize=False):
192 """Sync property changes back to the device tree
194 This updates the device tree blob with any changes to this property
198 auto_resize: Resize the device tree automatically if it does not
199 have enough space for the update
202 FdtException if auto_resize is False and there is not enough space
204 if self._offset is None or self.dirty:
206 fdt_obj = node._fdt._fdt_obj
208 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
209 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
210 fdt_obj.resize(fdt_obj.totalsize() + 1024)
211 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
213 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
217 """A device tree node
220 offset: Integer offset in the device tree
221 name: Device tree node tname
222 path: Full path to node, along with the node name itself
223 _fdt: Device tree object
224 subnodes: A list of subnodes for this node, each a Node object
225 props: A dict of properties for this node, each a Prop object.
226 Keyed by property name
228 def __init__(self, fdt, parent, offset, name, path):
231 self._offset = offset
238 """Get the Fdt object for this node
245 def FindNode(self, name):
246 """Find a node given its name
249 name: Node name to look for
251 Node object if found, else None
253 for subnode in self.subnodes:
254 if subnode.name == name:
259 """Returns the offset of a node, after checking the cache
261 This should be used instead of self._offset directly, to ensure that
262 the cache does not contain invalid offsets.
264 self._fdt.CheckCache()
268 """Scan a node's properties and subnodes
270 This fills in the props and subnodes properties, recursively
271 searching into subnodes so that the entire tree is built.
273 fdt_obj = self._fdt._fdt_obj
274 self.props = self._fdt.GetProps(self)
275 phandle = fdt_obj.get_phandle(self.Offset())
277 self._fdt.phandle_to_node[phandle] = self
279 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
281 sep = '' if self.path[-1] == '/' else '/'
282 name = fdt_obj.get_name(offset)
283 path = self.path + sep + name
284 node = Node(self._fdt, self, offset, name, path)
285 self.subnodes.append(node)
288 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
290 def Refresh(self, my_offset):
291 """Fix up the _offset for each node, recursively
293 Note: This does not take account of property offsets - these will not
296 fdt_obj = self._fdt._fdt_obj
297 if self._offset != my_offset:
298 self._offset = my_offset
299 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
300 for subnode in self.subnodes:
301 if subnode.name != fdt_obj.get_name(offset):
302 raise ValueError('Internal error, node name mismatch %s != %s' %
303 (subnode.name, fdt_obj.get_name(offset)))
304 subnode.Refresh(offset)
305 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
306 if offset != -libfdt.FDT_ERR_NOTFOUND:
307 raise ValueError('Internal error, offset == %d' % offset)
309 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
311 p = fdt_obj.get_property_by_offset(poffset)
312 prop = self.props.get(p.name)
314 raise ValueError("Internal error, property '%s' missing, "
315 'offset %d' % (p.name, poffset))
316 prop.RefreshOffset(poffset)
317 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
319 def DeleteProp(self, prop_name):
320 """Delete a property of a node
322 The property is deleted and the offset cache is invalidated.
325 prop_name: Name of the property to delete
327 ValueError if the property does not exist
329 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
330 "Node '%s': delete property: '%s'" % (self.path, prop_name))
331 del self.props[prop_name]
332 self._fdt.Invalidate()
334 def AddZeroProp(self, prop_name):
335 """Add a new property to the device tree with an integer value of 0.
338 prop_name: Name of property
340 self.props[prop_name] = Prop(self, None, prop_name,
341 tools.GetBytes(0, 4))
343 def AddEmptyProp(self, prop_name, len):
344 """Add a property with a fixed data size, for filling in later
346 The device tree is marked dirty so that the value will be written to
347 the blob on the next sync.
350 prop_name: Name of property
351 len: Length of data in property
353 value = tools.GetBytes(0, len)
354 self.props[prop_name] = Prop(self, None, prop_name, value)
356 def SetInt(self, prop_name, val):
357 """Update an integer property int the device tree.
359 This is not allowed to change the size of the FDT.
361 The device tree is marked dirty so that the value will be written to
362 the blob on the next sync.
365 prop_name: Name of property
368 self.props[prop_name].SetInt(val)
370 def SetData(self, prop_name, val):
371 """Set the data value of a property
373 The device tree is marked dirty so that the value will be written to
374 the blob on the next sync.
377 prop_name: Name of property to set
378 val: Data value to set
380 self.props[prop_name].SetData(val)
382 def SetString(self, prop_name, val):
383 """Set the string value of a property
385 The device tree is marked dirty so that the value will be written to
386 the blob on the next sync.
389 prop_name: Name of property to set
390 val: String value to set (will be \0-terminated in DT)
392 self.props[prop_name].SetData(val + chr(0))
394 def AddString(self, prop_name, val):
395 """Add a new string property to a node
397 The device tree is marked dirty so that the value will be written to
398 the blob on the next sync.
401 prop_name: Name of property to add
402 val: String value of property
404 self.props[prop_name] = Prop(self, None, prop_name, val + chr(0))
406 def AddSubnode(self, name):
407 """Add a new subnode to the node
410 name: name of node to add
413 New subnode that was created
415 path = self.path + '/' + name
416 subnode = Node(self._fdt, self, None, name, path)
417 self.subnodes.append(subnode)
420 def Sync(self, auto_resize=False):
421 """Sync node changes back to the device tree
423 This updates the device tree blob with any changes to this node and its
424 subnodes since the last sync.
427 auto_resize: Resize the device tree automatically if it does not
428 have enough space for the update
431 FdtException if auto_resize is False and there is not enough space
433 if self._offset is None:
434 # The subnode doesn't exist yet, so add it
435 fdt_obj = self._fdt._fdt_obj
438 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
440 if offset != -libfdt.NOSPACE:
442 fdt_obj.resize(fdt_obj.totalsize() + 1024)
444 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
445 self._offset = offset
447 # Sync subnodes in reverse so that we don't disturb node offsets for
448 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
450 for node in reversed(self.subnodes):
451 node.Sync(auto_resize)
453 # Sync properties now, whose offsets should not have been disturbed.
454 # We do this after subnodes, since this disturbs the offsets of these
456 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
458 for prop in prop_list:
459 prop.Sync(auto_resize)
463 """Provides simple access to a flat device tree blob using libfdts.
466 fname: Filename of fdt
467 _root: Root of device tree (a Node object)
469 def __init__(self, fname):
471 self._cached_offsets = False
472 self.phandle_to_node = {}
474 self._fname = fdt_util.EnsureCompiled(self._fname)
476 with open(self._fname, 'rb') as fd:
477 self._fdt_obj = libfdt.Fdt(fd.read())
481 """Create a new Fdt object from the given data
484 data: Device-tree data blob
487 Fdt object containing the data
490 fdt._fdt_obj = libfdt.Fdt(bytearray(data))
493 def LookupPhandle(self, phandle):
497 phandle: Phandle to look up (int)
500 Node object the phandle points to
502 return self.phandle_to_node.get(phandle)
504 def Scan(self, root='/'):
505 """Scan a device tree, building up a tree of Node objects
507 This fills in the self._root property
512 TODO(sjg@chromium.org): Implement the 'root' parameter
514 self._cached_offsets = True
515 self._root = self.Node(self, None, 0, '/', '/')
519 """Get the root Node of the device tree
526 def GetNode(self, path):
527 """Look up a node from its path
530 path: Path to look up, e.g. '/microcode/update@0'
532 Node object, or None if not found
535 parts = path.split('/')
538 for part in parts[1:]:
539 node = node.FindNode(part)
545 """Flush device tree changes back to the file
547 If the device tree has changed in memory, write it back to the file.
549 with open(self._fname, 'wb') as fd:
550 fd.write(self._fdt_obj.as_bytearray())
552 def Sync(self, auto_resize=False):
553 """Make sure any DT changes are written to the blob
556 auto_resize: Resize the device tree automatically if it does not
557 have enough space for the update
560 FdtException if auto_resize is False and there is not enough space
562 self._root.Sync(auto_resize)
566 """Pack the device tree down to its minimum size
568 When nodes and properties shrink or are deleted, wasted space can
569 build up in the device tree binary.
571 CheckErr(self._fdt_obj.pack(), 'pack')
574 def GetContents(self):
575 """Get the contents of the FDT
578 The FDT contents as a string of bytes
580 return self._fdt_obj.as_bytearray()
583 """Get the contents of the FDT
586 The FDT contents as a libfdt.Fdt object
590 def GetProps(self, node):
591 """Get all properties from a node.
594 node: Full path to node name to look in.
597 A dictionary containing all the properties, indexed by node name.
598 The entries are Prop objects.
601 ValueError: if the node does not exist.
604 poffset = self._fdt_obj.first_property_offset(node._offset,
607 p = self._fdt_obj.get_property_by_offset(poffset)
608 prop = Prop(node, poffset, p.name, p)
609 props_dict[prop.name] = prop
611 poffset = self._fdt_obj.next_property_offset(poffset,
615 def Invalidate(self):
616 """Mark our offset cache as invalid"""
617 self._cached_offsets = False
619 def CheckCache(self):
620 """Refresh the offset cache if needed"""
621 if self._cached_offsets:
624 self._cached_offsets = True
627 """Refresh the offset cache"""
628 self._root.Refresh(0)
630 def GetStructOffset(self, offset):
631 """Get the file offset of a given struct offset
634 offset: Offset within the 'struct' region of the device tree
636 Position of @offset within the device tree binary
638 return self._fdt_obj.off_dt_struct() + offset
641 def Node(self, fdt, parent, offset, name, path):
644 This is used by Fdt.Scan() to create a new node using the correct
649 parent: Parent node, or None if this is the root node
650 offset: Offset of node
652 path: Full path to node
654 node = Node(fdt, parent, offset, name, path)
658 """Returns a new Fdt object"""