blob: 25ce5136ebff5871a2e106e45339d2ce7c1b363e [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
Simon Glass5ea9dcc2020-11-08 20:36:17 -07008from enum import IntEnum
Simon Glassa06a34b2016-07-25 18:59:04 -06009import struct
10import sys
11
Simon Glassbf776672020-04-17 18:09:04 -060012from dtoc import fdt_util
Simon Glass7b75b442017-05-27 07:38:28 -060013import libfdt
Simon Glass117f57b2018-07-06 10:27:26 -060014from libfdt import QUIET_NOTFOUND
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import tools
Simon Glassa06a34b2016-07-25 18:59:04 -060016
17# This deals with a device tree, presenting it as an assortment of Node and
18# Prop objects, representing nodes and properties, respectively. This file
Simon Glass99ed4a22017-05-27 07:38:30 -060019# contains the base classes and defines the high-level API. You can use
20# FdtScan() as a convenience function to create and scan an Fdt.
Simon Glass7b75b442017-05-27 07:38:28 -060021
22# This implementation uses a libfdt Python library to access the device tree,
23# so it is fairly efficient.
Simon Glassa06a34b2016-07-25 18:59:04 -060024
Simon Glassbc1dea32016-07-25 18:59:05 -060025# A list of types we support
Simon Glass5ea9dcc2020-11-08 20:36:17 -070026class Type(IntEnum):
27 (BYTE, INT, STRING, BOOL, INT64) = range(5)
28
29 def is_wider_than(self, other):
30 """Check if another type is 'wider' than this one
31
32 A wider type is one that holds more information than an earlier one,
33 similar to the concept of type-widening in C.
34
35 This uses a simple arithmetic comparison, since type values are in order
36 from narrowest (BYTE) to widest (INT64).
37
38 Args:
39 other: Other type to compare against
40
41 Return:
42 True if the other type is wider
43 """
44 return self.value > other.value
Simon Glassbc1dea32016-07-25 18:59:05 -060045
Simon Glassa06a34b2016-07-25 18:59:04 -060046def CheckErr(errnum, msg):
47 if errnum:
48 raise ValueError('Error %d: %s: %s' %
49 (errnum, libfdt.fdt_strerror(errnum), msg))
50
Simon Glass7e6952d2019-05-17 22:00:34 -060051
Simon Glass2b6ed5e2019-05-17 22:00:35 -060052def BytesToValue(data):
Simon Glass7e6952d2019-05-17 22:00:34 -060053 """Converts a string of bytes into a type and value
54
55 Args:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060056 A bytes value (which on Python 2 is an alias for str)
Simon Glass7e6952d2019-05-17 22:00:34 -060057
58 Return:
59 A tuple:
60 Type of data
61 Data, either a single element or a list of elements. Each element
62 is one of:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070063 Type.STRING: str/bytes value from the property
64 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
65 Type.BYTE: a byte stored as a single-byte str/bytes
Simon Glass7e6952d2019-05-17 22:00:34 -060066 """
Simon Glass2b6ed5e2019-05-17 22:00:35 -060067 data = bytes(data)
68 size = len(data)
69 strings = data.split(b'\0')
Simon Glass7e6952d2019-05-17 22:00:34 -060070 is_string = True
71 count = len(strings) - 1
Simon Glass2b6ed5e2019-05-17 22:00:35 -060072 if count > 0 and not len(strings[-1]):
Simon Glass7e6952d2019-05-17 22:00:34 -060073 for string in strings[:-1]:
74 if not string:
75 is_string = False
76 break
77 for ch in string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060078 if ch < 32 or ch > 127:
Simon Glass7e6952d2019-05-17 22:00:34 -060079 is_string = False
80 break
81 else:
82 is_string = False
83 if is_string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060084 if count == 1:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070085 return Type.STRING, strings[0].decode()
Simon Glass7e6952d2019-05-17 22:00:34 -060086 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070087 return Type.STRING, [s.decode() for s in strings[:-1]]
Simon Glass7e6952d2019-05-17 22:00:34 -060088 if size % 4:
89 if size == 1:
Simon Glass479dd302020-11-08 20:36:20 -070090 return Type.BYTE, chr(data[0])
Simon Glass7e6952d2019-05-17 22:00:34 -060091 else:
Simon Glass479dd302020-11-08 20:36:20 -070092 return Type.BYTE, [chr(ch) for ch in list(data)]
Simon Glass7e6952d2019-05-17 22:00:34 -060093 val = []
94 for i in range(0, size, 4):
Simon Glass2b6ed5e2019-05-17 22:00:35 -060095 val.append(data[i:i + 4])
Simon Glass7e6952d2019-05-17 22:00:34 -060096 if size == 4:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070097 return Type.INT, val[0]
Simon Glass7e6952d2019-05-17 22:00:34 -060098 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070099 return Type.INT, val
Simon Glass7e6952d2019-05-17 22:00:34 -0600100
101
Simon Glass7b75b442017-05-27 07:38:28 -0600102class Prop:
Simon Glassa06a34b2016-07-25 18:59:04 -0600103 """A device tree property
104
105 Properties:
106 name: Property name (as per the device tree)
107 value: Property value as a string of bytes, or a list of strings of
108 bytes
109 type: Value type
110 """
Simon Glass928527f2019-05-17 22:00:37 -0600111 def __init__(self, node, offset, name, data):
Simon Glassa06a34b2016-07-25 18:59:04 -0600112 self._node = node
113 self._offset = offset
114 self.name = name
115 self.value = None
Simon Glass928527f2019-05-17 22:00:37 -0600116 self.bytes = bytes(data)
Simon Glassfa80c252018-09-14 04:57:13 -0600117 self.dirty = False
Simon Glass928527f2019-05-17 22:00:37 -0600118 if not data:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700119 self.type = Type.BOOL
Simon Glass7b75b442017-05-27 07:38:28 -0600120 self.value = True
121 return
Simon Glass928527f2019-05-17 22:00:37 -0600122 self.type, self.value = BytesToValue(bytes(data))
Simon Glassa06a34b2016-07-25 18:59:04 -0600123
Simon Glassf9b88b32018-07-06 10:27:29 -0600124 def RefreshOffset(self, poffset):
125 self._offset = poffset
126
Simon Glassc322a852016-07-25 18:59:06 -0600127 def Widen(self, newprop):
128 """Figure out which property type is more general
129
130 Given a current property and a new property, this function returns the
131 one that is less specific as to type. The less specific property will
132 be ble to represent the data in the more specific property. This is
133 used for things like:
134
135 node1 {
136 compatible = "fred";
137 value = <1>;
138 };
139 node1 {
140 compatible = "fred";
141 value = <1 2>;
142 };
143
144 He we want to use an int array for 'value'. The first property
145 suggests that a single int is enough, but the second one shows that
146 it is not. Calling this function with these two propertes would
147 update the current property to be like the second, since it is less
148 specific.
149 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700150 if self.type.is_wider_than(newprop.type):
151 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glasse144caf2020-10-03 11:31:27 -0600152 if type(self.value) == list:
153 new_value = []
154 for val in self.value:
Simon Glass479dd302020-11-08 20:36:20 -0700155 new_value += [chr(by) for by in val]
Simon Glasse144caf2020-10-03 11:31:27 -0600156 else:
Simon Glass479dd302020-11-08 20:36:20 -0700157 new_value = [chr(by) for by in self.value]
Simon Glasse144caf2020-10-03 11:31:27 -0600158 self.value = new_value
Simon Glassc322a852016-07-25 18:59:06 -0600159 self.type = newprop.type
160
161 if type(newprop.value) == list and type(self.value) != list:
162 self.value = [self.value]
163
164 if type(self.value) == list and len(newprop.value) > len(self.value):
165 val = self.GetEmpty(self.type)
166 while len(self.value) < len(newprop.value):
167 self.value.append(val)
168
Simon Glass2ba98752018-07-06 10:27:24 -0600169 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600170 def GetEmpty(self, type):
171 """Get an empty / zero value of the given type
172
173 Returns:
174 A single value of the given type
175 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700176 if type == Type.BYTE:
Simon Glassbc1dea32016-07-25 18:59:05 -0600177 return chr(0)
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700178 elif type == Type.INT:
Simon Glassaf53f5a2018-09-14 04:57:14 -0600179 return struct.pack('>I', 0);
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700180 elif type == Type.STRING:
Simon Glassbc1dea32016-07-25 18:59:05 -0600181 return ''
182 else:
183 return True
184
Simon Glassbabdbde2016-07-25 18:59:16 -0600185 def GetOffset(self):
186 """Get the offset of a property
187
Simon Glassbabdbde2016-07-25 18:59:16 -0600188 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600189 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600190 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600191 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600192 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600193
Simon Glassfa80c252018-09-14 04:57:13 -0600194 def SetInt(self, val):
195 """Set the integer value of the property
196
197 The device tree is marked dirty so that the value will be written to
198 the block on the next sync.
199
200 Args:
201 val: Integer value (32-bit, single cell)
202 """
203 self.bytes = struct.pack('>I', val);
Simon Glass41b781d2018-10-01 12:22:49 -0600204 self.value = self.bytes
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700205 self.type = Type.INT
Simon Glassfa80c252018-09-14 04:57:13 -0600206 self.dirty = True
207
Simon Glass64349612018-09-14 04:57:16 -0600208 def SetData(self, bytes):
209 """Set the value of a property as bytes
210
211 Args:
212 bytes: New property value to set
213 """
Simon Glassf6b64812019-05-17 22:00:36 -0600214 self.bytes = bytes
Simon Glass7e6952d2019-05-17 22:00:34 -0600215 self.type, self.value = BytesToValue(bytes)
Simon Glass64349612018-09-14 04:57:16 -0600216 self.dirty = True
217
Simon Glassfa80c252018-09-14 04:57:13 -0600218 def Sync(self, auto_resize=False):
219 """Sync property changes back to the device tree
220
221 This updates the device tree blob with any changes to this property
222 since the last sync.
223
224 Args:
225 auto_resize: Resize the device tree automatically if it does not
226 have enough space for the update
227
228 Raises:
229 FdtException if auto_resize is False and there is not enough space
230 """
231 if self._offset is None or self.dirty:
232 node = self._node
233 fdt_obj = node._fdt._fdt_obj
234 if auto_resize:
235 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
236 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassc0639172020-07-09 18:39:44 -0600237 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
238 len(self.bytes))
Simon Glassfa80c252018-09-14 04:57:13 -0600239 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
240 else:
241 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
242
243
Simon Glass7b75b442017-05-27 07:38:28 -0600244class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600245 """A device tree node
246
247 Properties:
248 offset: Integer offset in the device tree
249 name: Device tree node tname
250 path: Full path to node, along with the node name itself
251 _fdt: Device tree object
252 subnodes: A list of subnodes for this node, each a Node object
253 props: A dict of properties for this node, each a Prop object.
254 Keyed by property name
255 """
Simon Glass979ab022017-08-29 14:15:47 -0600256 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600257 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600258 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600259 self._offset = offset
260 self.name = name
261 self.path = path
262 self.subnodes = []
263 self.props = {}
264
Simon Glass94a7c602018-07-17 13:25:46 -0600265 def GetFdt(self):
266 """Get the Fdt object for this node
267
268 Returns:
269 Fdt object
270 """
271 return self._fdt
272
Simon Glass1d858882018-07-17 13:25:41 -0600273 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600274 """Find a node given its name
275
276 Args:
277 name: Node name to look for
278 Returns:
279 Node object if found, else None
280 """
281 for subnode in self.subnodes:
282 if subnode.name == name:
283 return subnode
284 return None
285
Simon Glass7b75b442017-05-27 07:38:28 -0600286 def Offset(self):
287 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600288
Simon Glass7b75b442017-05-27 07:38:28 -0600289 This should be used instead of self._offset directly, to ensure that
290 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600291 """
Simon Glass7b75b442017-05-27 07:38:28 -0600292 self._fdt.CheckCache()
293 return self._offset
294
295 def Scan(self):
296 """Scan a node's properties and subnodes
297
298 This fills in the props and subnodes properties, recursively
299 searching into subnodes so that the entire tree is built.
300 """
Simon Glass117f57b2018-07-06 10:27:26 -0600301 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600302 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600303 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600304 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600305 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600306
Simon Glass117f57b2018-07-06 10:27:26 -0600307 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600308 while offset >= 0:
309 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600310 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600311 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600312 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600313 self.subnodes.append(node)
314
315 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600316 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600317
318 def Refresh(self, my_offset):
319 """Fix up the _offset for each node, recursively
320
321 Note: This does not take account of property offsets - these will not
322 be updated.
323 """
Simon Glass96066242018-07-06 10:27:27 -0600324 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600325 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600326 self._offset = my_offset
Simon Glass96066242018-07-06 10:27:27 -0600327 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600328 for subnode in self.subnodes:
Simon Glassf9b88b32018-07-06 10:27:29 -0600329 if subnode.name != fdt_obj.get_name(offset):
330 raise ValueError('Internal error, node name mismatch %s != %s' %
331 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600332 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600333 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600334 if offset != -libfdt.FDT_ERR_NOTFOUND:
335 raise ValueError('Internal error, offset == %d' % offset)
336
337 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
338 while poffset >= 0:
339 p = fdt_obj.get_property_by_offset(poffset)
340 prop = self.props.get(p.name)
341 if not prop:
342 raise ValueError("Internal error, property '%s' missing, "
343 'offset %d' % (p.name, poffset))
344 prop.RefreshOffset(poffset)
345 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600346
Simon Glass2a70d892016-07-25 18:59:14 -0600347 def DeleteProp(self, prop_name):
348 """Delete a property of a node
349
Simon Glass7b75b442017-05-27 07:38:28 -0600350 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600351
352 Args:
353 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600354 Raises:
355 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600356 """
Simon Glass96066242018-07-06 10:27:27 -0600357 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600358 "Node '%s': delete property: '%s'" % (self.path, prop_name))
359 del self.props[prop_name]
360 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600361
Simon Glass116adec2018-07-06 10:27:38 -0600362 def AddZeroProp(self, prop_name):
363 """Add a new property to the device tree with an integer value of 0.
364
365 Args:
366 prop_name: Name of property
367 """
Simon Glass194b8d52019-05-17 22:00:33 -0600368 self.props[prop_name] = Prop(self, None, prop_name,
369 tools.GetBytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600370
Simon Glass64349612018-09-14 04:57:16 -0600371 def AddEmptyProp(self, prop_name, len):
372 """Add a property with a fixed data size, for filling in later
373
374 The device tree is marked dirty so that the value will be written to
375 the blob on the next sync.
376
377 Args:
378 prop_name: Name of property
379 len: Length of data in property
380 """
Simon Glass194b8d52019-05-17 22:00:33 -0600381 value = tools.GetBytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600382 self.props[prop_name] = Prop(self, None, prop_name, value)
383
Simon Glassd9dad102019-07-20 12:23:37 -0600384 def _CheckProp(self, prop_name):
385 """Check if a property is present
386
387 Args:
388 prop_name: Name of property
389
390 Returns:
391 self
392
393 Raises:
394 ValueError if the property is missing
395 """
396 if prop_name not in self.props:
397 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
398 (self._fdt._fname, self.path, prop_name))
399 return self
400
Simon Glass116adec2018-07-06 10:27:38 -0600401 def SetInt(self, prop_name, val):
402 """Update an integer property int the device tree.
403
404 This is not allowed to change the size of the FDT.
405
Simon Glass64349612018-09-14 04:57:16 -0600406 The device tree is marked dirty so that the value will be written to
407 the blob on the next sync.
408
Simon Glass116adec2018-07-06 10:27:38 -0600409 Args:
410 prop_name: Name of property
411 val: Value to set
412 """
Simon Glassd9dad102019-07-20 12:23:37 -0600413 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600414
Simon Glass64349612018-09-14 04:57:16 -0600415 def SetData(self, prop_name, val):
416 """Set the data value of a property
417
418 The device tree is marked dirty so that the value will be written to
419 the blob on the next sync.
420
421 Args:
422 prop_name: Name of property to set
423 val: Data value to set
424 """
Simon Glassd9dad102019-07-20 12:23:37 -0600425 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600426
427 def SetString(self, prop_name, val):
428 """Set the string value of a property
429
430 The device tree is marked dirty so that the value will be written to
431 the blob on the next sync.
432
433 Args:
434 prop_name: Name of property to set
435 val: String value to set (will be \0-terminated in DT)
436 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600437 if type(val) == str:
438 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600439 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600440
Simon Glassc0639172020-07-09 18:39:44 -0600441 def AddData(self, prop_name, val):
442 """Add a new property to a node
443
444 The device tree is marked dirty so that the value will be written to
445 the blob on the next sync.
446
447 Args:
448 prop_name: Name of property to add
449 val: Bytes value of property
450 """
451 self.props[prop_name] = Prop(self, None, prop_name, val)
452
Simon Glass64349612018-09-14 04:57:16 -0600453 def AddString(self, prop_name, val):
454 """Add a new string property to a node
455
456 The device tree is marked dirty so that the value will be written to
457 the blob on the next sync.
458
459 Args:
460 prop_name: Name of property to add
461 val: String value of property
462 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700463 val = bytes(val, 'utf-8')
Simon Glassc0639172020-07-09 18:39:44 -0600464 self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600465
Simon Glass6eb99322021-01-06 21:35:18 -0700466 def AddInt(self, prop_name, val):
467 """Add a new integer property to a node
468
469 The device tree is marked dirty so that the value will be written to
470 the blob on the next sync.
471
472 Args:
473 prop_name: Name of property to add
474 val: Integer value of property
475 """
476 self.AddData(prop_name, struct.pack('>I', val))
477
Simon Glasse21c27a2018-09-14 04:57:15 -0600478 def AddSubnode(self, name):
Simon Glass64349612018-09-14 04:57:16 -0600479 """Add a new subnode to the node
480
481 Args:
482 name: name of node to add
483
484 Returns:
485 New subnode that was created
486 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600487 path = self.path + '/' + name
488 subnode = Node(self._fdt, self, None, name, path)
489 self.subnodes.append(subnode)
490 return subnode
491
Simon Glassfa80c252018-09-14 04:57:13 -0600492 def Sync(self, auto_resize=False):
493 """Sync node changes back to the device tree
494
495 This updates the device tree blob with any changes to this node and its
496 subnodes since the last sync.
497
498 Args:
499 auto_resize: Resize the device tree automatically if it does not
500 have enough space for the update
501
502 Raises:
503 FdtException if auto_resize is False and there is not enough space
504 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600505 if self._offset is None:
506 # The subnode doesn't exist yet, so add it
507 fdt_obj = self._fdt._fdt_obj
508 if auto_resize:
509 while True:
510 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
511 (libfdt.NOSPACE,))
512 if offset != -libfdt.NOSPACE:
513 break
514 fdt_obj.resize(fdt_obj.totalsize() + 1024)
515 else:
516 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
517 self._offset = offset
518
Simon Glassfa80c252018-09-14 04:57:13 -0600519 # Sync subnodes in reverse so that we don't disturb node offsets for
520 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
521 # node offsets.
522 for node in reversed(self.subnodes):
523 node.Sync(auto_resize)
524
525 # Sync properties now, whose offsets should not have been disturbed.
526 # We do this after subnodes, since this disturbs the offsets of these
Simon Glass63518052019-05-17 22:00:38 -0600527 # properties. Note that new properties will have an offset of None here,
528 # which Python 3 cannot sort against int. So use a large value instead
529 # to ensure that the new properties are added first.
530 prop_list = sorted(self.props.values(),
531 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600532 reverse=True)
533 for prop in prop_list:
534 prop.Sync(auto_resize)
Simon Glass116adec2018-07-06 10:27:38 -0600535
536
Simon Glassa06a34b2016-07-25 18:59:04 -0600537class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600538 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600539
540 Properties:
541 fname: Filename of fdt
542 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600543 name: Helpful name for this Fdt for the user (useful when creating the
544 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600545 """
546 def __init__(self, fname):
547 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600548 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600549 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600550 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600551 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600552 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600553 self._fname = fdt_util.EnsureCompiled(self._fname)
554
Simon Glass3e4b51e2019-05-14 15:53:43 -0600555 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600556 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600557
Simon Glass746aee32018-09-14 04:57:17 -0600558 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600559 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600560 """Create a new Fdt object from the given data
561
562 Args:
563 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600564 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600565
566 Returns:
567 Fdt object containing the data
568 """
569 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600570 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600571 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600572 return fdt
573
Simon Glass94a7c602018-07-17 13:25:46 -0600574 def LookupPhandle(self, phandle):
575 """Look up a phandle
576
577 Args:
578 phandle: Phandle to look up (int)
579
580 Returns:
581 Node object the phandle points to
582 """
583 return self.phandle_to_node.get(phandle)
584
Simon Glassf7a2aee2016-07-25 18:59:07 -0600585 def Scan(self, root='/'):
586 """Scan a device tree, building up a tree of Node objects
587
588 This fills in the self._root property
589
590 Args:
591 root: Ignored
592
593 TODO(sjg@chromium.org): Implement the 'root' parameter
594 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600595 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600596 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600597 self._root.Scan()
598
599 def GetRoot(self):
600 """Get the root Node of the device tree
601
602 Returns:
603 The root Node object
604 """
605 return self._root
606
607 def GetNode(self, path):
608 """Look up a node from its path
609
610 Args:
611 path: Path to look up, e.g. '/microcode/update@0'
612 Returns:
613 Node object, or None if not found
614 """
615 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600616 parts = path.split('/')
617 if len(parts) < 2:
618 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600619 if len(parts) == 2 and parts[1] == '':
620 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600621 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600622 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600623 if not node:
624 return None
625 return node
626
Simon Glassda5f7492016-07-25 18:59:15 -0600627 def Flush(self):
628 """Flush device tree changes back to the file
629
630 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600631 """
Simon Glass7b75b442017-05-27 07:38:28 -0600632 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600633 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600634
Simon Glassfa80c252018-09-14 04:57:13 -0600635 def Sync(self, auto_resize=False):
636 """Make sure any DT changes are written to the blob
637
638 Args:
639 auto_resize: Resize the device tree automatically if it does not
640 have enough space for the update
641
642 Raises:
643 FdtException if auto_resize is False and there is not enough space
644 """
645 self._root.Sync(auto_resize)
646 self.Invalidate()
647
Simon Glassda5f7492016-07-25 18:59:15 -0600648 def Pack(self):
649 """Pack the device tree down to its minimum size
650
651 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600652 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600653 """
Simon Glass117f57b2018-07-06 10:27:26 -0600654 CheckErr(self._fdt_obj.pack(), 'pack')
655 self.Invalidate()
Simon Glass7b75b442017-05-27 07:38:28 -0600656
Simon Glass96066242018-07-06 10:27:27 -0600657 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600658 """Get the contents of the FDT
659
660 Returns:
661 The FDT contents as a string of bytes
662 """
Simon Glassf6b64812019-05-17 22:00:36 -0600663 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600664
Simon Glass2ba98752018-07-06 10:27:24 -0600665 def GetFdtObj(self):
666 """Get the contents of the FDT
667
668 Returns:
669 The FDT contents as a libfdt.Fdt object
670 """
671 return self._fdt_obj
672
Simon Glass7b75b442017-05-27 07:38:28 -0600673 def GetProps(self, node):
674 """Get all properties from a node.
675
676 Args:
677 node: Full path to node name to look in.
678
679 Returns:
680 A dictionary containing all the properties, indexed by node name.
681 The entries are Prop objects.
682
683 Raises:
684 ValueError: if the node does not exist.
685 """
686 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600687 poffset = self._fdt_obj.first_property_offset(node._offset,
688 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600689 while poffset >= 0:
690 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600691 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600692 props_dict[prop.name] = prop
693
Simon Glass117f57b2018-07-06 10:27:26 -0600694 poffset = self._fdt_obj.next_property_offset(poffset,
695 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600696 return props_dict
697
698 def Invalidate(self):
699 """Mark our offset cache as invalid"""
700 self._cached_offsets = False
701
702 def CheckCache(self):
703 """Refresh the offset cache if needed"""
704 if self._cached_offsets:
705 return
706 self.Refresh()
707 self._cached_offsets = True
708
709 def Refresh(self):
710 """Refresh the offset cache"""
711 self._root.Refresh(0)
712
713 def GetStructOffset(self, offset):
714 """Get the file offset of a given struct offset
715
716 Args:
717 offset: Offset within the 'struct' region of the device tree
718 Returns:
719 Position of @offset within the device tree binary
720 """
Simon Glass117f57b2018-07-06 10:27:26 -0600721 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600722
723 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600724 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600725 """Create a new node
726
727 This is used by Fdt.Scan() to create a new node using the correct
728 class.
729
730 Args:
731 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600732 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600733 offset: Offset of node
734 name: Node name
735 path: Full path to node
736 """
Simon Glass979ab022017-08-29 14:15:47 -0600737 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600738 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600739
Simon Glassf6e02492019-07-20 12:24:08 -0600740 def GetFilename(self):
741 """Get the filename of the device tree
742
743 Returns:
744 String filename
745 """
746 return self._fname
747
Simon Glass99ed4a22017-05-27 07:38:30 -0600748def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600749 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600750 dtb = Fdt(fname)
751 dtb.Scan()
752 return dtb