blob: 3996971e39cde85ab52735ae58d7d8ab8477dc0f [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:
Simon Glass37ba9842021-03-21 18:24:35 +1300106 node: Node containing this property
107 offset: Offset of the property (None if still to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600108 name: Property name (as per the device tree)
109 value: Property value as a string of bytes, or a list of strings of
110 bytes
111 type: Value type
112 """
Simon Glass928527f2019-05-17 22:00:37 -0600113 def __init__(self, node, offset, name, data):
Simon Glassa06a34b2016-07-25 18:59:04 -0600114 self._node = node
115 self._offset = offset
116 self.name = name
117 self.value = None
Simon Glass928527f2019-05-17 22:00:37 -0600118 self.bytes = bytes(data)
Simon Glass37ba9842021-03-21 18:24:35 +1300119 self.dirty = offset is None
Simon Glass928527f2019-05-17 22:00:37 -0600120 if not data:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700121 self.type = Type.BOOL
Simon Glass7b75b442017-05-27 07:38:28 -0600122 self.value = True
123 return
Simon Glass928527f2019-05-17 22:00:37 -0600124 self.type, self.value = BytesToValue(bytes(data))
Simon Glassa06a34b2016-07-25 18:59:04 -0600125
Simon Glassf9b88b32018-07-06 10:27:29 -0600126 def RefreshOffset(self, poffset):
127 self._offset = poffset
128
Simon Glassc322a852016-07-25 18:59:06 -0600129 def Widen(self, newprop):
130 """Figure out which property type is more general
131
132 Given a current property and a new property, this function returns the
133 one that is less specific as to type. The less specific property will
134 be ble to represent the data in the more specific property. This is
135 used for things like:
136
137 node1 {
138 compatible = "fred";
139 value = <1>;
140 };
141 node1 {
142 compatible = "fred";
143 value = <1 2>;
144 };
145
146 He we want to use an int array for 'value'. The first property
147 suggests that a single int is enough, but the second one shows that
148 it is not. Calling this function with these two propertes would
149 update the current property to be like the second, since it is less
150 specific.
151 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700152 if self.type.is_wider_than(newprop.type):
153 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glasse144caf2020-10-03 11:31:27 -0600154 if type(self.value) == list:
155 new_value = []
156 for val in self.value:
Simon Glass479dd302020-11-08 20:36:20 -0700157 new_value += [chr(by) for by in val]
Simon Glasse144caf2020-10-03 11:31:27 -0600158 else:
Simon Glass479dd302020-11-08 20:36:20 -0700159 new_value = [chr(by) for by in self.value]
Simon Glasse144caf2020-10-03 11:31:27 -0600160 self.value = new_value
Simon Glassc322a852016-07-25 18:59:06 -0600161 self.type = newprop.type
162
163 if type(newprop.value) == list and type(self.value) != list:
164 self.value = [self.value]
165
166 if type(self.value) == list and len(newprop.value) > len(self.value):
167 val = self.GetEmpty(self.type)
168 while len(self.value) < len(newprop.value):
169 self.value.append(val)
170
Simon Glass2ba98752018-07-06 10:27:24 -0600171 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600172 def GetEmpty(self, type):
173 """Get an empty / zero value of the given type
174
175 Returns:
176 A single value of the given type
177 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700178 if type == Type.BYTE:
Simon Glassbc1dea32016-07-25 18:59:05 -0600179 return chr(0)
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700180 elif type == Type.INT:
Simon Glassaf53f5a2018-09-14 04:57:14 -0600181 return struct.pack('>I', 0);
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700182 elif type == Type.STRING:
Simon Glassbc1dea32016-07-25 18:59:05 -0600183 return ''
184 else:
185 return True
186
Simon Glassbabdbde2016-07-25 18:59:16 -0600187 def GetOffset(self):
188 """Get the offset of a property
189
Simon Glassbabdbde2016-07-25 18:59:16 -0600190 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600191 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600192 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600193 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600194 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600195
Simon Glassfa80c252018-09-14 04:57:13 -0600196 def SetInt(self, val):
197 """Set the integer value of the property
198
199 The device tree is marked dirty so that the value will be written to
200 the block on the next sync.
201
202 Args:
203 val: Integer value (32-bit, single cell)
204 """
205 self.bytes = struct.pack('>I', val);
Simon Glass41b781d2018-10-01 12:22:49 -0600206 self.value = self.bytes
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700207 self.type = Type.INT
Simon Glassfa80c252018-09-14 04:57:13 -0600208 self.dirty = True
209
Simon Glass64349612018-09-14 04:57:16 -0600210 def SetData(self, bytes):
211 """Set the value of a property as bytes
212
213 Args:
214 bytes: New property value to set
215 """
Simon Glassf6b64812019-05-17 22:00:36 -0600216 self.bytes = bytes
Simon Glass7e6952d2019-05-17 22:00:34 -0600217 self.type, self.value = BytesToValue(bytes)
Simon Glass64349612018-09-14 04:57:16 -0600218 self.dirty = True
219
Simon Glassfa80c252018-09-14 04:57:13 -0600220 def Sync(self, auto_resize=False):
221 """Sync property changes back to the device tree
222
223 This updates the device tree blob with any changes to this property
224 since the last sync.
225
226 Args:
227 auto_resize: Resize the device tree automatically if it does not
228 have enough space for the update
229
230 Raises:
231 FdtException if auto_resize is False and there is not enough space
232 """
Simon Glass37ba9842021-03-21 18:24:35 +1300233 if self.dirty:
Simon Glassfa80c252018-09-14 04:57:13 -0600234 node = self._node
235 fdt_obj = node._fdt._fdt_obj
Simon Glass5d1bec32021-03-21 18:24:39 +1300236 node_name = fdt_obj.get_name(node._offset)
237 if node_name and node_name != node.name:
238 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
239 (node.path, node_name))
240
Simon Glassfa80c252018-09-14 04:57:13 -0600241 if auto_resize:
242 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
243 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassc0639172020-07-09 18:39:44 -0600244 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
245 len(self.bytes))
Simon Glassfa80c252018-09-14 04:57:13 -0600246 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
247 else:
248 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
Simon Glass37ba9842021-03-21 18:24:35 +1300249 self.dirty = False
Simon Glassfa80c252018-09-14 04:57:13 -0600250
251
Simon Glass7b75b442017-05-27 07:38:28 -0600252class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600253 """A device tree node
254
255 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300256 parent: Parent Node
257 offset: Integer offset in the device tree (None if to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600258 name: Device tree node tname
259 path: Full path to node, along with the node name itself
260 _fdt: Device tree object
261 subnodes: A list of subnodes for this node, each a Node object
262 props: A dict of properties for this node, each a Prop object.
263 Keyed by property name
264 """
Simon Glass979ab022017-08-29 14:15:47 -0600265 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600266 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600267 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600268 self._offset = offset
269 self.name = name
270 self.path = path
271 self.subnodes = []
272 self.props = {}
273
Simon Glass94a7c602018-07-17 13:25:46 -0600274 def GetFdt(self):
275 """Get the Fdt object for this node
276
277 Returns:
278 Fdt object
279 """
280 return self._fdt
281
Simon Glass1d858882018-07-17 13:25:41 -0600282 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600283 """Find a node given its name
284
285 Args:
286 name: Node name to look for
287 Returns:
288 Node object if found, else None
289 """
290 for subnode in self.subnodes:
291 if subnode.name == name:
292 return subnode
293 return None
294
Simon Glass7b75b442017-05-27 07:38:28 -0600295 def Offset(self):
296 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600297
Simon Glass7b75b442017-05-27 07:38:28 -0600298 This should be used instead of self._offset directly, to ensure that
299 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600300 """
Simon Glass7b75b442017-05-27 07:38:28 -0600301 self._fdt.CheckCache()
302 return self._offset
303
304 def Scan(self):
305 """Scan a node's properties and subnodes
306
307 This fills in the props and subnodes properties, recursively
308 searching into subnodes so that the entire tree is built.
309 """
Simon Glass117f57b2018-07-06 10:27:26 -0600310 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600311 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600312 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600313 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600314 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600315
Simon Glass117f57b2018-07-06 10:27:26 -0600316 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600317 while offset >= 0:
318 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600319 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600320 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600321 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600322 self.subnodes.append(node)
323
324 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600325 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600326
327 def Refresh(self, my_offset):
328 """Fix up the _offset for each node, recursively
329
330 Note: This does not take account of property offsets - these will not
331 be updated.
332 """
Simon Glass96066242018-07-06 10:27:27 -0600333 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600334 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600335 self._offset = my_offset
Simon Glass5d1bec32021-03-21 18:24:39 +1300336 name = fdt_obj.get_name(self._offset)
337 if name and self.name != name:
338 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
339 (self.path, name))
340
Simon Glass96066242018-07-06 10:27:27 -0600341 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600342 for subnode in self.subnodes:
Simon Glassf9b88b32018-07-06 10:27:29 -0600343 if subnode.name != fdt_obj.get_name(offset):
344 raise ValueError('Internal error, node name mismatch %s != %s' %
345 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600346 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600347 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600348 if offset != -libfdt.FDT_ERR_NOTFOUND:
349 raise ValueError('Internal error, offset == %d' % offset)
350
351 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
352 while poffset >= 0:
353 p = fdt_obj.get_property_by_offset(poffset)
354 prop = self.props.get(p.name)
355 if not prop:
Simon Glassacd98612021-03-21 18:24:34 +1300356 raise ValueError("Internal error, node '%s' property '%s' missing, "
357 'offset %d' % (self.path, p.name, poffset))
Simon Glassf9b88b32018-07-06 10:27:29 -0600358 prop.RefreshOffset(poffset)
359 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600360
Simon Glass2a70d892016-07-25 18:59:14 -0600361 def DeleteProp(self, prop_name):
362 """Delete a property of a node
363
Simon Glass7b75b442017-05-27 07:38:28 -0600364 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600365
366 Args:
367 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600368 Raises:
369 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600370 """
Simon Glass96066242018-07-06 10:27:27 -0600371 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600372 "Node '%s': delete property: '%s'" % (self.path, prop_name))
373 del self.props[prop_name]
374 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600375
Simon Glass116adec2018-07-06 10:27:38 -0600376 def AddZeroProp(self, prop_name):
377 """Add a new property to the device tree with an integer value of 0.
378
379 Args:
380 prop_name: Name of property
381 """
Simon Glass194b8d52019-05-17 22:00:33 -0600382 self.props[prop_name] = Prop(self, None, prop_name,
383 tools.GetBytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600384
Simon Glass64349612018-09-14 04:57:16 -0600385 def AddEmptyProp(self, prop_name, len):
386 """Add a property with a fixed data size, for filling in later
387
388 The device tree is marked dirty so that the value will be written to
389 the blob on the next sync.
390
391 Args:
392 prop_name: Name of property
393 len: Length of data in property
394 """
Simon Glass194b8d52019-05-17 22:00:33 -0600395 value = tools.GetBytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600396 self.props[prop_name] = Prop(self, None, prop_name, value)
397
Simon Glassd9dad102019-07-20 12:23:37 -0600398 def _CheckProp(self, prop_name):
399 """Check if a property is present
400
401 Args:
402 prop_name: Name of property
403
404 Returns:
405 self
406
407 Raises:
408 ValueError if the property is missing
409 """
410 if prop_name not in self.props:
411 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
412 (self._fdt._fname, self.path, prop_name))
413 return self
414
Simon Glass116adec2018-07-06 10:27:38 -0600415 def SetInt(self, prop_name, val):
416 """Update an integer property int the device tree.
417
418 This is not allowed to change the size of the FDT.
419
Simon Glass64349612018-09-14 04:57:16 -0600420 The device tree is marked dirty so that the value will be written to
421 the blob on the next sync.
422
Simon Glass116adec2018-07-06 10:27:38 -0600423 Args:
424 prop_name: Name of property
425 val: Value to set
426 """
Simon Glassd9dad102019-07-20 12:23:37 -0600427 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600428
Simon Glass64349612018-09-14 04:57:16 -0600429 def SetData(self, prop_name, val):
430 """Set the data value of a property
431
432 The device tree is marked dirty so that the value will be written to
433 the blob on the next sync.
434
435 Args:
436 prop_name: Name of property to set
437 val: Data value to set
438 """
Simon Glassd9dad102019-07-20 12:23:37 -0600439 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600440
441 def SetString(self, prop_name, val):
442 """Set the string value of a property
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 set
449 val: String value to set (will be \0-terminated in DT)
450 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600451 if type(val) == str:
452 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600453 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600454
Simon Glassc0639172020-07-09 18:39:44 -0600455 def AddData(self, prop_name, val):
456 """Add a new property to a node
457
458 The device tree is marked dirty so that the value will be written to
459 the blob on the next sync.
460
461 Args:
462 prop_name: Name of property to add
463 val: Bytes value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300464
465 Returns:
466 Prop added
Simon Glassc0639172020-07-09 18:39:44 -0600467 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300468 prop = Prop(self, None, prop_name, val)
469 self.props[prop_name] = prop
470 return prop
Simon Glassc0639172020-07-09 18:39:44 -0600471
Simon Glass64349612018-09-14 04:57:16 -0600472 def AddString(self, prop_name, val):
473 """Add a new string property to a node
474
475 The device tree is marked dirty so that the value will be written to
476 the blob on the next sync.
477
478 Args:
479 prop_name: Name of property to add
480 val: String value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300481
482 Returns:
483 Prop added
Simon Glass64349612018-09-14 04:57:16 -0600484 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700485 val = bytes(val, 'utf-8')
Simon Glass5d1bec32021-03-21 18:24:39 +1300486 return self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600487
Simon Glass6eb99322021-01-06 21:35:18 -0700488 def AddInt(self, prop_name, val):
489 """Add a new integer property to a node
490
491 The device tree is marked dirty so that the value will be written to
492 the blob on the next sync.
493
494 Args:
495 prop_name: Name of property to add
496 val: Integer value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300497
498 Returns:
499 Prop added
Simon Glass6eb99322021-01-06 21:35:18 -0700500 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300501 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glass6eb99322021-01-06 21:35:18 -0700502
Simon Glasse21c27a2018-09-14 04:57:15 -0600503 def AddSubnode(self, name):
Simon Glass64349612018-09-14 04:57:16 -0600504 """Add a new subnode to the node
505
506 Args:
507 name: name of node to add
508
509 Returns:
510 New subnode that was created
511 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600512 path = self.path + '/' + name
513 subnode = Node(self._fdt, self, None, name, path)
514 self.subnodes.append(subnode)
515 return subnode
516
Simon Glassfa80c252018-09-14 04:57:13 -0600517 def Sync(self, auto_resize=False):
518 """Sync node changes back to the device tree
519
520 This updates the device tree blob with any changes to this node and its
521 subnodes since the last sync.
522
523 Args:
524 auto_resize: Resize the device tree automatically if it does not
525 have enough space for the update
526
Simon Glassf6176652021-03-21 18:24:38 +1300527 Returns:
528 True if the node had to be added, False if it already existed
529
Simon Glassfa80c252018-09-14 04:57:13 -0600530 Raises:
531 FdtException if auto_resize is False and there is not enough space
532 """
Simon Glassf6176652021-03-21 18:24:38 +1300533 added = False
Simon Glasse21c27a2018-09-14 04:57:15 -0600534 if self._offset is None:
535 # The subnode doesn't exist yet, so add it
536 fdt_obj = self._fdt._fdt_obj
537 if auto_resize:
538 while True:
539 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
540 (libfdt.NOSPACE,))
541 if offset != -libfdt.NOSPACE:
542 break
543 fdt_obj.resize(fdt_obj.totalsize() + 1024)
544 else:
545 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
546 self._offset = offset
Simon Glassf6176652021-03-21 18:24:38 +1300547 added = True
Simon Glasse21c27a2018-09-14 04:57:15 -0600548
Simon Glassf6176652021-03-21 18:24:38 +1300549 # Sync the existing subnodes first, so that we can rely on the offsets
550 # being correct. As soon as we add new subnodes, it pushes all the
551 # existing subnodes up.
Simon Glassfa80c252018-09-14 04:57:13 -0600552 for node in reversed(self.subnodes):
Simon Glassf6176652021-03-21 18:24:38 +1300553 if node._offset is not None:
554 node.Sync(auto_resize)
Simon Glassfa80c252018-09-14 04:57:13 -0600555
Simon Glassf6176652021-03-21 18:24:38 +1300556 # Sync subnodes in reverse so that we get the expected order. Each
557 # new node goes at the start of the subnode list. This avoids an O(n^2)
558 # rescan of node offsets.
559 num_added = 0
560 for node in reversed(self.subnodes):
561 if node.Sync(auto_resize):
562 num_added += 1
563 if num_added:
564 # Reorder our list of nodes to put the new ones first, since that's
565 # what libfdt does
566 old_count = len(self.subnodes) - num_added
567 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
568 self.subnodes = subnodes
569
570 # Sync properties now, whose offsets should not have been disturbed,
571 # since properties come before subnodes. This is done after all the
572 # subnode processing above, since updating properties can disturb the
573 # offsets of those subnodes.
574 # Properties are synced in reverse order, with new properties added
575 # before existing properties are synced. This ensures that the offsets
576 # of earlier properties are not disturbed.
577 # Note that new properties will have an offset of None here, which
578 # Python cannot sort against int. So use a large value instead so that
579 # new properties are added first.
Simon Glass63518052019-05-17 22:00:38 -0600580 prop_list = sorted(self.props.values(),
581 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600582 reverse=True)
583 for prop in prop_list:
584 prop.Sync(auto_resize)
Simon Glassf6176652021-03-21 18:24:38 +1300585 return added
Simon Glass116adec2018-07-06 10:27:38 -0600586
587
Simon Glassa06a34b2016-07-25 18:59:04 -0600588class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600589 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600590
591 Properties:
592 fname: Filename of fdt
593 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600594 name: Helpful name for this Fdt for the user (useful when creating the
595 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600596 """
597 def __init__(self, fname):
598 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600599 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600600 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600601 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600602 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600603 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600604 self._fname = fdt_util.EnsureCompiled(self._fname)
605
Simon Glass3e4b51e2019-05-14 15:53:43 -0600606 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600607 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600608
Simon Glass746aee32018-09-14 04:57:17 -0600609 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600610 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600611 """Create a new Fdt object from the given data
612
613 Args:
614 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600615 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600616
617 Returns:
618 Fdt object containing the data
619 """
620 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600621 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600622 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600623 return fdt
624
Simon Glass94a7c602018-07-17 13:25:46 -0600625 def LookupPhandle(self, phandle):
626 """Look up a phandle
627
628 Args:
629 phandle: Phandle to look up (int)
630
631 Returns:
632 Node object the phandle points to
633 """
634 return self.phandle_to_node.get(phandle)
635
Simon Glassf7a2aee2016-07-25 18:59:07 -0600636 def Scan(self, root='/'):
637 """Scan a device tree, building up a tree of Node objects
638
639 This fills in the self._root property
640
641 Args:
642 root: Ignored
643
644 TODO(sjg@chromium.org): Implement the 'root' parameter
645 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600646 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600647 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600648 self._root.Scan()
649
650 def GetRoot(self):
651 """Get the root Node of the device tree
652
653 Returns:
654 The root Node object
655 """
656 return self._root
657
658 def GetNode(self, path):
659 """Look up a node from its path
660
661 Args:
662 path: Path to look up, e.g. '/microcode/update@0'
663 Returns:
664 Node object, or None if not found
665 """
666 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600667 parts = path.split('/')
668 if len(parts) < 2:
669 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600670 if len(parts) == 2 and parts[1] == '':
671 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600672 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600673 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600674 if not node:
675 return None
676 return node
677
Simon Glassda5f7492016-07-25 18:59:15 -0600678 def Flush(self):
679 """Flush device tree changes back to the file
680
681 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600682 """
Simon Glass7b75b442017-05-27 07:38:28 -0600683 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600684 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600685
Simon Glassfa80c252018-09-14 04:57:13 -0600686 def Sync(self, auto_resize=False):
687 """Make sure any DT changes are written to the blob
688
689 Args:
690 auto_resize: Resize the device tree automatically if it does not
691 have enough space for the update
692
693 Raises:
694 FdtException if auto_resize is False and there is not enough space
695 """
Simon Glass71719e12021-03-21 18:24:36 +1300696 self.CheckCache()
Simon Glassfa80c252018-09-14 04:57:13 -0600697 self._root.Sync(auto_resize)
Simon Glass71719e12021-03-21 18:24:36 +1300698 self.Refresh()
Simon Glassfa80c252018-09-14 04:57:13 -0600699
Simon Glassda5f7492016-07-25 18:59:15 -0600700 def Pack(self):
701 """Pack the device tree down to its minimum size
702
703 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600704 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600705 """
Simon Glass117f57b2018-07-06 10:27:26 -0600706 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glass71719e12021-03-21 18:24:36 +1300707 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600708
Simon Glass96066242018-07-06 10:27:27 -0600709 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600710 """Get the contents of the FDT
711
712 Returns:
713 The FDT contents as a string of bytes
714 """
Simon Glassf6b64812019-05-17 22:00:36 -0600715 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600716
Simon Glass2ba98752018-07-06 10:27:24 -0600717 def GetFdtObj(self):
718 """Get the contents of the FDT
719
720 Returns:
721 The FDT contents as a libfdt.Fdt object
722 """
723 return self._fdt_obj
724
Simon Glass7b75b442017-05-27 07:38:28 -0600725 def GetProps(self, node):
726 """Get all properties from a node.
727
728 Args:
729 node: Full path to node name to look in.
730
731 Returns:
732 A dictionary containing all the properties, indexed by node name.
733 The entries are Prop objects.
734
735 Raises:
736 ValueError: if the node does not exist.
737 """
738 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600739 poffset = self._fdt_obj.first_property_offset(node._offset,
740 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600741 while poffset >= 0:
742 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600743 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600744 props_dict[prop.name] = prop
745
Simon Glass117f57b2018-07-06 10:27:26 -0600746 poffset = self._fdt_obj.next_property_offset(poffset,
747 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600748 return props_dict
749
750 def Invalidate(self):
751 """Mark our offset cache as invalid"""
752 self._cached_offsets = False
753
754 def CheckCache(self):
755 """Refresh the offset cache if needed"""
756 if self._cached_offsets:
757 return
758 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600759
760 def Refresh(self):
761 """Refresh the offset cache"""
762 self._root.Refresh(0)
Simon Glass71719e12021-03-21 18:24:36 +1300763 self._cached_offsets = True
Simon Glass7b75b442017-05-27 07:38:28 -0600764
765 def GetStructOffset(self, offset):
766 """Get the file offset of a given struct offset
767
768 Args:
769 offset: Offset within the 'struct' region of the device tree
770 Returns:
771 Position of @offset within the device tree binary
772 """
Simon Glass117f57b2018-07-06 10:27:26 -0600773 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600774
775 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600776 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600777 """Create a new node
778
779 This is used by Fdt.Scan() to create a new node using the correct
780 class.
781
782 Args:
783 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600784 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600785 offset: Offset of node
786 name: Node name
787 path: Full path to node
788 """
Simon Glass979ab022017-08-29 14:15:47 -0600789 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600790 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600791
Simon Glassf6e02492019-07-20 12:24:08 -0600792 def GetFilename(self):
793 """Get the filename of the device tree
794
795 Returns:
796 String filename
797 """
798 return self._fname
799
Simon Glass99ed4a22017-05-27 07:38:30 -0600800def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600801 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600802 dtb = Fdt(fname)
803 dtb.Scan()
804 return dtb