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 if ch < 32 or ch > 127:
66 return TYPE_STRING, strings[0].decode()
68 return TYPE_STRING, [s.decode() for s in strings[:-1]]
71 return TYPE_BYTE, tools.ToChar(data[0])
73 return TYPE_BYTE, [tools.ToChar(ch) for ch in list(data)]
75 for i in range(0, size, 4):
76 val.append(data[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, data):
97 self.bytes = bytes(data)
100 self.type = TYPE_BOOL
103 self.type, self.value = BytesToValue(bytes(data))
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
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 _CheckProp(self, prop_name):
357 """Check if a property is present
360 prop_name: Name of property
366 ValueError if the property is missing
368 if prop_name not in self.props:
369 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
370 (self._fdt._fname, self.path, prop_name))
373 def SetInt(self, prop_name, val):
374 """Update an integer property int the device tree.
376 This is not allowed to change the size of the FDT.
378 The device tree is marked dirty so that the value will be written to
379 the blob on the next sync.
382 prop_name: Name of property
385 self._CheckProp(prop_name).props[prop_name].SetInt(val)
387 def SetData(self, prop_name, val):
388 """Set the data value of a property
390 The device tree is marked dirty so that the value will be written to
391 the blob on the next sync.
394 prop_name: Name of property to set
395 val: Data value to set
397 self._CheckProp(prop_name).props[prop_name].SetData(val)
399 def SetString(self, prop_name, val):
400 """Set the string value of a property
402 The device tree is marked dirty so that the value will be written to
403 the blob on the next sync.
406 prop_name: Name of property to set
407 val: String value to set (will be \0-terminated in DT)
410 val = val.encode('utf-8')
411 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
413 def AddString(self, prop_name, val):
414 """Add a new string property to a node
416 The device tree is marked dirty so that the value will be written to
417 the blob on the next sync.
420 prop_name: Name of property to add
421 val: String value of property
423 if sys.version_info[0] >= 3: # pragma: no cover
424 val = bytes(val, 'utf-8')
425 self.props[prop_name] = Prop(self, None, prop_name, val + b'\0')
427 def AddSubnode(self, name):
428 """Add a new subnode to the node
431 name: name of node to add
434 New subnode that was created
436 path = self.path + '/' + name
437 subnode = Node(self._fdt, self, None, name, path)
438 self.subnodes.append(subnode)
441 def Sync(self, auto_resize=False):
442 """Sync node changes back to the device tree
444 This updates the device tree blob with any changes to this node and its
445 subnodes since the last sync.
448 auto_resize: Resize the device tree automatically if it does not
449 have enough space for the update
452 FdtException if auto_resize is False and there is not enough space
454 if self._offset is None:
455 # The subnode doesn't exist yet, so add it
456 fdt_obj = self._fdt._fdt_obj
459 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
461 if offset != -libfdt.NOSPACE:
463 fdt_obj.resize(fdt_obj.totalsize() + 1024)
465 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
466 self._offset = offset
468 # Sync subnodes in reverse so that we don't disturb node offsets for
469 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
471 for node in reversed(self.subnodes):
472 node.Sync(auto_resize)
474 # Sync properties now, whose offsets should not have been disturbed.
475 # We do this after subnodes, since this disturbs the offsets of these
476 # properties. Note that new properties will have an offset of None here,
477 # which Python 3 cannot sort against int. So use a large value instead
478 # to ensure that the new properties are added first.
479 prop_list = sorted(self.props.values(),
480 key=lambda prop: prop._offset or 1 << 31,
482 for prop in prop_list:
483 prop.Sync(auto_resize)
487 """Provides simple access to a flat device tree blob using libfdts.
490 fname: Filename of fdt
491 _root: Root of device tree (a Node object)
492 name: Helpful name for this Fdt for the user (useful when creating the
493 DT from data rather than a file)
495 def __init__(self, fname):
497 self._cached_offsets = False
498 self.phandle_to_node = {}
501 self.name = self._fname
502 self._fname = fdt_util.EnsureCompiled(self._fname)
504 with open(self._fname, 'rb') as fd:
505 self._fdt_obj = libfdt.Fdt(fd.read())
508 def FromData(data, name=''):
509 """Create a new Fdt object from the given data
512 data: Device-tree data blob
513 name: Helpful name for this Fdt for the user
516 Fdt object containing the data
519 fdt._fdt_obj = libfdt.Fdt(bytes(data))
523 def LookupPhandle(self, phandle):
527 phandle: Phandle to look up (int)
530 Node object the phandle points to
532 return self.phandle_to_node.get(phandle)
534 def Scan(self, root='/'):
535 """Scan a device tree, building up a tree of Node objects
537 This fills in the self._root property
542 TODO(sjg@chromium.org): Implement the 'root' parameter
544 self._cached_offsets = True
545 self._root = self.Node(self, None, 0, '/', '/')
549 """Get the root Node of the device tree
556 def GetNode(self, path):
557 """Look up a node from its path
560 path: Path to look up, e.g. '/microcode/update@0'
562 Node object, or None if not found
565 parts = path.split('/')
568 if len(parts) == 2 and parts[1] == '':
570 for part in parts[1:]:
571 node = node.FindNode(part)
577 """Flush device tree changes back to the file
579 If the device tree has changed in memory, write it back to the file.
581 with open(self._fname, 'wb') as fd:
582 fd.write(self._fdt_obj.as_bytearray())
584 def Sync(self, auto_resize=False):
585 """Make sure any DT changes are written to the blob
588 auto_resize: Resize the device tree automatically if it does not
589 have enough space for the update
592 FdtException if auto_resize is False and there is not enough space
594 self._root.Sync(auto_resize)
598 """Pack the device tree down to its minimum size
600 When nodes and properties shrink or are deleted, wasted space can
601 build up in the device tree binary.
603 CheckErr(self._fdt_obj.pack(), 'pack')
606 def GetContents(self):
607 """Get the contents of the FDT
610 The FDT contents as a string of bytes
612 return bytes(self._fdt_obj.as_bytearray())
615 """Get the contents of the FDT
618 The FDT contents as a libfdt.Fdt object
622 def GetProps(self, node):
623 """Get all properties from a node.
626 node: Full path to node name to look in.
629 A dictionary containing all the properties, indexed by node name.
630 The entries are Prop objects.
633 ValueError: if the node does not exist.
636 poffset = self._fdt_obj.first_property_offset(node._offset,
639 p = self._fdt_obj.get_property_by_offset(poffset)
640 prop = Prop(node, poffset, p.name, p)
641 props_dict[prop.name] = prop
643 poffset = self._fdt_obj.next_property_offset(poffset,
647 def Invalidate(self):
648 """Mark our offset cache as invalid"""
649 self._cached_offsets = False
651 def CheckCache(self):
652 """Refresh the offset cache if needed"""
653 if self._cached_offsets:
656 self._cached_offsets = True
659 """Refresh the offset cache"""
660 self._root.Refresh(0)
662 def GetStructOffset(self, offset):
663 """Get the file offset of a given struct offset
666 offset: Offset within the 'struct' region of the device tree
668 Position of @offset within the device tree binary
670 return self._fdt_obj.off_dt_struct() + offset
673 def Node(self, fdt, parent, offset, name, path):
676 This is used by Fdt.Scan() to create a new node using the correct
681 parent: Parent node, or None if this is the root node
682 offset: Offset of node
684 path: Full path to node
686 node = Node(fdt, parent, offset, name, path)
689 def GetFilename(self):
690 """Get the filename of the device tree
698 """Returns a new Fdt object"""