blob: fd016bb8ce10921949f47dcbc585064c4323409d [file] [log] [blame]
Simon Glassa06a34b2016-07-25 18:59:04 -06001#!/usr/bin/python
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassa06a34b2016-07-25 18:59:04 -06003#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
Simon Glassa06a34b2016-07-25 18:59:04 -06007
8import struct
9import sys
10
11import fdt_util
Simon Glass7b75b442017-05-27 07:38:28 -060012import libfdt
Simon Glass117f57b2018-07-06 10:27:26 -060013from libfdt import QUIET_NOTFOUND
Simon Glassa06a34b2016-07-25 18:59:04 -060014
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
Simon Glass99ed4a22017-05-27 07:38:30 -060017# 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.
Simon Glass7b75b442017-05-27 07:38:28 -060019
20# This implementation uses a libfdt Python library to access the device tree,
21# so it is fairly efficient.
Simon Glassa06a34b2016-07-25 18:59:04 -060022
Simon Glassbc1dea32016-07-25 18:59:05 -060023# A list of types we support
Simon Glassfbdfd222017-08-29 14:15:48 -060024(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
Simon Glassbc1dea32016-07-25 18:59:05 -060025
Simon Glassa06a34b2016-07-25 18:59:04 -060026def CheckErr(errnum, msg):
27 if errnum:
28 raise ValueError('Error %d: %s: %s' %
29 (errnum, libfdt.fdt_strerror(errnum), msg))
30
Simon Glass7b75b442017-05-27 07:38:28 -060031class Prop:
Simon Glassa06a34b2016-07-25 18:59:04 -060032 """A device tree property
33
34 Properties:
35 name: Property name (as per the device tree)
36 value: Property value as a string of bytes, or a list of strings of
37 bytes
38 type: Value type
39 """
Simon Glass7b75b442017-05-27 07:38:28 -060040 def __init__(self, node, offset, name, bytes):
Simon Glassa06a34b2016-07-25 18:59:04 -060041 self._node = node
42 self._offset = offset
43 self.name = name
44 self.value = None
Simon Glass7b75b442017-05-27 07:38:28 -060045 self.bytes = str(bytes)
46 if not bytes:
47 self.type = TYPE_BOOL
48 self.value = True
49 return
50 self.type, self.value = self.BytesToValue(bytes)
Simon Glassa06a34b2016-07-25 18:59:04 -060051
Simon Glassc322a852016-07-25 18:59:06 -060052 def Widen(self, newprop):
53 """Figure out which property type is more general
54
55 Given a current property and a new property, this function returns the
56 one that is less specific as to type. The less specific property will
57 be ble to represent the data in the more specific property. This is
58 used for things like:
59
60 node1 {
61 compatible = "fred";
62 value = <1>;
63 };
64 node1 {
65 compatible = "fred";
66 value = <1 2>;
67 };
68
69 He we want to use an int array for 'value'. The first property
70 suggests that a single int is enough, but the second one shows that
71 it is not. Calling this function with these two propertes would
72 update the current property to be like the second, since it is less
73 specific.
74 """
75 if newprop.type < self.type:
76 self.type = newprop.type
77
78 if type(newprop.value) == list and type(self.value) != list:
79 self.value = [self.value]
80
81 if type(self.value) == list and len(newprop.value) > len(self.value):
82 val = self.GetEmpty(self.type)
83 while len(self.value) < len(newprop.value):
84 self.value.append(val)
85
Simon Glassbc1dea32016-07-25 18:59:05 -060086 def BytesToValue(self, bytes):
87 """Converts a string of bytes into a type and value
88
89 Args:
90 A string containing bytes
91
92 Return:
93 A tuple:
94 Type of data
95 Data, either a single element or a list of elements. Each element
96 is one of:
97 TYPE_STRING: string value from the property
98 TYPE_INT: a byte-swapped integer stored as a 4-byte string
99 TYPE_BYTE: a byte stored as a single-byte string
100 """
Simon Glassb4360202017-05-27 07:38:22 -0600101 bytes = str(bytes)
Simon Glassbc1dea32016-07-25 18:59:05 -0600102 size = len(bytes)
103 strings = bytes.split('\0')
104 is_string = True
105 count = len(strings) - 1
106 if count > 0 and not strings[-1]:
107 for string in strings[:-1]:
108 if not string:
109 is_string = False
110 break
111 for ch in string:
112 if ch < ' ' or ch > '~':
113 is_string = False
114 break
115 else:
116 is_string = False
117 if is_string:
118 if count == 1:
119 return TYPE_STRING, strings[0]
120 else:
121 return TYPE_STRING, strings[:-1]
122 if size % 4:
123 if size == 1:
124 return TYPE_BYTE, bytes[0]
125 else:
126 return TYPE_BYTE, list(bytes)
127 val = []
128 for i in range(0, size, 4):
129 val.append(bytes[i:i + 4])
130 if size == 4:
131 return TYPE_INT, val[0]
132 else:
133 return TYPE_INT, val
134
Simon Glass2ba98752018-07-06 10:27:24 -0600135 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600136 def GetEmpty(self, type):
137 """Get an empty / zero value of the given type
138
139 Returns:
140 A single value of the given type
141 """
142 if type == TYPE_BYTE:
143 return chr(0)
144 elif type == TYPE_INT:
145 return struct.pack('<I', 0);
146 elif type == TYPE_STRING:
147 return ''
148 else:
149 return True
150
Simon Glassbabdbde2016-07-25 18:59:16 -0600151 def GetOffset(self):
152 """Get the offset of a property
153
Simon Glassbabdbde2016-07-25 18:59:16 -0600154 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600155 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600156 """
Simon Glass7b75b442017-05-27 07:38:28 -0600157 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600158
Simon Glass7b75b442017-05-27 07:38:28 -0600159class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600160 """A device tree node
161
162 Properties:
163 offset: Integer offset in the device tree
164 name: Device tree node tname
165 path: Full path to node, along with the node name itself
166 _fdt: Device tree object
167 subnodes: A list of subnodes for this node, each a Node object
168 props: A dict of properties for this node, each a Prop object.
169 Keyed by property name
170 """
Simon Glass979ab022017-08-29 14:15:47 -0600171 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600172 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600173 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600174 self._offset = offset
175 self.name = name
176 self.path = path
177 self.subnodes = []
178 self.props = {}
179
Simon Glassf7a2aee2016-07-25 18:59:07 -0600180 def _FindNode(self, name):
181 """Find a node given its name
182
183 Args:
184 name: Node name to look for
185 Returns:
186 Node object if found, else None
187 """
188 for subnode in self.subnodes:
189 if subnode.name == name:
190 return subnode
191 return None
192
Simon Glass7b75b442017-05-27 07:38:28 -0600193 def Offset(self):
194 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600195
Simon Glass7b75b442017-05-27 07:38:28 -0600196 This should be used instead of self._offset directly, to ensure that
197 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600198 """
Simon Glass7b75b442017-05-27 07:38:28 -0600199 self._fdt.CheckCache()
200 return self._offset
201
202 def Scan(self):
203 """Scan a node's properties and subnodes
204
205 This fills in the props and subnodes properties, recursively
206 searching into subnodes so that the entire tree is built.
207 """
Simon Glass117f57b2018-07-06 10:27:26 -0600208 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600209 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600210 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600211 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600212 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600213
Simon Glass117f57b2018-07-06 10:27:26 -0600214 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600215 while offset >= 0:
216 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600217 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600218 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600219 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600220 self.subnodes.append(node)
221
222 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600223 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600224
225 def Refresh(self, my_offset):
226 """Fix up the _offset for each node, recursively
227
228 Note: This does not take account of property offsets - these will not
229 be updated.
230 """
Simon Glass96066242018-07-06 10:27:27 -0600231 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600232 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600233 self._offset = my_offset
Simon Glass96066242018-07-06 10:27:27 -0600234 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600235 for subnode in self.subnodes:
236 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600237 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600238
Simon Glass2a70d892016-07-25 18:59:14 -0600239 def DeleteProp(self, prop_name):
240 """Delete a property of a node
241
Simon Glass7b75b442017-05-27 07:38:28 -0600242 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600243
244 Args:
245 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600246 Raises:
247 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600248 """
Simon Glass96066242018-07-06 10:27:27 -0600249 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600250 "Node '%s': delete property: '%s'" % (self.path, prop_name))
251 del self.props[prop_name]
252 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600253
Simon Glassa06a34b2016-07-25 18:59:04 -0600254class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600255 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600256
257 Properties:
258 fname: Filename of fdt
259 _root: Root of device tree (a Node object)
260 """
261 def __init__(self, fname):
262 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600263 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600264 self.phandle_to_node = {}
Simon Glass7b75b442017-05-27 07:38:28 -0600265 if self._fname:
266 self._fname = fdt_util.EnsureCompiled(self._fname)
267
268 with open(self._fname) as fd:
Simon Glass96066242018-07-06 10:27:27 -0600269 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600270
271 def Scan(self, root='/'):
272 """Scan a device tree, building up a tree of Node objects
273
274 This fills in the self._root property
275
276 Args:
277 root: Ignored
278
279 TODO(sjg@chromium.org): Implement the 'root' parameter
280 """
Simon Glass979ab022017-08-29 14:15:47 -0600281 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600282 self._root.Scan()
283
284 def GetRoot(self):
285 """Get the root Node of the device tree
286
287 Returns:
288 The root Node object
289 """
290 return self._root
291
292 def GetNode(self, path):
293 """Look up a node from its path
294
295 Args:
296 path: Path to look up, e.g. '/microcode/update@0'
297 Returns:
298 Node object, or None if not found
299 """
300 node = self._root
301 for part in path.split('/')[1:]:
302 node = node._FindNode(part)
303 if not node:
304 return None
305 return node
306
Simon Glassda5f7492016-07-25 18:59:15 -0600307 def Flush(self):
308 """Flush device tree changes back to the file
309
310 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600311 """
Simon Glass7b75b442017-05-27 07:38:28 -0600312 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600313 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600314
315 def Pack(self):
316 """Pack the device tree down to its minimum size
317
318 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600319 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600320 """
Simon Glass117f57b2018-07-06 10:27:26 -0600321 CheckErr(self._fdt_obj.pack(), 'pack')
322 self.Invalidate()
Simon Glass7b75b442017-05-27 07:38:28 -0600323
Simon Glass96066242018-07-06 10:27:27 -0600324 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600325 """Get the contents of the FDT
326
327 Returns:
328 The FDT contents as a string of bytes
329 """
Simon Glass96066242018-07-06 10:27:27 -0600330 return self._fdt_obj.as_bytearray()
Simon Glass7b75b442017-05-27 07:38:28 -0600331
Simon Glass2ba98752018-07-06 10:27:24 -0600332 def GetFdtObj(self):
333 """Get the contents of the FDT
334
335 Returns:
336 The FDT contents as a libfdt.Fdt object
337 """
338 return self._fdt_obj
339
Simon Glass7b75b442017-05-27 07:38:28 -0600340 def GetProps(self, node):
341 """Get all properties from a node.
342
343 Args:
344 node: Full path to node name to look in.
345
346 Returns:
347 A dictionary containing all the properties, indexed by node name.
348 The entries are Prop objects.
349
350 Raises:
351 ValueError: if the node does not exist.
352 """
353 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600354 poffset = self._fdt_obj.first_property_offset(node._offset,
355 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600356 while poffset >= 0:
357 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600358 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600359 props_dict[prop.name] = prop
360
Simon Glass117f57b2018-07-06 10:27:26 -0600361 poffset = self._fdt_obj.next_property_offset(poffset,
362 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600363 return props_dict
364
365 def Invalidate(self):
366 """Mark our offset cache as invalid"""
367 self._cached_offsets = False
368
369 def CheckCache(self):
370 """Refresh the offset cache if needed"""
371 if self._cached_offsets:
372 return
373 self.Refresh()
374 self._cached_offsets = True
375
376 def Refresh(self):
377 """Refresh the offset cache"""
378 self._root.Refresh(0)
379
380 def GetStructOffset(self, offset):
381 """Get the file offset of a given struct offset
382
383 Args:
384 offset: Offset within the 'struct' region of the device tree
385 Returns:
386 Position of @offset within the device tree binary
387 """
Simon Glass117f57b2018-07-06 10:27:26 -0600388 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600389
390 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600391 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600392 """Create a new node
393
394 This is used by Fdt.Scan() to create a new node using the correct
395 class.
396
397 Args:
398 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600399 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600400 offset: Offset of node
401 name: Node name
402 path: Full path to node
403 """
Simon Glass979ab022017-08-29 14:15:47 -0600404 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600405 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600406
407def FdtScan(fname):
408 """Returns a new Fdt object from the implementation we are using"""
409 dtb = Fdt(fname)
410 dtb.Scan()
411 return dtb