blob: 5cde8c125ae64b1fd8d9634b76a76dea862911b6 [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 Glassa06a34b2016-07-25 18:59:04 -060013
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
Simon Glass99ed4a22017-05-27 07:38:30 -060016# 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.
Simon Glass7b75b442017-05-27 07:38:28 -060018
19# This implementation uses a libfdt Python library to access the device tree,
20# so it is fairly efficient.
Simon Glassa06a34b2016-07-25 18:59:04 -060021
Simon Glassbc1dea32016-07-25 18:59:05 -060022# A list of types we support
Simon Glassfbdfd222017-08-29 14:15:48 -060023(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
Simon Glassbc1dea32016-07-25 18:59:05 -060024
Simon Glassa06a34b2016-07-25 18:59:04 -060025def CheckErr(errnum, msg):
26 if errnum:
27 raise ValueError('Error %d: %s: %s' %
28 (errnum, libfdt.fdt_strerror(errnum), msg))
29
Simon Glass7b75b442017-05-27 07:38:28 -060030class Prop:
Simon Glassa06a34b2016-07-25 18:59:04 -060031 """A device tree property
32
33 Properties:
34 name: Property name (as per the device tree)
35 value: Property value as a string of bytes, or a list of strings of
36 bytes
37 type: Value type
38 """
Simon Glass7b75b442017-05-27 07:38:28 -060039 def __init__(self, node, offset, name, bytes):
Simon Glassa06a34b2016-07-25 18:59:04 -060040 self._node = node
41 self._offset = offset
42 self.name = name
43 self.value = None
Simon Glass7b75b442017-05-27 07:38:28 -060044 self.bytes = str(bytes)
45 if not bytes:
46 self.type = TYPE_BOOL
47 self.value = True
48 return
49 self.type, self.value = self.BytesToValue(bytes)
Simon Glassa06a34b2016-07-25 18:59:04 -060050
Simon Glassc322a852016-07-25 18:59:06 -060051 def GetPhandle(self):
52 """Get a (single) phandle value from a property
53
54 Gets the phandle valuie from a property and returns it as an integer
55 """
56 return fdt_util.fdt32_to_cpu(self.value[:4])
57
58 def Widen(self, newprop):
59 """Figure out which property type is more general
60
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
64 used for things like:
65
66 node1 {
67 compatible = "fred";
68 value = <1>;
69 };
70 node1 {
71 compatible = "fred";
72 value = <1 2>;
73 };
74
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
79 specific.
80 """
81 if newprop.type < self.type:
82 self.type = newprop.type
83
84 if type(newprop.value) == list and type(self.value) != list:
85 self.value = [self.value]
86
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)
91
Simon Glassbc1dea32016-07-25 18:59:05 -060092 def BytesToValue(self, bytes):
93 """Converts a string of bytes into a type and value
94
95 Args:
96 A string containing bytes
97
98 Return:
99 A tuple:
100 Type of data
101 Data, either a single element or a list of elements. Each element
102 is one of:
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
106 """
Simon Glassb4360202017-05-27 07:38:22 -0600107 bytes = str(bytes)
Simon Glassbc1dea32016-07-25 18:59:05 -0600108 size = len(bytes)
109 strings = bytes.split('\0')
110 is_string = True
111 count = len(strings) - 1
112 if count > 0 and not strings[-1]:
113 for string in strings[:-1]:
114 if not string:
115 is_string = False
116 break
117 for ch in string:
118 if ch < ' ' or ch > '~':
119 is_string = False
120 break
121 else:
122 is_string = False
123 if is_string:
124 if count == 1:
125 return TYPE_STRING, strings[0]
126 else:
127 return TYPE_STRING, strings[:-1]
128 if size % 4:
129 if size == 1:
130 return TYPE_BYTE, bytes[0]
131 else:
132 return TYPE_BYTE, list(bytes)
133 val = []
134 for i in range(0, size, 4):
135 val.append(bytes[i:i + 4])
136 if size == 4:
137 return TYPE_INT, val[0]
138 else:
139 return TYPE_INT, val
140
Simon Glass2ba98752018-07-06 10:27:24 -0600141 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600142 def GetEmpty(self, type):
143 """Get an empty / zero value of the given type
144
145 Returns:
146 A single value of the given type
147 """
148 if type == TYPE_BYTE:
149 return chr(0)
150 elif type == TYPE_INT:
151 return struct.pack('<I', 0);
152 elif type == TYPE_STRING:
153 return ''
154 else:
155 return True
156
Simon Glassbabdbde2016-07-25 18:59:16 -0600157 def GetOffset(self):
158 """Get the offset of a property
159
Simon Glassbabdbde2016-07-25 18:59:16 -0600160 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600161 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600162 """
Simon Glass7b75b442017-05-27 07:38:28 -0600163 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600164
Simon Glass7b75b442017-05-27 07:38:28 -0600165class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600166 """A device tree node
167
168 Properties:
169 offset: Integer offset in the device tree
170 name: Device tree node tname
171 path: Full path to node, along with the node name itself
172 _fdt: Device tree object
173 subnodes: A list of subnodes for this node, each a Node object
174 props: A dict of properties for this node, each a Prop object.
175 Keyed by property name
176 """
Simon Glass979ab022017-08-29 14:15:47 -0600177 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600178 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600179 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600180 self._offset = offset
181 self.name = name
182 self.path = path
183 self.subnodes = []
184 self.props = {}
185
Simon Glassf7a2aee2016-07-25 18:59:07 -0600186 def _FindNode(self, name):
187 """Find a node given its name
188
189 Args:
190 name: Node name to look for
191 Returns:
192 Node object if found, else None
193 """
194 for subnode in self.subnodes:
195 if subnode.name == name:
196 return subnode
197 return None
198
Simon Glass7b75b442017-05-27 07:38:28 -0600199 def Offset(self):
200 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600201
Simon Glass7b75b442017-05-27 07:38:28 -0600202 This should be used instead of self._offset directly, to ensure that
203 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600204 """
Simon Glass7b75b442017-05-27 07:38:28 -0600205 self._fdt.CheckCache()
206 return self._offset
207
208 def Scan(self):
209 """Scan a node's properties and subnodes
210
211 This fills in the props and subnodes properties, recursively
212 searching into subnodes so that the entire tree is built.
213 """
214 self.props = self._fdt.GetProps(self)
Simon Glass09264e02017-08-29 14:15:52 -0600215 phandle = self.props.get('phandle')
216 if phandle:
217 val = fdt_util.fdt32_to_cpu(phandle.value)
218 self._fdt.phandle_to_node[val] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600219
220 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
221 while offset >= 0:
222 sep = '' if self.path[-1] == '/' else '/'
223 name = self._fdt._fdt_obj.get_name(offset)
224 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600225 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600226 self.subnodes.append(node)
227
228 node.Scan()
229 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
230
231 def Refresh(self, my_offset):
232 """Fix up the _offset for each node, recursively
233
234 Note: This does not take account of property offsets - these will not
235 be updated.
236 """
237 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600238 self._offset = my_offset
239 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
240 for subnode in self.subnodes:
241 subnode.Refresh(offset)
242 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600243
Simon Glass2a70d892016-07-25 18:59:14 -0600244 def DeleteProp(self, prop_name):
245 """Delete a property of a node
246
Simon Glass7b75b442017-05-27 07:38:28 -0600247 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600248
249 Args:
250 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600251 Raises:
252 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600253 """
Simon Glass7b75b442017-05-27 07:38:28 -0600254 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
255 "Node '%s': delete property: '%s'" % (self.path, prop_name))
256 del self.props[prop_name]
257 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600258
Simon Glassa06a34b2016-07-25 18:59:04 -0600259class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600260 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600261
262 Properties:
263 fname: Filename of fdt
264 _root: Root of device tree (a Node object)
265 """
266 def __init__(self, fname):
267 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600268 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600269 self.phandle_to_node = {}
Simon Glass7b75b442017-05-27 07:38:28 -0600270 if self._fname:
271 self._fname = fdt_util.EnsureCompiled(self._fname)
272
273 with open(self._fname) as fd:
274 self._fdt = bytearray(fd.read())
275 self._fdt_obj = libfdt.Fdt(self._fdt)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600276
277 def Scan(self, root='/'):
278 """Scan a device tree, building up a tree of Node objects
279
280 This fills in the self._root property
281
282 Args:
283 root: Ignored
284
285 TODO(sjg@chromium.org): Implement the 'root' parameter
286 """
Simon Glass979ab022017-08-29 14:15:47 -0600287 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600288 self._root.Scan()
289
290 def GetRoot(self):
291 """Get the root Node of the device tree
292
293 Returns:
294 The root Node object
295 """
296 return self._root
297
298 def GetNode(self, path):
299 """Look up a node from its path
300
301 Args:
302 path: Path to look up, e.g. '/microcode/update@0'
303 Returns:
304 Node object, or None if not found
305 """
306 node = self._root
307 for part in path.split('/')[1:]:
308 node = node._FindNode(part)
309 if not node:
310 return None
311 return node
312
Simon Glassda5f7492016-07-25 18:59:15 -0600313 def Flush(self):
314 """Flush device tree changes back to the file
315
316 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600317 """
Simon Glass7b75b442017-05-27 07:38:28 -0600318 with open(self._fname, 'wb') as fd:
319 fd.write(self._fdt)
Simon Glassda5f7492016-07-25 18:59:15 -0600320
321 def Pack(self):
322 """Pack the device tree down to its minimum size
323
324 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600325 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600326 """
Simon Glass7b75b442017-05-27 07:38:28 -0600327 CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
328 fdt_len = libfdt.fdt_totalsize(self._fdt)
329 del self._fdt[fdt_len:]
330
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 = {}
366 poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
367 while poffset >= 0:
368 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600369 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600370 props_dict[prop.name] = prop
371
372 poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
373 return props_dict
374
375 def Invalidate(self):
376 """Mark our offset cache as invalid"""
377 self._cached_offsets = False
378
379 def CheckCache(self):
380 """Refresh the offset cache if needed"""
381 if self._cached_offsets:
382 return
383 self.Refresh()
384 self._cached_offsets = True
385
386 def Refresh(self):
387 """Refresh the offset cache"""
388 self._root.Refresh(0)
389
390 def GetStructOffset(self, offset):
391 """Get the file offset of a given struct offset
392
393 Args:
394 offset: Offset within the 'struct' region of the device tree
395 Returns:
396 Position of @offset within the device tree binary
397 """
398 return libfdt.fdt_off_dt_struct(self._fdt) + offset
399
400 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600401 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600402 """Create a new node
403
404 This is used by Fdt.Scan() to create a new node using the correct
405 class.
406
407 Args:
408 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600409 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600410 offset: Offset of node
411 name: Node name
412 path: Full path to node
413 """
Simon Glass979ab022017-08-29 14:15:47 -0600414 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600415 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600416
417def FdtScan(fname):
418 """Returns a new Fdt object from the implementation we are using"""
419 dtb = Fdt(fname)
420 dtb.Scan()
421 return dtb