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
184 def _FindNode(self, name):
185 """Find a node given its name
188 name: Node name to look for
190 Node object if found, else None
192 for subnode in self.subnodes:
193 if subnode.name == name:
198 """Returns the offset of a node, after checking the cache
200 This should be used instead of self._offset directly, to ensure that
201 the cache does not contain invalid offsets.
203 self._fdt.CheckCache()
207 """Scan a node's properties and subnodes
209 This fills in the props and subnodes properties, recursively
210 searching into subnodes so that the entire tree is built.
212 fdt_obj = self._fdt._fdt_obj
213 self.props = self._fdt.GetProps(self)
214 phandle = fdt_obj.get_phandle(self.Offset())
216 self._fdt.phandle_to_node[phandle] = self
218 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
220 sep = '' if self.path[-1] == '/' else '/'
221 name = fdt_obj.get_name(offset)
222 path = self.path + sep + name
223 node = Node(self._fdt, self, offset, name, path)
224 self.subnodes.append(node)
227 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
229 def Refresh(self, my_offset):
230 """Fix up the _offset for each node, recursively
232 Note: This does not take account of property offsets - these will not
235 fdt_obj = self._fdt._fdt_obj
236 if self._offset != my_offset:
237 self._offset = my_offset
238 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
239 for subnode in self.subnodes:
240 if subnode.name != fdt_obj.get_name(offset):
241 raise ValueError('Internal error, node name mismatch %s != %s' %
242 (subnode.name, fdt_obj.get_name(offset)))
243 subnode.Refresh(offset)
244 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
245 if offset != -libfdt.FDT_ERR_NOTFOUND:
246 raise ValueError('Internal error, offset == %d' % offset)
248 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
250 p = fdt_obj.get_property_by_offset(poffset)
251 prop = self.props.get(p.name)
253 raise ValueError("Internal error, property '%s' missing, "
254 'offset %d' % (p.name, poffset))
255 prop.RefreshOffset(poffset)
256 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
258 def DeleteProp(self, prop_name):
259 """Delete a property of a node
261 The property is deleted and the offset cache is invalidated.
264 prop_name: Name of the property to delete
266 ValueError if the property does not exist
268 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
269 "Node '%s': delete property: '%s'" % (self.path, prop_name))
270 del self.props[prop_name]
271 self._fdt.Invalidate()
273 def AddZeroProp(self, prop_name):
274 """Add a new property to the device tree with an integer value of 0.
277 prop_name: Name of property
279 fdt_obj = self._fdt._fdt_obj
280 if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
281 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
282 fdt_obj.open_into(fdt_obj.totalsize() + 1024)
283 fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
284 self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
285 self._fdt.Invalidate()
287 def SetInt(self, prop_name, val):
288 """Update an integer property int the device tree.
290 This is not allowed to change the size of the FDT.
293 prop_name: Name of property
296 fdt_obj = self._fdt._fdt_obj
297 fdt_obj.setprop_u32(self.Offset(), prop_name, val)
301 """Provides simple access to a flat device tree blob using libfdts.
304 fname: Filename of fdt
305 _root: Root of device tree (a Node object)
307 def __init__(self, fname):
309 self._cached_offsets = False
310 self.phandle_to_node = {}
312 self._fname = fdt_util.EnsureCompiled(self._fname)
314 with open(self._fname) as fd:
315 self._fdt_obj = libfdt.Fdt(fd.read())
317 def Scan(self, root='/'):
318 """Scan a device tree, building up a tree of Node objects
320 This fills in the self._root property
325 TODO(sjg@chromium.org): Implement the 'root' parameter
327 self._cached_offsets = True
328 self._root = self.Node(self, None, 0, '/', '/')
332 """Get the root Node of the device tree
339 def GetNode(self, path):
340 """Look up a node from its path
343 path: Path to look up, e.g. '/microcode/update@0'
345 Node object, or None if not found
348 parts = path.split('/')
351 for part in parts[1:]:
352 node = node._FindNode(part)
358 """Flush device tree changes back to the file
360 If the device tree has changed in memory, write it back to the file.
362 with open(self._fname, 'wb') as fd:
363 fd.write(self._fdt_obj.as_bytearray())
366 """Pack the device tree down to its minimum size
368 When nodes and properties shrink or are deleted, wasted space can
369 build up in the device tree binary.
371 CheckErr(self._fdt_obj.pack(), 'pack')
374 def GetContents(self):
375 """Get the contents of the FDT
378 The FDT contents as a string of bytes
380 return self._fdt_obj.as_bytearray()
383 """Get the contents of the FDT
386 The FDT contents as a libfdt.Fdt object
390 def GetProps(self, node):
391 """Get all properties from a node.
394 node: Full path to node name to look in.
397 A dictionary containing all the properties, indexed by node name.
398 The entries are Prop objects.
401 ValueError: if the node does not exist.
404 poffset = self._fdt_obj.first_property_offset(node._offset,
407 p = self._fdt_obj.get_property_by_offset(poffset)
408 prop = Prop(node, poffset, p.name, p)
409 props_dict[prop.name] = prop
411 poffset = self._fdt_obj.next_property_offset(poffset,
415 def Invalidate(self):
416 """Mark our offset cache as invalid"""
417 self._cached_offsets = False
419 def CheckCache(self):
420 """Refresh the offset cache if needed"""
421 if self._cached_offsets:
424 self._cached_offsets = True
427 """Refresh the offset cache"""
428 self._root.Refresh(0)
430 def GetStructOffset(self, offset):
431 """Get the file offset of a given struct offset
434 offset: Offset within the 'struct' region of the device tree
436 Position of @offset within the device tree binary
438 return self._fdt_obj.off_dt_struct() + offset
441 def Node(self, fdt, parent, offset, name, path):
444 This is used by Fdt.Scan() to create a new node using the correct
449 parent: Parent node, or None if this is the root node
450 offset: Offset of node
452 path: Full path to node
454 node = Node(fdt, parent, offset, name, path)
458 """Returns a new Fdt object"""