blob: 49409a62ec23380f486c4e8ddc494d071a9adc7c [file] [log] [blame]
Simon Glassa06a34b2016-07-25 18:59:04 -06001#!/usr/bin/python
2#
3# Copyright (C) 2016 Google, Inc
4# Written by Simon Glass <sjg@chromium.org>
5#
6# SPDX-License-Identifier: GPL-2.0+
7#
8
9import struct
10import sys
11
12import fdt_util
Simon Glass7b75b442017-05-27 07:38:28 -060013import libfdt
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
24(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL) = range(4)
25
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
142 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)
215
216 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
217 while offset >= 0:
218 sep = '' if self.path[-1] == '/' else '/'
219 name = self._fdt._fdt_obj.get_name(offset)
220 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600221 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600222 self.subnodes.append(node)
223
224 node.Scan()
225 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
226
227 def Refresh(self, my_offset):
228 """Fix up the _offset for each node, recursively
229
230 Note: This does not take account of property offsets - these will not
231 be updated.
232 """
233 if self._offset != my_offset:
234 #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
235 self._offset = my_offset
236 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
237 for subnode in self.subnodes:
238 subnode.Refresh(offset)
239 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600240
Simon Glass2a70d892016-07-25 18:59:14 -0600241 def DeleteProp(self, prop_name):
242 """Delete a property of a node
243
Simon Glass7b75b442017-05-27 07:38:28 -0600244 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600245
246 Args:
247 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600248 Raises:
249 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600250 """
Simon Glass7b75b442017-05-27 07:38:28 -0600251 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
252 "Node '%s': delete property: '%s'" % (self.path, prop_name))
253 del self.props[prop_name]
254 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600255
Simon Glassa06a34b2016-07-25 18:59:04 -0600256class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600257 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600258
259 Properties:
260 fname: Filename of fdt
261 _root: Root of device tree (a Node object)
262 """
263 def __init__(self, fname):
264 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600265 self._cached_offsets = False
266 if self._fname:
267 self._fname = fdt_util.EnsureCompiled(self._fname)
268
269 with open(self._fname) as fd:
270 self._fdt = bytearray(fd.read())
271 self._fdt_obj = libfdt.Fdt(self._fdt)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600272
273 def Scan(self, root='/'):
274 """Scan a device tree, building up a tree of Node objects
275
276 This fills in the self._root property
277
278 Args:
279 root: Ignored
280
281 TODO(sjg@chromium.org): Implement the 'root' parameter
282 """
Simon Glass979ab022017-08-29 14:15:47 -0600283 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600284 self._root.Scan()
285
286 def GetRoot(self):
287 """Get the root Node of the device tree
288
289 Returns:
290 The root Node object
291 """
292 return self._root
293
294 def GetNode(self, path):
295 """Look up a node from its path
296
297 Args:
298 path: Path to look up, e.g. '/microcode/update@0'
299 Returns:
300 Node object, or None if not found
301 """
302 node = self._root
303 for part in path.split('/')[1:]:
304 node = node._FindNode(part)
305 if not node:
306 return None
307 return node
308
Simon Glassda5f7492016-07-25 18:59:15 -0600309 def Flush(self):
310 """Flush device tree changes back to the file
311
312 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600313 """
Simon Glass7b75b442017-05-27 07:38:28 -0600314 with open(self._fname, 'wb') as fd:
315 fd.write(self._fdt)
Simon Glassda5f7492016-07-25 18:59:15 -0600316
317 def Pack(self):
318 """Pack the device tree down to its minimum size
319
320 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600321 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600322 """
Simon Glass7b75b442017-05-27 07:38:28 -0600323 CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
324 fdt_len = libfdt.fdt_totalsize(self._fdt)
325 del self._fdt[fdt_len:]
326
327 def GetFdt(self):
328 """Get the contents of the FDT
329
330 Returns:
331 The FDT contents as a string of bytes
332 """
333 return self._fdt
334
335 def CheckErr(errnum, msg):
336 if errnum:
337 raise ValueError('Error %d: %s: %s' %
338 (errnum, libfdt.fdt_strerror(errnum), msg))
339
340
341 def GetProps(self, node):
342 """Get all properties from a node.
343
344 Args:
345 node: Full path to node name to look in.
346
347 Returns:
348 A dictionary containing all the properties, indexed by node name.
349 The entries are Prop objects.
350
351 Raises:
352 ValueError: if the node does not exist.
353 """
354 props_dict = {}
355 poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
356 while poffset >= 0:
357 p = self._fdt_obj.get_property_by_offset(poffset)
358 prop = Prop(node, poffset, p.name, p.value)
359 props_dict[prop.name] = prop
360
361 poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
362 return props_dict
363
364 def Invalidate(self):
365 """Mark our offset cache as invalid"""
366 self._cached_offsets = False
367
368 def CheckCache(self):
369 """Refresh the offset cache if needed"""
370 if self._cached_offsets:
371 return
372 self.Refresh()
373 self._cached_offsets = True
374
375 def Refresh(self):
376 """Refresh the offset cache"""
377 self._root.Refresh(0)
378
379 def GetStructOffset(self, offset):
380 """Get the file offset of a given struct offset
381
382 Args:
383 offset: Offset within the 'struct' region of the device tree
384 Returns:
385 Position of @offset within the device tree binary
386 """
387 return libfdt.fdt_off_dt_struct(self._fdt) + offset
388
389 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600390 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600391 """Create a new node
392
393 This is used by Fdt.Scan() to create a new node using the correct
394 class.
395
396 Args:
397 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600398 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600399 offset: Offset of node
400 name: Node name
401 path: Full path to node
402 """
Simon Glass979ab022017-08-29 14:15:47 -0600403 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600404 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600405
406def FdtScan(fname):
407 """Returns a new Fdt object from the implementation we are using"""
408 dtb = Fdt(fname)
409 dtb.Scan()
410 return dtb