blob: a8e05349a7205cccf7cf2d1ae72355716c48e2a9 [file] [log] [blame]
Simon Glass1f941b62016-07-25 18:59:04 -06001#!/usr/bin/python
Tom Rini10e47792018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glass1f941b62016-07-25 18:59:04 -06003#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
Simon Glass1f941b62016-07-25 18:59:04 -06007
Simon Glassc9a032c2020-11-08 20:36:17 -07008from enum import IntEnum
Simon Glass1f941b62016-07-25 18:59:04 -06009import struct
10import sys
11
Simon Glassa997ea52020-04-17 18:09:04 -060012from dtoc import fdt_util
Simon Glass059ae522017-05-27 07:38:28 -060013import libfdt
Simon Glass151aeed2018-07-06 10:27:26 -060014from libfdt import QUIET_NOTFOUND
Simon Glass131444f2023-02-23 18:18:04 -070015from u_boot_pylib import tools
Simon Glass1f941b62016-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 Glassa9440932017-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 Glass059ae522017-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 Glass1f941b62016-07-25 18:59:04 -060024
Simon Glassb1a5e262016-07-25 18:59:05 -060025# A list of types we support
Simon Glassc9a032c2020-11-08 20:36:17 -070026class Type(IntEnum):
Simon Glassfd4078e2021-07-28 19:23:09 -060027 # Types in order from widest to narrowest
Simon Glassc9a032c2020-11-08 20:36:17 -070028 (BYTE, INT, STRING, BOOL, INT64) = range(5)
29
Simon Glassfd4078e2021-07-28 19:23:09 -060030 def needs_widening(self, other):
31 """Check if this type needs widening to hold a value from another type
Simon Glassc9a032c2020-11-08 20:36:17 -070032
Simon Glassfd4078e2021-07-28 19:23:09 -060033 A wider type is one that can hold a wider array of information than
34 another one, or is less restrictive, so it can hold the information of
35 another type as well as its own. This is similar to the concept of
36 type-widening in C.
Simon Glassc9a032c2020-11-08 20:36:17 -070037
38 This uses a simple arithmetic comparison, since type values are in order
Simon Glassfd4078e2021-07-28 19:23:09 -060039 from widest (BYTE) to narrowest (INT64).
Simon Glassc9a032c2020-11-08 20:36:17 -070040
41 Args:
42 other: Other type to compare against
43
44 Return:
45 True if the other type is wider
46 """
47 return self.value > other.value
Simon Glassb1a5e262016-07-25 18:59:05 -060048
Simon Glass1f941b62016-07-25 18:59:04 -060049def CheckErr(errnum, msg):
50 if errnum:
51 raise ValueError('Error %d: %s: %s' %
52 (errnum, libfdt.fdt_strerror(errnum), msg))
53
Simon Glassb73d4482019-05-17 22:00:34 -060054
Simon Glassd8f593f2019-05-17 22:00:35 -060055def BytesToValue(data):
Simon Glassb73d4482019-05-17 22:00:34 -060056 """Converts a string of bytes into a type and value
57
58 Args:
Simon Glassd8f593f2019-05-17 22:00:35 -060059 A bytes value (which on Python 2 is an alias for str)
Simon Glassb73d4482019-05-17 22:00:34 -060060
61 Return:
62 A tuple:
63 Type of data
64 Data, either a single element or a list of elements. Each element
65 is one of:
Simon Glassc9a032c2020-11-08 20:36:17 -070066 Type.STRING: str/bytes value from the property
67 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
68 Type.BYTE: a byte stored as a single-byte str/bytes
Simon Glassb73d4482019-05-17 22:00:34 -060069 """
Simon Glassd8f593f2019-05-17 22:00:35 -060070 data = bytes(data)
71 size = len(data)
72 strings = data.split(b'\0')
Simon Glassb73d4482019-05-17 22:00:34 -060073 is_string = True
74 count = len(strings) - 1
Simon Glassd8f593f2019-05-17 22:00:35 -060075 if count > 0 and not len(strings[-1]):
Simon Glassb73d4482019-05-17 22:00:34 -060076 for string in strings[:-1]:
77 if not string:
78 is_string = False
79 break
80 for ch in string:
Simon Glassd8f593f2019-05-17 22:00:35 -060081 if ch < 32 or ch > 127:
Simon Glassb73d4482019-05-17 22:00:34 -060082 is_string = False
83 break
84 else:
85 is_string = False
86 if is_string:
Simon Glassd8f593f2019-05-17 22:00:35 -060087 if count == 1:
Simon Glassc9a032c2020-11-08 20:36:17 -070088 return Type.STRING, strings[0].decode()
Simon Glassb73d4482019-05-17 22:00:34 -060089 else:
Simon Glassc9a032c2020-11-08 20:36:17 -070090 return Type.STRING, [s.decode() for s in strings[:-1]]
Simon Glassb73d4482019-05-17 22:00:34 -060091 if size % 4:
92 if size == 1:
Simon Glass632b84c2020-11-08 20:36:20 -070093 return Type.BYTE, chr(data[0])
Simon Glassb73d4482019-05-17 22:00:34 -060094 else:
Simon Glass632b84c2020-11-08 20:36:20 -070095 return Type.BYTE, [chr(ch) for ch in list(data)]
Simon Glassb73d4482019-05-17 22:00:34 -060096 val = []
97 for i in range(0, size, 4):
Simon Glassd8f593f2019-05-17 22:00:35 -060098 val.append(data[i:i + 4])
Simon Glassb73d4482019-05-17 22:00:34 -060099 if size == 4:
Simon Glassc9a032c2020-11-08 20:36:17 -0700100 return Type.INT, val[0]
Simon Glassb73d4482019-05-17 22:00:34 -0600101 else:
Simon Glassc9a032c2020-11-08 20:36:17 -0700102 return Type.INT, val
Simon Glassb73d4482019-05-17 22:00:34 -0600103
104
Simon Glass059ae522017-05-27 07:38:28 -0600105class Prop:
Simon Glass1f941b62016-07-25 18:59:04 -0600106 """A device tree property
107
108 Properties:
Simon Glass459df472021-03-21 18:24:35 +1300109 node: Node containing this property
110 offset: Offset of the property (None if still to be synced)
Simon Glass1f941b62016-07-25 18:59:04 -0600111 name: Property name (as per the device tree)
112 value: Property value as a string of bytes, or a list of strings of
113 bytes
114 type: Value type
115 """
Simon Glass74b472e2019-05-17 22:00:37 -0600116 def __init__(self, node, offset, name, data):
Simon Glass1f941b62016-07-25 18:59:04 -0600117 self._node = node
118 self._offset = offset
119 self.name = name
120 self.value = None
Simon Glass74b472e2019-05-17 22:00:37 -0600121 self.bytes = bytes(data)
Simon Glass459df472021-03-21 18:24:35 +1300122 self.dirty = offset is None
Simon Glass74b472e2019-05-17 22:00:37 -0600123 if not data:
Simon Glassc9a032c2020-11-08 20:36:17 -0700124 self.type = Type.BOOL
Simon Glass059ae522017-05-27 07:38:28 -0600125 self.value = True
126 return
Simon Glass74b472e2019-05-17 22:00:37 -0600127 self.type, self.value = BytesToValue(bytes(data))
Simon Glass1f941b62016-07-25 18:59:04 -0600128
Simon Glass4df8a0c2018-07-06 10:27:29 -0600129 def RefreshOffset(self, poffset):
130 self._offset = poffset
131
Simon Glass248ccd22016-07-25 18:59:06 -0600132 def Widen(self, newprop):
133 """Figure out which property type is more general
134
135 Given a current property and a new property, this function returns the
136 one that is less specific as to type. The less specific property will
137 be ble to represent the data in the more specific property. This is
138 used for things like:
139
140 node1 {
141 compatible = "fred";
142 value = <1>;
143 };
144 node1 {
145 compatible = "fred";
146 value = <1 2>;
147 };
148
149 He we want to use an int array for 'value'. The first property
150 suggests that a single int is enough, but the second one shows that
151 it is not. Calling this function with these two propertes would
152 update the current property to be like the second, since it is less
153 specific.
154 """
Simon Glassfd4078e2021-07-28 19:23:09 -0600155 if self.type.needs_widening(newprop.type):
Simon Glass43118322021-07-28 19:23:11 -0600156
157 # A boolean has an empty value: if it exists it is True and if not
158 # it is False. So when widening we always start with an empty list
159 # since the only valid integer property would be an empty list of
160 # integers.
161 # e.g. this is a boolean:
162 # some-prop;
163 # and it would be widened to int list by:
164 # some-prop = <1 2>;
165 if self.type == Type.BOOL:
166 self.type = Type.INT
167 self.value = [self.GetEmpty(self.type)]
Simon Glassc9a032c2020-11-08 20:36:17 -0700168 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glass8034e4d2020-10-03 11:31:27 -0600169 if type(self.value) == list:
170 new_value = []
171 for val in self.value:
Simon Glass632b84c2020-11-08 20:36:20 -0700172 new_value += [chr(by) for by in val]
Simon Glass8034e4d2020-10-03 11:31:27 -0600173 else:
Simon Glass632b84c2020-11-08 20:36:20 -0700174 new_value = [chr(by) for by in self.value]
Simon Glass8034e4d2020-10-03 11:31:27 -0600175 self.value = new_value
Simon Glass248ccd22016-07-25 18:59:06 -0600176 self.type = newprop.type
177
Simon Glassa7d66982021-07-28 19:23:10 -0600178 if type(newprop.value) == list:
179 if type(self.value) != list:
180 self.value = [self.value]
Simon Glass248ccd22016-07-25 18:59:06 -0600181
Simon Glassa7d66982021-07-28 19:23:10 -0600182 if len(newprop.value) > len(self.value):
183 val = self.GetEmpty(self.type)
184 while len(self.value) < len(newprop.value):
185 self.value.append(val)
Simon Glass248ccd22016-07-25 18:59:06 -0600186
Simon Glass0ed50752018-07-06 10:27:24 -0600187 @classmethod
Simon Glassb1a5e262016-07-25 18:59:05 -0600188 def GetEmpty(self, type):
189 """Get an empty / zero value of the given type
190
191 Returns:
192 A single value of the given type
193 """
Simon Glassc9a032c2020-11-08 20:36:17 -0700194 if type == Type.BYTE:
Simon Glassb1a5e262016-07-25 18:59:05 -0600195 return chr(0)
Simon Glassc9a032c2020-11-08 20:36:17 -0700196 elif type == Type.INT:
Simon Glassc330b142018-09-14 04:57:14 -0600197 return struct.pack('>I', 0);
Simon Glassc9a032c2020-11-08 20:36:17 -0700198 elif type == Type.STRING:
Simon Glassb1a5e262016-07-25 18:59:05 -0600199 return ''
200 else:
201 return True
202
Simon Glass54cec6d2016-07-25 18:59:16 -0600203 def GetOffset(self):
204 """Get the offset of a property
205
Simon Glass54cec6d2016-07-25 18:59:16 -0600206 Returns:
Simon Glass059ae522017-05-27 07:38:28 -0600207 The offset of the property (struct fdt_property) within the file
Simon Glass54cec6d2016-07-25 18:59:16 -0600208 """
Simon Glass4df8a0c2018-07-06 10:27:29 -0600209 self._node._fdt.CheckCache()
Simon Glass059ae522017-05-27 07:38:28 -0600210 return self._node._fdt.GetStructOffset(self._offset)
Simon Glass54cec6d2016-07-25 18:59:16 -0600211
Simon Glasseddd7292018-09-14 04:57:13 -0600212 def SetInt(self, val):
213 """Set the integer value of the property
214
215 The device tree is marked dirty so that the value will be written to
216 the block on the next sync.
217
218 Args:
219 val: Integer value (32-bit, single cell)
220 """
221 self.bytes = struct.pack('>I', val);
Simon Glassa2b9acb2018-10-01 12:22:49 -0600222 self.value = self.bytes
Simon Glassc9a032c2020-11-08 20:36:17 -0700223 self.type = Type.INT
Simon Glasseddd7292018-09-14 04:57:13 -0600224 self.dirty = True
225
Simon Glassccd25262018-09-14 04:57:16 -0600226 def SetData(self, bytes):
227 """Set the value of a property as bytes
228
229 Args:
230 bytes: New property value to set
231 """
Simon Glass1cd40082019-05-17 22:00:36 -0600232 self.bytes = bytes
Simon Glassb73d4482019-05-17 22:00:34 -0600233 self.type, self.value = BytesToValue(bytes)
Simon Glassccd25262018-09-14 04:57:16 -0600234 self.dirty = True
235
Simon Glasseddd7292018-09-14 04:57:13 -0600236 def Sync(self, auto_resize=False):
237 """Sync property changes back to the device tree
238
239 This updates the device tree blob with any changes to this property
240 since the last sync.
241
242 Args:
243 auto_resize: Resize the device tree automatically if it does not
244 have enough space for the update
245
246 Raises:
247 FdtException if auto_resize is False and there is not enough space
248 """
Simon Glass459df472021-03-21 18:24:35 +1300249 if self.dirty:
Simon Glasseddd7292018-09-14 04:57:13 -0600250 node = self._node
251 fdt_obj = node._fdt._fdt_obj
Simon Glassd8bee462021-03-21 18:24:39 +1300252 node_name = fdt_obj.get_name(node._offset)
253 if node_name and node_name != node.name:
254 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
255 (node.path, node_name))
256
Simon Glasseddd7292018-09-14 04:57:13 -0600257 if auto_resize:
258 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
259 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassf67c99c2020-07-09 18:39:44 -0600260 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
261 len(self.bytes))
Simon Glasseddd7292018-09-14 04:57:13 -0600262 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
263 else:
264 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
Simon Glass459df472021-03-21 18:24:35 +1300265 self.dirty = False
Simon Glasseddd7292018-09-14 04:57:13 -0600266
267
Simon Glass059ae522017-05-27 07:38:28 -0600268class Node:
Simon Glass1f941b62016-07-25 18:59:04 -0600269 """A device tree node
270
271 Properties:
Simon Glass459df472021-03-21 18:24:35 +1300272 parent: Parent Node
273 offset: Integer offset in the device tree (None if to be synced)
Simon Glass1f941b62016-07-25 18:59:04 -0600274 name: Device tree node tname
275 path: Full path to node, along with the node name itself
276 _fdt: Device tree object
277 subnodes: A list of subnodes for this node, each a Node object
278 props: A dict of properties for this node, each a Prop object.
279 Keyed by property name
280 """
Simon Glass80ef7052017-08-29 14:15:47 -0600281 def __init__(self, fdt, parent, offset, name, path):
Simon Glass1f941b62016-07-25 18:59:04 -0600282 self._fdt = fdt
Simon Glass80ef7052017-08-29 14:15:47 -0600283 self.parent = parent
Simon Glass1f941b62016-07-25 18:59:04 -0600284 self._offset = offset
285 self.name = name
286 self.path = path
287 self.subnodes = []
288 self.props = {}
289
Simon Glasse2d65282018-07-17 13:25:46 -0600290 def GetFdt(self):
291 """Get the Fdt object for this node
292
293 Returns:
294 Fdt object
295 """
296 return self._fdt
297
Simon Glassaa1a5d72018-07-17 13:25:41 -0600298 def FindNode(self, name):
Simon Glasscc346a72016-07-25 18:59:07 -0600299 """Find a node given its name
300
301 Args:
302 name: Node name to look for
303 Returns:
304 Node object if found, else None
305 """
306 for subnode in self.subnodes:
307 if subnode.name == name:
308 return subnode
309 return None
310
Simon Glass059ae522017-05-27 07:38:28 -0600311 def Offset(self):
312 """Returns the offset of a node, after checking the cache
313
314 This should be used instead of self._offset directly, to ensure that
315 the cache does not contain invalid offsets.
316 """
317 self._fdt.CheckCache()
318 return self._offset
319
Simon Glasscc346a72016-07-25 18:59:07 -0600320 def Scan(self):
Simon Glass059ae522017-05-27 07:38:28 -0600321 """Scan a node's properties and subnodes
Simon Glasscc346a72016-07-25 18:59:07 -0600322
Simon Glass059ae522017-05-27 07:38:28 -0600323 This fills in the props and subnodes properties, recursively
324 searching into subnodes so that the entire tree is built.
Simon Glasscc346a72016-07-25 18:59:07 -0600325 """
Simon Glass151aeed2018-07-06 10:27:26 -0600326 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600327 self.props = self._fdt.GetProps(self)
Simon Glass151aeed2018-07-06 10:27:26 -0600328 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glassa3f94442017-08-29 14:15:52 -0600329 if phandle:
Simon Glass151aeed2018-07-06 10:27:26 -0600330 self._fdt.phandle_to_node[phandle] = self
Simon Glass059ae522017-05-27 07:38:28 -0600331
Simon Glass151aeed2018-07-06 10:27:26 -0600332 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600333 while offset >= 0:
334 sep = '' if self.path[-1] == '/' else '/'
Simon Glass151aeed2018-07-06 10:27:26 -0600335 name = fdt_obj.get_name(offset)
Simon Glass059ae522017-05-27 07:38:28 -0600336 path = self.path + sep + name
Simon Glass80ef7052017-08-29 14:15:47 -0600337 node = Node(self._fdt, self, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600338 self.subnodes.append(node)
339
340 node.Scan()
Simon Glass151aeed2018-07-06 10:27:26 -0600341 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600342
343 def Refresh(self, my_offset):
344 """Fix up the _offset for each node, recursively
345
346 Note: This does not take account of property offsets - these will not
347 be updated.
348 """
Simon Glass792d2392018-07-06 10:27:27 -0600349 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600350 if self._offset != my_offset:
Simon Glass059ae522017-05-27 07:38:28 -0600351 self._offset = my_offset
Simon Glassd8bee462021-03-21 18:24:39 +1300352 name = fdt_obj.get_name(self._offset)
353 if name and self.name != name:
354 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
355 (self.path, name))
356
Simon Glass792d2392018-07-06 10:27:27 -0600357 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600358 for subnode in self.subnodes:
Simon Glass00238612022-02-08 11:49:52 -0700359 if subnode._offset is None:
360 continue
Simon Glass4df8a0c2018-07-06 10:27:29 -0600361 if subnode.name != fdt_obj.get_name(offset):
362 raise ValueError('Internal error, node name mismatch %s != %s' %
363 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass059ae522017-05-27 07:38:28 -0600364 subnode.Refresh(offset)
Simon Glass792d2392018-07-06 10:27:27 -0600365 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass4df8a0c2018-07-06 10:27:29 -0600366 if offset != -libfdt.FDT_ERR_NOTFOUND:
367 raise ValueError('Internal error, offset == %d' % offset)
368
369 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
370 while poffset >= 0:
371 p = fdt_obj.get_property_by_offset(poffset)
372 prop = self.props.get(p.name)
373 if not prop:
Simon Glass93f18a12021-03-21 18:24:34 +1300374 raise ValueError("Internal error, node '%s' property '%s' missing, "
375 'offset %d' % (self.path, p.name, poffset))
Simon Glass4df8a0c2018-07-06 10:27:29 -0600376 prop.RefreshOffset(poffset)
377 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glasscc346a72016-07-25 18:59:07 -0600378
Simon Glassc719e422016-07-25 18:59:14 -0600379 def DeleteProp(self, prop_name):
380 """Delete a property of a node
381
Simon Glass059ae522017-05-27 07:38:28 -0600382 The property is deleted and the offset cache is invalidated.
Simon Glassc719e422016-07-25 18:59:14 -0600383
384 Args:
385 prop_name: Name of the property to delete
Simon Glass059ae522017-05-27 07:38:28 -0600386 Raises:
387 ValueError if the property does not exist
Simon Glassc719e422016-07-25 18:59:14 -0600388 """
Simon Glass792d2392018-07-06 10:27:27 -0600389 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass059ae522017-05-27 07:38:28 -0600390 "Node '%s': delete property: '%s'" % (self.path, prop_name))
391 del self.props[prop_name]
392 self._fdt.Invalidate()
Simon Glassc719e422016-07-25 18:59:14 -0600393
Simon Glasse80c5562018-07-06 10:27:38 -0600394 def AddZeroProp(self, prop_name):
395 """Add a new property to the device tree with an integer value of 0.
396
397 Args:
398 prop_name: Name of property
399 """
Simon Glass6bbfafb2019-05-17 22:00:33 -0600400 self.props[prop_name] = Prop(self, None, prop_name,
Simon Glass80025522022-01-29 14:14:04 -0700401 tools.get_bytes(0, 4))
Simon Glasse80c5562018-07-06 10:27:38 -0600402
Simon Glassccd25262018-09-14 04:57:16 -0600403 def AddEmptyProp(self, prop_name, len):
404 """Add a property with a fixed data size, for filling in later
405
406 The device tree is marked dirty so that the value will be written to
407 the blob on the next sync.
408
409 Args:
410 prop_name: Name of property
411 len: Length of data in property
412 """
Simon Glass80025522022-01-29 14:14:04 -0700413 value = tools.get_bytes(0, len)
Simon Glassccd25262018-09-14 04:57:16 -0600414 self.props[prop_name] = Prop(self, None, prop_name, value)
415
Simon Glassa683a5f2019-07-20 12:23:37 -0600416 def _CheckProp(self, prop_name):
417 """Check if a property is present
418
419 Args:
420 prop_name: Name of property
421
422 Returns:
423 self
424
425 Raises:
426 ValueError if the property is missing
427 """
428 if prop_name not in self.props:
429 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
430 (self._fdt._fname, self.path, prop_name))
431 return self
432
Simon Glasse80c5562018-07-06 10:27:38 -0600433 def SetInt(self, prop_name, val):
434 """Update an integer property int the device tree.
435
436 This is not allowed to change the size of the FDT.
437
Simon Glassccd25262018-09-14 04:57:16 -0600438 The device tree is marked dirty so that the value will be written to
439 the blob on the next sync.
440
Simon Glasse80c5562018-07-06 10:27:38 -0600441 Args:
442 prop_name: Name of property
443 val: Value to set
444 """
Simon Glassa683a5f2019-07-20 12:23:37 -0600445 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glasseddd7292018-09-14 04:57:13 -0600446
Simon Glassccd25262018-09-14 04:57:16 -0600447 def SetData(self, prop_name, val):
448 """Set the data value of a property
449
450 The device tree is marked dirty so that the value will be written to
451 the blob on the next sync.
452
453 Args:
454 prop_name: Name of property to set
455 val: Data value to set
456 """
Simon Glassa683a5f2019-07-20 12:23:37 -0600457 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glassccd25262018-09-14 04:57:16 -0600458
459 def SetString(self, prop_name, val):
460 """Set the string value of a property
461
462 The device tree is marked dirty so that the value will be written to
463 the blob on the next sync.
464
465 Args:
466 prop_name: Name of property to set
467 val: String value to set (will be \0-terminated in DT)
468 """
Simon Glass39e22332019-10-31 07:43:04 -0600469 if type(val) == str:
470 val = val.encode('utf-8')
Simon Glassa683a5f2019-07-20 12:23:37 -0600471 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glassccd25262018-09-14 04:57:16 -0600472
Simon Glassf67c99c2020-07-09 18:39:44 -0600473 def AddData(self, prop_name, val):
474 """Add a new property to a node
475
476 The device tree is marked dirty so that the value will be written to
477 the blob on the next sync.
478
479 Args:
480 prop_name: Name of property to add
481 val: Bytes value of property
Simon Glassd8bee462021-03-21 18:24:39 +1300482
483 Returns:
484 Prop added
Simon Glassf67c99c2020-07-09 18:39:44 -0600485 """
Simon Glassd8bee462021-03-21 18:24:39 +1300486 prop = Prop(self, None, prop_name, val)
487 self.props[prop_name] = prop
488 return prop
Simon Glassf67c99c2020-07-09 18:39:44 -0600489
Simon Glassccd25262018-09-14 04:57:16 -0600490 def AddString(self, prop_name, val):
491 """Add a new string property to a node
492
493 The device tree is marked dirty so that the value will be written to
494 the blob on the next sync.
495
496 Args:
497 prop_name: Name of property to add
498 val: String value of property
Simon Glassd8bee462021-03-21 18:24:39 +1300499
500 Returns:
501 Prop added
Simon Glassccd25262018-09-14 04:57:16 -0600502 """
Simon Glassd35a8522021-01-06 21:35:10 -0700503 val = bytes(val, 'utf-8')
Simon Glassd8bee462021-03-21 18:24:39 +1300504 return self.AddData(prop_name, val + b'\0')
Simon Glassccd25262018-09-14 04:57:16 -0600505
Simon Glass452be422022-02-08 11:49:50 -0700506 def AddStringList(self, prop_name, val):
507 """Add a new string-list property to a node
508
509 The device tree is marked dirty so that the value will be written to
510 the blob on the next sync.
511
512 Args:
513 prop_name: Name of property to add
514 val (list of str): List of strings to add
515
516 Returns:
517 Prop added
518 """
Simon Glass120fa002022-03-05 20:18:56 -0700519 out = b'\0'.join(bytes(s, 'utf-8') for s in val) + b'\0' if val else b''
Simon Glass452be422022-02-08 11:49:50 -0700520 return self.AddData(prop_name, out)
521
Simon Glassa2af7302021-01-06 21:35:18 -0700522 def AddInt(self, prop_name, val):
523 """Add a new integer property to a node
524
525 The device tree is marked dirty so that the value will be written to
526 the blob on the next sync.
527
528 Args:
529 prop_name: Name of property to add
530 val: Integer value of property
Simon Glassd8bee462021-03-21 18:24:39 +1300531
532 Returns:
533 Prop added
Simon Glassa2af7302021-01-06 21:35:18 -0700534 """
Simon Glassd8bee462021-03-21 18:24:39 +1300535 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glassa2af7302021-01-06 21:35:18 -0700536
Simon Glassf3a17962018-09-14 04:57:15 -0600537 def AddSubnode(self, name):
Simon Glassccd25262018-09-14 04:57:16 -0600538 """Add a new subnode to the node
539
540 Args:
541 name: name of node to add
542
543 Returns:
544 New subnode that was created
545 """
Simon Glassf3a17962018-09-14 04:57:15 -0600546 path = self.path + '/' + name
547 subnode = Node(self._fdt, self, None, name, path)
548 self.subnodes.append(subnode)
549 return subnode
550
Simon Glassb9b5cb32022-02-08 11:49:51 -0700551 def Delete(self):
552 """Delete a node
553
554 The node is deleted and the offset cache is invalidated.
555
556 Args:
557 node (Node): Node to delete
558
559 Raises:
560 ValueError if the node does not exist
561 """
562 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
563 "Node '%s': delete" % self.path)
564 parent = self.parent
565 self._fdt.Invalidate()
566 parent.subnodes.remove(self)
567
Simon Glasseddd7292018-09-14 04:57:13 -0600568 def Sync(self, auto_resize=False):
569 """Sync node changes back to the device tree
570
571 This updates the device tree blob with any changes to this node and its
572 subnodes since the last sync.
573
574 Args:
575 auto_resize: Resize the device tree automatically if it does not
576 have enough space for the update
577
Simon Glass3be798a2021-03-21 18:24:38 +1300578 Returns:
579 True if the node had to be added, False if it already existed
580
Simon Glasseddd7292018-09-14 04:57:13 -0600581 Raises:
582 FdtException if auto_resize is False and there is not enough space
583 """
Simon Glass3be798a2021-03-21 18:24:38 +1300584 added = False
Simon Glassf3a17962018-09-14 04:57:15 -0600585 if self._offset is None:
586 # The subnode doesn't exist yet, so add it
587 fdt_obj = self._fdt._fdt_obj
588 if auto_resize:
589 while True:
590 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
591 (libfdt.NOSPACE,))
592 if offset != -libfdt.NOSPACE:
593 break
594 fdt_obj.resize(fdt_obj.totalsize() + 1024)
595 else:
596 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
597 self._offset = offset
Simon Glass3be798a2021-03-21 18:24:38 +1300598 added = True
599
600 # Sync the existing subnodes first, so that we can rely on the offsets
601 # being correct. As soon as we add new subnodes, it pushes all the
602 # existing subnodes up.
603 for node in reversed(self.subnodes):
604 if node._offset is not None:
605 node.Sync(auto_resize)
Simon Glassf3a17962018-09-14 04:57:15 -0600606
Simon Glass3be798a2021-03-21 18:24:38 +1300607 # Sync subnodes in reverse so that we get the expected order. Each
608 # new node goes at the start of the subnode list. This avoids an O(n^2)
609 # rescan of node offsets.
610 num_added = 0
Simon Glasseddd7292018-09-14 04:57:13 -0600611 for node in reversed(self.subnodes):
Simon Glass3be798a2021-03-21 18:24:38 +1300612 if node.Sync(auto_resize):
613 num_added += 1
614 if num_added:
615 # Reorder our list of nodes to put the new ones first, since that's
616 # what libfdt does
617 old_count = len(self.subnodes) - num_added
618 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
619 self.subnodes = subnodes
Simon Glasse80c5562018-07-06 10:27:38 -0600620
Simon Glass3be798a2021-03-21 18:24:38 +1300621 # Sync properties now, whose offsets should not have been disturbed,
622 # since properties come before subnodes. This is done after all the
623 # subnode processing above, since updating properties can disturb the
624 # offsets of those subnodes.
625 # Properties are synced in reverse order, with new properties added
626 # before existing properties are synced. This ensures that the offsets
627 # of earlier properties are not disturbed.
628 # Note that new properties will have an offset of None here, which
629 # Python cannot sort against int. So use a large value instead so that
630 # new properties are added first.
Simon Glassa57cfee2019-05-17 22:00:38 -0600631 prop_list = sorted(self.props.values(),
632 key=lambda prop: prop._offset or 1 << 31,
Simon Glasseddd7292018-09-14 04:57:13 -0600633 reverse=True)
634 for prop in prop_list:
635 prop.Sync(auto_resize)
Simon Glass3be798a2021-03-21 18:24:38 +1300636 return added
Simon Glasseddd7292018-09-14 04:57:13 -0600637
Simon Glasse80c5562018-07-06 10:27:38 -0600638
Simon Glass1f941b62016-07-25 18:59:04 -0600639class Fdt:
Simon Glass059ae522017-05-27 07:38:28 -0600640 """Provides simple access to a flat device tree blob using libfdts.
Simon Glass1f941b62016-07-25 18:59:04 -0600641
642 Properties:
643 fname: Filename of fdt
644 _root: Root of device tree (a Node object)
Simon Glass50cfc6e2019-07-20 12:23:38 -0600645 name: Helpful name for this Fdt for the user (useful when creating the
646 DT from data rather than a file)
Simon Glass1f941b62016-07-25 18:59:04 -0600647 """
648 def __init__(self, fname):
649 self._fname = fname
Simon Glass059ae522017-05-27 07:38:28 -0600650 self._cached_offsets = False
Simon Glassa3f94442017-08-29 14:15:52 -0600651 self.phandle_to_node = {}
Simon Glass50cfc6e2019-07-20 12:23:38 -0600652 self.name = ''
Simon Glass059ae522017-05-27 07:38:28 -0600653 if self._fname:
Simon Glass50cfc6e2019-07-20 12:23:38 -0600654 self.name = self._fname
Simon Glass059ae522017-05-27 07:38:28 -0600655 self._fname = fdt_util.EnsureCompiled(self._fname)
656
Simon Glass116236f2019-05-14 15:53:43 -0600657 with open(self._fname, 'rb') as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600658 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glasscc346a72016-07-25 18:59:07 -0600659
Simon Glassb8a49292018-09-14 04:57:17 -0600660 @staticmethod
Simon Glass50cfc6e2019-07-20 12:23:38 -0600661 def FromData(data, name=''):
Simon Glassb8a49292018-09-14 04:57:17 -0600662 """Create a new Fdt object from the given data
663
664 Args:
665 data: Device-tree data blob
Simon Glass50cfc6e2019-07-20 12:23:38 -0600666 name: Helpful name for this Fdt for the user
Simon Glassb8a49292018-09-14 04:57:17 -0600667
668 Returns:
669 Fdt object containing the data
670 """
671 fdt = Fdt(None)
Simon Glass1cd40082019-05-17 22:00:36 -0600672 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass50cfc6e2019-07-20 12:23:38 -0600673 fdt.name = name
Simon Glassb8a49292018-09-14 04:57:17 -0600674 return fdt
675
Simon Glasse2d65282018-07-17 13:25:46 -0600676 def LookupPhandle(self, phandle):
677 """Look up a phandle
678
679 Args:
680 phandle: Phandle to look up (int)
681
682 Returns:
683 Node object the phandle points to
684 """
685 return self.phandle_to_node.get(phandle)
686
Simon Glasscc346a72016-07-25 18:59:07 -0600687 def Scan(self, root='/'):
688 """Scan a device tree, building up a tree of Node objects
689
690 This fills in the self._root property
691
692 Args:
693 root: Ignored
694
695 TODO(sjg@chromium.org): Implement the 'root' parameter
696 """
Simon Glass4df8a0c2018-07-06 10:27:29 -0600697 self._cached_offsets = True
Simon Glass80ef7052017-08-29 14:15:47 -0600698 self._root = self.Node(self, None, 0, '/', '/')
Simon Glasscc346a72016-07-25 18:59:07 -0600699 self._root.Scan()
700
701 def GetRoot(self):
702 """Get the root Node of the device tree
703
704 Returns:
705 The root Node object
706 """
707 return self._root
708
709 def GetNode(self, path):
710 """Look up a node from its path
711
712 Args:
713 path: Path to look up, e.g. '/microcode/update@0'
714 Returns:
715 Node object, or None if not found
716 """
717 node = self._root
Simon Glassc5eddc82018-07-06 10:27:30 -0600718 parts = path.split('/')
719 if len(parts) < 2:
720 return None
Simon Glass3b9a8292019-07-20 12:23:39 -0600721 if len(parts) == 2 and parts[1] == '':
722 return node
Simon Glassc5eddc82018-07-06 10:27:30 -0600723 for part in parts[1:]:
Simon Glassaa1a5d72018-07-17 13:25:41 -0600724 node = node.FindNode(part)
Simon Glasscc346a72016-07-25 18:59:07 -0600725 if not node:
726 return None
727 return node
728
Simon Glass32d98272016-07-25 18:59:15 -0600729 def Flush(self):
730 """Flush device tree changes back to the file
731
732 If the device tree has changed in memory, write it back to the file.
Simon Glass32d98272016-07-25 18:59:15 -0600733 """
Simon Glass059ae522017-05-27 07:38:28 -0600734 with open(self._fname, 'wb') as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600735 fd.write(self._fdt_obj.as_bytearray())
Simon Glass32d98272016-07-25 18:59:15 -0600736
Simon Glasseddd7292018-09-14 04:57:13 -0600737 def Sync(self, auto_resize=False):
738 """Make sure any DT changes are written to the blob
739
740 Args:
741 auto_resize: Resize the device tree automatically if it does not
742 have enough space for the update
743
744 Raises:
745 FdtException if auto_resize is False and there is not enough space
746 """
Simon Glassb2e8ac42021-03-21 18:24:36 +1300747 self.CheckCache()
Simon Glasseddd7292018-09-14 04:57:13 -0600748 self._root.Sync(auto_resize)
Simon Glassb2e8ac42021-03-21 18:24:36 +1300749 self.Refresh()
Simon Glasseddd7292018-09-14 04:57:13 -0600750
Simon Glass32d98272016-07-25 18:59:15 -0600751 def Pack(self):
752 """Pack the device tree down to its minimum size
753
754 When nodes and properties shrink or are deleted, wasted space can
Simon Glass059ae522017-05-27 07:38:28 -0600755 build up in the device tree binary.
756 """
Simon Glass151aeed2018-07-06 10:27:26 -0600757 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glassb2e8ac42021-03-21 18:24:36 +1300758 self.Refresh()
Simon Glass059ae522017-05-27 07:38:28 -0600759
Simon Glass792d2392018-07-06 10:27:27 -0600760 def GetContents(self):
Simon Glass059ae522017-05-27 07:38:28 -0600761 """Get the contents of the FDT
762
763 Returns:
764 The FDT contents as a string of bytes
Simon Glass32d98272016-07-25 18:59:15 -0600765 """
Simon Glass1cd40082019-05-17 22:00:36 -0600766 return bytes(self._fdt_obj.as_bytearray())
Simon Glass059ae522017-05-27 07:38:28 -0600767
Simon Glass0ed50752018-07-06 10:27:24 -0600768 def GetFdtObj(self):
769 """Get the contents of the FDT
770
771 Returns:
772 The FDT contents as a libfdt.Fdt object
773 """
774 return self._fdt_obj
775
Simon Glass059ae522017-05-27 07:38:28 -0600776 def GetProps(self, node):
777 """Get all properties from a node.
778
779 Args:
780 node: Full path to node name to look in.
781
782 Returns:
783 A dictionary containing all the properties, indexed by node name.
784 The entries are Prop objects.
785
786 Raises:
787 ValueError: if the node does not exist.
788 """
789 props_dict = {}
Simon Glass151aeed2018-07-06 10:27:26 -0600790 poffset = self._fdt_obj.first_property_offset(node._offset,
791 QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600792 while poffset >= 0:
793 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass70cd0d72018-07-06 10:27:20 -0600794 prop = Prop(node, poffset, p.name, p)
Simon Glass059ae522017-05-27 07:38:28 -0600795 props_dict[prop.name] = prop
796
Simon Glass151aeed2018-07-06 10:27:26 -0600797 poffset = self._fdt_obj.next_property_offset(poffset,
798 QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600799 return props_dict
800
801 def Invalidate(self):
802 """Mark our offset cache as invalid"""
803 self._cached_offsets = False
804
805 def CheckCache(self):
806 """Refresh the offset cache if needed"""
807 if self._cached_offsets:
808 return
809 self.Refresh()
Simon Glass059ae522017-05-27 07:38:28 -0600810
811 def Refresh(self):
812 """Refresh the offset cache"""
813 self._root.Refresh(0)
Simon Glassb2e8ac42021-03-21 18:24:36 +1300814 self._cached_offsets = True
Simon Glass059ae522017-05-27 07:38:28 -0600815
816 def GetStructOffset(self, offset):
817 """Get the file offset of a given struct offset
818
819 Args:
820 offset: Offset within the 'struct' region of the device tree
821 Returns:
822 Position of @offset within the device tree binary
823 """
Simon Glass151aeed2018-07-06 10:27:26 -0600824 return self._fdt_obj.off_dt_struct() + offset
Simon Glass059ae522017-05-27 07:38:28 -0600825
826 @classmethod
Simon Glass80ef7052017-08-29 14:15:47 -0600827 def Node(self, fdt, parent, offset, name, path):
Simon Glass059ae522017-05-27 07:38:28 -0600828 """Create a new node
829
830 This is used by Fdt.Scan() to create a new node using the correct
831 class.
832
833 Args:
834 fdt: Fdt object
Simon Glass80ef7052017-08-29 14:15:47 -0600835 parent: Parent node, or None if this is the root node
Simon Glass059ae522017-05-27 07:38:28 -0600836 offset: Offset of node
837 name: Node name
838 path: Full path to node
839 """
Simon Glass80ef7052017-08-29 14:15:47 -0600840 node = Node(fdt, parent, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600841 return node
Simon Glassa9440932017-05-27 07:38:30 -0600842
Simon Glass74f5feb2019-07-20 12:24:08 -0600843 def GetFilename(self):
844 """Get the filename of the device tree
845
846 Returns:
847 String filename
848 """
849 return self._fname
850
Simon Glassa9440932017-05-27 07:38:30 -0600851def FdtScan(fname):
Simon Glassc38fee042018-07-06 10:27:32 -0600852 """Returns a new Fdt object"""
Simon Glassa9440932017-05-27 07:38:30 -0600853 dtb = Fdt(fname)
854 dtb.Scan()
855 return dtb