2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2016 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
11 from dtoc import fdt_util
13 from libfdt import QUIET_NOTFOUND
14 from patman import tools
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 +
212 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
214 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
218 """A device tree node
221 offset: Integer offset in the device tree
222 name: Device tree node tname
223 path: Full path to node, along with the node name itself
224 _fdt: Device tree object
225 subnodes: A list of subnodes for this node, each a Node object
226 props: A dict of properties for this node, each a Prop object.
227 Keyed by property name
229 def __init__(self, fdt, parent, offset, name, path):
232 self._offset = offset
239 """Get the Fdt object for this node
246 def FindNode(self, name):
247 """Find a node given its name
250 name: Node name to look for
252 Node object if found, else None
254 for subnode in self.subnodes:
255 if subnode.name == name:
260 """Returns the offset of a node, after checking the cache
262 This should be used instead of self._offset directly, to ensure that
263 the cache does not contain invalid offsets.
265 self._fdt.CheckCache()
269 """Scan a node's properties and subnodes
271 This fills in the props and subnodes properties, recursively
272 searching into subnodes so that the entire tree is built.
274 fdt_obj = self._fdt._fdt_obj
275 self.props = self._fdt.GetProps(self)
276 phandle = fdt_obj.get_phandle(self.Offset())
278 self._fdt.phandle_to_node[phandle] = self
280 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
282 sep = '' if self.path[-1] == '/' else '/'
283 name = fdt_obj.get_name(offset)
284 path = self.path + sep + name
285 node = Node(self._fdt, self, offset, name, path)
286 self.subnodes.append(node)
289 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
291 def Refresh(self, my_offset):
292 """Fix up the _offset for each node, recursively
294 Note: This does not take account of property offsets - these will not
297 fdt_obj = self._fdt._fdt_obj
298 if self._offset != my_offset:
299 self._offset = my_offset
300 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
301 for subnode in self.subnodes:
302 if subnode.name != fdt_obj.get_name(offset):
303 raise ValueError('Internal error, node name mismatch %s != %s' %
304 (subnode.name, fdt_obj.get_name(offset)))
305 subnode.Refresh(offset)
306 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
307 if offset != -libfdt.FDT_ERR_NOTFOUND:
308 raise ValueError('Internal error, offset == %d' % offset)
310 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
312 p = fdt_obj.get_property_by_offset(poffset)
313 prop = self.props.get(p.name)
315 raise ValueError("Internal error, property '%s' missing, "
316 'offset %d' % (p.name, poffset))
317 prop.RefreshOffset(poffset)
318 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
320 def DeleteProp(self, prop_name):
321 """Delete a property of a node
323 The property is deleted and the offset cache is invalidated.
326 prop_name: Name of the property to delete
328 ValueError if the property does not exist
330 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
331 "Node '%s': delete property: '%s'" % (self.path, prop_name))
332 del self.props[prop_name]
333 self._fdt.Invalidate()
335 def AddZeroProp(self, prop_name):
336 """Add a new property to the device tree with an integer value of 0.
339 prop_name: Name of property
341 self.props[prop_name] = Prop(self, None, prop_name,
342 tools.GetBytes(0, 4))
344 def AddEmptyProp(self, prop_name, len):
345 """Add a property with a fixed data size, for filling in later
347 The device tree is marked dirty so that the value will be written to
348 the blob on the next sync.
351 prop_name: Name of property
352 len: Length of data in property
354 value = tools.GetBytes(0, len)
355 self.props[prop_name] = Prop(self, None, prop_name, value)
357 def _CheckProp(self, prop_name):
358 """Check if a property is present
361 prop_name: Name of property
367 ValueError if the property is missing
369 if prop_name not in self.props:
370 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
371 (self._fdt._fname, self.path, prop_name))
374 def SetInt(self, prop_name, val):
375 """Update an integer property int the device tree.
377 This is not allowed to change the size of the FDT.
379 The device tree is marked dirty so that the value will be written to
380 the blob on the next sync.
383 prop_name: Name of property
386 self._CheckProp(prop_name).props[prop_name].SetInt(val)
388 def SetData(self, prop_name, val):
389 """Set the data value of a property
391 The device tree is marked dirty so that the value will be written to
392 the blob on the next sync.
395 prop_name: Name of property to set
396 val: Data value to set
398 self._CheckProp(prop_name).props[prop_name].SetData(val)
400 def SetString(self, prop_name, val):
401 """Set the string value of a property
403 The device tree is marked dirty so that the value will be written to
404 the blob on the next sync.
407 prop_name: Name of property to set
408 val: String value to set (will be \0-terminated in DT)
411 val = val.encode('utf-8')
412 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
414 def AddData(self, prop_name, val):
415 """Add a new property to a node
417 The device tree is marked dirty so that the value will be written to
418 the blob on the next sync.
421 prop_name: Name of property to add
422 val: Bytes value of property
424 self.props[prop_name] = Prop(self, None, prop_name, val)
426 def AddString(self, prop_name, val):
427 """Add a new string property to a node
429 The device tree is marked dirty so that the value will be written to
430 the blob on the next sync.
433 prop_name: Name of property to add
434 val: String value of property
436 if sys.version_info[0] >= 3: # pragma: no cover
437 val = bytes(val, 'utf-8')
438 self.AddData(prop_name, val + b'\0')
440 def AddSubnode(self, name):
441 """Add a new subnode to the node
444 name: name of node to add
447 New subnode that was created
449 path = self.path + '/' + name
450 subnode = Node(self._fdt, self, None, name, path)
451 self.subnodes.append(subnode)
454 def Sync(self, auto_resize=False):
455 """Sync node changes back to the device tree
457 This updates the device tree blob with any changes to this node and its
458 subnodes since the last sync.
461 auto_resize: Resize the device tree automatically if it does not
462 have enough space for the update
465 FdtException if auto_resize is False and there is not enough space
467 if self._offset is None:
468 # The subnode doesn't exist yet, so add it
469 fdt_obj = self._fdt._fdt_obj
472 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
474 if offset != -libfdt.NOSPACE:
476 fdt_obj.resize(fdt_obj.totalsize() + 1024)
478 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
479 self._offset = offset
481 # Sync subnodes in reverse so that we don't disturb node offsets for
482 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
484 for node in reversed(self.subnodes):
485 node.Sync(auto_resize)
487 # Sync properties now, whose offsets should not have been disturbed.
488 # We do this after subnodes, since this disturbs the offsets of these
489 # properties. Note that new properties will have an offset of None here,
490 # which Python 3 cannot sort against int. So use a large value instead
491 # to ensure that the new properties are added first.
492 prop_list = sorted(self.props.values(),
493 key=lambda prop: prop._offset or 1 << 31,
495 for prop in prop_list:
496 prop.Sync(auto_resize)
500 """Provides simple access to a flat device tree blob using libfdts.
503 fname: Filename of fdt
504 _root: Root of device tree (a Node object)
505 name: Helpful name for this Fdt for the user (useful when creating the
506 DT from data rather than a file)
508 def __init__(self, fname):
510 self._cached_offsets = False
511 self.phandle_to_node = {}
514 self.name = self._fname
515 self._fname = fdt_util.EnsureCompiled(self._fname)
517 with open(self._fname, 'rb') as fd:
518 self._fdt_obj = libfdt.Fdt(fd.read())
521 def FromData(data, name=''):
522 """Create a new Fdt object from the given data
525 data: Device-tree data blob
526 name: Helpful name for this Fdt for the user
529 Fdt object containing the data
532 fdt._fdt_obj = libfdt.Fdt(bytes(data))
536 def LookupPhandle(self, phandle):
540 phandle: Phandle to look up (int)
543 Node object the phandle points to
545 return self.phandle_to_node.get(phandle)
547 def Scan(self, root='/'):
548 """Scan a device tree, building up a tree of Node objects
550 This fills in the self._root property
555 TODO(sjg@chromium.org): Implement the 'root' parameter
557 self._cached_offsets = True
558 self._root = self.Node(self, None, 0, '/', '/')
562 """Get the root Node of the device tree
569 def GetNode(self, path):
570 """Look up a node from its path
573 path: Path to look up, e.g. '/microcode/update@0'
575 Node object, or None if not found
578 parts = path.split('/')
581 if len(parts) == 2 and parts[1] == '':
583 for part in parts[1:]:
584 node = node.FindNode(part)
590 """Flush device tree changes back to the file
592 If the device tree has changed in memory, write it back to the file.
594 with open(self._fname, 'wb') as fd:
595 fd.write(self._fdt_obj.as_bytearray())
597 def Sync(self, auto_resize=False):
598 """Make sure any DT changes are written to the blob
601 auto_resize: Resize the device tree automatically if it does not
602 have enough space for the update
605 FdtException if auto_resize is False and there is not enough space
607 self._root.Sync(auto_resize)
611 """Pack the device tree down to its minimum size
613 When nodes and properties shrink or are deleted, wasted space can
614 build up in the device tree binary.
616 CheckErr(self._fdt_obj.pack(), 'pack')
619 def GetContents(self):
620 """Get the contents of the FDT
623 The FDT contents as a string of bytes
625 return bytes(self._fdt_obj.as_bytearray())
628 """Get the contents of the FDT
631 The FDT contents as a libfdt.Fdt object
635 def GetProps(self, node):
636 """Get all properties from a node.
639 node: Full path to node name to look in.
642 A dictionary containing all the properties, indexed by node name.
643 The entries are Prop objects.
646 ValueError: if the node does not exist.
649 poffset = self._fdt_obj.first_property_offset(node._offset,
652 p = self._fdt_obj.get_property_by_offset(poffset)
653 prop = Prop(node, poffset, p.name, p)
654 props_dict[prop.name] = prop
656 poffset = self._fdt_obj.next_property_offset(poffset,
660 def Invalidate(self):
661 """Mark our offset cache as invalid"""
662 self._cached_offsets = False
664 def CheckCache(self):
665 """Refresh the offset cache if needed"""
666 if self._cached_offsets:
669 self._cached_offsets = True
672 """Refresh the offset cache"""
673 self._root.Refresh(0)
675 def GetStructOffset(self, offset):
676 """Get the file offset of a given struct offset
679 offset: Offset within the 'struct' region of the device tree
681 Position of @offset within the device tree binary
683 return self._fdt_obj.off_dt_struct() + offset
686 def Node(self, fdt, parent, offset, name, path):
689 This is used by Fdt.Scan() to create a new node using the correct
694 parent: Parent node, or None if this is the root node
695 offset: Offset of node
697 path: Full path to node
699 node = Node(fdt, parent, offset, name, path)
702 def GetFilename(self):
703 """Get the filename of the device tree
711 """Returns a new Fdt object"""