blob: 9d69b426c14b9cbe1b1b6c6cacdd57694e10b329 [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 Glassf9b88b32018-07-06 10:27:29 -060052 def RefreshOffset(self, poffset):
53 self._offset = poffset
54
Simon Glassc322a852016-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 Glassbc1dea32016-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 Glassb4360202017-05-27 07:38:22 -0600104 bytes = str(bytes)
Simon Glassbc1dea32016-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 Glass2ba98752018-07-06 10:27:24 -0600138 @classmethod
Simon Glassbc1dea32016-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 Glassbabdbde2016-07-25 18:59:16 -0600154 def GetOffset(self):
155 """Get the offset of a property
156
Simon Glassbabdbde2016-07-25 18:59:16 -0600157 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600158 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600159 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600160 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600161 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600162
Simon Glass7b75b442017-05-27 07:38:28 -0600163class Node:
Simon Glassa06a34b2016-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 Glass979ab022017-08-29 14:15:47 -0600175 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600176 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600177 self.parent = parent
Simon Glassa06a34b2016-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 Glassf7a2aee2016-07-25 18:59:07 -0600184 def _FindNode(self, name):
185 """Find a node given its name
186
187 Args:
188 name: Node name to look for
189 Returns:
190 Node object if found, else None
191 """
192 for subnode in self.subnodes:
193 if subnode.name == name:
194 return subnode
195 return None
196
Simon Glass7b75b442017-05-27 07:38:28 -0600197 def Offset(self):
198 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600199
Simon Glass7b75b442017-05-27 07:38:28 -0600200 This should be used instead of self._offset directly, to ensure that
201 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600202 """
Simon Glass7b75b442017-05-27 07:38:28 -0600203 self._fdt.CheckCache()
204 return self._offset
205
206 def Scan(self):
207 """Scan a node's properties and subnodes
208
209 This fills in the props and subnodes properties, recursively
210 searching into subnodes so that the entire tree is built.
211 """
Simon Glass117f57b2018-07-06 10:27:26 -0600212 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600213 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600214 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600215 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600216 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600217
Simon Glass117f57b2018-07-06 10:27:26 -0600218 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600219 while offset >= 0:
220 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600221 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600222 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600223 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600224 self.subnodes.append(node)
225
226 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600227 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600228
229 def Refresh(self, my_offset):
230 """Fix up the _offset for each node, recursively
231
232 Note: This does not take account of property offsets - these will not
233 be updated.
234 """
Simon Glass96066242018-07-06 10:27:27 -0600235 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600236 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600237 self._offset = my_offset
Simon Glass96066242018-07-06 10:27:27 -0600238 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600239 for subnode in self.subnodes:
Simon Glassf9b88b32018-07-06 10:27:29 -0600240 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)))
Simon Glass7b75b442017-05-27 07:38:28 -0600243 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600244 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600245 if offset != -libfdt.FDT_ERR_NOTFOUND:
246 raise ValueError('Internal error, offset == %d' % offset)
247
248 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
249 while poffset >= 0:
250 p = fdt_obj.get_property_by_offset(poffset)
251 prop = self.props.get(p.name)
252 if not prop:
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)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600257
Simon Glass2a70d892016-07-25 18:59:14 -0600258 def DeleteProp(self, prop_name):
259 """Delete a property of a node
260
Simon Glass7b75b442017-05-27 07:38:28 -0600261 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600262
263 Args:
264 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600265 Raises:
266 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600267 """
Simon Glass96066242018-07-06 10:27:27 -0600268 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600269 "Node '%s': delete property: '%s'" % (self.path, prop_name))
270 del self.props[prop_name]
271 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600272
Simon Glass116adec2018-07-06 10:27:38 -0600273 def AddZeroProp(self, prop_name):
274 """Add a new property to the device tree with an integer value of 0.
275
276 Args:
277 prop_name: Name of property
278 """
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()
286
287 def SetInt(self, prop_name, val):
288 """Update an integer property int the device tree.
289
290 This is not allowed to change the size of the FDT.
291
292 Args:
293 prop_name: Name of property
294 val: Value to set
295 """
296 fdt_obj = self._fdt._fdt_obj
297 fdt_obj.setprop_u32(self.Offset(), prop_name, val)
298
299
Simon Glassa06a34b2016-07-25 18:59:04 -0600300class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600301 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600302
303 Properties:
304 fname: Filename of fdt
305 _root: Root of device tree (a Node object)
306 """
307 def __init__(self, fname):
308 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600309 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600310 self.phandle_to_node = {}
Simon Glass7b75b442017-05-27 07:38:28 -0600311 if self._fname:
312 self._fname = fdt_util.EnsureCompiled(self._fname)
313
314 with open(self._fname) as fd:
Simon Glass96066242018-07-06 10:27:27 -0600315 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600316
317 def Scan(self, root='/'):
318 """Scan a device tree, building up a tree of Node objects
319
320 This fills in the self._root property
321
322 Args:
323 root: Ignored
324
325 TODO(sjg@chromium.org): Implement the 'root' parameter
326 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600327 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600328 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600329 self._root.Scan()
330
331 def GetRoot(self):
332 """Get the root Node of the device tree
333
334 Returns:
335 The root Node object
336 """
337 return self._root
338
339 def GetNode(self, path):
340 """Look up a node from its path
341
342 Args:
343 path: Path to look up, e.g. '/microcode/update@0'
344 Returns:
345 Node object, or None if not found
346 """
347 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600348 parts = path.split('/')
349 if len(parts) < 2:
350 return None
351 for part in parts[1:]:
Simon Glassf7a2aee2016-07-25 18:59:07 -0600352 node = node._FindNode(part)
353 if not node:
354 return None
355 return node
356
Simon Glassda5f7492016-07-25 18:59:15 -0600357 def Flush(self):
358 """Flush device tree changes back to the file
359
360 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600361 """
Simon Glass7b75b442017-05-27 07:38:28 -0600362 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600363 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600364
365 def Pack(self):
366 """Pack the device tree down to its minimum size
367
368 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600369 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600370 """
Simon Glass117f57b2018-07-06 10:27:26 -0600371 CheckErr(self._fdt_obj.pack(), 'pack')
372 self.Invalidate()
Simon Glass7b75b442017-05-27 07:38:28 -0600373
Simon Glass96066242018-07-06 10:27:27 -0600374 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600375 """Get the contents of the FDT
376
377 Returns:
378 The FDT contents as a string of bytes
379 """
Simon Glass96066242018-07-06 10:27:27 -0600380 return self._fdt_obj.as_bytearray()
Simon Glass7b75b442017-05-27 07:38:28 -0600381
Simon Glass2ba98752018-07-06 10:27:24 -0600382 def GetFdtObj(self):
383 """Get the contents of the FDT
384
385 Returns:
386 The FDT contents as a libfdt.Fdt object
387 """
388 return self._fdt_obj
389
Simon Glass7b75b442017-05-27 07:38:28 -0600390 def GetProps(self, node):
391 """Get all properties from a node.
392
393 Args:
394 node: Full path to node name to look in.
395
396 Returns:
397 A dictionary containing all the properties, indexed by node name.
398 The entries are Prop objects.
399
400 Raises:
401 ValueError: if the node does not exist.
402 """
403 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600404 poffset = self._fdt_obj.first_property_offset(node._offset,
405 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600406 while poffset >= 0:
407 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600408 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600409 props_dict[prop.name] = prop
410
Simon Glass117f57b2018-07-06 10:27:26 -0600411 poffset = self._fdt_obj.next_property_offset(poffset,
412 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600413 return props_dict
414
415 def Invalidate(self):
416 """Mark our offset cache as invalid"""
417 self._cached_offsets = False
418
419 def CheckCache(self):
420 """Refresh the offset cache if needed"""
421 if self._cached_offsets:
422 return
423 self.Refresh()
424 self._cached_offsets = True
425
426 def Refresh(self):
427 """Refresh the offset cache"""
428 self._root.Refresh(0)
429
430 def GetStructOffset(self, offset):
431 """Get the file offset of a given struct offset
432
433 Args:
434 offset: Offset within the 'struct' region of the device tree
435 Returns:
436 Position of @offset within the device tree binary
437 """
Simon Glass117f57b2018-07-06 10:27:26 -0600438 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600439
440 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600441 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600442 """Create a new node
443
444 This is used by Fdt.Scan() to create a new node using the correct
445 class.
446
447 Args:
448 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600449 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600450 offset: Offset of node
451 name: Node name
452 path: Full path to node
453 """
Simon Glass979ab022017-08-29 14:15:47 -0600454 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600455 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600456
457def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600458 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600459 dtb = Fdt(fname)
460 dtb.Scan()
461 return dtb