blob: fd0f3e94f5c0181a0c1c5585868d4882db220b85 [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 Glassadf8e052023-07-18 07:24:02 -060016from u_boot_pylib import tout
Simon Glass1f941b62016-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 Glassa9440932017-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 Glass059ae522017-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 Glass1f941b62016-07-25 18:59:04 -060025
Simon Glassb1a5e262016-07-25 18:59:05 -060026# A list of types we support
Simon Glassc9a032c2020-11-08 20:36:17 -070027class Type(IntEnum):
Simon Glassfd4078e2021-07-28 19:23:09 -060028 # Types in order from widest to narrowest
Simon Glassc9a032c2020-11-08 20:36:17 -070029 (BYTE, INT, STRING, BOOL, INT64) = range(5)
30
Simon Glassfd4078e2021-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 Glassc9a032c2020-11-08 20:36:17 -070033
Simon Glassfd4078e2021-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 Glassc9a032c2020-11-08 20:36:17 -070038
39 This uses a simple arithmetic comparison, since type values are in order
Simon Glassfd4078e2021-07-28 19:23:09 -060040 from widest (BYTE) to narrowest (INT64).
Simon Glassc9a032c2020-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 Glassb1a5e262016-07-25 18:59:05 -060049
Simon Glass1f941b62016-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 Glassb73d4482019-05-17 22:00:34 -060055
Simon Glassd8f593f2019-05-17 22:00:35 -060056def BytesToValue(data):
Simon Glassb73d4482019-05-17 22:00:34 -060057 """Converts a string of bytes into a type and value
58
59 Args:
Simon Glassd8f593f2019-05-17 22:00:35 -060060 A bytes value (which on Python 2 is an alias for str)
Simon Glassb73d4482019-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 Glassc9a032c2020-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 Glassb73d4482019-05-17 22:00:34 -060070 """
Simon Glassd8f593f2019-05-17 22:00:35 -060071 data = bytes(data)
72 size = len(data)
73 strings = data.split(b'\0')
Simon Glassb73d4482019-05-17 22:00:34 -060074 is_string = True
75 count = len(strings) - 1
Simon Glassd8f593f2019-05-17 22:00:35 -060076 if count > 0 and not len(strings[-1]):
Simon Glassb73d4482019-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 Glassd8f593f2019-05-17 22:00:35 -060082 if ch < 32 or ch > 127:
Simon Glassb73d4482019-05-17 22:00:34 -060083 is_string = False
84 break
85 else:
86 is_string = False
87 if is_string:
Simon Glassd8f593f2019-05-17 22:00:35 -060088 if count == 1:
Simon Glassc9a032c2020-11-08 20:36:17 -070089 return Type.STRING, strings[0].decode()
Simon Glassb73d4482019-05-17 22:00:34 -060090 else:
Simon Glassc9a032c2020-11-08 20:36:17 -070091 return Type.STRING, [s.decode() for s in strings[:-1]]
Simon Glassb73d4482019-05-17 22:00:34 -060092 if size % 4:
93 if size == 1:
Simon Glass632b84c2020-11-08 20:36:20 -070094 return Type.BYTE, chr(data[0])
Simon Glassb73d4482019-05-17 22:00:34 -060095 else:
Simon Glass632b84c2020-11-08 20:36:20 -070096 return Type.BYTE, [chr(ch) for ch in list(data)]
Simon Glassb73d4482019-05-17 22:00:34 -060097 val = []
98 for i in range(0, size, 4):
Simon Glassd8f593f2019-05-17 22:00:35 -060099 val.append(data[i:i + 4])
Simon Glassb73d4482019-05-17 22:00:34 -0600100 if size == 4:
Simon Glassc9a032c2020-11-08 20:36:17 -0700101 return Type.INT, val[0]
Simon Glassb73d4482019-05-17 22:00:34 -0600102 else:
Simon Glassc9a032c2020-11-08 20:36:17 -0700103 return Type.INT, val
Simon Glassb73d4482019-05-17 22:00:34 -0600104
105
Simon Glass059ae522017-05-27 07:38:28 -0600106class Prop:
Simon Glass1f941b62016-07-25 18:59:04 -0600107 """A device tree property
108
109 Properties:
Simon Glass459df472021-03-21 18:24:35 +1300110 node: Node containing this property
111 offset: Offset of the property (None if still to be synced)
Simon Glass1f941b62016-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 Glass74b472e2019-05-17 22:00:37 -0600117 def __init__(self, node, offset, name, data):
Simon Glass1f941b62016-07-25 18:59:04 -0600118 self._node = node
119 self._offset = offset
120 self.name = name
121 self.value = None
Simon Glass74b472e2019-05-17 22:00:37 -0600122 self.bytes = bytes(data)
Simon Glass459df472021-03-21 18:24:35 +1300123 self.dirty = offset is None
Simon Glass74b472e2019-05-17 22:00:37 -0600124 if not data:
Simon Glassc9a032c2020-11-08 20:36:17 -0700125 self.type = Type.BOOL
Simon Glass059ae522017-05-27 07:38:28 -0600126 self.value = True
127 return
Simon Glass74b472e2019-05-17 22:00:37 -0600128 self.type, self.value = BytesToValue(bytes(data))
Simon Glass1f941b62016-07-25 18:59:04 -0600129
Simon Glass4df8a0c2018-07-06 10:27:29 -0600130 def RefreshOffset(self, poffset):
131 self._offset = poffset
132
Simon Glass248ccd22016-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 Glassfd4078e2021-07-28 19:23:09 -0600156 if self.type.needs_widening(newprop.type):
Simon Glass43118322021-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 Glassc9a032c2020-11-08 20:36:17 -0700169 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glass8034e4d2020-10-03 11:31:27 -0600170 if type(self.value) == list:
171 new_value = []
172 for val in self.value:
Simon Glass632b84c2020-11-08 20:36:20 -0700173 new_value += [chr(by) for by in val]
Simon Glass8034e4d2020-10-03 11:31:27 -0600174 else:
Simon Glass632b84c2020-11-08 20:36:20 -0700175 new_value = [chr(by) for by in self.value]
Simon Glass8034e4d2020-10-03 11:31:27 -0600176 self.value = new_value
Simon Glass248ccd22016-07-25 18:59:06 -0600177 self.type = newprop.type
178
Simon Glassa7d66982021-07-28 19:23:10 -0600179 if type(newprop.value) == list:
180 if type(self.value) != list:
181 self.value = [self.value]
Simon Glass248ccd22016-07-25 18:59:06 -0600182
Simon Glassa7d66982021-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 Glass248ccd22016-07-25 18:59:06 -0600187
Simon Glass0ed50752018-07-06 10:27:24 -0600188 @classmethod
Simon Glassb1a5e262016-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 Glassc9a032c2020-11-08 20:36:17 -0700195 if type == Type.BYTE:
Simon Glassb1a5e262016-07-25 18:59:05 -0600196 return chr(0)
Simon Glassc9a032c2020-11-08 20:36:17 -0700197 elif type == Type.INT:
Simon Glassc330b142018-09-14 04:57:14 -0600198 return struct.pack('>I', 0);
Simon Glassc9a032c2020-11-08 20:36:17 -0700199 elif type == Type.STRING:
Simon Glassb1a5e262016-07-25 18:59:05 -0600200 return ''
201 else:
202 return True
203
Simon Glass54cec6d2016-07-25 18:59:16 -0600204 def GetOffset(self):
205 """Get the offset of a property
206
Simon Glass54cec6d2016-07-25 18:59:16 -0600207 Returns:
Simon Glass059ae522017-05-27 07:38:28 -0600208 The offset of the property (struct fdt_property) within the file
Simon Glass54cec6d2016-07-25 18:59:16 -0600209 """
Simon Glass4df8a0c2018-07-06 10:27:29 -0600210 self._node._fdt.CheckCache()
Simon Glass059ae522017-05-27 07:38:28 -0600211 return self._node._fdt.GetStructOffset(self._offset)
Simon Glass54cec6d2016-07-25 18:59:16 -0600212
Simon Glasseddd7292018-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 Glassa2b9acb2018-10-01 12:22:49 -0600223 self.value = self.bytes
Simon Glassc9a032c2020-11-08 20:36:17 -0700224 self.type = Type.INT
Simon Glasseddd7292018-09-14 04:57:13 -0600225 self.dirty = True
226
Simon Glassccd25262018-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 Glass1cd40082019-05-17 22:00:36 -0600233 self.bytes = bytes
Simon Glassb73d4482019-05-17 22:00:34 -0600234 self.type, self.value = BytesToValue(bytes)
Simon Glassccd25262018-09-14 04:57:16 -0600235 self.dirty = True
236
Simon Glasseddd7292018-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 Glass459df472021-03-21 18:24:35 +1300250 if self.dirty:
Simon Glasseddd7292018-09-14 04:57:13 -0600251 node = self._node
252 fdt_obj = node._fdt._fdt_obj
Simon Glassd8bee462021-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 Glasseddd7292018-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 Glassf67c99c2020-07-09 18:39:44 -0600261 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
262 len(self.bytes))
Simon Glasseddd7292018-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 Glass459df472021-03-21 18:24:35 +1300266 self.dirty = False
Simon Glasseddd7292018-09-14 04:57:13 -0600267
Simon Glassadf8e052023-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 Glasseddd7292018-09-14 04:57:13 -0600275
Simon Glass059ae522017-05-27 07:38:28 -0600276class Node:
Simon Glass1f941b62016-07-25 18:59:04 -0600277 """A device tree node
278
279 Properties:
Simon Glass459df472021-03-21 18:24:35 +1300280 parent: Parent Node
281 offset: Integer offset in the device tree (None if to be synced)
Simon Glass1f941b62016-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 Glass80ef7052017-08-29 14:15:47 -0600289 def __init__(self, fdt, parent, offset, name, path):
Simon Glass1f941b62016-07-25 18:59:04 -0600290 self._fdt = fdt
Simon Glass80ef7052017-08-29 14:15:47 -0600291 self.parent = parent
Simon Glass1f941b62016-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 Glasse2d65282018-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 Glassaa1a5d72018-07-17 13:25:41 -0600306 def FindNode(self, name):
Simon Glasscc346a72016-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 Glass059ae522017-05-27 07:38:28 -0600319 def Offset(self):
320 """Returns the offset of a node, after checking the cache
321
322 This should be used instead of self._offset directly, to ensure that
323 the cache does not contain invalid offsets.
324 """
325 self._fdt.CheckCache()
326 return self._offset
327
Simon Glasscc346a72016-07-25 18:59:07 -0600328 def Scan(self):
Simon Glass059ae522017-05-27 07:38:28 -0600329 """Scan a node's properties and subnodes
Simon Glasscc346a72016-07-25 18:59:07 -0600330
Simon Glass059ae522017-05-27 07:38:28 -0600331 This fills in the props and subnodes properties, recursively
332 searching into subnodes so that the entire tree is built.
Simon Glasscc346a72016-07-25 18:59:07 -0600333 """
Simon Glass151aeed2018-07-06 10:27:26 -0600334 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600335 self.props = self._fdt.GetProps(self)
Simon Glass151aeed2018-07-06 10:27:26 -0600336 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glassa3f94442017-08-29 14:15:52 -0600337 if phandle:
Simon Glass151aeed2018-07-06 10:27:26 -0600338 self._fdt.phandle_to_node[phandle] = self
Simon Glass059ae522017-05-27 07:38:28 -0600339
Simon Glass151aeed2018-07-06 10:27:26 -0600340 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600341 while offset >= 0:
342 sep = '' if self.path[-1] == '/' else '/'
Simon Glass151aeed2018-07-06 10:27:26 -0600343 name = fdt_obj.get_name(offset)
Simon Glass059ae522017-05-27 07:38:28 -0600344 path = self.path + sep + name
Simon Glass80ef7052017-08-29 14:15:47 -0600345 node = Node(self._fdt, self, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600346 self.subnodes.append(node)
347
348 node.Scan()
Simon Glass151aeed2018-07-06 10:27:26 -0600349 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass059ae522017-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 Glass792d2392018-07-06 10:27:27 -0600357 fdt_obj = self._fdt._fdt_obj
Simon Glass059ae522017-05-27 07:38:28 -0600358 if self._offset != my_offset:
Simon Glass059ae522017-05-27 07:38:28 -0600359 self._offset = my_offset
Simon Glassd8bee462021-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 Glass792d2392018-07-06 10:27:27 -0600365 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600366 for subnode in self.subnodes:
Simon Glass00238612022-02-08 11:49:52 -0700367 if subnode._offset is None:
368 continue
Simon Glass4df8a0c2018-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 Glass059ae522017-05-27 07:38:28 -0600372 subnode.Refresh(offset)
Simon Glass792d2392018-07-06 10:27:27 -0600373 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass4df8a0c2018-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 Glass93f18a12021-03-21 18:24:34 +1300382 raise ValueError("Internal error, node '%s' property '%s' missing, "
383 'offset %d' % (self.path, p.name, poffset))
Simon Glass4df8a0c2018-07-06 10:27:29 -0600384 prop.RefreshOffset(poffset)
385 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glasscc346a72016-07-25 18:59:07 -0600386
Simon Glassc719e422016-07-25 18:59:14 -0600387 def DeleteProp(self, prop_name):
388 """Delete a property of a node
389
Simon Glass059ae522017-05-27 07:38:28 -0600390 The property is deleted and the offset cache is invalidated.
Simon Glassc719e422016-07-25 18:59:14 -0600391
392 Args:
393 prop_name: Name of the property to delete
Simon Glass059ae522017-05-27 07:38:28 -0600394 Raises:
395 ValueError if the property does not exist
Simon Glassc719e422016-07-25 18:59:14 -0600396 """
Simon Glass792d2392018-07-06 10:27:27 -0600397 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass059ae522017-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 Glassc719e422016-07-25 18:59:14 -0600401
Simon Glasse80c5562018-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 Glass6bbfafb2019-05-17 22:00:33 -0600408 self.props[prop_name] = Prop(self, None, prop_name,
Simon Glass80025522022-01-29 14:14:04 -0700409 tools.get_bytes(0, 4))
Simon Glasse80c5562018-07-06 10:27:38 -0600410
Simon Glassccd25262018-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 Glass80025522022-01-29 14:14:04 -0700421 value = tools.get_bytes(0, len)
Simon Glassccd25262018-09-14 04:57:16 -0600422 self.props[prop_name] = Prop(self, None, prop_name, value)
423
Simon Glassa683a5f2019-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 Glasse80c5562018-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 Glassccd25262018-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 Glasse80c5562018-07-06 10:27:38 -0600449 Args:
450 prop_name: Name of property
451 val: Value to set
452 """
Simon Glassa683a5f2019-07-20 12:23:37 -0600453 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glasseddd7292018-09-14 04:57:13 -0600454
Simon Glassccd25262018-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 Glassa683a5f2019-07-20 12:23:37 -0600465 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glassccd25262018-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 Glass39e22332019-10-31 07:43:04 -0600477 if type(val) == str:
478 val = val.encode('utf-8')
Simon Glassa683a5f2019-07-20 12:23:37 -0600479 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glassccd25262018-09-14 04:57:16 -0600480
Simon Glassf67c99c2020-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 Glassd8bee462021-03-21 18:24:39 +1300490
491 Returns:
492 Prop added
Simon Glassf67c99c2020-07-09 18:39:44 -0600493 """
Simon Glassd8bee462021-03-21 18:24:39 +1300494 prop = Prop(self, None, prop_name, val)
495 self.props[prop_name] = prop
496 return prop
Simon Glassf67c99c2020-07-09 18:39:44 -0600497
Simon Glassccd25262018-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 Glassd8bee462021-03-21 18:24:39 +1300507
508 Returns:
509 Prop added
Simon Glassccd25262018-09-14 04:57:16 -0600510 """
Simon Glassd35a8522021-01-06 21:35:10 -0700511 val = bytes(val, 'utf-8')
Simon Glassd8bee462021-03-21 18:24:39 +1300512 return self.AddData(prop_name, val + b'\0')
Simon Glassccd25262018-09-14 04:57:16 -0600513
Simon Glass452be422022-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 Glass120fa002022-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 Glass452be422022-02-08 11:49:50 -0700528 return self.AddData(prop_name, out)
529
Simon Glassa2af7302021-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 Glassd8bee462021-03-21 18:24:39 +1300539
540 Returns:
541 Prop added
Simon Glassa2af7302021-01-06 21:35:18 -0700542 """
Simon Glassd8bee462021-03-21 18:24:39 +1300543 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glassa2af7302021-01-06 21:35:18 -0700544
Simon Glassadf8e052023-07-18 07:24:02 -0600545 def Subnode(self, name):
546 """Create new subnode for the node
Simon Glassccd25262018-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 Glassf3a17962018-09-14 04:57:15 -0600554 path = self.path + '/' + name
Simon Glassadf8e052023-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 Glassf3a17962018-09-14 04:57:15 -0600567 self.subnodes.append(subnode)
568 return subnode
569
Simon Glassadf8e052023-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 Glassb9b5cb32022-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 Glasseddd7292018-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 Glass3be798a2021-03-21 18:24:38 +1300648 Returns:
649 True if the node had to be added, False if it already existed
650
Simon Glasseddd7292018-09-14 04:57:13 -0600651 Raises:
652 FdtException if auto_resize is False and there is not enough space
653 """
Simon Glass3be798a2021-03-21 18:24:38 +1300654 added = False
Simon Glassf3a17962018-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 Glass3be798a2021-03-21 18:24:38 +1300668 added = True
669
670 # 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.
673 for node in reversed(self.subnodes):
674 if node._offset is not None:
675 node.Sync(auto_resize)
Simon Glassf3a17962018-09-14 04:57:15 -0600676
Simon Glass3be798a2021-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
Simon Glasseddd7292018-09-14 04:57:13 -0600681 for node in reversed(self.subnodes):
Simon Glass3be798a2021-03-21 18:24:38 +1300682 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
Simon Glasse80c5562018-07-06 10:27:38 -0600690
Simon Glass3be798a2021-03-21 18:24:38 +1300691 # 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 Glassa57cfee2019-05-17 22:00:38 -0600701 prop_list = sorted(self.props.values(),
702 key=lambda prop: prop._offset or 1 << 31,
Simon Glasseddd7292018-09-14 04:57:13 -0600703 reverse=True)
704 for prop in prop_list:
705 prop.Sync(auto_resize)
Simon Glass3be798a2021-03-21 18:24:38 +1300706 return added
Simon Glasseddd7292018-09-14 04:57:13 -0600707
Simon Glassadf8e052023-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 Glasscd971192023-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 Glasse80c5562018-07-06 10:27:38 -0600773
Simon Glass1f941b62016-07-25 18:59:04 -0600774class Fdt:
Simon Glass059ae522017-05-27 07:38:28 -0600775 """Provides simple access to a flat device tree blob using libfdts.
Simon Glass1f941b62016-07-25 18:59:04 -0600776
777 Properties:
778 fname: Filename of fdt
779 _root: Root of device tree (a Node object)
Simon Glass50cfc6e2019-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 Glass1f941b62016-07-25 18:59:04 -0600782 """
783 def __init__(self, fname):
784 self._fname = fname
Simon Glass059ae522017-05-27 07:38:28 -0600785 self._cached_offsets = False
Simon Glassa3f94442017-08-29 14:15:52 -0600786 self.phandle_to_node = {}
Simon Glass50cfc6e2019-07-20 12:23:38 -0600787 self.name = ''
Simon Glass059ae522017-05-27 07:38:28 -0600788 if self._fname:
Simon Glass50cfc6e2019-07-20 12:23:38 -0600789 self.name = self._fname
Simon Glass059ae522017-05-27 07:38:28 -0600790 self._fname = fdt_util.EnsureCompiled(self._fname)
791
Simon Glass116236f2019-05-14 15:53:43 -0600792 with open(self._fname, 'rb') as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600793 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glasscc346a72016-07-25 18:59:07 -0600794
Simon Glassb8a49292018-09-14 04:57:17 -0600795 @staticmethod
Simon Glass50cfc6e2019-07-20 12:23:38 -0600796 def FromData(data, name=''):
Simon Glassb8a49292018-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 Glass50cfc6e2019-07-20 12:23:38 -0600801 name: Helpful name for this Fdt for the user
Simon Glassb8a49292018-09-14 04:57:17 -0600802
803 Returns:
804 Fdt object containing the data
805 """
806 fdt = Fdt(None)
Simon Glass1cd40082019-05-17 22:00:36 -0600807 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass50cfc6e2019-07-20 12:23:38 -0600808 fdt.name = name
Simon Glassb8a49292018-09-14 04:57:17 -0600809 return fdt
810
Simon Glasse2d65282018-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 Glasscc346a72016-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 Glass4df8a0c2018-07-06 10:27:29 -0600832 self._cached_offsets = True
Simon Glass80ef7052017-08-29 14:15:47 -0600833 self._root = self.Node(self, None, 0, '/', '/')
Simon Glasscc346a72016-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 Glassc5eddc82018-07-06 10:27:30 -0600853 parts = path.split('/')
854 if len(parts) < 2:
855 return None
Simon Glass3b9a8292019-07-20 12:23:39 -0600856 if len(parts) == 2 and parts[1] == '':
857 return node
Simon Glassc5eddc82018-07-06 10:27:30 -0600858 for part in parts[1:]:
Simon Glassaa1a5d72018-07-17 13:25:41 -0600859 node = node.FindNode(part)
Simon Glasscc346a72016-07-25 18:59:07 -0600860 if not node:
861 return None
862 return node
863
Simon Glass32d98272016-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 Glass32d98272016-07-25 18:59:15 -0600868 """
Simon Glass059ae522017-05-27 07:38:28 -0600869 with open(self._fname, 'wb') as fd:
Simon Glass792d2392018-07-06 10:27:27 -0600870 fd.write(self._fdt_obj.as_bytearray())
Simon Glass32d98272016-07-25 18:59:15 -0600871
Simon Glasseddd7292018-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 Glassb2e8ac42021-03-21 18:24:36 +1300882 self.CheckCache()
Simon Glasseddd7292018-09-14 04:57:13 -0600883 self._root.Sync(auto_resize)
Simon Glassb2e8ac42021-03-21 18:24:36 +1300884 self.Refresh()
Simon Glasseddd7292018-09-14 04:57:13 -0600885
Simon Glass32d98272016-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 Glass059ae522017-05-27 07:38:28 -0600890 build up in the device tree binary.
891 """
Simon Glass151aeed2018-07-06 10:27:26 -0600892 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glassb2e8ac42021-03-21 18:24:36 +1300893 self.Refresh()
Simon Glass059ae522017-05-27 07:38:28 -0600894
Simon Glass792d2392018-07-06 10:27:27 -0600895 def GetContents(self):
Simon Glass059ae522017-05-27 07:38:28 -0600896 """Get the contents of the FDT
897
898 Returns:
899 The FDT contents as a string of bytes
Simon Glass32d98272016-07-25 18:59:15 -0600900 """
Simon Glass1cd40082019-05-17 22:00:36 -0600901 return bytes(self._fdt_obj.as_bytearray())
Simon Glass059ae522017-05-27 07:38:28 -0600902
Simon Glass0ed50752018-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 Glass059ae522017-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 Glass151aeed2018-07-06 10:27:26 -0600925 poffset = self._fdt_obj.first_property_offset(node._offset,
926 QUIET_NOTFOUND)
Simon Glass059ae522017-05-27 07:38:28 -0600927 while poffset >= 0:
928 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass70cd0d72018-07-06 10:27:20 -0600929 prop = Prop(node, poffset, p.name, p)
Simon Glass059ae522017-05-27 07:38:28 -0600930 props_dict[prop.name] = prop
931
Simon Glass151aeed2018-07-06 10:27:26 -0600932 poffset = self._fdt_obj.next_property_offset(poffset,
933 QUIET_NOTFOUND)
Simon Glass059ae522017-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 Glass059ae522017-05-27 07:38:28 -0600945
946 def Refresh(self):
947 """Refresh the offset cache"""
948 self._root.Refresh(0)
Simon Glassb2e8ac42021-03-21 18:24:36 +1300949 self._cached_offsets = True
Simon Glass059ae522017-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 Glass151aeed2018-07-06 10:27:26 -0600959 return self._fdt_obj.off_dt_struct() + offset
Simon Glass059ae522017-05-27 07:38:28 -0600960
961 @classmethod
Simon Glass80ef7052017-08-29 14:15:47 -0600962 def Node(self, fdt, parent, offset, name, path):
Simon Glass059ae522017-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 Glass80ef7052017-08-29 14:15:47 -0600970 parent: Parent node, or None if this is the root node
Simon Glass059ae522017-05-27 07:38:28 -0600971 offset: Offset of node
972 name: Node name
973 path: Full path to node
974 """
Simon Glass80ef7052017-08-29 14:15:47 -0600975 node = Node(fdt, parent, offset, name, path)
Simon Glass059ae522017-05-27 07:38:28 -0600976 return node
Simon Glassa9440932017-05-27 07:38:30 -0600977
Simon Glass74f5feb2019-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 Glassa9440932017-05-27 07:38:30 -0600986def FdtScan(fname):
Simon Glassc38fee042018-07-06 10:27:32 -0600987 """Returns a new Fdt object"""
Simon Glassa9440932017-05-27 07:38:30 -0600988 dtb = Fdt(fname)
989 dtb.Scan()
990 return dtb