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)
50 self.type, self.value = self.BytesToValue(bytes)
52 def RefreshOffset(self, poffset):
53 self._offset = poffset
55 def Widen(self, newprop):
56 """Figure out which property type is more general
58 Given a current property and a new property, this function returns the
59 one that is less specific as to type. The less specific property will
60 be ble to represent the data in the more specific property. This is
72 He we want to use an int array for 'value'. The first property
73 suggests that a single int is enough, but the second one shows that
74 it is not. Calling this function with these two propertes would
75 update the current property to be like the second, since it is less
78 if newprop.type < self.type:
79 self.type = newprop.type
81 if type(newprop.value) == list and type(self.value) != list:
82 self.value = [self.value]
84 if type(self.value) == list and len(newprop.value) > len(self.value):
85 val = self.GetEmpty(self.type)
86 while len(self.value) < len(newprop.value):
87 self.value.append(val)
89 def BytesToValue(self, bytes):
90 """Converts a string of bytes into a type and value
93 A string containing bytes
98 Data, either a single element or a list of elements. Each element
100 TYPE_STRING: string value from the property
101 TYPE_INT: a byte-swapped integer stored as a 4-byte string
102 TYPE_BYTE: a byte stored as a single-byte string
106 strings = bytes.split('\0')
108 count = len(strings) - 1
109 if count > 0 and not strings[-1]:
110 for string in strings[:-1]:
115 if ch < ' ' or ch > '~':
122 return TYPE_STRING, strings[0]
124 return TYPE_STRING, strings[:-1]
127 return TYPE_BYTE, bytes[0]
129 return TYPE_BYTE, list(bytes)
131 for i in range(0, size, 4):
132 val.append(bytes[i:i + 4])
134 return TYPE_INT, val[0]
139 def GetEmpty(self, type):
140 """Get an empty / zero value of the given type
143 A single value of the given type
145 if type == TYPE_BYTE:
147 elif type == TYPE_INT:
148 return struct.pack('<I', 0);
149 elif type == TYPE_STRING:
155 """Get the offset of a property
158 The offset of the property (struct fdt_property) within the file
160 self._node._fdt.CheckCache()
161 return self._node._fdt.GetStructOffset(self._offset)
164 """A device tree node
167 offset: Integer offset in the device tree
168 name: Device tree node tname
169 path: Full path to node, along with the node name itself
170 _fdt: Device tree object
171 subnodes: A list of subnodes for this node, each a Node object
172 props: A dict of properties for this node, each a Prop object.
173 Keyed by property name
175 def __init__(self, fdt, parent, offset, name, path):
178 self._offset = offset
185 """Get the Fdt object for this node
192 def FindNode(self, name):
193 """Find a node given its name
196 name: Node name to look for
198 Node object if found, else None
200 for subnode in self.subnodes:
201 if subnode.name == name:
206 """Returns the offset of a node, after checking the cache
208 This should be used instead of self._offset directly, to ensure that
209 the cache does not contain invalid offsets.
211 self._fdt.CheckCache()
215 """Scan a node's properties and subnodes
217 This fills in the props and subnodes properties, recursively
218 searching into subnodes so that the entire tree is built.
220 fdt_obj = self._fdt._fdt_obj
221 self.props = self._fdt.GetProps(self)
222 phandle = fdt_obj.get_phandle(self.Offset())
224 self._fdt.phandle_to_node[phandle] = self
226 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
228 sep = '' if self.path[-1] == '/' else '/'
229 name = fdt_obj.get_name(offset)
230 path = self.path + sep + name
231 node = Node(self._fdt, self, offset, name, path)
232 self.subnodes.append(node)
235 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
237 def Refresh(self, my_offset):
238 """Fix up the _offset for each node, recursively
240 Note: This does not take account of property offsets - these will not
243 fdt_obj = self._fdt._fdt_obj
244 if self._offset != my_offset:
245 self._offset = my_offset
246 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
247 for subnode in self.subnodes:
248 if subnode.name != fdt_obj.get_name(offset):
249 raise ValueError('Internal error, node name mismatch %s != %s' %
250 (subnode.name, fdt_obj.get_name(offset)))
251 subnode.Refresh(offset)
252 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
253 if offset != -libfdt.FDT_ERR_NOTFOUND:
254 raise ValueError('Internal error, offset == %d' % offset)
256 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
258 p = fdt_obj.get_property_by_offset(poffset)
259 prop = self.props.get(p.name)
261 raise ValueError("Internal error, property '%s' missing, "
262 'offset %d' % (p.name, poffset))
263 prop.RefreshOffset(poffset)
264 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
266 def DeleteProp(self, prop_name):
267 """Delete a property of a node
269 The property is deleted and the offset cache is invalidated.
272 prop_name: Name of the property to delete
274 ValueError if the property does not exist
276 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
277 "Node '%s': delete property: '%s'" % (self.path, prop_name))
278 del self.props[prop_name]
279 self._fdt.Invalidate()
281 def AddZeroProp(self, prop_name):
282 """Add a new property to the device tree with an integer value of 0.
285 prop_name: Name of property
287 fdt_obj = self._fdt._fdt_obj
288 if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
289 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
290 fdt_obj.open_into(fdt_obj.totalsize() + 1024)
291 fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
292 self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
293 self._fdt.Invalidate()
295 def SetInt(self, prop_name, val):
296 """Update an integer property int the device tree.
298 This is not allowed to change the size of the FDT.
301 prop_name: Name of property
304 fdt_obj = self._fdt._fdt_obj
305 fdt_obj.setprop_u32(self.Offset(), prop_name, val)
309 """Provides simple access to a flat device tree blob using libfdts.
312 fname: Filename of fdt
313 _root: Root of device tree (a Node object)
315 def __init__(self, fname):
317 self._cached_offsets = False
318 self.phandle_to_node = {}
320 self._fname = fdt_util.EnsureCompiled(self._fname)
322 with open(self._fname) as fd:
323 self._fdt_obj = libfdt.Fdt(fd.read())
325 def LookupPhandle(self, phandle):
329 phandle: Phandle to look up (int)
332 Node object the phandle points to
334 return self.phandle_to_node.get(phandle)
336 def Scan(self, root='/'):
337 """Scan a device tree, building up a tree of Node objects
339 This fills in the self._root property
344 TODO(sjg@chromium.org): Implement the 'root' parameter
346 self._cached_offsets = True
347 self._root = self.Node(self, None, 0, '/', '/')
351 """Get the root Node of the device tree
358 def GetNode(self, path):
359 """Look up a node from its path
362 path: Path to look up, e.g. '/microcode/update@0'
364 Node object, or None if not found
367 parts = path.split('/')
370 for part in parts[1:]:
371 node = node.FindNode(part)
377 """Flush device tree changes back to the file
379 If the device tree has changed in memory, write it back to the file.
381 with open(self._fname, 'wb') as fd:
382 fd.write(self._fdt_obj.as_bytearray())
385 """Pack the device tree down to its minimum size
387 When nodes and properties shrink or are deleted, wasted space can
388 build up in the device tree binary.
390 CheckErr(self._fdt_obj.pack(), 'pack')
393 def GetContents(self):
394 """Get the contents of the FDT
397 The FDT contents as a string of bytes
399 return self._fdt_obj.as_bytearray()
402 """Get the contents of the FDT
405 The FDT contents as a libfdt.Fdt object
409 def GetProps(self, node):
410 """Get all properties from a node.
413 node: Full path to node name to look in.
416 A dictionary containing all the properties, indexed by node name.
417 The entries are Prop objects.
420 ValueError: if the node does not exist.
423 poffset = self._fdt_obj.first_property_offset(node._offset,
426 p = self._fdt_obj.get_property_by_offset(poffset)
427 prop = Prop(node, poffset, p.name, p)
428 props_dict[prop.name] = prop
430 poffset = self._fdt_obj.next_property_offset(poffset,
434 def Invalidate(self):
435 """Mark our offset cache as invalid"""
436 self._cached_offsets = False
438 def CheckCache(self):
439 """Refresh the offset cache if needed"""
440 if self._cached_offsets:
443 self._cached_offsets = True
446 """Refresh the offset cache"""
447 self._root.Refresh(0)
449 def GetStructOffset(self, offset):
450 """Get the file offset of a given struct offset
453 offset: Offset within the 'struct' region of the device tree
455 Position of @offset within the device tree binary
457 return self._fdt_obj.off_dt_struct() + offset
460 def Node(self, fdt, parent, offset, name, path):
463 This is used by Fdt.Scan() to create a new node using the correct
468 parent: Parent node, or None if this is the root node
469 offset: Offset of node
471 path: Full path to node
473 node = Node(fdt, parent, offset, name, path)
477 """Returns a new Fdt object"""