blob: d08b0b53e613e718aa85cb8256121d1654bd8bd5 [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
141 def GetEmpty(self, type):
142 """Get an empty / zero value of the given type
143
144 Returns:
145 A single value of the given type
146 """
147 if type == TYPE_BYTE:
148 return chr(0)
149 elif type == TYPE_INT:
150 return struct.pack('<I', 0);
151 elif type == TYPE_STRING:
152 return ''
153 else:
154 return True
155
Simon Glassbabdbde2016-07-25 18:59:16 -0600156 def GetOffset(self):
157 """Get the offset of a property
158
Simon Glassbabdbde2016-07-25 18:59:16 -0600159 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600160 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600161 """
Simon Glass7b75b442017-05-27 07:38:28 -0600162 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600163
Simon Glass7b75b442017-05-27 07:38:28 -0600164class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600165 """A device tree node
166
167 Properties:
168 offset: Integer offset in the device tree
169 name: Device tree node tname
170 path: Full path to node, along with the node name itself
171 _fdt: Device tree object
172 subnodes: A list of subnodes for this node, each a Node object
173 props: A dict of properties for this node, each a Prop object.
174 Keyed by property name
175 """
Simon Glass979ab022017-08-29 14:15:47 -0600176 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600177 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600178 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600179 self._offset = offset
180 self.name = name
181 self.path = path
182 self.subnodes = []
183 self.props = {}
184
Simon Glassf7a2aee2016-07-25 18:59:07 -0600185 def _FindNode(self, name):
186 """Find a node given its name
187
188 Args:
189 name: Node name to look for
190 Returns:
191 Node object if found, else None
192 """
193 for subnode in self.subnodes:
194 if subnode.name == name:
195 return subnode
196 return None
197
Simon Glass7b75b442017-05-27 07:38:28 -0600198 def Offset(self):
199 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600200
Simon Glass7b75b442017-05-27 07:38:28 -0600201 This should be used instead of self._offset directly, to ensure that
202 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600203 """
Simon Glass7b75b442017-05-27 07:38:28 -0600204 self._fdt.CheckCache()
205 return self._offset
206
207 def Scan(self):
208 """Scan a node's properties and subnodes
209
210 This fills in the props and subnodes properties, recursively
211 searching into subnodes so that the entire tree is built.
212 """
213 self.props = self._fdt.GetProps(self)
Simon Glass09264e02017-08-29 14:15:52 -0600214 phandle = self.props.get('phandle')
215 if phandle:
216 val = fdt_util.fdt32_to_cpu(phandle.value)
217 self._fdt.phandle_to_node[val] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600218
219 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
220 while offset >= 0:
221 sep = '' if self.path[-1] == '/' else '/'
222 name = self._fdt._fdt_obj.get_name(offset)
223 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600224 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600225 self.subnodes.append(node)
226
227 node.Scan()
228 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
229
230 def Refresh(self, my_offset):
231 """Fix up the _offset for each node, recursively
232
233 Note: This does not take account of property offsets - these will not
234 be updated.
235 """
236 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600237 self._offset = my_offset
238 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
239 for subnode in self.subnodes:
240 subnode.Refresh(offset)
241 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600242
Simon Glass2a70d892016-07-25 18:59:14 -0600243 def DeleteProp(self, prop_name):
244 """Delete a property of a node
245
Simon Glass7b75b442017-05-27 07:38:28 -0600246 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600247
248 Args:
249 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600250 Raises:
251 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600252 """
Simon Glass7b75b442017-05-27 07:38:28 -0600253 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
254 "Node '%s': delete property: '%s'" % (self.path, prop_name))
255 del self.props[prop_name]
256 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600257
Simon Glassa06a34b2016-07-25 18:59:04 -0600258class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600259 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600260
261 Properties:
262 fname: Filename of fdt
263 _root: Root of device tree (a Node object)
264 """
265 def __init__(self, fname):
266 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600267 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600268 self.phandle_to_node = {}
Simon Glass7b75b442017-05-27 07:38:28 -0600269 if self._fname:
270 self._fname = fdt_util.EnsureCompiled(self._fname)
271
272 with open(self._fname) as fd:
273 self._fdt = bytearray(fd.read())
274 self._fdt_obj = libfdt.Fdt(self._fdt)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600275
276 def Scan(self, root='/'):
277 """Scan a device tree, building up a tree of Node objects
278
279 This fills in the self._root property
280
281 Args:
282 root: Ignored
283
284 TODO(sjg@chromium.org): Implement the 'root' parameter
285 """
Simon Glass979ab022017-08-29 14:15:47 -0600286 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600287 self._root.Scan()
288
289 def GetRoot(self):
290 """Get the root Node of the device tree
291
292 Returns:
293 The root Node object
294 """
295 return self._root
296
297 def GetNode(self, path):
298 """Look up a node from its path
299
300 Args:
301 path: Path to look up, e.g. '/microcode/update@0'
302 Returns:
303 Node object, or None if not found
304 """
305 node = self._root
306 for part in path.split('/')[1:]:
307 node = node._FindNode(part)
308 if not node:
309 return None
310 return node
311
Simon Glassda5f7492016-07-25 18:59:15 -0600312 def Flush(self):
313 """Flush device tree changes back to the file
314
315 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600316 """
Simon Glass7b75b442017-05-27 07:38:28 -0600317 with open(self._fname, 'wb') as fd:
318 fd.write(self._fdt)
Simon Glassda5f7492016-07-25 18:59:15 -0600319
320 def Pack(self):
321 """Pack the device tree down to its minimum size
322
323 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600324 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600325 """
Simon Glass7b75b442017-05-27 07:38:28 -0600326 CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
327 fdt_len = libfdt.fdt_totalsize(self._fdt)
328 del self._fdt[fdt_len:]
329
330 def GetFdt(self):
331 """Get the contents of the FDT
332
333 Returns:
334 The FDT contents as a string of bytes
335 """
336 return self._fdt
337
338 def CheckErr(errnum, msg):
339 if errnum:
340 raise ValueError('Error %d: %s: %s' %
341 (errnum, libfdt.fdt_strerror(errnum), msg))
342
343
344 def GetProps(self, node):
345 """Get all properties from a node.
346
347 Args:
348 node: Full path to node name to look in.
349
350 Returns:
351 A dictionary containing all the properties, indexed by node name.
352 The entries are Prop objects.
353
354 Raises:
355 ValueError: if the node does not exist.
356 """
357 props_dict = {}
358 poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
359 while poffset >= 0:
360 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600361 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600362 props_dict[prop.name] = prop
363
364 poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
365 return props_dict
366
367 def Invalidate(self):
368 """Mark our offset cache as invalid"""
369 self._cached_offsets = False
370
371 def CheckCache(self):
372 """Refresh the offset cache if needed"""
373 if self._cached_offsets:
374 return
375 self.Refresh()
376 self._cached_offsets = True
377
378 def Refresh(self):
379 """Refresh the offset cache"""
380 self._root.Refresh(0)
381
382 def GetStructOffset(self, offset):
383 """Get the file offset of a given struct offset
384
385 Args:
386 offset: Offset within the 'struct' region of the device tree
387 Returns:
388 Position of @offset within the device tree binary
389 """
390 return libfdt.fdt_off_dt_struct(self._fdt) + offset
391
392 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600393 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600394 """Create a new node
395
396 This is used by Fdt.Scan() to create a new node using the correct
397 class.
398
399 Args:
400 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600401 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600402 offset: Offset of node
403 name: Node name
404 path: Full path to node
405 """
Simon Glass979ab022017-08-29 14:15:47 -0600406 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600407 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600408
409def FdtScan(fname):
410 """Returns a new Fdt object from the implementation we are using"""
411 dtb = Fdt(fname)
412 dtb.Scan()
413 return dtb