2 # SPDX-License-Identifier: GPL-2.0+
4 # Copyright (C) 2016 Google, Inc
5 # Written by Simon Glass <sjg@chromium.org>
14 # This deals with a device tree, presenting it as an assortment of Node and
15 # Prop objects, representing nodes and properties, respectively. This file
16 # contains the base classes and defines the high-level API. You can use
17 # FdtScan() as a convenience function to create and scan an Fdt.
19 # This implementation uses a libfdt Python library to access the device tree,
20 # so it is fairly efficient.
22 # A list of types we support
23 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
25 def CheckErr(errnum, msg):
27 raise ValueError('Error %d: %s: %s' %
28 (errnum, libfdt.fdt_strerror(errnum), msg))
31 """A device tree property
34 name: Property name (as per the device tree)
35 value: Property value as a string of bytes, or a list of strings of
39 def __init__(self, node, offset, name, bytes):
44 self.bytes = str(bytes)
49 self.type, self.value = self.BytesToValue(bytes)
52 """Get a (single) phandle value from a property
54 Gets the phandle valuie from a property and returns it as an integer
56 return fdt_util.fdt32_to_cpu(self.value[:4])
58 def Widen(self, newprop):
59 """Figure out which property type is more general
61 Given a current property and a new property, this function returns the
62 one that is less specific as to type. The less specific property will
63 be ble to represent the data in the more specific property. This is
75 He we want to use an int array for 'value'. The first property
76 suggests that a single int is enough, but the second one shows that
77 it is not. Calling this function with these two propertes would
78 update the current property to be like the second, since it is less
81 if newprop.type < self.type:
82 self.type = newprop.type
84 if type(newprop.value) == list and type(self.value) != list:
85 self.value = [self.value]
87 if type(self.value) == list and len(newprop.value) > len(self.value):
88 val = self.GetEmpty(self.type)
89 while len(self.value) < len(newprop.value):
90 self.value.append(val)
92 def BytesToValue(self, bytes):
93 """Converts a string of bytes into a type and value
96 A string containing bytes
101 Data, either a single element or a list of elements. Each element
103 TYPE_STRING: string value from the property
104 TYPE_INT: a byte-swapped integer stored as a 4-byte string
105 TYPE_BYTE: a byte stored as a single-byte string
109 strings = bytes.split('\0')
111 count = len(strings) - 1
112 if count > 0 and not strings[-1]:
113 for string in strings[:-1]:
118 if ch < ' ' or ch > '~':
125 return TYPE_STRING, strings[0]
127 return TYPE_STRING, strings[:-1]
130 return TYPE_BYTE, bytes[0]
132 return TYPE_BYTE, list(bytes)
134 for i in range(0, size, 4):
135 val.append(bytes[i:i + 4])
137 return TYPE_INT, val[0]
141 def GetEmpty(self, type):
142 """Get an empty / zero value of the given type
145 A single value of the given type
147 if type == TYPE_BYTE:
149 elif type == TYPE_INT:
150 return struct.pack('<I', 0);
151 elif type == TYPE_STRING:
157 """Get the offset of a property
160 The offset of the property (struct fdt_property) within the file
162 return self._node._fdt.GetStructOffset(self._offset)
165 """A device tree node
168 offset: Integer offset in the device tree
169 name: Device tree node tname
170 path: Full path to node, along with the node name itself
171 _fdt: Device tree object
172 subnodes: A list of subnodes for this node, each a Node object
173 props: A dict of properties for this node, each a Prop object.
174 Keyed by property name
176 def __init__(self, fdt, parent, offset, name, path):
179 self._offset = offset
185 def _FindNode(self, name):
186 """Find a node given its name
189 name: Node name to look for
191 Node object if found, else None
193 for subnode in self.subnodes:
194 if subnode.name == name:
199 """Returns the offset of a node, after checking the cache
201 This should be used instead of self._offset directly, to ensure that
202 the cache does not contain invalid offsets.
204 self._fdt.CheckCache()
208 """Scan a node's properties and subnodes
210 This fills in the props and subnodes properties, recursively
211 searching into subnodes so that the entire tree is built.
213 self.props = self._fdt.GetProps(self)
214 phandle = self.props.get('phandle')
216 val = fdt_util.fdt32_to_cpu(phandle.value)
217 self._fdt.phandle_to_node[val] = self
219 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
221 sep = '' if self.path[-1] == '/' else '/'
222 name = self._fdt._fdt_obj.get_name(offset)
223 path = self.path + sep + name
224 node = Node(self._fdt, self, offset, name, path)
225 self.subnodes.append(node)
228 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
230 def Refresh(self, my_offset):
231 """Fix up the _offset for each node, recursively
233 Note: This does not take account of property offsets - these will not
236 if self._offset != my_offset:
237 self._offset = my_offset
238 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
239 for subnode in self.subnodes:
240 subnode.Refresh(offset)
241 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
243 def DeleteProp(self, prop_name):
244 """Delete a property of a node
246 The property is deleted and the offset cache is invalidated.
249 prop_name: Name of the property to delete
251 ValueError if the property does not exist
253 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
254 "Node '%s': delete property: '%s'" % (self.path, prop_name))
255 del self.props[prop_name]
256 self._fdt.Invalidate()
259 """Provides simple access to a flat device tree blob using libfdts.
262 fname: Filename of fdt
263 _root: Root of device tree (a Node object)
265 def __init__(self, fname):
267 self._cached_offsets = False
268 self.phandle_to_node = {}
270 self._fname = fdt_util.EnsureCompiled(self._fname)
272 with open(self._fname) as fd:
273 self._fdt = bytearray(fd.read())
274 self._fdt_obj = libfdt.Fdt(self._fdt)
276 def Scan(self, root='/'):
277 """Scan a device tree, building up a tree of Node objects
279 This fills in the self._root property
284 TODO(sjg@chromium.org): Implement the 'root' parameter
286 self._root = self.Node(self, None, 0, '/', '/')
290 """Get the root Node of the device tree
297 def GetNode(self, path):
298 """Look up a node from its path
301 path: Path to look up, e.g. '/microcode/update@0'
303 Node object, or None if not found
306 for part in path.split('/')[1:]:
307 node = node._FindNode(part)
313 """Flush device tree changes back to the file
315 If the device tree has changed in memory, write it back to the file.
317 with open(self._fname, 'wb') as fd:
321 """Pack the device tree down to its minimum size
323 When nodes and properties shrink or are deleted, wasted space can
324 build up in the device tree binary.
326 CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
327 fdt_len = libfdt.fdt_totalsize(self._fdt)
328 del self._fdt[fdt_len:]
331 """Get the contents of the FDT
334 The FDT contents as a string of bytes
338 def CheckErr(errnum, msg):
340 raise ValueError('Error %d: %s: %s' %
341 (errnum, libfdt.fdt_strerror(errnum), msg))
344 def GetProps(self, node):
345 """Get all properties from a node.
348 node: Full path to node name to look in.
351 A dictionary containing all the properties, indexed by node name.
352 The entries are Prop objects.
355 ValueError: if the node does not exist.
358 poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
360 p = self._fdt_obj.get_property_by_offset(poffset)
361 prop = Prop(node, poffset, p.name, p)
362 props_dict[prop.name] = prop
364 poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
367 def Invalidate(self):
368 """Mark our offset cache as invalid"""
369 self._cached_offsets = False
371 def CheckCache(self):
372 """Refresh the offset cache if needed"""
373 if self._cached_offsets:
376 self._cached_offsets = True
379 """Refresh the offset cache"""
380 self._root.Refresh(0)
382 def GetStructOffset(self, offset):
383 """Get the file offset of a given struct offset
386 offset: Offset within the 'struct' region of the device tree
388 Position of @offset within the device tree binary
390 return libfdt.fdt_off_dt_struct(self._fdt) + offset
393 def Node(self, fdt, parent, offset, name, path):
396 This is used by Fdt.Scan() to create a new node using the correct
401 parent: Parent node, or None if this is the root node
402 offset: Offset of node
404 path: Full path to node
406 node = Node(fdt, parent, offset, name, path)
410 """Returns a new Fdt object from the implementation we are using"""