blob: 18cde2604ff5970a249e8a4e37add89fdf978324 [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 GetPhandle(self):
53 """Get a (single) phandle value from a property
54
55 Gets the phandle valuie from a property and returns it as an integer
56 """
57 return fdt_util.fdt32_to_cpu(self.value[:4])
58
59 def Widen(self, newprop):
60 """Figure out which property type is more general
61
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
65 used for things like:
66
67 node1 {
68 compatible = "fred";
69 value = <1>;
70 };
71 node1 {
72 compatible = "fred";
73 value = <1 2>;
74 };
75
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
80 specific.
81 """
82 if newprop.type < self.type:
83 self.type = newprop.type
84
85 if type(newprop.value) == list and type(self.value) != list:
86 self.value = [self.value]
87
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)
92
Simon Glassbc1dea32016-07-25 18:59:05 -060093 def BytesToValue(self, bytes):
94 """Converts a string of bytes into a type and value
95
96 Args:
97 A string containing bytes
98
99 Return:
100 A tuple:
101 Type of data
102 Data, either a single element or a list of elements. Each element
103 is one of:
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
107 """
Simon Glassb4360202017-05-27 07:38:22 -0600108 bytes = str(bytes)
Simon Glassbc1dea32016-07-25 18:59:05 -0600109 size = len(bytes)
110 strings = bytes.split('\0')
111 is_string = True
112 count = len(strings) - 1
113 if count > 0 and not strings[-1]:
114 for string in strings[:-1]:
115 if not string:
116 is_string = False
117 break
118 for ch in string:
119 if ch < ' ' or ch > '~':
120 is_string = False
121 break
122 else:
123 is_string = False
124 if is_string:
125 if count == 1:
126 return TYPE_STRING, strings[0]
127 else:
128 return TYPE_STRING, strings[:-1]
129 if size % 4:
130 if size == 1:
131 return TYPE_BYTE, bytes[0]
132 else:
133 return TYPE_BYTE, list(bytes)
134 val = []
135 for i in range(0, size, 4):
136 val.append(bytes[i:i + 4])
137 if size == 4:
138 return TYPE_INT, val[0]
139 else:
140 return TYPE_INT, val
141
Simon Glass2ba98752018-07-06 10:27:24 -0600142 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600143 def GetEmpty(self, type):
144 """Get an empty / zero value of the given type
145
146 Returns:
147 A single value of the given type
148 """
149 if type == TYPE_BYTE:
150 return chr(0)
151 elif type == TYPE_INT:
152 return struct.pack('<I', 0);
153 elif type == TYPE_STRING:
154 return ''
155 else:
156 return True
157
Simon Glassbabdbde2016-07-25 18:59:16 -0600158 def GetOffset(self):
159 """Get the offset of a property
160
Simon Glassbabdbde2016-07-25 18:59:16 -0600161 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600162 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600163 """
Simon Glass7b75b442017-05-27 07:38:28 -0600164 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600165
Simon Glass7b75b442017-05-27 07:38:28 -0600166class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600167 """A device tree node
168
169 Properties:
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
177 """
Simon Glass979ab022017-08-29 14:15:47 -0600178 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600179 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600180 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600181 self._offset = offset
182 self.name = name
183 self.path = path
184 self.subnodes = []
185 self.props = {}
186
Simon Glassf7a2aee2016-07-25 18:59:07 -0600187 def _FindNode(self, name):
188 """Find a node given its name
189
190 Args:
191 name: Node name to look for
192 Returns:
193 Node object if found, else None
194 """
195 for subnode in self.subnodes:
196 if subnode.name == name:
197 return subnode
198 return None
199
Simon Glass7b75b442017-05-27 07:38:28 -0600200 def Offset(self):
201 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600202
Simon Glass7b75b442017-05-27 07:38:28 -0600203 This should be used instead of self._offset directly, to ensure that
204 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600205 """
Simon Glass7b75b442017-05-27 07:38:28 -0600206 self._fdt.CheckCache()
207 return self._offset
208
209 def Scan(self):
210 """Scan a node's properties and subnodes
211
212 This fills in the props and subnodes properties, recursively
213 searching into subnodes so that the entire tree is built.
214 """
Simon Glass117f57b2018-07-06 10:27:26 -0600215 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600216 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600217 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600218 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600219 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600220
Simon Glass117f57b2018-07-06 10:27:26 -0600221 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600222 while offset >= 0:
223 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600224 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600225 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600226 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600227 self.subnodes.append(node)
228
229 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600230 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600231
232 def Refresh(self, my_offset):
233 """Fix up the _offset for each node, recursively
234
235 Note: This does not take account of property offsets - these will not
236 be updated.
237 """
238 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600239 self._offset = my_offset
240 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
241 for subnode in self.subnodes:
242 subnode.Refresh(offset)
243 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600244
Simon Glass2a70d892016-07-25 18:59:14 -0600245 def DeleteProp(self, prop_name):
246 """Delete a property of a node
247
Simon Glass7b75b442017-05-27 07:38:28 -0600248 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600249
250 Args:
251 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600252 Raises:
253 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600254 """
Simon Glass7b75b442017-05-27 07:38:28 -0600255 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
256 "Node '%s': delete property: '%s'" % (self.path, prop_name))
257 del self.props[prop_name]
258 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600259
Simon Glassa06a34b2016-07-25 18:59:04 -0600260class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600261 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600262
263 Properties:
264 fname: Filename of fdt
265 _root: Root of device tree (a Node object)
266 """
267 def __init__(self, fname):
268 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600269 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600270 self.phandle_to_node = {}
Simon Glass7b75b442017-05-27 07:38:28 -0600271 if self._fname:
272 self._fname = fdt_util.EnsureCompiled(self._fname)
273
274 with open(self._fname) as fd:
275 self._fdt = bytearray(fd.read())
276 self._fdt_obj = libfdt.Fdt(self._fdt)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600277
278 def Scan(self, root='/'):
279 """Scan a device tree, building up a tree of Node objects
280
281 This fills in the self._root property
282
283 Args:
284 root: Ignored
285
286 TODO(sjg@chromium.org): Implement the 'root' parameter
287 """
Simon Glass979ab022017-08-29 14:15:47 -0600288 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600289 self._root.Scan()
290
291 def GetRoot(self):
292 """Get the root Node of the device tree
293
294 Returns:
295 The root Node object
296 """
297 return self._root
298
299 def GetNode(self, path):
300 """Look up a node from its path
301
302 Args:
303 path: Path to look up, e.g. '/microcode/update@0'
304 Returns:
305 Node object, or None if not found
306 """
307 node = self._root
308 for part in path.split('/')[1:]:
309 node = node._FindNode(part)
310 if not node:
311 return None
312 return node
313
Simon Glassda5f7492016-07-25 18:59:15 -0600314 def Flush(self):
315 """Flush device tree changes back to the file
316
317 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600318 """
Simon Glass7b75b442017-05-27 07:38:28 -0600319 with open(self._fname, 'wb') as fd:
320 fd.write(self._fdt)
Simon Glassda5f7492016-07-25 18:59:15 -0600321
322 def Pack(self):
323 """Pack the device tree down to its minimum size
324
325 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600326 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600327 """
Simon Glass117f57b2018-07-06 10:27:26 -0600328 CheckErr(self._fdt_obj.pack(), 'pack')
329 self.Invalidate()
Simon Glass7b75b442017-05-27 07:38:28 -0600330
331 def GetFdt(self):
332 """Get the contents of the FDT
333
334 Returns:
335 The FDT contents as a string of bytes
336 """
337 return self._fdt
338
Simon Glass2ba98752018-07-06 10:27:24 -0600339 def GetFdtObj(self):
340 """Get the contents of the FDT
341
342 Returns:
343 The FDT contents as a libfdt.Fdt object
344 """
345 return self._fdt_obj
346
347 def CheckErr(self, errnum, msg):
Simon Glass7b75b442017-05-27 07:38:28 -0600348 if errnum:
349 raise ValueError('Error %d: %s: %s' %
350 (errnum, libfdt.fdt_strerror(errnum), msg))
351
Simon Glass7b75b442017-05-27 07:38:28 -0600352 def GetProps(self, node):
353 """Get all properties from a node.
354
355 Args:
356 node: Full path to node name to look in.
357
358 Returns:
359 A dictionary containing all the properties, indexed by node name.
360 The entries are Prop objects.
361
362 Raises:
363 ValueError: if the node does not exist.
364 """
365 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600366 poffset = self._fdt_obj.first_property_offset(node._offset,
367 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600368 while poffset >= 0:
369 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600370 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600371 props_dict[prop.name] = prop
372
Simon Glass117f57b2018-07-06 10:27:26 -0600373 poffset = self._fdt_obj.next_property_offset(poffset,
374 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600375 return props_dict
376
377 def Invalidate(self):
378 """Mark our offset cache as invalid"""
379 self._cached_offsets = False
380
381 def CheckCache(self):
382 """Refresh the offset cache if needed"""
383 if self._cached_offsets:
384 return
385 self.Refresh()
386 self._cached_offsets = True
387
388 def Refresh(self):
389 """Refresh the offset cache"""
390 self._root.Refresh(0)
391
392 def GetStructOffset(self, offset):
393 """Get the file offset of a given struct offset
394
395 Args:
396 offset: Offset within the 'struct' region of the device tree
397 Returns:
398 Position of @offset within the device tree binary
399 """
Simon Glass117f57b2018-07-06 10:27:26 -0600400 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600401
402 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600403 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600404 """Create a new node
405
406 This is used by Fdt.Scan() to create a new node using the correct
407 class.
408
409 Args:
410 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600411 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600412 offset: Offset of node
413 name: Node name
414 path: Full path to node
415 """
Simon Glass979ab022017-08-29 14:15:47 -0600416 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600417 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600418
419def FdtScan(fname):
420 """Returns a new Fdt object from the implementation we are using"""
421 dtb = Fdt(fname)
422 dtb.Scan()
423 return dtb