blob: fd0f3e94f5c0181a0c1c5585868d4882db220b85 [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 Glass4583c002023-02-23 18:18:04 -070015from u_boot_pylib import tools
Simon Glass4df457b2023-07-18 07:24:02 -060016from u_boot_pylib import tout
Simon Glassa06a34b2016-07-25 18:59:04 -060017
18# This deals with a device tree, presenting it as an assortment of Node and
19# Prop objects, representing nodes and properties, respectively. This file
Simon Glass99ed4a22017-05-27 07:38:30 -060020# contains the base classes and defines the high-level API. You can use
21# FdtScan() as a convenience function to create and scan an Fdt.
Simon Glass7b75b442017-05-27 07:38:28 -060022
23# This implementation uses a libfdt Python library to access the device tree,
24# so it is fairly efficient.
Simon Glassa06a34b2016-07-25 18:59:04 -060025
Simon Glassbc1dea32016-07-25 18:59:05 -060026# A list of types we support
Simon Glass5ea9dcc2020-11-08 20:36:17 -070027class Type(IntEnum):
Simon Glassdf82de82021-07-28 19:23:09 -060028 # Types in order from widest to narrowest
Simon Glass5ea9dcc2020-11-08 20:36:17 -070029 (BYTE, INT, STRING, BOOL, INT64) = range(5)
30
Simon Glassdf82de82021-07-28 19:23:09 -060031 def needs_widening(self, other):
32 """Check if this type needs widening to hold a value from another type
Simon Glass5ea9dcc2020-11-08 20:36:17 -070033
Simon Glassdf82de82021-07-28 19:23:09 -060034 A wider type is one that can hold a wider array of information than
35 another one, or is less restrictive, so it can hold the information of
36 another type as well as its own. This is similar to the concept of
37 type-widening in C.
Simon Glass5ea9dcc2020-11-08 20:36:17 -070038
39 This uses a simple arithmetic comparison, since type values are in order
Simon Glassdf82de82021-07-28 19:23:09 -060040 from widest (BYTE) to narrowest (INT64).
Simon Glass5ea9dcc2020-11-08 20:36:17 -070041
42 Args:
43 other: Other type to compare against
44
45 Return:
46 True if the other type is wider
47 """
48 return self.value > other.value
Simon Glassbc1dea32016-07-25 18:59:05 -060049
Simon Glassa06a34b2016-07-25 18:59:04 -060050def CheckErr(errnum, msg):
51 if errnum:
52 raise ValueError('Error %d: %s: %s' %
53 (errnum, libfdt.fdt_strerror(errnum), msg))
54
Simon Glass7e6952d2019-05-17 22:00:34 -060055
Simon Glass2b6ed5e2019-05-17 22:00:35 -060056def BytesToValue(data):
Simon Glass7e6952d2019-05-17 22:00:34 -060057 """Converts a string of bytes into a type and value
58
59 Args:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060060 A bytes value (which on Python 2 is an alias for str)
Simon Glass7e6952d2019-05-17 22:00:34 -060061
62 Return:
63 A tuple:
64 Type of data
65 Data, either a single element or a list of elements. Each element
66 is one of:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070067 Type.STRING: str/bytes value from the property
68 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
69 Type.BYTE: a byte stored as a single-byte str/bytes
Simon Glass7e6952d2019-05-17 22:00:34 -060070 """
Simon Glass2b6ed5e2019-05-17 22:00:35 -060071 data = bytes(data)
72 size = len(data)
73 strings = data.split(b'\0')
Simon Glass7e6952d2019-05-17 22:00:34 -060074 is_string = True
75 count = len(strings) - 1
Simon Glass2b6ed5e2019-05-17 22:00:35 -060076 if count > 0 and not len(strings[-1]):
Simon Glass7e6952d2019-05-17 22:00:34 -060077 for string in strings[:-1]:
78 if not string:
79 is_string = False
80 break
81 for ch in string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060082 if ch < 32 or ch > 127:
Simon Glass7e6952d2019-05-17 22:00:34 -060083 is_string = False
84 break
85 else:
86 is_string = False
87 if is_string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060088 if count == 1:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070089 return Type.STRING, strings[0].decode()
Simon Glass7e6952d2019-05-17 22:00:34 -060090 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070091 return Type.STRING, [s.decode() for s in strings[:-1]]
Simon Glass7e6952d2019-05-17 22:00:34 -060092 if size % 4:
93 if size == 1:
Simon Glass479dd302020-11-08 20:36:20 -070094 return Type.BYTE, chr(data[0])
Simon Glass7e6952d2019-05-17 22:00:34 -060095 else:
Simon Glass479dd302020-11-08 20:36:20 -070096 return Type.BYTE, [chr(ch) for ch in list(data)]
Simon Glass7e6952d2019-05-17 22:00:34 -060097 val = []
98 for i in range(0, size, 4):
Simon Glass2b6ed5e2019-05-17 22:00:35 -060099 val.append(data[i:i + 4])
Simon Glass7e6952d2019-05-17 22:00:34 -0600100 if size == 4:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700101 return Type.INT, val[0]
Simon Glass7e6952d2019-05-17 22:00:34 -0600102 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700103 return Type.INT, val
Simon Glass7e6952d2019-05-17 22:00:34 -0600104
105
Simon Glass7b75b442017-05-27 07:38:28 -0600106class Prop:
Simon Glassa06a34b2016-07-25 18:59:04 -0600107 """A device tree property
108
109 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300110 node: Node containing this property
111 offset: Offset of the property (None if still to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600112 name: Property name (as per the device tree)
113 value: Property value as a string of bytes, or a list of strings of
114 bytes
115 type: Value type
116 """
Simon Glass928527f2019-05-17 22:00:37 -0600117 def __init__(self, node, offset, name, data):
Simon Glassa06a34b2016-07-25 18:59:04 -0600118 self._node = node
119 self._offset = offset
120 self.name = name
121 self.value = None
Simon Glass928527f2019-05-17 22:00:37 -0600122 self.bytes = bytes(data)
Simon Glass37ba9842021-03-21 18:24:35 +1300123 self.dirty = offset is None
Simon Glass928527f2019-05-17 22:00:37 -0600124 if not data:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700125 self.type = Type.BOOL
Simon Glass7b75b442017-05-27 07:38:28 -0600126 self.value = True
127 return
Simon Glass928527f2019-05-17 22:00:37 -0600128 self.type, self.value = BytesToValue(bytes(data))
Simon Glassa06a34b2016-07-25 18:59:04 -0600129
Simon Glassf9b88b32018-07-06 10:27:29 -0600130 def RefreshOffset(self, poffset):
131 self._offset = poffset
132
Simon Glassc322a852016-07-25 18:59:06 -0600133 def Widen(self, newprop):
134 """Figure out which property type is more general
135
136 Given a current property and a new property, this function returns the
137 one that is less specific as to type. The less specific property will
138 be ble to represent the data in the more specific property. This is
139 used for things like:
140
141 node1 {
142 compatible = "fred";
143 value = <1>;
144 };
145 node1 {
146 compatible = "fred";
147 value = <1 2>;
148 };
149
150 He we want to use an int array for 'value'. The first property
151 suggests that a single int is enough, but the second one shows that
152 it is not. Calling this function with these two propertes would
153 update the current property to be like the second, since it is less
154 specific.
155 """
Simon Glassdf82de82021-07-28 19:23:09 -0600156 if self.type.needs_widening(newprop.type):
Simon Glasseec44c72021-07-28 19:23:11 -0600157
158 # A boolean has an empty value: if it exists it is True and if not
159 # it is False. So when widening we always start with an empty list
160 # since the only valid integer property would be an empty list of
161 # integers.
162 # e.g. this is a boolean:
163 # some-prop;
164 # and it would be widened to int list by:
165 # some-prop = <1 2>;
166 if self.type == Type.BOOL:
167 self.type = Type.INT
168 self.value = [self.GetEmpty(self.type)]
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700169 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glasse144caf2020-10-03 11:31:27 -0600170 if type(self.value) == list:
171 new_value = []
172 for val in self.value:
Simon Glass479dd302020-11-08 20:36:20 -0700173 new_value += [chr(by) for by in val]
Simon Glasse144caf2020-10-03 11:31:27 -0600174 else:
Simon Glass479dd302020-11-08 20:36:20 -0700175 new_value = [chr(by) for by in self.value]
Simon Glasse144caf2020-10-03 11:31:27 -0600176 self.value = new_value
Simon Glassc322a852016-07-25 18:59:06 -0600177 self.type = newprop.type
178
Simon Glassca044942021-07-28 19:23:10 -0600179 if type(newprop.value) == list:
180 if type(self.value) != list:
181 self.value = [self.value]
Simon Glassc322a852016-07-25 18:59:06 -0600182
Simon Glassca044942021-07-28 19:23:10 -0600183 if len(newprop.value) > len(self.value):
184 val = self.GetEmpty(self.type)
185 while len(self.value) < len(newprop.value):
186 self.value.append(val)
Simon Glassc322a852016-07-25 18:59:06 -0600187
Simon Glass2ba98752018-07-06 10:27:24 -0600188 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600189 def GetEmpty(self, type):
190 """Get an empty / zero value of the given type
191
192 Returns:
193 A single value of the given type
194 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700195 if type == Type.BYTE:
Simon Glassbc1dea32016-07-25 18:59:05 -0600196 return chr(0)
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700197 elif type == Type.INT:
Simon Glassaf53f5a2018-09-14 04:57:14 -0600198 return struct.pack('>I', 0);
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700199 elif type == Type.STRING:
Simon Glassbc1dea32016-07-25 18:59:05 -0600200 return ''
201 else:
202 return True
203
Simon Glassbabdbde2016-07-25 18:59:16 -0600204 def GetOffset(self):
205 """Get the offset of a property
206
Simon Glassbabdbde2016-07-25 18:59:16 -0600207 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600208 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600209 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600210 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600211 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600212
Simon Glassfa80c252018-09-14 04:57:13 -0600213 def SetInt(self, val):
214 """Set the integer value of the property
215
216 The device tree is marked dirty so that the value will be written to
217 the block on the next sync.
218
219 Args:
220 val: Integer value (32-bit, single cell)
221 """
222 self.bytes = struct.pack('>I', val);
Simon Glass41b781d2018-10-01 12:22:49 -0600223 self.value = self.bytes
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700224 self.type = Type.INT
Simon Glassfa80c252018-09-14 04:57:13 -0600225 self.dirty = True
226
Simon Glass64349612018-09-14 04:57:16 -0600227 def SetData(self, bytes):
228 """Set the value of a property as bytes
229
230 Args:
231 bytes: New property value to set
232 """
Simon Glassf6b64812019-05-17 22:00:36 -0600233 self.bytes = bytes
Simon Glass7e6952d2019-05-17 22:00:34 -0600234 self.type, self.value = BytesToValue(bytes)
Simon Glass64349612018-09-14 04:57:16 -0600235 self.dirty = True
236
Simon Glassfa80c252018-09-14 04:57:13 -0600237 def Sync(self, auto_resize=False):
238 """Sync property changes back to the device tree
239
240 This updates the device tree blob with any changes to this property
241 since the last sync.
242
243 Args:
244 auto_resize: Resize the device tree automatically if it does not
245 have enough space for the update
246
247 Raises:
248 FdtException if auto_resize is False and there is not enough space
249 """
Simon Glass37ba9842021-03-21 18:24:35 +1300250 if self.dirty:
Simon Glassfa80c252018-09-14 04:57:13 -0600251 node = self._node
252 fdt_obj = node._fdt._fdt_obj
Simon Glass5d1bec32021-03-21 18:24:39 +1300253 node_name = fdt_obj.get_name(node._offset)
254 if node_name and node_name != node.name:
255 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
256 (node.path, node_name))
257
Simon Glassfa80c252018-09-14 04:57:13 -0600258 if auto_resize:
259 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
260 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassc0639172020-07-09 18:39:44 -0600261 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
262 len(self.bytes))
Simon Glassfa80c252018-09-14 04:57:13 -0600263 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
264 else:
265 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
Simon Glass37ba9842021-03-21 18:24:35 +1300266 self.dirty = False
Simon Glassfa80c252018-09-14 04:57:13 -0600267
Simon Glass4df457b2023-07-18 07:24:02 -0600268 def purge(self):
269 """Set a property offset to None
270
271 The property remains in the tree structure and will be recreated when
272 the FDT is synced
273 """
274 self._offset = None
Simon Glassfa80c252018-09-14 04:57:13 -0600275
Simon Glass7b75b442017-05-27 07:38:28 -0600276class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600277 """A device tree node
278
279 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300280 parent: Parent Node
281 offset: Integer offset in the device tree (None if to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600282 name: Device tree node tname
283 path: Full path to node, along with the node name itself
284 _fdt: Device tree object
285 subnodes: A list of subnodes for this node, each a Node object
286 props: A dict of properties for this node, each a Prop object.
287 Keyed by property name
288 """
Simon Glass979ab022017-08-29 14:15:47 -0600289 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600290 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600291 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600292 self._offset = offset
293 self.name = name
294 self.path = path
295 self.subnodes = []
296 self.props = {}
297
Simon Glass94a7c602018-07-17 13:25:46 -0600298 def GetFdt(self):
299 """Get the Fdt object for this node
300
301 Returns:
302 Fdt object
303 """
304 return self._fdt
305
Simon Glass1d858882018-07-17 13:25:41 -0600306 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600307 """Find a node given its name
308
309 Args:
310 name: Node name to look for
311 Returns:
312 Node object if found, else None
313 """
314 for subnode in self.subnodes:
315 if subnode.name == name:
316 return subnode
317 return None
318
Simon Glass7b75b442017-05-27 07:38:28 -0600319 def Offset(self):
320 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600321
Simon Glass7b75b442017-05-27 07:38:28 -0600322 This should be used instead of self._offset directly, to ensure that
323 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600324 """
Simon Glass7b75b442017-05-27 07:38:28 -0600325 self._fdt.CheckCache()
326 return self._offset
327
328 def Scan(self):
329 """Scan a node's properties and subnodes
330
331 This fills in the props and subnodes properties, recursively
332 searching into subnodes so that the entire tree is built.
333 """
Simon Glass117f57b2018-07-06 10:27:26 -0600334 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600335 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600336 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600337 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600338 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600339
Simon Glass117f57b2018-07-06 10:27:26 -0600340 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600341 while offset >= 0:
342 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600343 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600344 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600345 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600346 self.subnodes.append(node)
347
348 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600349 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600350
351 def Refresh(self, my_offset):
352 """Fix up the _offset for each node, recursively
353
354 Note: This does not take account of property offsets - these will not
355 be updated.
356 """
Simon Glass96066242018-07-06 10:27:27 -0600357 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600358 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600359 self._offset = my_offset
Simon Glass5d1bec32021-03-21 18:24:39 +1300360 name = fdt_obj.get_name(self._offset)
361 if name and self.name != name:
362 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
363 (self.path, name))
364
Simon Glass96066242018-07-06 10:27:27 -0600365 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600366 for subnode in self.subnodes:
Simon Glassdd857ee2022-02-08 11:49:52 -0700367 if subnode._offset is None:
368 continue
Simon Glassf9b88b32018-07-06 10:27:29 -0600369 if subnode.name != fdt_obj.get_name(offset):
370 raise ValueError('Internal error, node name mismatch %s != %s' %
371 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600372 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600373 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600374 if offset != -libfdt.FDT_ERR_NOTFOUND:
375 raise ValueError('Internal error, offset == %d' % offset)
376
377 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
378 while poffset >= 0:
379 p = fdt_obj.get_property_by_offset(poffset)
380 prop = self.props.get(p.name)
381 if not prop:
Simon Glassacd98612021-03-21 18:24:34 +1300382 raise ValueError("Internal error, node '%s' property '%s' missing, "
383 'offset %d' % (self.path, p.name, poffset))
Simon Glassf9b88b32018-07-06 10:27:29 -0600384 prop.RefreshOffset(poffset)
385 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600386
Simon Glass2a70d892016-07-25 18:59:14 -0600387 def DeleteProp(self, prop_name):
388 """Delete a property of a node
389
Simon Glass7b75b442017-05-27 07:38:28 -0600390 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600391
392 Args:
393 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600394 Raises:
395 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600396 """
Simon Glass96066242018-07-06 10:27:27 -0600397 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600398 "Node '%s': delete property: '%s'" % (self.path, prop_name))
399 del self.props[prop_name]
400 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600401
Simon Glass116adec2018-07-06 10:27:38 -0600402 def AddZeroProp(self, prop_name):
403 """Add a new property to the device tree with an integer value of 0.
404
405 Args:
406 prop_name: Name of property
407 """
Simon Glass194b8d52019-05-17 22:00:33 -0600408 self.props[prop_name] = Prop(self, None, prop_name,
Simon Glassc1aa66e2022-01-29 14:14:04 -0700409 tools.get_bytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600410
Simon Glass64349612018-09-14 04:57:16 -0600411 def AddEmptyProp(self, prop_name, len):
412 """Add a property with a fixed data size, for filling in later
413
414 The device tree is marked dirty so that the value will be written to
415 the blob on the next sync.
416
417 Args:
418 prop_name: Name of property
419 len: Length of data in property
420 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700421 value = tools.get_bytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600422 self.props[prop_name] = Prop(self, None, prop_name, value)
423
Simon Glassd9dad102019-07-20 12:23:37 -0600424 def _CheckProp(self, prop_name):
425 """Check if a property is present
426
427 Args:
428 prop_name: Name of property
429
430 Returns:
431 self
432
433 Raises:
434 ValueError if the property is missing
435 """
436 if prop_name not in self.props:
437 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
438 (self._fdt._fname, self.path, prop_name))
439 return self
440
Simon Glass116adec2018-07-06 10:27:38 -0600441 def SetInt(self, prop_name, val):
442 """Update an integer property int the device tree.
443
444 This is not allowed to change the size of the FDT.
445
Simon Glass64349612018-09-14 04:57:16 -0600446 The device tree is marked dirty so that the value will be written to
447 the blob on the next sync.
448
Simon Glass116adec2018-07-06 10:27:38 -0600449 Args:
450 prop_name: Name of property
451 val: Value to set
452 """
Simon Glassd9dad102019-07-20 12:23:37 -0600453 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600454
Simon Glass64349612018-09-14 04:57:16 -0600455 def SetData(self, prop_name, val):
456 """Set the data value of a property
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 set
463 val: Data value to set
464 """
Simon Glassd9dad102019-07-20 12:23:37 -0600465 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600466
467 def SetString(self, prop_name, val):
468 """Set the string value of a property
469
470 The device tree is marked dirty so that the value will be written to
471 the blob on the next sync.
472
473 Args:
474 prop_name: Name of property to set
475 val: String value to set (will be \0-terminated in DT)
476 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600477 if type(val) == str:
478 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600479 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600480
Simon Glassc0639172020-07-09 18:39:44 -0600481 def AddData(self, prop_name, val):
482 """Add a new property to a node
483
484 The device tree is marked dirty so that the value will be written to
485 the blob on the next sync.
486
487 Args:
488 prop_name: Name of property to add
489 val: Bytes value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300490
491 Returns:
492 Prop added
Simon Glassc0639172020-07-09 18:39:44 -0600493 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300494 prop = Prop(self, None, prop_name, val)
495 self.props[prop_name] = prop
496 return prop
Simon Glassc0639172020-07-09 18:39:44 -0600497
Simon Glass64349612018-09-14 04:57:16 -0600498 def AddString(self, prop_name, val):
499 """Add a new string property to a node
500
501 The device tree is marked dirty so that the value will be written to
502 the blob on the next sync.
503
504 Args:
505 prop_name: Name of property to add
506 val: String value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300507
508 Returns:
509 Prop added
Simon Glass64349612018-09-14 04:57:16 -0600510 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700511 val = bytes(val, 'utf-8')
Simon Glass5d1bec32021-03-21 18:24:39 +1300512 return self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600513
Simon Glassbc116022022-02-08 11:49:50 -0700514 def AddStringList(self, prop_name, val):
515 """Add a new string-list property to a node
516
517 The device tree is marked dirty so that the value will be written to
518 the blob on the next sync.
519
520 Args:
521 prop_name: Name of property to add
522 val (list of str): List of strings to add
523
524 Returns:
525 Prop added
526 """
Simon Glass0ded4d42022-03-05 20:18:56 -0700527 out = b'\0'.join(bytes(s, 'utf-8') for s in val) + b'\0' if val else b''
Simon Glassbc116022022-02-08 11:49:50 -0700528 return self.AddData(prop_name, out)
529
Simon Glass6eb99322021-01-06 21:35:18 -0700530 def AddInt(self, prop_name, val):
531 """Add a new integer property to a node
532
533 The device tree is marked dirty so that the value will be written to
534 the blob on the next sync.
535
536 Args:
537 prop_name: Name of property to add
538 val: Integer value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300539
540 Returns:
541 Prop added
Simon Glass6eb99322021-01-06 21:35:18 -0700542 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300543 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glass6eb99322021-01-06 21:35:18 -0700544
Simon Glass4df457b2023-07-18 07:24:02 -0600545 def Subnode(self, name):
546 """Create new subnode for the node
Simon Glass64349612018-09-14 04:57:16 -0600547
548 Args:
549 name: name of node to add
550
551 Returns:
552 New subnode that was created
553 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600554 path = self.path + '/' + name
Simon Glass4df457b2023-07-18 07:24:02 -0600555 return Node(self._fdt, self, None, name, path)
556
557 def AddSubnode(self, name):
558 """Add a new subnode to the node, after all other subnodes
559
560 Args:
561 name: name of node to add
562
563 Returns:
564 New subnode that was created
565 """
566 subnode = self.Subnode(name)
Simon Glasse21c27a2018-09-14 04:57:15 -0600567 self.subnodes.append(subnode)
568 return subnode
569
Simon Glass4df457b2023-07-18 07:24:02 -0600570 def insert_subnode(self, name):
571 """Add a new subnode to the node, before all other subnodes
572
573 This deletes other subnodes and sets their offset to None, so that they
574 will be recreated after this one.
575
576 Args:
577 name: name of node to add
578
579 Returns:
580 New subnode that was created
581 """
582 # Deleting a node invalidates the offsets of all following nodes, so
583 # process in reverse order so that the offset of each node remains valid
584 # until deletion.
585 for subnode in reversed(self.subnodes):
586 subnode.purge(True)
587 subnode = self.Subnode(name)
588 self.subnodes.insert(0, subnode)
589 return subnode
590
591 def purge(self, delete_it=False):
592 """Purge this node, setting offset to None and deleting from FDT"""
593 if self._offset is not None:
594 if delete_it:
595 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
596 "Node '%s': delete" % self.path)
597 self._offset = None
598 self._fdt.Invalidate()
599
600 for prop in self.props.values():
601 prop.purge()
602
603 for subnode in self.subnodes:
604 subnode.purge(False)
605
606 def move_to_first(self):
607 """Move the current node to first in its parent's node list"""
608 parent = self.parent
609 if parent.subnodes and parent.subnodes[0] == self:
610 return
611 for subnode in reversed(parent.subnodes):
612 subnode.purge(True)
613
614 new_subnodes = [self]
615 for subnode in parent.subnodes:
616 #subnode.purge(False)
617 if subnode != self:
618 new_subnodes.append(subnode)
619 parent.subnodes = new_subnodes
620
Simon Glassa30c39f2022-02-08 11:49:51 -0700621 def Delete(self):
622 """Delete a node
623
624 The node is deleted and the offset cache is invalidated.
625
626 Args:
627 node (Node): Node to delete
628
629 Raises:
630 ValueError if the node does not exist
631 """
632 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
633 "Node '%s': delete" % self.path)
634 parent = self.parent
635 self._fdt.Invalidate()
636 parent.subnodes.remove(self)
637
Simon Glassfa80c252018-09-14 04:57:13 -0600638 def Sync(self, auto_resize=False):
639 """Sync node changes back to the device tree
640
641 This updates the device tree blob with any changes to this node and its
642 subnodes since the last sync.
643
644 Args:
645 auto_resize: Resize the device tree automatically if it does not
646 have enough space for the update
647
Simon Glassf6176652021-03-21 18:24:38 +1300648 Returns:
649 True if the node had to be added, False if it already existed
650
Simon Glassfa80c252018-09-14 04:57:13 -0600651 Raises:
652 FdtException if auto_resize is False and there is not enough space
653 """
Simon Glassf6176652021-03-21 18:24:38 +1300654 added = False
Simon Glasse21c27a2018-09-14 04:57:15 -0600655 if self._offset is None:
656 # The subnode doesn't exist yet, so add it
657 fdt_obj = self._fdt._fdt_obj
658 if auto_resize:
659 while True:
660 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
661 (libfdt.NOSPACE,))
662 if offset != -libfdt.NOSPACE:
663 break
664 fdt_obj.resize(fdt_obj.totalsize() + 1024)
665 else:
666 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
667 self._offset = offset
Simon Glassf6176652021-03-21 18:24:38 +1300668 added = True
Simon Glasse21c27a2018-09-14 04:57:15 -0600669
Simon Glassf6176652021-03-21 18:24:38 +1300670 # Sync the existing subnodes first, so that we can rely on the offsets
671 # being correct. As soon as we add new subnodes, it pushes all the
672 # existing subnodes up.
Simon Glassfa80c252018-09-14 04:57:13 -0600673 for node in reversed(self.subnodes):
Simon Glassf6176652021-03-21 18:24:38 +1300674 if node._offset is not None:
675 node.Sync(auto_resize)
Simon Glassfa80c252018-09-14 04:57:13 -0600676
Simon Glassf6176652021-03-21 18:24:38 +1300677 # Sync subnodes in reverse so that we get the expected order. Each
678 # new node goes at the start of the subnode list. This avoids an O(n^2)
679 # rescan of node offsets.
680 num_added = 0
681 for node in reversed(self.subnodes):
682 if node.Sync(auto_resize):
683 num_added += 1
684 if num_added:
685 # Reorder our list of nodes to put the new ones first, since that's
686 # what libfdt does
687 old_count = len(self.subnodes) - num_added
688 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
689 self.subnodes = subnodes
690
691 # Sync properties now, whose offsets should not have been disturbed,
692 # since properties come before subnodes. This is done after all the
693 # subnode processing above, since updating properties can disturb the
694 # offsets of those subnodes.
695 # Properties are synced in reverse order, with new properties added
696 # before existing properties are synced. This ensures that the offsets
697 # of earlier properties are not disturbed.
698 # Note that new properties will have an offset of None here, which
699 # Python cannot sort against int. So use a large value instead so that
700 # new properties are added first.
Simon Glass63518052019-05-17 22:00:38 -0600701 prop_list = sorted(self.props.values(),
702 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600703 reverse=True)
704 for prop in prop_list:
705 prop.Sync(auto_resize)
Simon Glassf6176652021-03-21 18:24:38 +1300706 return added
Simon Glass116adec2018-07-06 10:27:38 -0600707
Simon Glass4df457b2023-07-18 07:24:02 -0600708 def merge_props(self, src):
709 """Copy missing properties (except 'phandle') from another node
710
711 Args:
712 src (Node): Node containing properties to copy
713
714 Adds properties which are present in src but not in this node. Any
715 'phandle' property is not copied since this might result in two nodes
716 with the same phandle, thus making phandle references ambiguous.
717 """
718 for name, src_prop in src.props.items():
719 if name != 'phandle' and name not in self.props:
720 self.props[name] = Prop(self, None, name, src_prop.bytes)
721
722 def copy_node(self, src):
723 """Copy a node and all its subnodes into this node
724
725 Args:
726 src (Node): Node to copy
727
728 Returns:
729 Node: Resulting destination node
730
731 This works recursively.
732
733 The new node is put before all other nodes. If the node already
734 exists, just its subnodes and properties are copied, placing them before
735 any existing subnodes. Properties which exist in the destination node
736 already are not copied.
737 """
738 dst = self.FindNode(src.name)
739 if dst:
740 dst.move_to_first()
741 else:
742 dst = self.insert_subnode(src.name)
743 dst.merge_props(src)
744
745 # Process in reverse order so that they appear correctly in the result,
746 # since copy_node() puts the node first in the list
747 for node in reversed(src.subnodes):
748 dst.copy_node(node)
749 return dst
750
Simon Glass55e12782023-07-18 07:24:03 -0600751 def copy_subnodes_from_phandles(self, phandle_list):
752 """Copy subnodes of a list of nodes into another node
753
754 Args:
755 phandle_list (list of int): List of phandles of nodes to copy
756
757 For each node in the phandle list, its subnodes and their properties are
758 copied recursively. Note that it does not copy the node itself, nor its
759 properties.
760 """
761 # Process in reverse order, since new nodes are inserted at the start of
762 # the destination's node list. We want them to appear in order of the
763 # phandle list
764 for phandle in phandle_list.__reversed__():
765 parent = self.GetFdt().LookupPhandle(phandle)
766 tout.debug(f'adding template {parent.path} to node {self.path}')
767 for node in parent.subnodes.__reversed__():
768 dst = self.copy_node(node)
769
770 tout.debug(f'merge props from {parent.path} to {dst.path}')
771 self.merge_props(parent)
772
Simon Glass116adec2018-07-06 10:27:38 -0600773
Simon Glassa06a34b2016-07-25 18:59:04 -0600774class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600775 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600776
777 Properties:
778 fname: Filename of fdt
779 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600780 name: Helpful name for this Fdt for the user (useful when creating the
781 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600782 """
783 def __init__(self, fname):
784 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600785 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600786 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600787 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600788 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600789 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600790 self._fname = fdt_util.EnsureCompiled(self._fname)
791
Simon Glass3e4b51e2019-05-14 15:53:43 -0600792 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600793 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600794
Simon Glass746aee32018-09-14 04:57:17 -0600795 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600796 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600797 """Create a new Fdt object from the given data
798
799 Args:
800 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600801 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600802
803 Returns:
804 Fdt object containing the data
805 """
806 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600807 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600808 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600809 return fdt
810
Simon Glass94a7c602018-07-17 13:25:46 -0600811 def LookupPhandle(self, phandle):
812 """Look up a phandle
813
814 Args:
815 phandle: Phandle to look up (int)
816
817 Returns:
818 Node object the phandle points to
819 """
820 return self.phandle_to_node.get(phandle)
821
Simon Glassf7a2aee2016-07-25 18:59:07 -0600822 def Scan(self, root='/'):
823 """Scan a device tree, building up a tree of Node objects
824
825 This fills in the self._root property
826
827 Args:
828 root: Ignored
829
830 TODO(sjg@chromium.org): Implement the 'root' parameter
831 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600832 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600833 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600834 self._root.Scan()
835
836 def GetRoot(self):
837 """Get the root Node of the device tree
838
839 Returns:
840 The root Node object
841 """
842 return self._root
843
844 def GetNode(self, path):
845 """Look up a node from its path
846
847 Args:
848 path: Path to look up, e.g. '/microcode/update@0'
849 Returns:
850 Node object, or None if not found
851 """
852 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600853 parts = path.split('/')
854 if len(parts) < 2:
855 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600856 if len(parts) == 2 and parts[1] == '':
857 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600858 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600859 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600860 if not node:
861 return None
862 return node
863
Simon Glassda5f7492016-07-25 18:59:15 -0600864 def Flush(self):
865 """Flush device tree changes back to the file
866
867 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600868 """
Simon Glass7b75b442017-05-27 07:38:28 -0600869 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600870 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600871
Simon Glassfa80c252018-09-14 04:57:13 -0600872 def Sync(self, auto_resize=False):
873 """Make sure any DT changes are written to the blob
874
875 Args:
876 auto_resize: Resize the device tree automatically if it does not
877 have enough space for the update
878
879 Raises:
880 FdtException if auto_resize is False and there is not enough space
881 """
Simon Glass71719e12021-03-21 18:24:36 +1300882 self.CheckCache()
Simon Glassfa80c252018-09-14 04:57:13 -0600883 self._root.Sync(auto_resize)
Simon Glass71719e12021-03-21 18:24:36 +1300884 self.Refresh()
Simon Glassfa80c252018-09-14 04:57:13 -0600885
Simon Glassda5f7492016-07-25 18:59:15 -0600886 def Pack(self):
887 """Pack the device tree down to its minimum size
888
889 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600890 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600891 """
Simon Glass117f57b2018-07-06 10:27:26 -0600892 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glass71719e12021-03-21 18:24:36 +1300893 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600894
Simon Glass96066242018-07-06 10:27:27 -0600895 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600896 """Get the contents of the FDT
897
898 Returns:
899 The FDT contents as a string of bytes
900 """
Simon Glassf6b64812019-05-17 22:00:36 -0600901 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600902
Simon Glass2ba98752018-07-06 10:27:24 -0600903 def GetFdtObj(self):
904 """Get the contents of the FDT
905
906 Returns:
907 The FDT contents as a libfdt.Fdt object
908 """
909 return self._fdt_obj
910
Simon Glass7b75b442017-05-27 07:38:28 -0600911 def GetProps(self, node):
912 """Get all properties from a node.
913
914 Args:
915 node: Full path to node name to look in.
916
917 Returns:
918 A dictionary containing all the properties, indexed by node name.
919 The entries are Prop objects.
920
921 Raises:
922 ValueError: if the node does not exist.
923 """
924 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600925 poffset = self._fdt_obj.first_property_offset(node._offset,
926 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600927 while poffset >= 0:
928 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600929 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600930 props_dict[prop.name] = prop
931
Simon Glass117f57b2018-07-06 10:27:26 -0600932 poffset = self._fdt_obj.next_property_offset(poffset,
933 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600934 return props_dict
935
936 def Invalidate(self):
937 """Mark our offset cache as invalid"""
938 self._cached_offsets = False
939
940 def CheckCache(self):
941 """Refresh the offset cache if needed"""
942 if self._cached_offsets:
943 return
944 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600945
946 def Refresh(self):
947 """Refresh the offset cache"""
948 self._root.Refresh(0)
Simon Glass71719e12021-03-21 18:24:36 +1300949 self._cached_offsets = True
Simon Glass7b75b442017-05-27 07:38:28 -0600950
951 def GetStructOffset(self, offset):
952 """Get the file offset of a given struct offset
953
954 Args:
955 offset: Offset within the 'struct' region of the device tree
956 Returns:
957 Position of @offset within the device tree binary
958 """
Simon Glass117f57b2018-07-06 10:27:26 -0600959 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600960
961 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600962 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600963 """Create a new node
964
965 This is used by Fdt.Scan() to create a new node using the correct
966 class.
967
968 Args:
969 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600970 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600971 offset: Offset of node
972 name: Node name
973 path: Full path to node
974 """
Simon Glass979ab022017-08-29 14:15:47 -0600975 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600976 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600977
Simon Glassf6e02492019-07-20 12:24:08 -0600978 def GetFilename(self):
979 """Get the filename of the device tree
980
981 Returns:
982 String filename
983 """
984 return self._fname
985
Simon Glass99ed4a22017-05-27 07:38:30 -0600986def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600987 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600988 dtb = Fdt(fname)
989 dtb.Scan()
990 return dtb