blob: 2589be990ae9c6634bd5daa6bbeaa882819e551f [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 Glass71556462023-07-22 21:43:53 -0600275 self.dirty = True
Simon Glassfa80c252018-09-14 04:57:13 -0600276
Simon Glass7b75b442017-05-27 07:38:28 -0600277class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600278 """A device tree node
279
280 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300281 parent: Parent Node
282 offset: Integer offset in the device tree (None if to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600283 name: Device tree node tname
284 path: Full path to node, along with the node name itself
285 _fdt: Device tree object
286 subnodes: A list of subnodes for this node, each a Node object
287 props: A dict of properties for this node, each a Prop object.
288 Keyed by property name
289 """
Simon Glass979ab022017-08-29 14:15:47 -0600290 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600291 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600292 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600293 self._offset = offset
294 self.name = name
295 self.path = path
296 self.subnodes = []
297 self.props = {}
298
Simon Glass94a7c602018-07-17 13:25:46 -0600299 def GetFdt(self):
300 """Get the Fdt object for this node
301
302 Returns:
303 Fdt object
304 """
305 return self._fdt
306
Simon Glass1d858882018-07-17 13:25:41 -0600307 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600308 """Find a node given its name
309
310 Args:
311 name: Node name to look for
312 Returns:
313 Node object if found, else None
314 """
315 for subnode in self.subnodes:
316 if subnode.name == name:
317 return subnode
318 return None
319
Simon Glass7b75b442017-05-27 07:38:28 -0600320 def Offset(self):
321 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600322
Simon Glass7b75b442017-05-27 07:38:28 -0600323 This should be used instead of self._offset directly, to ensure that
324 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600325 """
Simon Glass7b75b442017-05-27 07:38:28 -0600326 self._fdt.CheckCache()
327 return self._offset
328
329 def Scan(self):
330 """Scan a node's properties and subnodes
331
332 This fills in the props and subnodes properties, recursively
333 searching into subnodes so that the entire tree is built.
334 """
Simon Glass117f57b2018-07-06 10:27:26 -0600335 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600336 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600337 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600338 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600339 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600340
Simon Glass117f57b2018-07-06 10:27:26 -0600341 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600342 while offset >= 0:
343 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600344 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600345 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600346 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600347 self.subnodes.append(node)
348
349 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600350 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600351
352 def Refresh(self, my_offset):
353 """Fix up the _offset for each node, recursively
354
355 Note: This does not take account of property offsets - these will not
356 be updated.
357 """
Simon Glass96066242018-07-06 10:27:27 -0600358 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600359 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600360 self._offset = my_offset
Simon Glass5d1bec32021-03-21 18:24:39 +1300361 name = fdt_obj.get_name(self._offset)
362 if name and self.name != name:
363 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
364 (self.path, name))
365
Simon Glass96066242018-07-06 10:27:27 -0600366 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600367 for subnode in self.subnodes:
Simon Glassdd857ee2022-02-08 11:49:52 -0700368 if subnode._offset is None:
369 continue
Simon Glassf9b88b32018-07-06 10:27:29 -0600370 if subnode.name != fdt_obj.get_name(offset):
371 raise ValueError('Internal error, node name mismatch %s != %s' %
372 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600373 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600374 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600375 if offset != -libfdt.FDT_ERR_NOTFOUND:
376 raise ValueError('Internal error, offset == %d' % offset)
377
378 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
379 while poffset >= 0:
380 p = fdt_obj.get_property_by_offset(poffset)
381 prop = self.props.get(p.name)
382 if not prop:
Simon Glassacd98612021-03-21 18:24:34 +1300383 raise ValueError("Internal error, node '%s' property '%s' missing, "
384 'offset %d' % (self.path, p.name, poffset))
Simon Glassf9b88b32018-07-06 10:27:29 -0600385 prop.RefreshOffset(poffset)
386 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600387
Simon Glass2a70d892016-07-25 18:59:14 -0600388 def DeleteProp(self, prop_name):
389 """Delete a property of a node
390
Simon Glass7b75b442017-05-27 07:38:28 -0600391 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600392
393 Args:
394 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600395 Raises:
396 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600397 """
Simon Glass96066242018-07-06 10:27:27 -0600398 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600399 "Node '%s': delete property: '%s'" % (self.path, prop_name))
400 del self.props[prop_name]
401 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600402
Simon Glass116adec2018-07-06 10:27:38 -0600403 def AddZeroProp(self, prop_name):
404 """Add a new property to the device tree with an integer value of 0.
405
406 Args:
407 prop_name: Name of property
408 """
Simon Glass194b8d52019-05-17 22:00:33 -0600409 self.props[prop_name] = Prop(self, None, prop_name,
Simon Glassc1aa66e2022-01-29 14:14:04 -0700410 tools.get_bytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600411
Simon Glass64349612018-09-14 04:57:16 -0600412 def AddEmptyProp(self, prop_name, len):
413 """Add a property with a fixed data size, for filling in later
414
415 The device tree is marked dirty so that the value will be written to
416 the blob on the next sync.
417
418 Args:
419 prop_name: Name of property
420 len: Length of data in property
421 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700422 value = tools.get_bytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600423 self.props[prop_name] = Prop(self, None, prop_name, value)
424
Simon Glassd9dad102019-07-20 12:23:37 -0600425 def _CheckProp(self, prop_name):
426 """Check if a property is present
427
428 Args:
429 prop_name: Name of property
430
431 Returns:
432 self
433
434 Raises:
435 ValueError if the property is missing
436 """
437 if prop_name not in self.props:
438 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
439 (self._fdt._fname, self.path, prop_name))
440 return self
441
Simon Glass116adec2018-07-06 10:27:38 -0600442 def SetInt(self, prop_name, val):
443 """Update an integer property int the device tree.
444
445 This is not allowed to change the size of the FDT.
446
Simon Glass64349612018-09-14 04:57:16 -0600447 The device tree is marked dirty so that the value will be written to
448 the blob on the next sync.
449
Simon Glass116adec2018-07-06 10:27:38 -0600450 Args:
451 prop_name: Name of property
452 val: Value to set
453 """
Simon Glassd9dad102019-07-20 12:23:37 -0600454 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600455
Simon Glass64349612018-09-14 04:57:16 -0600456 def SetData(self, prop_name, val):
457 """Set the data value of a property
458
459 The device tree is marked dirty so that the value will be written to
460 the blob on the next sync.
461
462 Args:
463 prop_name: Name of property to set
464 val: Data value to set
465 """
Simon Glassd9dad102019-07-20 12:23:37 -0600466 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600467
468 def SetString(self, prop_name, val):
469 """Set the string value of a property
470
471 The device tree is marked dirty so that the value will be written to
472 the blob on the next sync.
473
474 Args:
475 prop_name: Name of property to set
476 val: String value to set (will be \0-terminated in DT)
477 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600478 if type(val) == str:
479 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600480 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600481
Simon Glassc0639172020-07-09 18:39:44 -0600482 def AddData(self, prop_name, val):
483 """Add a new property to a node
484
485 The device tree is marked dirty so that the value will be written to
486 the blob on the next sync.
487
488 Args:
489 prop_name: Name of property to add
490 val: Bytes value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300491
492 Returns:
493 Prop added
Simon Glassc0639172020-07-09 18:39:44 -0600494 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300495 prop = Prop(self, None, prop_name, val)
496 self.props[prop_name] = prop
497 return prop
Simon Glassc0639172020-07-09 18:39:44 -0600498
Simon Glass64349612018-09-14 04:57:16 -0600499 def AddString(self, prop_name, val):
500 """Add a new string property to a node
501
502 The device tree is marked dirty so that the value will be written to
503 the blob on the next sync.
504
505 Args:
506 prop_name: Name of property to add
507 val: String value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300508
509 Returns:
510 Prop added
Simon Glass64349612018-09-14 04:57:16 -0600511 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700512 val = bytes(val, 'utf-8')
Simon Glass5d1bec32021-03-21 18:24:39 +1300513 return self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600514
Simon Glassbc116022022-02-08 11:49:50 -0700515 def AddStringList(self, prop_name, val):
516 """Add a new string-list property to a node
517
518 The device tree is marked dirty so that the value will be written to
519 the blob on the next sync.
520
521 Args:
522 prop_name: Name of property to add
523 val (list of str): List of strings to add
524
525 Returns:
526 Prop added
527 """
Simon Glass0ded4d42022-03-05 20:18:56 -0700528 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 -0700529 return self.AddData(prop_name, out)
530
Simon Glass6eb99322021-01-06 21:35:18 -0700531 def AddInt(self, prop_name, val):
532 """Add a new integer property to a node
533
534 The device tree is marked dirty so that the value will be written to
535 the blob on the next sync.
536
537 Args:
538 prop_name: Name of property to add
539 val: Integer value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300540
541 Returns:
542 Prop added
Simon Glass6eb99322021-01-06 21:35:18 -0700543 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300544 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glass6eb99322021-01-06 21:35:18 -0700545
Simon Glass4df457b2023-07-18 07:24:02 -0600546 def Subnode(self, name):
547 """Create new subnode for the node
Simon Glass64349612018-09-14 04:57:16 -0600548
549 Args:
550 name: name of node to add
551
552 Returns:
553 New subnode that was created
554 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600555 path = self.path + '/' + name
Simon Glass4df457b2023-07-18 07:24:02 -0600556 return Node(self._fdt, self, None, name, path)
557
558 def AddSubnode(self, name):
559 """Add a new subnode to the node, after all other subnodes
560
561 Args:
562 name: name of node to add
563
564 Returns:
565 New subnode that was created
566 """
567 subnode = self.Subnode(name)
Simon Glasse21c27a2018-09-14 04:57:15 -0600568 self.subnodes.append(subnode)
569 return subnode
570
Simon Glass4df457b2023-07-18 07:24:02 -0600571 def insert_subnode(self, name):
572 """Add a new subnode to the node, before all other subnodes
573
574 This deletes other subnodes and sets their offset to None, so that they
575 will be recreated after this one.
576
577 Args:
578 name: name of node to add
579
580 Returns:
581 New subnode that was created
582 """
583 # Deleting a node invalidates the offsets of all following nodes, so
584 # process in reverse order so that the offset of each node remains valid
585 # until deletion.
586 for subnode in reversed(self.subnodes):
587 subnode.purge(True)
588 subnode = self.Subnode(name)
589 self.subnodes.insert(0, subnode)
590 return subnode
591
592 def purge(self, delete_it=False):
593 """Purge this node, setting offset to None and deleting from FDT"""
594 if self._offset is not None:
595 if delete_it:
596 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
597 "Node '%s': delete" % self.path)
598 self._offset = None
599 self._fdt.Invalidate()
600
601 for prop in self.props.values():
602 prop.purge()
603
604 for subnode in self.subnodes:
605 subnode.purge(False)
606
607 def move_to_first(self):
608 """Move the current node to first in its parent's node list"""
609 parent = self.parent
610 if parent.subnodes and parent.subnodes[0] == self:
611 return
612 for subnode in reversed(parent.subnodes):
613 subnode.purge(True)
614
615 new_subnodes = [self]
616 for subnode in parent.subnodes:
617 #subnode.purge(False)
618 if subnode != self:
619 new_subnodes.append(subnode)
620 parent.subnodes = new_subnodes
621
Simon Glassa30c39f2022-02-08 11:49:51 -0700622 def Delete(self):
623 """Delete a node
624
625 The node is deleted and the offset cache is invalidated.
626
627 Args:
628 node (Node): Node to delete
629
630 Raises:
631 ValueError if the node does not exist
632 """
633 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()),
634 "Node '%s': delete" % self.path)
635 parent = self.parent
636 self._fdt.Invalidate()
637 parent.subnodes.remove(self)
638
Simon Glassfa80c252018-09-14 04:57:13 -0600639 def Sync(self, auto_resize=False):
640 """Sync node changes back to the device tree
641
642 This updates the device tree blob with any changes to this node and its
643 subnodes since the last sync.
644
645 Args:
646 auto_resize: Resize the device tree automatically if it does not
647 have enough space for the update
648
Simon Glassf6176652021-03-21 18:24:38 +1300649 Returns:
650 True if the node had to be added, False if it already existed
651
Simon Glassfa80c252018-09-14 04:57:13 -0600652 Raises:
653 FdtException if auto_resize is False and there is not enough space
654 """
Simon Glassf6176652021-03-21 18:24:38 +1300655 added = False
Simon Glasse21c27a2018-09-14 04:57:15 -0600656 if self._offset is None:
657 # The subnode doesn't exist yet, so add it
658 fdt_obj = self._fdt._fdt_obj
659 if auto_resize:
660 while True:
661 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
662 (libfdt.NOSPACE,))
663 if offset != -libfdt.NOSPACE:
664 break
665 fdt_obj.resize(fdt_obj.totalsize() + 1024)
666 else:
667 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
668 self._offset = offset
Simon Glassf6176652021-03-21 18:24:38 +1300669 added = True
Simon Glasse21c27a2018-09-14 04:57:15 -0600670
Simon Glassf6176652021-03-21 18:24:38 +1300671 # Sync the existing subnodes first, so that we can rely on the offsets
672 # being correct. As soon as we add new subnodes, it pushes all the
673 # existing subnodes up.
Simon Glassfa80c252018-09-14 04:57:13 -0600674 for node in reversed(self.subnodes):
Simon Glassf6176652021-03-21 18:24:38 +1300675 if node._offset is not None:
676 node.Sync(auto_resize)
Simon Glassfa80c252018-09-14 04:57:13 -0600677
Simon Glassf6176652021-03-21 18:24:38 +1300678 # Sync subnodes in reverse so that we get the expected order. Each
679 # new node goes at the start of the subnode list. This avoids an O(n^2)
680 # rescan of node offsets.
681 num_added = 0
682 for node in reversed(self.subnodes):
683 if node.Sync(auto_resize):
684 num_added += 1
685 if num_added:
686 # Reorder our list of nodes to put the new ones first, since that's
687 # what libfdt does
688 old_count = len(self.subnodes) - num_added
689 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
690 self.subnodes = subnodes
691
692 # Sync properties now, whose offsets should not have been disturbed,
693 # since properties come before subnodes. This is done after all the
694 # subnode processing above, since updating properties can disturb the
695 # offsets of those subnodes.
696 # Properties are synced in reverse order, with new properties added
697 # before existing properties are synced. This ensures that the offsets
698 # of earlier properties are not disturbed.
699 # Note that new properties will have an offset of None here, which
700 # Python cannot sort against int. So use a large value instead so that
701 # new properties are added first.
Simon Glass63518052019-05-17 22:00:38 -0600702 prop_list = sorted(self.props.values(),
703 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600704 reverse=True)
705 for prop in prop_list:
706 prop.Sync(auto_resize)
Simon Glassf6176652021-03-21 18:24:38 +1300707 return added
Simon Glass116adec2018-07-06 10:27:38 -0600708
Simon Glass4df457b2023-07-18 07:24:02 -0600709 def merge_props(self, src):
710 """Copy missing properties (except 'phandle') from another node
711
712 Args:
713 src (Node): Node containing properties to copy
714
715 Adds properties which are present in src but not in this node. Any
716 'phandle' property is not copied since this might result in two nodes
717 with the same phandle, thus making phandle references ambiguous.
718 """
719 for name, src_prop in src.props.items():
720 if name != 'phandle' and name not in self.props:
721 self.props[name] = Prop(self, None, name, src_prop.bytes)
722
723 def copy_node(self, src):
724 """Copy a node and all its subnodes into this node
725
726 Args:
727 src (Node): Node to copy
728
729 Returns:
730 Node: Resulting destination node
731
732 This works recursively.
733
734 The new node is put before all other nodes. If the node already
735 exists, just its subnodes and properties are copied, placing them before
736 any existing subnodes. Properties which exist in the destination node
737 already are not copied.
738 """
739 dst = self.FindNode(src.name)
740 if dst:
741 dst.move_to_first()
742 else:
743 dst = self.insert_subnode(src.name)
744 dst.merge_props(src)
745
746 # Process in reverse order so that they appear correctly in the result,
747 # since copy_node() puts the node first in the list
748 for node in reversed(src.subnodes):
749 dst.copy_node(node)
750 return dst
751
Simon Glass55e12782023-07-18 07:24:03 -0600752 def copy_subnodes_from_phandles(self, phandle_list):
753 """Copy subnodes of a list of nodes into another node
754
755 Args:
756 phandle_list (list of int): List of phandles of nodes to copy
757
758 For each node in the phandle list, its subnodes and their properties are
759 copied recursively. Note that it does not copy the node itself, nor its
760 properties.
761 """
762 # Process in reverse order, since new nodes are inserted at the start of
763 # the destination's node list. We want them to appear in order of the
764 # phandle list
765 for phandle in phandle_list.__reversed__():
766 parent = self.GetFdt().LookupPhandle(phandle)
767 tout.debug(f'adding template {parent.path} to node {self.path}')
768 for node in parent.subnodes.__reversed__():
769 dst = self.copy_node(node)
770
771 tout.debug(f'merge props from {parent.path} to {dst.path}')
772 self.merge_props(parent)
773
Simon Glass116adec2018-07-06 10:27:38 -0600774
Simon Glassa06a34b2016-07-25 18:59:04 -0600775class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600776 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600777
778 Properties:
779 fname: Filename of fdt
780 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600781 name: Helpful name for this Fdt for the user (useful when creating the
782 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600783 """
784 def __init__(self, fname):
785 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600786 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600787 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600788 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600789 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600790 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600791 self._fname = fdt_util.EnsureCompiled(self._fname)
792
Simon Glass3e4b51e2019-05-14 15:53:43 -0600793 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600794 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600795
Simon Glass746aee32018-09-14 04:57:17 -0600796 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600797 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600798 """Create a new Fdt object from the given data
799
800 Args:
801 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600802 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600803
804 Returns:
805 Fdt object containing the data
806 """
807 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600808 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600809 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600810 return fdt
811
Simon Glass94a7c602018-07-17 13:25:46 -0600812 def LookupPhandle(self, phandle):
813 """Look up a phandle
814
815 Args:
816 phandle: Phandle to look up (int)
817
818 Returns:
819 Node object the phandle points to
820 """
821 return self.phandle_to_node.get(phandle)
822
Simon Glassf7a2aee2016-07-25 18:59:07 -0600823 def Scan(self, root='/'):
824 """Scan a device tree, building up a tree of Node objects
825
826 This fills in the self._root property
827
828 Args:
829 root: Ignored
830
831 TODO(sjg@chromium.org): Implement the 'root' parameter
832 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600833 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600834 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600835 self._root.Scan()
836
837 def GetRoot(self):
838 """Get the root Node of the device tree
839
840 Returns:
841 The root Node object
842 """
843 return self._root
844
845 def GetNode(self, path):
846 """Look up a node from its path
847
848 Args:
849 path: Path to look up, e.g. '/microcode/update@0'
850 Returns:
851 Node object, or None if not found
852 """
853 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600854 parts = path.split('/')
855 if len(parts) < 2:
856 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600857 if len(parts) == 2 and parts[1] == '':
858 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600859 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600860 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600861 if not node:
862 return None
863 return node
864
Simon Glassda5f7492016-07-25 18:59:15 -0600865 def Flush(self):
866 """Flush device tree changes back to the file
867
868 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600869 """
Simon Glass7b75b442017-05-27 07:38:28 -0600870 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600871 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600872
Simon Glassfa80c252018-09-14 04:57:13 -0600873 def Sync(self, auto_resize=False):
874 """Make sure any DT changes are written to the blob
875
876 Args:
877 auto_resize: Resize the device tree automatically if it does not
878 have enough space for the update
879
880 Raises:
881 FdtException if auto_resize is False and there is not enough space
882 """
Simon Glass71719e12021-03-21 18:24:36 +1300883 self.CheckCache()
Simon Glassfa80c252018-09-14 04:57:13 -0600884 self._root.Sync(auto_resize)
Simon Glass71719e12021-03-21 18:24:36 +1300885 self.Refresh()
Simon Glassfa80c252018-09-14 04:57:13 -0600886
Simon Glassda5f7492016-07-25 18:59:15 -0600887 def Pack(self):
888 """Pack the device tree down to its minimum size
889
890 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600891 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600892 """
Simon Glass117f57b2018-07-06 10:27:26 -0600893 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glass71719e12021-03-21 18:24:36 +1300894 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600895
Simon Glass96066242018-07-06 10:27:27 -0600896 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600897 """Get the contents of the FDT
898
899 Returns:
900 The FDT contents as a string of bytes
901 """
Simon Glassf6b64812019-05-17 22:00:36 -0600902 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600903
Simon Glass2ba98752018-07-06 10:27:24 -0600904 def GetFdtObj(self):
905 """Get the contents of the FDT
906
907 Returns:
908 The FDT contents as a libfdt.Fdt object
909 """
910 return self._fdt_obj
911
Simon Glass7b75b442017-05-27 07:38:28 -0600912 def GetProps(self, node):
913 """Get all properties from a node.
914
915 Args:
916 node: Full path to node name to look in.
917
918 Returns:
919 A dictionary containing all the properties, indexed by node name.
920 The entries are Prop objects.
921
922 Raises:
923 ValueError: if the node does not exist.
924 """
925 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600926 poffset = self._fdt_obj.first_property_offset(node._offset,
927 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600928 while poffset >= 0:
929 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600930 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600931 props_dict[prop.name] = prop
932
Simon Glass117f57b2018-07-06 10:27:26 -0600933 poffset = self._fdt_obj.next_property_offset(poffset,
934 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600935 return props_dict
936
937 def Invalidate(self):
938 """Mark our offset cache as invalid"""
939 self._cached_offsets = False
940
941 def CheckCache(self):
942 """Refresh the offset cache if needed"""
943 if self._cached_offsets:
944 return
945 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600946
947 def Refresh(self):
948 """Refresh the offset cache"""
949 self._root.Refresh(0)
Simon Glass71719e12021-03-21 18:24:36 +1300950 self._cached_offsets = True
Simon Glass7b75b442017-05-27 07:38:28 -0600951
952 def GetStructOffset(self, offset):
953 """Get the file offset of a given struct offset
954
955 Args:
956 offset: Offset within the 'struct' region of the device tree
957 Returns:
958 Position of @offset within the device tree binary
959 """
Simon Glass117f57b2018-07-06 10:27:26 -0600960 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600961
962 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600963 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600964 """Create a new node
965
966 This is used by Fdt.Scan() to create a new node using the correct
967 class.
968
969 Args:
970 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600971 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600972 offset: Offset of node
973 name: Node name
974 path: Full path to node
975 """
Simon Glass979ab022017-08-29 14:15:47 -0600976 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600977 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600978
Simon Glassf6e02492019-07-20 12:24:08 -0600979 def GetFilename(self):
980 """Get the filename of the device tree
981
982 Returns:
983 String filename
984 """
985 return self._fname
986
Simon Glass99ed4a22017-05-27 07:38:30 -0600987def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600988 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600989 dtb = Fdt(fname)
990 dtb.Scan()
991 return dtb