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 Sync(self, auto_resize=False):
179 """Sync property changes back to the device tree
181 This updates the device tree blob with any changes to this property
185 auto_resize: Resize the device tree automatically if it does not
186 have enough space for the update
189 FdtException if auto_resize is False and there is not enough space
191 if self._offset is None or self.dirty:
193 fdt_obj = node._fdt._fdt_obj
195 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
196 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
197 fdt_obj.resize(fdt_obj.totalsize() + 1024)
198 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
200 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
204 """A device tree node
207 offset: Integer offset in the device tree
208 name: Device tree node tname
209 path: Full path to node, along with the node name itself
210 _fdt: Device tree object
211 subnodes: A list of subnodes for this node, each a Node object
212 props: A dict of properties for this node, each a Prop object.
213 Keyed by property name
215 def __init__(self, fdt, parent, offset, name, path):
218 self._offset = offset
225 """Get the Fdt object for this node
232 def FindNode(self, name):
233 """Find a node given its name
236 name: Node name to look for
238 Node object if found, else None
240 for subnode in self.subnodes:
241 if subnode.name == name:
246 """Returns the offset of a node, after checking the cache
248 This should be used instead of self._offset directly, to ensure that
249 the cache does not contain invalid offsets.
251 self._fdt.CheckCache()
255 """Scan a node's properties and subnodes
257 This fills in the props and subnodes properties, recursively
258 searching into subnodes so that the entire tree is built.
260 fdt_obj = self._fdt._fdt_obj
261 self.props = self._fdt.GetProps(self)
262 phandle = fdt_obj.get_phandle(self.Offset())
264 self._fdt.phandle_to_node[phandle] = self
266 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
268 sep = '' if self.path[-1] == '/' else '/'
269 name = fdt_obj.get_name(offset)
270 path = self.path + sep + name
271 node = Node(self._fdt, self, offset, name, path)
272 self.subnodes.append(node)
275 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
277 def Refresh(self, my_offset):
278 """Fix up the _offset for each node, recursively
280 Note: This does not take account of property offsets - these will not
283 fdt_obj = self._fdt._fdt_obj
284 if self._offset != my_offset:
285 self._offset = my_offset
286 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
287 for subnode in self.subnodes:
288 if subnode.name != fdt_obj.get_name(offset):
289 raise ValueError('Internal error, node name mismatch %s != %s' %
290 (subnode.name, fdt_obj.get_name(offset)))
291 subnode.Refresh(offset)
292 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
293 if offset != -libfdt.FDT_ERR_NOTFOUND:
294 raise ValueError('Internal error, offset == %d' % offset)
296 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
298 p = fdt_obj.get_property_by_offset(poffset)
299 prop = self.props.get(p.name)
301 raise ValueError("Internal error, property '%s' missing, "
302 'offset %d' % (p.name, poffset))
303 prop.RefreshOffset(poffset)
304 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
306 def DeleteProp(self, prop_name):
307 """Delete a property of a node
309 The property is deleted and the offset cache is invalidated.
312 prop_name: Name of the property to delete
314 ValueError if the property does not exist
316 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
317 "Node '%s': delete property: '%s'" % (self.path, prop_name))
318 del self.props[prop_name]
319 self._fdt.Invalidate()
321 def AddZeroProp(self, prop_name):
322 """Add a new property to the device tree with an integer value of 0.
325 prop_name: Name of property
327 self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4)
329 def SetInt(self, prop_name, val):
330 """Update an integer property int the device tree.
332 This is not allowed to change the size of the FDT.
335 prop_name: Name of property
338 self.props[prop_name].SetInt(val)
340 def Sync(self, auto_resize=False):
341 """Sync node changes back to the device tree
343 This updates the device tree blob with any changes to this node and its
344 subnodes since the last sync.
347 auto_resize: Resize the device tree automatically if it does not
348 have enough space for the update
351 FdtException if auto_resize is False and there is not enough space
353 # Sync subnodes in reverse so that we don't disturb node offsets for
354 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
356 for node in reversed(self.subnodes):
357 node.Sync(auto_resize)
359 # Sync properties now, whose offsets should not have been disturbed.
360 # We do this after subnodes, since this disturbs the offsets of these
362 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
364 for prop in prop_list:
365 prop.Sync(auto_resize)
369 """Provides simple access to a flat device tree blob using libfdts.
372 fname: Filename of fdt
373 _root: Root of device tree (a Node object)
375 def __init__(self, fname):
377 self._cached_offsets = False
378 self.phandle_to_node = {}
380 self._fname = fdt_util.EnsureCompiled(self._fname)
382 with open(self._fname) as fd:
383 self._fdt_obj = libfdt.Fdt(fd.read())
385 def LookupPhandle(self, phandle):
389 phandle: Phandle to look up (int)
392 Node object the phandle points to
394 return self.phandle_to_node.get(phandle)
396 def Scan(self, root='/'):
397 """Scan a device tree, building up a tree of Node objects
399 This fills in the self._root property
404 TODO(sjg@chromium.org): Implement the 'root' parameter
406 self._cached_offsets = True
407 self._root = self.Node(self, None, 0, '/', '/')
411 """Get the root Node of the device tree
418 def GetNode(self, path):
419 """Look up a node from its path
422 path: Path to look up, e.g. '/microcode/update@0'
424 Node object, or None if not found
427 parts = path.split('/')
430 for part in parts[1:]:
431 node = node.FindNode(part)
437 """Flush device tree changes back to the file
439 If the device tree has changed in memory, write it back to the file.
441 with open(self._fname, 'wb') as fd:
442 fd.write(self._fdt_obj.as_bytearray())
444 def Sync(self, auto_resize=False):
445 """Make sure any DT changes are written to the blob
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 self._root.Sync(auto_resize)
458 """Pack the device tree down to its minimum size
460 When nodes and properties shrink or are deleted, wasted space can
461 build up in the device tree binary.
463 CheckErr(self._fdt_obj.pack(), 'pack')
466 def GetContents(self):
467 """Get the contents of the FDT
470 The FDT contents as a string of bytes
472 return self._fdt_obj.as_bytearray()
475 """Get the contents of the FDT
478 The FDT contents as a libfdt.Fdt object
482 def GetProps(self, node):
483 """Get all properties from a node.
486 node: Full path to node name to look in.
489 A dictionary containing all the properties, indexed by node name.
490 The entries are Prop objects.
493 ValueError: if the node does not exist.
496 poffset = self._fdt_obj.first_property_offset(node._offset,
499 p = self._fdt_obj.get_property_by_offset(poffset)
500 prop = Prop(node, poffset, p.name, p)
501 props_dict[prop.name] = prop
503 poffset = self._fdt_obj.next_property_offset(poffset,
507 def Invalidate(self):
508 """Mark our offset cache as invalid"""
509 self._cached_offsets = False
511 def CheckCache(self):
512 """Refresh the offset cache if needed"""
513 if self._cached_offsets:
516 self._cached_offsets = True
519 """Refresh the offset cache"""
520 self._root.Refresh(0)
522 def GetStructOffset(self, offset):
523 """Get the file offset of a given struct offset
526 offset: Offset within the 'struct' region of the device tree
528 Position of @offset within the device tree binary
530 return self._fdt_obj.off_dt_struct() + offset
533 def Node(self, fdt, parent, offset, name, path):
536 This is used by Fdt.Scan() to create a new node using the correct
541 parent: Parent node, or None if this is the root node
542 offset: Offset of node
544 path: Full path to node
546 node = Node(fdt, parent, offset, name, path)
550 """Returns a new Fdt object"""