blob: 63a32ea2d7bfcaeb9897406a986ff8dc65ac61c5 [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 """
177 def __init__(self, fdt, offset, name, path):
178 self._fdt = fdt
179 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)
214
215 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
216 while offset >= 0:
217 sep = '' if self.path[-1] == '/' else '/'
218 name = self._fdt._fdt_obj.get_name(offset)
219 path = self.path + sep + name
220 node = Node(self._fdt, offset, name, path)
221 self.subnodes.append(node)
222
223 node.Scan()
224 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
225
226 def Refresh(self, my_offset):
227 """Fix up the _offset for each node, recursively
228
229 Note: This does not take account of property offsets - these will not
230 be updated.
231 """
232 if self._offset != my_offset:
233 #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
234 self._offset = my_offset
235 offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
236 for subnode in self.subnodes:
237 subnode.Refresh(offset)
238 offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600239
Simon Glass2a70d892016-07-25 18:59:14 -0600240 def DeleteProp(self, prop_name):
241 """Delete a property of a node
242
Simon Glass7b75b442017-05-27 07:38:28 -0600243 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600244
245 Args:
246 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600247 Raises:
248 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600249 """
Simon Glass7b75b442017-05-27 07:38:28 -0600250 CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
251 "Node '%s': delete property: '%s'" % (self.path, prop_name))
252 del self.props[prop_name]
253 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600254
Simon Glassa06a34b2016-07-25 18:59:04 -0600255class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600256 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600257
258 Properties:
259 fname: Filename of fdt
260 _root: Root of device tree (a Node object)
261 """
262 def __init__(self, fname):
263 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600264 self._cached_offsets = False
265 if self._fname:
266 self._fname = fdt_util.EnsureCompiled(self._fname)
267
268 with open(self._fname) as fd:
269 self._fdt = bytearray(fd.read())
270 self._fdt_obj = libfdt.Fdt(self._fdt)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600271
272 def Scan(self, root='/'):
273 """Scan a device tree, building up a tree of Node objects
274
275 This fills in the self._root property
276
277 Args:
278 root: Ignored
279
280 TODO(sjg@chromium.org): Implement the 'root' parameter
281 """
282 self._root = self.Node(self, 0, '/', '/')
283 self._root.Scan()
284
285 def GetRoot(self):
286 """Get the root Node of the device tree
287
288 Returns:
289 The root Node object
290 """
291 return self._root
292
293 def GetNode(self, path):
294 """Look up a node from its path
295
296 Args:
297 path: Path to look up, e.g. '/microcode/update@0'
298 Returns:
299 Node object, or None if not found
300 """
301 node = self._root
302 for part in path.split('/')[1:]:
303 node = node._FindNode(part)
304 if not node:
305 return None
306 return node
307
Simon Glassda5f7492016-07-25 18:59:15 -0600308 def Flush(self):
309 """Flush device tree changes back to the file
310
311 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600312 """
Simon Glass7b75b442017-05-27 07:38:28 -0600313 with open(self._fname, 'wb') as fd:
314 fd.write(self._fdt)
Simon Glassda5f7492016-07-25 18:59:15 -0600315
316 def Pack(self):
317 """Pack the device tree down to its minimum size
318
319 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600320 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600321 """
Simon Glass7b75b442017-05-27 07:38:28 -0600322 CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
323 fdt_len = libfdt.fdt_totalsize(self._fdt)
324 del self._fdt[fdt_len:]
325
326 def GetFdt(self):
327 """Get the contents of the FDT
328
329 Returns:
330 The FDT contents as a string of bytes
331 """
332 return self._fdt
333
334 def CheckErr(errnum, msg):
335 if errnum:
336 raise ValueError('Error %d: %s: %s' %
337 (errnum, libfdt.fdt_strerror(errnum), msg))
338
339
340 def GetProps(self, node):
341 """Get all properties from a node.
342
343 Args:
344 node: Full path to node name to look in.
345
346 Returns:
347 A dictionary containing all the properties, indexed by node name.
348 The entries are Prop objects.
349
350 Raises:
351 ValueError: if the node does not exist.
352 """
353 props_dict = {}
354 poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
355 while poffset >= 0:
356 p = self._fdt_obj.get_property_by_offset(poffset)
357 prop = Prop(node, poffset, p.name, p.value)
358 props_dict[prop.name] = prop
359
360 poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
361 return props_dict
362
363 def Invalidate(self):
364 """Mark our offset cache as invalid"""
365 self._cached_offsets = False
366
367 def CheckCache(self):
368 """Refresh the offset cache if needed"""
369 if self._cached_offsets:
370 return
371 self.Refresh()
372 self._cached_offsets = True
373
374 def Refresh(self):
375 """Refresh the offset cache"""
376 self._root.Refresh(0)
377
378 def GetStructOffset(self, offset):
379 """Get the file offset of a given struct offset
380
381 Args:
382 offset: Offset within the 'struct' region of the device tree
383 Returns:
384 Position of @offset within the device tree binary
385 """
386 return libfdt.fdt_off_dt_struct(self._fdt) + offset
387
388 @classmethod
389 def Node(self, fdt, offset, name, path):
390 """Create a new node
391
392 This is used by Fdt.Scan() to create a new node using the correct
393 class.
394
395 Args:
396 fdt: Fdt object
397 offset: Offset of node
398 name: Node name
399 path: Full path to node
400 """
401 node = Node(fdt, offset, name, path)
402 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600403
404def FdtScan(fname):
405 """Returns a new Fdt object from the implementation we are using"""
406 dtb = Fdt(fname)
407 dtb.Scan()
408 return dtb