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)
53 """Get a (single) phandle value from a property
55 Gets the phandle valuie from a property and returns it as an integer
57 return fdt_util.fdt32_to_cpu(self.value[:4])
59 def Widen(self, newprop):
60 """Figure out which property type is more general
62 Given a current property and a new property, this function returns the
63 one that is less specific as to type. The less specific property will
64 be ble to represent the data in the more specific property. This is
76 He we want to use an int array for 'value'. The first property
77 suggests that a single int is enough, but the second one shows that
78 it is not. Calling this function with these two propertes would
79 update the current property to be like the second, since it is less
82 if newprop.type < self.type:
83 self.type = newprop.type
85 if type(newprop.value) == list and type(self.value) != list:
86 self.value = [self.value]
88 if type(self.value) == list and len(newprop.value) > len(self.value):
89 val = self.GetEmpty(self.type)
90 while len(self.value) < len(newprop.value):
91 self.value.append(val)
93 def BytesToValue(self, bytes):
94 """Converts a string of bytes into a type and value
97 A string containing bytes
102 Data, either a single element or a list of elements. Each element
104 TYPE_STRING: string value from the property
105 TYPE_INT: a byte-swapped integer stored as a 4-byte string
106 TYPE_BYTE: a byte stored as a single-byte string
110 strings = bytes.split('\0')
112 count = len(strings) - 1
113 if count > 0 and not strings[-1]:
114 for string in strings[:-1]:
119 if ch < ' ' or ch > '~':
126 return TYPE_STRING, strings[0]
128 return TYPE_STRING, strings[:-1]
131 return TYPE_BYTE, bytes[0]
133 return TYPE_BYTE, list(bytes)
135 for i in range(0, size, 4):
136 val.append(bytes[i:i + 4])
138 return TYPE_INT, val[0]
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 return self._node._fdt.GetStructOffset(self._offset)
167 """A device tree node
170 offset: Integer offset in the device tree
171 name: Device tree node tname
172 path: Full path to node, along with the node name itself
173 _fdt: Device tree object
174 subnodes: A list of subnodes for this node, each a Node object
175 props: A dict of properties for this node, each a Prop object.
176 Keyed by property name
178 def __init__(self, fdt, parent, offset, name, path):
181 self._offset = offset
187 def _FindNode(self, name):
188 """Find a node given its name
191 name: Node name to look for
193 Node object if found, else None
195 for subnode in self.subnodes:
196 if subnode.name == name:
201 """Returns the offset of a node, after checking the cache
203 This should be used instead of self._offset directly, to ensure that
204 the cache does not contain invalid offsets.
206 self._fdt.CheckCache()
210 """Scan a node's properties and subnodes
212 This fills in the props and subnodes properties, recursively
213 searching into subnodes so that the entire tree is built.
215 fdt_obj = self._fdt._fdt_obj
216 self.props = self._fdt.GetProps(self)
217 phandle = fdt_obj.get_phandle(self.Offset())
219 self._fdt.phandle_to_node[phandle] = self
221 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
223 sep = '' if self.path[-1] == '/' else '/'
224 name = fdt_obj.get_name(offset)
225 path = self.path + sep + name
226 node = Node(self._fdt, self, offset, name, path)
227 self.subnodes.append(node)
230 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
232 def Refresh(self, my_offset):
233 """Fix up the _offset for each node, recursively
235 Note: This does not take account of property offsets - these will not
238 fdt_obj = self._fdt._fdt_obj
239 if self._offset != my_offset:
240 self._offset = my_offset
241 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
242 for subnode in self.subnodes:
243 subnode.Refresh(offset)
244 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
246 def DeleteProp(self, prop_name):
247 """Delete a property of a node
249 The property is deleted and the offset cache is invalidated.
252 prop_name: Name of the property to delete
254 ValueError if the property does not exist
256 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
257 "Node '%s': delete property: '%s'" % (self.path, prop_name))
258 del self.props[prop_name]
259 self._fdt.Invalidate()
262 """Provides simple access to a flat device tree blob using libfdts.
265 fname: Filename of fdt
266 _root: Root of device tree (a Node object)
268 def __init__(self, fname):
270 self._cached_offsets = False
271 self.phandle_to_node = {}
273 self._fname = fdt_util.EnsureCompiled(self._fname)
275 with open(self._fname) as fd:
276 self._fdt_obj = libfdt.Fdt(fd.read())
278 def Scan(self, root='/'):
279 """Scan a device tree, building up a tree of Node objects
281 This fills in the self._root property
286 TODO(sjg@chromium.org): Implement the 'root' parameter
288 self._root = self.Node(self, None, 0, '/', '/')
292 """Get the root Node of the device tree
299 def GetNode(self, path):
300 """Look up a node from its path
303 path: Path to look up, e.g. '/microcode/update@0'
305 Node object, or None if not found
308 for part in path.split('/')[1:]:
309 node = node._FindNode(part)
315 """Flush device tree changes back to the file
317 If the device tree has changed in memory, write it back to the file.
319 with open(self._fname, 'wb') as fd:
320 fd.write(self._fdt_obj.as_bytearray())
323 """Pack the device tree down to its minimum size
325 When nodes and properties shrink or are deleted, wasted space can
326 build up in the device tree binary.
328 CheckErr(self._fdt_obj.pack(), 'pack')
331 def GetContents(self):
332 """Get the contents of the FDT
335 The FDT contents as a string of bytes
337 return self._fdt_obj.as_bytearray()
340 """Get the contents of the FDT
343 The FDT contents as a libfdt.Fdt object
347 def CheckErr(self, errnum, msg):
349 raise ValueError('Error %d: %s: %s' %
350 (errnum, libfdt.fdt_strerror(errnum), msg))
352 def GetProps(self, node):
353 """Get all properties from a node.
356 node: Full path to node name to look in.
359 A dictionary containing all the properties, indexed by node name.
360 The entries are Prop objects.
363 ValueError: if the node does not exist.
366 poffset = self._fdt_obj.first_property_offset(node._offset,
369 p = self._fdt_obj.get_property_by_offset(poffset)
370 prop = Prop(node, poffset, p.name, p)
371 props_dict[prop.name] = prop
373 poffset = self._fdt_obj.next_property_offset(poffset,
377 def Invalidate(self):
378 """Mark our offset cache as invalid"""
379 self._cached_offsets = False
381 def CheckCache(self):
382 """Refresh the offset cache if needed"""
383 if self._cached_offsets:
386 self._cached_offsets = True
389 """Refresh the offset cache"""
390 self._root.Refresh(0)
392 def GetStructOffset(self, offset):
393 """Get the file offset of a given struct offset
396 offset: Offset within the 'struct' region of the device tree
398 Position of @offset within the device tree binary
400 return self._fdt_obj.off_dt_struct() + offset
403 def Node(self, fdt, parent, offset, name, path):
406 This is used by Fdt.Scan() to create a new node using the correct
411 parent: Parent node, or None if this is the root node
412 offset: Offset of node
414 path: Full path to node
416 node = Node(fdt, parent, offset, name, path)
420 """Returns a new Fdt object from the implementation we are using"""