blob: 55baa3857f7937475c1f4600fff2b72c408bc6f6 [file] [log] [blame]
Simon Glass1f941b62016-07-25 18:59:04 -06001#!/usr/bin/python
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass1f941b62016-07-25 18:59:04 -06003#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
Simon Glass1f941b62016-07-25 18:59:04 -06007
8import struct
9import sys
10
11import fdt_util
Simon Glass059ae522017-05-27 07:38:28 -060012import libfdt
Simon Glass151aeed2018-07-06 10:27:26 -060013from libfdt import QUIET_NOTFOUND
Simon Glass1f941b62016-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 Glassa9440932017-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 Glass059ae522017-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 Glass1f941b62016-07-25 18:59:04 -060022
Simon Glassb1a5e262016-07-25 18:59:05 -060023# A list of types we support
Simon Glassfc3ae9c2017-08-29 14:15:48 -060024(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
Simon Glassb1a5e262016-07-25 18:59:05 -060025
Simon Glass1f941b62016-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 Glass059ae522017-05-27 07:38:28 -060031class Prop:
Simon Glass1f941b62016-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 Glass059ae522017-05-27 07:38:28 -060040 def __init__(self, node, offset, name, bytes):
Simon Glass1f941b62016-07-25 18:59:04 -060041 self._node = node
42 self._offset = offset
43 self.name = name
44 self.value = None
Simon Glass059ae522017-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 Glass1f941b62016-07-25 18:59:04 -060051
Simon Glass4df8a0c2018-07-06 10:27:29 -060052 def RefreshOffset(self, poffset):
53 self._offset = poffset
54
Simon Glass248ccd22016-07-25 18:59:06 -060055 def Widen(self, newprop):
56 """Figure out which property type is more general
57
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
61 used for things like:
62
63 node1 {
64 compatible = "fred";
65 value = <1>;
66 };
67 node1 {
68 compatible = "fred";
69 value = <1 2>;
70 };
71
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
76 specific.
77 """
78 if newprop.type < self.type:
79 self.type = newprop.type
80
81 if type(newprop.value) == list and type(self.value) != list:
82 self.value = [self.value]
83
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)
88
Simon Glassb1a5e262016-07-25 18:59:05 -060089 def BytesToValue(self, bytes):
90 """Converts a string of bytes into a type and value
91
92 Args:
93 A string containing bytes
94
95 Return:
96 A tuple:
97 Type of data
98 Data, either a single element or a list of elements. Each element
99 is one of:
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
103 """
Simon Glass55901ff2017-05-27 07:38:22 -0600104 bytes = str(bytes)
Simon Glassb1a5e262016-07-25 18:59:05 -0600105 size = len(bytes)
106 strings = bytes.split('\0')
107 is_string = True
108 count = len(strings) - 1
109 if count > 0 and not strings[-1]:
110 for string in strings[:-1]:
111 if not string:
112 is_string = False
113 break
114 for ch in string:
115 if ch < ' ' or ch > '~':
116 is_string = False
117 break
118 else:
119 is_string = False
120 if is_string:
121 if count == 1:
122 return TYPE_STRING, strings[0]
123 else:
124 return TYPE_STRING, strings[:-1]
125 if size % 4:
126 if size == 1:
127 return TYPE_BYTE, bytes[0]
128 else:
129 return TYPE_BYTE, list(bytes)
130 val = []
131 for i in range(0, size, 4):
132 val.append(bytes[i:i + 4])
133 if size == 4:
134 return TYPE_INT, val[0]
135 else:
136 return TYPE_INT, val
137
Simon Glass0ed50752018-07-06 10:27:24 -0600138 @classmethod
Simon Glassb1a5e262016-07-25 18:59:05 -0600139 def GetEmpty(self, type):
140 """Get an empty / zero value of the given type
141
142 Returns:
143 A single value of the given type
144 """
145 if type == TYPE_BYTE:
146 return chr(0)
147 elif type == TYPE_INT:
148 return struct.pack('<I', 0);
149 elif type == TYPE_STRING:
150 return ''
151 else:
152 return True
153
Simon Glass54cec6d2016-07-25 18:59:16 -0600154 def GetOffset(self):
155 """Get the offset of a property
156
Simon Glass54cec6d2016-07-25 18:59:16 -0600157 Returns:
Simon Glass059ae522017-05-27 07:38:28 -0600158 The offset of the property (struct fdt_property) within the file
Simon Glass54cec6d2016-07-25 18:59:16 -0600159 """
Simon Glass4df8a0c2018-07-06 10:27:29 -0600160 self._node._fdt.CheckCache()
Simon Glass059ae522017-05-27 07:38:28 -0600161 return self._node._fdt.GetStructOffset(self._offset)
Simon Glass54cec6d2016-07-25 18:59:16 -0600162
Simon Glass059ae522017-05-27 07:38:28 -0600163class Node:
Simon Glass1f941b62016-07-25 18:59:04 -0600164 """A device tree node
165
166 Properties:
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
174 """
Simon Glass80ef7052017-08-29 14:15:47 -0600175 def __init__(self, fdt, parent, offset, name, path):
Simon Glass1f941b62016-07-25 18:59:04 -0600176 self._fdt = fdt
Simon Glass80ef7052017-08-29 14:15:47 -0600177 self.parent = parent
Simon Glass1f941b62016-07-25 18:59:04 -0600178 self._offset = offset
179 self.name = name
180 self.path = path
181 self.subnodes = []
182 self.props = {}
183
Simon Glasse2d65282018-07-17 13:25:46 -0600184 def GetFdt(self):
185 """Get the Fdt object for this node
186
187 Returns:
188 Fdt object
189 """
190 return self._fdt
191
Simon Glassaa1a5d72018-07-17 13:25:41 -0600192 def FindNode(self, name):
Simon Glasscc346a72016-07-25 18:59:07 -0600193 """Find a node given its name
194
195 Args:
196 name: Node name to look for
197 Returns:
198 Node object if found, else None
199 """
200 for subnode in self.subnodes:
201 if subnode.name == name:
202 return subnode
203 return None
204
Simon Glass059ae522017-05-27 07:38:28 -0600205 def Offset(self):
206 """Returns the offset of a node, after checking the cache
207
208 This should be used instead of self._offset directly, to ensure that
209 the cache does not contain invalid offsets.
210 """
211 self._fdt.CheckCache()
212 return self._offset
213
Simon Glasscc346a72016-07-25 18:59:07 -0600214 def Scan(self):
Simon Glass059ae522017-05-27 07:38:28 -0600215 """Scan a node's properties and subnodes
Simon Glasscc346a72016-07-25 18:59:07 -0600216
Simon Glass059ae522017-05-27 07:38:28 -0600217 This fills in the props and subnodes properties, recursively
218 searching into subnodes so that the entire tree is built.
Simon Glasscc346a72016-07-25 18:59:07 -0600219 """
Simon Glass151aeed2018-07-06 10:27:26 -0600220 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600221 self.props = self._fdt.GetProps(self)
Simon Glass151aeed2018-07-06 10:27:26 -0600222 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glassa3f94442017-08-29 14:15:52 -0600223 if phandle:
Simon Glass151aeed2018-07-06 10:27:26 -0600224 self._fdt.phandle_to_node[phandle] = self
Simon Glass059ae522017-05-27 07:38:28 -0600225
Simon Glass151aeed2018-07-06 10:27:26 -0600226 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600227 while offset >= 0:
228 sep = '' if self.path[-1] == '/' else '/'
Simon Glass151aeed2018-07-06 10:27:26 -0600229 name = fdt_obj.get_name(offset)
Simon Glass059ae522017-05-27 07:38:28 -0600230 path = self.path + sep + name
Simon Glass80ef7052017-08-29 14:15:47 -0600231 node = Node(self._fdt, self, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600232 self.subnodes.append(node)
233
234 node.Scan()
Simon Glass151aeed2018-07-06 10:27:26 -0600235 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600236
237 def Refresh(self, my_offset):
238 """Fix up the _offset for each node, recursively
239
240 Note: This does not take account of property offsets - these will not
241 be updated.
242 """
Simon Glass792d2392018-07-06 10:27:27 -0600243 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600244 if self._offset != my_offset:
Simon Glass059ae522017-05-27 07:38:28 -0600245 self._offset = my_offset
Simon Glass792d2392018-07-06 10:27:27 -0600246 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600247 for subnode in self.subnodes:
Simon Glass4df8a0c2018-07-06 10:27:29 -0600248 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)))
Simon Glass059ae522017-05-27 07:38:28 -0600251 subnode.Refresh(offset)
Simon Glass792d2392018-07-06 10:27:27 -0600252 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass4df8a0c2018-07-06 10:27:29 -0600253 if offset != -libfdt.FDT_ERR_NOTFOUND:
254 raise ValueError('Internal error, offset == %d' % offset)
255
256 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
257 while poffset >= 0:
258 p = fdt_obj.get_property_by_offset(poffset)
259 prop = self.props.get(p.name)
260 if not prop:
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)
Simon Glasscc346a72016-07-25 18:59:07 -0600265
Simon Glassc719e422016-07-25 18:59:14 -0600266 def DeleteProp(self, prop_name):
267 """Delete a property of a node
268
Simon Glass059ae522017-05-27 07:38:28 -0600269 The property is deleted and the offset cache is invalidated.
Simon Glassc719e422016-07-25 18:59:14 -0600270
271 Args:
272 prop_name: Name of the property to delete
Simon Glass059ae522017-05-27 07:38:28 -0600273 Raises:
274 ValueError if the property does not exist
Simon Glassc719e422016-07-25 18:59:14 -0600275 """
Simon Glass792d2392018-07-06 10:27:27 -0600276 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass059ae522017-05-27 07:38:28 -0600277 "Node '%s': delete property: '%s'" % (self.path, prop_name))
278 del self.props[prop_name]
279 self._fdt.Invalidate()
Simon Glassc719e422016-07-25 18:59:14 -0600280
Simon Glasse80c5562018-07-06 10:27:38 -0600281 def AddZeroProp(self, prop_name):
282 """Add a new property to the device tree with an integer value of 0.
283
284 Args:
285 prop_name: Name of property
286 """
287 fdt_obj = self._fdt._fdt_obj
288 if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
289 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassb474c762018-07-26 14:02:13 -0600290 fdt_obj.resize(fdt_obj.totalsize() + 1024)
Simon Glasse80c5562018-07-06 10:27:38 -0600291 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()
294
295 def SetInt(self, prop_name, val):
296 """Update an integer property int the device tree.
297
298 This is not allowed to change the size of the FDT.
299
300 Args:
301 prop_name: Name of property
302 val: Value to set
303 """
304 fdt_obj = self._fdt._fdt_obj
305 fdt_obj.setprop_u32(self.Offset(), prop_name, val)
306
307
Simon Glass1f941b62016-07-25 18:59:04 -0600308class Fdt:
Simon Glass059ae522017-05-27 07:38:28 -0600309 """Provides simple access to a flat device tree blob using libfdts.
Simon Glass1f941b62016-07-25 18:59:04 -0600310
311 Properties:
312 fname: Filename of fdt
313 _root: Root of device tree (a Node object)
314 """
315 def __init__(self, fname):
316 self._fname = fname
Simon Glass059ae522017-05-27 07:38:28 -0600317 self._cached_offsets = False
Simon Glassa3f94442017-08-29 14:15:52 -0600318 self.phandle_to_node = {}
Simon Glass059ae522017-05-27 07:38:28 -0600319 if self._fname:
320 self._fname = fdt_util.EnsureCompiled(self._fname)
321
322 with open(self._fname) as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600323 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glasscc346a72016-07-25 18:59:07 -0600324
Simon Glasse2d65282018-07-17 13:25:46 -0600325 def LookupPhandle(self, phandle):
326 """Look up a phandle
327
328 Args:
329 phandle: Phandle to look up (int)
330
331 Returns:
332 Node object the phandle points to
333 """
334 return self.phandle_to_node.get(phandle)
335
Simon Glasscc346a72016-07-25 18:59:07 -0600336 def Scan(self, root='/'):
337 """Scan a device tree, building up a tree of Node objects
338
339 This fills in the self._root property
340
341 Args:
342 root: Ignored
343
344 TODO(sjg@chromium.org): Implement the 'root' parameter
345 """
Simon Glass4df8a0c2018-07-06 10:27:29 -0600346 self._cached_offsets = True
Simon Glass80ef7052017-08-29 14:15:47 -0600347 self._root = self.Node(self, None, 0, '/', '/')
Simon Glasscc346a72016-07-25 18:59:07 -0600348 self._root.Scan()
349
350 def GetRoot(self):
351 """Get the root Node of the device tree
352
353 Returns:
354 The root Node object
355 """
356 return self._root
357
358 def GetNode(self, path):
359 """Look up a node from its path
360
361 Args:
362 path: Path to look up, e.g. '/microcode/update@0'
363 Returns:
364 Node object, or None if not found
365 """
366 node = self._root
Simon Glassc5eddc82018-07-06 10:27:30 -0600367 parts = path.split('/')
368 if len(parts) < 2:
369 return None
370 for part in parts[1:]:
Simon Glassaa1a5d72018-07-17 13:25:41 -0600371 node = node.FindNode(part)
Simon Glasscc346a72016-07-25 18:59:07 -0600372 if not node:
373 return None
374 return node
375
Simon Glass32d98272016-07-25 18:59:15 -0600376 def Flush(self):
377 """Flush device tree changes back to the file
378
379 If the device tree has changed in memory, write it back to the file.
Simon Glass32d98272016-07-25 18:59:15 -0600380 """
Simon Glass059ae522017-05-27 07:38:28 -0600381 with open(self._fname, 'wb') as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600382 fd.write(self._fdt_obj.as_bytearray())
Simon Glass32d98272016-07-25 18:59:15 -0600383
384 def Pack(self):
385 """Pack the device tree down to its minimum size
386
387 When nodes and properties shrink or are deleted, wasted space can
Simon Glass059ae522017-05-27 07:38:28 -0600388 build up in the device tree binary.
389 """
Simon Glass151aeed2018-07-06 10:27:26 -0600390 CheckErr(self._fdt_obj.pack(), 'pack')
391 self.Invalidate()
Simon Glass059ae522017-05-27 07:38:28 -0600392
Simon Glass792d2392018-07-06 10:27:27 -0600393 def GetContents(self):
Simon Glass059ae522017-05-27 07:38:28 -0600394 """Get the contents of the FDT
395
396 Returns:
397 The FDT contents as a string of bytes
Simon Glass32d98272016-07-25 18:59:15 -0600398 """
Simon Glass792d2392018-07-06 10:27:27 -0600399 return self._fdt_obj.as_bytearray()
Simon Glass059ae522017-05-27 07:38:28 -0600400
Simon Glass0ed50752018-07-06 10:27:24 -0600401 def GetFdtObj(self):
402 """Get the contents of the FDT
403
404 Returns:
405 The FDT contents as a libfdt.Fdt object
406 """
407 return self._fdt_obj
408
Simon Glass059ae522017-05-27 07:38:28 -0600409 def GetProps(self, node):
410 """Get all properties from a node.
411
412 Args:
413 node: Full path to node name to look in.
414
415 Returns:
416 A dictionary containing all the properties, indexed by node name.
417 The entries are Prop objects.
418
419 Raises:
420 ValueError: if the node does not exist.
421 """
422 props_dict = {}
Simon Glass151aeed2018-07-06 10:27:26 -0600423 poffset = self._fdt_obj.first_property_offset(node._offset,
424 QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600425 while poffset >= 0:
426 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass70cd0d72018-07-06 10:27:20 -0600427 prop = Prop(node, poffset, p.name, p)
Simon Glass059ae522017-05-27 07:38:28 -0600428 props_dict[prop.name] = prop
429
Simon Glass151aeed2018-07-06 10:27:26 -0600430 poffset = self._fdt_obj.next_property_offset(poffset,
431 QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600432 return props_dict
433
434 def Invalidate(self):
435 """Mark our offset cache as invalid"""
436 self._cached_offsets = False
437
438 def CheckCache(self):
439 """Refresh the offset cache if needed"""
440 if self._cached_offsets:
441 return
442 self.Refresh()
443 self._cached_offsets = True
444
445 def Refresh(self):
446 """Refresh the offset cache"""
447 self._root.Refresh(0)
448
449 def GetStructOffset(self, offset):
450 """Get the file offset of a given struct offset
451
452 Args:
453 offset: Offset within the 'struct' region of the device tree
454 Returns:
455 Position of @offset within the device tree binary
456 """
Simon Glass151aeed2018-07-06 10:27:26 -0600457 return self._fdt_obj.off_dt_struct() + offset
Simon Glass059ae522017-05-27 07:38:28 -0600458
459 @classmethod
Simon Glass80ef7052017-08-29 14:15:47 -0600460 def Node(self, fdt, parent, offset, name, path):
Simon Glass059ae522017-05-27 07:38:28 -0600461 """Create a new node
462
463 This is used by Fdt.Scan() to create a new node using the correct
464 class.
465
466 Args:
467 fdt: Fdt object
Simon Glass80ef7052017-08-29 14:15:47 -0600468 parent: Parent node, or None if this is the root node
Simon Glass059ae522017-05-27 07:38:28 -0600469 offset: Offset of node
470 name: Node name
471 path: Full path to node
472 """
Simon Glass80ef7052017-08-29 14:15:47 -0600473 node = Node(fdt, parent, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600474 return node
Simon Glassa9440932017-05-27 07:38:30 -0600475
476def FdtScan(fname):
Simon Glassc38fee042018-07-06 10:27:32 -0600477 """Returns a new Fdt object"""
Simon Glassa9440932017-05-27 07:38:30 -0600478 dtb = Fdt(fname)
479 dtb.Scan()
480 return dtb