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 # Special handling to convert an int into bytes
133 if self.type == TYPE_INT and newprop.type == TYPE_BYTE:
134 if type(self.value) == list:
136 for val in self.value:
137 new_value += [tools.ToChar(by) for by in val]
139 new_value = [tools.ToChar(by) for by in self.value]
140 self.value = new_value
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 +
221 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
223 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
227 """A device tree node
230 offset: Integer offset in the device tree
231 name: Device tree node tname
232 path: Full path to node, along with the node name itself
233 _fdt: Device tree object
234 subnodes: A list of subnodes for this node, each a Node object
235 props: A dict of properties for this node, each a Prop object.
236 Keyed by property name
238 def __init__(self, fdt, parent, offset, name, path):
241 self._offset = offset
248 """Get the Fdt object for this node
255 def FindNode(self, name):
256 """Find a node given its name
259 name: Node name to look for
261 Node object if found, else None
263 for subnode in self.subnodes:
264 if subnode.name == name:
269 """Returns the offset of a node, after checking the cache
271 This should be used instead of self._offset directly, to ensure that
272 the cache does not contain invalid offsets.
274 self._fdt.CheckCache()
278 """Scan a node's properties and subnodes
280 This fills in the props and subnodes properties, recursively
281 searching into subnodes so that the entire tree is built.
283 fdt_obj = self._fdt._fdt_obj
284 self.props = self._fdt.GetProps(self)
285 phandle = fdt_obj.get_phandle(self.Offset())
287 self._fdt.phandle_to_node[phandle] = self
289 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
291 sep = '' if self.path[-1] == '/' else '/'
292 name = fdt_obj.get_name(offset)
293 path = self.path + sep + name
294 node = Node(self._fdt, self, offset, name, path)
295 self.subnodes.append(node)
298 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
300 def Refresh(self, my_offset):
301 """Fix up the _offset for each node, recursively
303 Note: This does not take account of property offsets - these will not
306 fdt_obj = self._fdt._fdt_obj
307 if self._offset != my_offset:
308 self._offset = my_offset
309 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
310 for subnode in self.subnodes:
311 if subnode.name != fdt_obj.get_name(offset):
312 raise ValueError('Internal error, node name mismatch %s != %s' %
313 (subnode.name, fdt_obj.get_name(offset)))
314 subnode.Refresh(offset)
315 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
316 if offset != -libfdt.FDT_ERR_NOTFOUND:
317 raise ValueError('Internal error, offset == %d' % offset)
319 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
321 p = fdt_obj.get_property_by_offset(poffset)
322 prop = self.props.get(p.name)
324 raise ValueError("Internal error, property '%s' missing, "
325 'offset %d' % (p.name, poffset))
326 prop.RefreshOffset(poffset)
327 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
329 def DeleteProp(self, prop_name):
330 """Delete a property of a node
332 The property is deleted and the offset cache is invalidated.
335 prop_name: Name of the property to delete
337 ValueError if the property does not exist
339 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
340 "Node '%s': delete property: '%s'" % (self.path, prop_name))
341 del self.props[prop_name]
342 self._fdt.Invalidate()
344 def AddZeroProp(self, prop_name):
345 """Add a new property to the device tree with an integer value of 0.
348 prop_name: Name of property
350 self.props[prop_name] = Prop(self, None, prop_name,
351 tools.GetBytes(0, 4))
353 def AddEmptyProp(self, prop_name, len):
354 """Add a property with a fixed data size, for filling in later
356 The device tree is marked dirty so that the value will be written to
357 the blob on the next sync.
360 prop_name: Name of property
361 len: Length of data in property
363 value = tools.GetBytes(0, len)
364 self.props[prop_name] = Prop(self, None, prop_name, value)
366 def _CheckProp(self, prop_name):
367 """Check if a property is present
370 prop_name: Name of property
376 ValueError if the property is missing
378 if prop_name not in self.props:
379 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
380 (self._fdt._fname, self.path, prop_name))
383 def SetInt(self, prop_name, val):
384 """Update an integer property int the device tree.
386 This is not allowed to change the size of the FDT.
388 The device tree is marked dirty so that the value will be written to
389 the blob on the next sync.
392 prop_name: Name of property
395 self._CheckProp(prop_name).props[prop_name].SetInt(val)
397 def SetData(self, prop_name, val):
398 """Set the data value of a property
400 The device tree is marked dirty so that the value will be written to
401 the blob on the next sync.
404 prop_name: Name of property to set
405 val: Data value to set
407 self._CheckProp(prop_name).props[prop_name].SetData(val)
409 def SetString(self, prop_name, val):
410 """Set the string value of a property
412 The device tree is marked dirty so that the value will be written to
413 the blob on the next sync.
416 prop_name: Name of property to set
417 val: String value to set (will be \0-terminated in DT)
420 val = val.encode('utf-8')
421 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
423 def AddData(self, prop_name, val):
424 """Add a new property to a node
426 The device tree is marked dirty so that the value will be written to
427 the blob on the next sync.
430 prop_name: Name of property to add
431 val: Bytes value of property
433 self.props[prop_name] = Prop(self, None, prop_name, val)
435 def AddString(self, prop_name, val):
436 """Add a new string property to a node
438 The device tree is marked dirty so that the value will be written to
439 the blob on the next sync.
442 prop_name: Name of property to add
443 val: String value of property
445 if sys.version_info[0] >= 3: # pragma: no cover
446 val = bytes(val, 'utf-8')
447 self.AddData(prop_name, val + b'\0')
449 def AddSubnode(self, name):
450 """Add a new subnode to the node
453 name: name of node to add
456 New subnode that was created
458 path = self.path + '/' + name
459 subnode = Node(self._fdt, self, None, name, path)
460 self.subnodes.append(subnode)
463 def Sync(self, auto_resize=False):
464 """Sync node changes back to the device tree
466 This updates the device tree blob with any changes to this node and its
467 subnodes since the last sync.
470 auto_resize: Resize the device tree automatically if it does not
471 have enough space for the update
474 FdtException if auto_resize is False and there is not enough space
476 if self._offset is None:
477 # The subnode doesn't exist yet, so add it
478 fdt_obj = self._fdt._fdt_obj
481 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
483 if offset != -libfdt.NOSPACE:
485 fdt_obj.resize(fdt_obj.totalsize() + 1024)
487 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
488 self._offset = offset
490 # Sync subnodes in reverse so that we don't disturb node offsets for
491 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
493 for node in reversed(self.subnodes):
494 node.Sync(auto_resize)
496 # Sync properties now, whose offsets should not have been disturbed.
497 # We do this after subnodes, since this disturbs the offsets of these
498 # properties. Note that new properties will have an offset of None here,
499 # which Python 3 cannot sort against int. So use a large value instead
500 # to ensure that the new properties are added first.
501 prop_list = sorted(self.props.values(),
502 key=lambda prop: prop._offset or 1 << 31,
504 for prop in prop_list:
505 prop.Sync(auto_resize)
509 """Provides simple access to a flat device tree blob using libfdts.
512 fname: Filename of fdt
513 _root: Root of device tree (a Node object)
514 name: Helpful name for this Fdt for the user (useful when creating the
515 DT from data rather than a file)
517 def __init__(self, fname):
519 self._cached_offsets = False
520 self.phandle_to_node = {}
523 self.name = self._fname
524 self._fname = fdt_util.EnsureCompiled(self._fname)
526 with open(self._fname, 'rb') as fd:
527 self._fdt_obj = libfdt.Fdt(fd.read())
530 def FromData(data, name=''):
531 """Create a new Fdt object from the given data
534 data: Device-tree data blob
535 name: Helpful name for this Fdt for the user
538 Fdt object containing the data
541 fdt._fdt_obj = libfdt.Fdt(bytes(data))
545 def LookupPhandle(self, phandle):
549 phandle: Phandle to look up (int)
552 Node object the phandle points to
554 return self.phandle_to_node.get(phandle)
556 def Scan(self, root='/'):
557 """Scan a device tree, building up a tree of Node objects
559 This fills in the self._root property
564 TODO(sjg@chromium.org): Implement the 'root' parameter
566 self._cached_offsets = True
567 self._root = self.Node(self, None, 0, '/', '/')
571 """Get the root Node of the device tree
578 def GetNode(self, path):
579 """Look up a node from its path
582 path: Path to look up, e.g. '/microcode/update@0'
584 Node object, or None if not found
587 parts = path.split('/')
590 if len(parts) == 2 and parts[1] == '':
592 for part in parts[1:]:
593 node = node.FindNode(part)
599 """Flush device tree changes back to the file
601 If the device tree has changed in memory, write it back to the file.
603 with open(self._fname, 'wb') as fd:
604 fd.write(self._fdt_obj.as_bytearray())
606 def Sync(self, auto_resize=False):
607 """Make sure any DT changes are written to the blob
610 auto_resize: Resize the device tree automatically if it does not
611 have enough space for the update
614 FdtException if auto_resize is False and there is not enough space
616 self._root.Sync(auto_resize)
620 """Pack the device tree down to its minimum size
622 When nodes and properties shrink or are deleted, wasted space can
623 build up in the device tree binary.
625 CheckErr(self._fdt_obj.pack(), 'pack')
628 def GetContents(self):
629 """Get the contents of the FDT
632 The FDT contents as a string of bytes
634 return bytes(self._fdt_obj.as_bytearray())
637 """Get the contents of the FDT
640 The FDT contents as a libfdt.Fdt object
644 def GetProps(self, node):
645 """Get all properties from a node.
648 node: Full path to node name to look in.
651 A dictionary containing all the properties, indexed by node name.
652 The entries are Prop objects.
655 ValueError: if the node does not exist.
658 poffset = self._fdt_obj.first_property_offset(node._offset,
661 p = self._fdt_obj.get_property_by_offset(poffset)
662 prop = Prop(node, poffset, p.name, p)
663 props_dict[prop.name] = prop
665 poffset = self._fdt_obj.next_property_offset(poffset,
669 def Invalidate(self):
670 """Mark our offset cache as invalid"""
671 self._cached_offsets = False
673 def CheckCache(self):
674 """Refresh the offset cache if needed"""
675 if self._cached_offsets:
678 self._cached_offsets = True
681 """Refresh the offset cache"""
682 self._root.Refresh(0)
684 def GetStructOffset(self, offset):
685 """Get the file offset of a given struct offset
688 offset: Offset within the 'struct' region of the device tree
690 Position of @offset within the device tree binary
692 return self._fdt_obj.off_dt_struct() + offset
695 def Node(self, fdt, parent, offset, name, path):
698 This is used by Fdt.Scan() to create a new node using the correct
703 parent: Parent node, or None if this is the root node
704 offset: Offset of node
706 path: Full path to node
708 node = Node(fdt, parent, offset, name, path)
711 def GetFilename(self):
712 """Get the filename of the device tree
720 """Returns a new Fdt object"""