blob: 026f7a273f9e97615914c721170f804810206a87 [file] [log] [blame]
Simon Glassa06a34b2016-07-25 18:59:04 -06001#!/usr/bin/python
Tom Rini83d290c2018-05-06 17:58:06 -04002# SPDX-License-Identifier: GPL-2.0+
Simon Glassa06a34b2016-07-25 18:59:04 -06003#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
Simon Glassa06a34b2016-07-25 18:59:04 -06007
Simon Glass5ea9dcc2020-11-08 20:36:17 -07008from enum import IntEnum
Simon Glassa06a34b2016-07-25 18:59:04 -06009import struct
10import sys
11
Simon Glassbf776672020-04-17 18:09:04 -060012from dtoc import fdt_util
Simon Glass7b75b442017-05-27 07:38:28 -060013import libfdt
Simon Glass117f57b2018-07-06 10:27:26 -060014from libfdt import QUIET_NOTFOUND
Simon Glassbf776672020-04-17 18:09:04 -060015from patman import tools
Simon Glassa06a34b2016-07-25 18:59:04 -060016
17# This deals with a device tree, presenting it as an assortment of Node and
18# Prop objects, representing nodes and properties, respectively. This file
Simon Glass99ed4a22017-05-27 07:38:30 -060019# contains the base classes and defines the high-level API. You can use
20# FdtScan() as a convenience function to create and scan an Fdt.
Simon Glass7b75b442017-05-27 07:38:28 -060021
22# This implementation uses a libfdt Python library to access the device tree,
23# so it is fairly efficient.
Simon Glassa06a34b2016-07-25 18:59:04 -060024
Simon Glassbc1dea32016-07-25 18:59:05 -060025# A list of types we support
Simon Glass5ea9dcc2020-11-08 20:36:17 -070026class Type(IntEnum):
Simon Glassdf82de82021-07-28 19:23:09 -060027 # Types in order from widest to narrowest
Simon Glass5ea9dcc2020-11-08 20:36:17 -070028 (BYTE, INT, STRING, BOOL, INT64) = range(5)
29
Simon Glassdf82de82021-07-28 19:23:09 -060030 def needs_widening(self, other):
31 """Check if this type needs widening to hold a value from another type
Simon Glass5ea9dcc2020-11-08 20:36:17 -070032
Simon Glassdf82de82021-07-28 19:23:09 -060033 A wider type is one that can hold a wider array of information than
34 another one, or is less restrictive, so it can hold the information of
35 another type as well as its own. This is similar to the concept of
36 type-widening in C.
Simon Glass5ea9dcc2020-11-08 20:36:17 -070037
38 This uses a simple arithmetic comparison, since type values are in order
Simon Glassdf82de82021-07-28 19:23:09 -060039 from widest (BYTE) to narrowest (INT64).
Simon Glass5ea9dcc2020-11-08 20:36:17 -070040
41 Args:
42 other: Other type to compare against
43
44 Return:
45 True if the other type is wider
46 """
47 return self.value > other.value
Simon Glassbc1dea32016-07-25 18:59:05 -060048
Simon Glassa06a34b2016-07-25 18:59:04 -060049def CheckErr(errnum, msg):
50 if errnum:
51 raise ValueError('Error %d: %s: %s' %
52 (errnum, libfdt.fdt_strerror(errnum), msg))
53
Simon Glass7e6952d2019-05-17 22:00:34 -060054
Simon Glass2b6ed5e2019-05-17 22:00:35 -060055def BytesToValue(data):
Simon Glass7e6952d2019-05-17 22:00:34 -060056 """Converts a string of bytes into a type and value
57
58 Args:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060059 A bytes value (which on Python 2 is an alias for str)
Simon Glass7e6952d2019-05-17 22:00:34 -060060
61 Return:
62 A tuple:
63 Type of data
64 Data, either a single element or a list of elements. Each element
65 is one of:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070066 Type.STRING: str/bytes value from the property
67 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
68 Type.BYTE: a byte stored as a single-byte str/bytes
Simon Glass7e6952d2019-05-17 22:00:34 -060069 """
Simon Glass2b6ed5e2019-05-17 22:00:35 -060070 data = bytes(data)
71 size = len(data)
72 strings = data.split(b'\0')
Simon Glass7e6952d2019-05-17 22:00:34 -060073 is_string = True
74 count = len(strings) - 1
Simon Glass2b6ed5e2019-05-17 22:00:35 -060075 if count > 0 and not len(strings[-1]):
Simon Glass7e6952d2019-05-17 22:00:34 -060076 for string in strings[:-1]:
77 if not string:
78 is_string = False
79 break
80 for ch in string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060081 if ch < 32 or ch > 127:
Simon Glass7e6952d2019-05-17 22:00:34 -060082 is_string = False
83 break
84 else:
85 is_string = False
86 if is_string:
Simon Glass2b6ed5e2019-05-17 22:00:35 -060087 if count == 1:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070088 return Type.STRING, strings[0].decode()
Simon Glass7e6952d2019-05-17 22:00:34 -060089 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -070090 return Type.STRING, [s.decode() for s in strings[:-1]]
Simon Glass7e6952d2019-05-17 22:00:34 -060091 if size % 4:
92 if size == 1:
Simon Glass479dd302020-11-08 20:36:20 -070093 return Type.BYTE, chr(data[0])
Simon Glass7e6952d2019-05-17 22:00:34 -060094 else:
Simon Glass479dd302020-11-08 20:36:20 -070095 return Type.BYTE, [chr(ch) for ch in list(data)]
Simon Glass7e6952d2019-05-17 22:00:34 -060096 val = []
97 for i in range(0, size, 4):
Simon Glass2b6ed5e2019-05-17 22:00:35 -060098 val.append(data[i:i + 4])
Simon Glass7e6952d2019-05-17 22:00:34 -060099 if size == 4:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700100 return Type.INT, val[0]
Simon Glass7e6952d2019-05-17 22:00:34 -0600101 else:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700102 return Type.INT, val
Simon Glass7e6952d2019-05-17 22:00:34 -0600103
104
Simon Glass7b75b442017-05-27 07:38:28 -0600105class Prop:
Simon Glassa06a34b2016-07-25 18:59:04 -0600106 """A device tree property
107
108 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300109 node: Node containing this property
110 offset: Offset of the property (None if still to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600111 name: Property name (as per the device tree)
112 value: Property value as a string of bytes, or a list of strings of
113 bytes
114 type: Value type
115 """
Simon Glass928527f2019-05-17 22:00:37 -0600116 def __init__(self, node, offset, name, data):
Simon Glassa06a34b2016-07-25 18:59:04 -0600117 self._node = node
118 self._offset = offset
119 self.name = name
120 self.value = None
Simon Glass928527f2019-05-17 22:00:37 -0600121 self.bytes = bytes(data)
Simon Glass37ba9842021-03-21 18:24:35 +1300122 self.dirty = offset is None
Simon Glass928527f2019-05-17 22:00:37 -0600123 if not data:
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700124 self.type = Type.BOOL
Simon Glass7b75b442017-05-27 07:38:28 -0600125 self.value = True
126 return
Simon Glass928527f2019-05-17 22:00:37 -0600127 self.type, self.value = BytesToValue(bytes(data))
Simon Glassa06a34b2016-07-25 18:59:04 -0600128
Simon Glassf9b88b32018-07-06 10:27:29 -0600129 def RefreshOffset(self, poffset):
130 self._offset = poffset
131
Simon Glassc322a852016-07-25 18:59:06 -0600132 def Widen(self, newprop):
133 """Figure out which property type is more general
134
135 Given a current property and a new property, this function returns the
136 one that is less specific as to type. The less specific property will
137 be ble to represent the data in the more specific property. This is
138 used for things like:
139
140 node1 {
141 compatible = "fred";
142 value = <1>;
143 };
144 node1 {
145 compatible = "fred";
146 value = <1 2>;
147 };
148
149 He we want to use an int array for 'value'. The first property
150 suggests that a single int is enough, but the second one shows that
151 it is not. Calling this function with these two propertes would
152 update the current property to be like the second, since it is less
153 specific.
154 """
Simon Glassdf82de82021-07-28 19:23:09 -0600155 if self.type.needs_widening(newprop.type):
Simon Glasseec44c72021-07-28 19:23:11 -0600156
157 # A boolean has an empty value: if it exists it is True and if not
158 # it is False. So when widening we always start with an empty list
159 # since the only valid integer property would be an empty list of
160 # integers.
161 # e.g. this is a boolean:
162 # some-prop;
163 # and it would be widened to int list by:
164 # some-prop = <1 2>;
165 if self.type == Type.BOOL:
166 self.type = Type.INT
167 self.value = [self.GetEmpty(self.type)]
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700168 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glasse144caf2020-10-03 11:31:27 -0600169 if type(self.value) == list:
170 new_value = []
171 for val in self.value:
Simon Glass479dd302020-11-08 20:36:20 -0700172 new_value += [chr(by) for by in val]
Simon Glasse144caf2020-10-03 11:31:27 -0600173 else:
Simon Glass479dd302020-11-08 20:36:20 -0700174 new_value = [chr(by) for by in self.value]
Simon Glasse144caf2020-10-03 11:31:27 -0600175 self.value = new_value
Simon Glassc322a852016-07-25 18:59:06 -0600176 self.type = newprop.type
177
Simon Glassca044942021-07-28 19:23:10 -0600178 if type(newprop.value) == list:
179 if type(self.value) != list:
180 self.value = [self.value]
Simon Glassc322a852016-07-25 18:59:06 -0600181
Simon Glassca044942021-07-28 19:23:10 -0600182 if len(newprop.value) > len(self.value):
183 val = self.GetEmpty(self.type)
184 while len(self.value) < len(newprop.value):
185 self.value.append(val)
Simon Glassc322a852016-07-25 18:59:06 -0600186
Simon Glass2ba98752018-07-06 10:27:24 -0600187 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600188 def GetEmpty(self, type):
189 """Get an empty / zero value of the given type
190
191 Returns:
192 A single value of the given type
193 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700194 if type == Type.BYTE:
Simon Glassbc1dea32016-07-25 18:59:05 -0600195 return chr(0)
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700196 elif type == Type.INT:
Simon Glassaf53f5a2018-09-14 04:57:14 -0600197 return struct.pack('>I', 0);
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700198 elif type == Type.STRING:
Simon Glassbc1dea32016-07-25 18:59:05 -0600199 return ''
200 else:
201 return True
202
Simon Glassbabdbde2016-07-25 18:59:16 -0600203 def GetOffset(self):
204 """Get the offset of a property
205
Simon Glassbabdbde2016-07-25 18:59:16 -0600206 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600207 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600208 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600209 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600210 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600211
Simon Glassfa80c252018-09-14 04:57:13 -0600212 def SetInt(self, val):
213 """Set the integer value of the property
214
215 The device tree is marked dirty so that the value will be written to
216 the block on the next sync.
217
218 Args:
219 val: Integer value (32-bit, single cell)
220 """
221 self.bytes = struct.pack('>I', val);
Simon Glass41b781d2018-10-01 12:22:49 -0600222 self.value = self.bytes
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700223 self.type = Type.INT
Simon Glassfa80c252018-09-14 04:57:13 -0600224 self.dirty = True
225
Simon Glass64349612018-09-14 04:57:16 -0600226 def SetData(self, bytes):
227 """Set the value of a property as bytes
228
229 Args:
230 bytes: New property value to set
231 """
Simon Glassf6b64812019-05-17 22:00:36 -0600232 self.bytes = bytes
Simon Glass7e6952d2019-05-17 22:00:34 -0600233 self.type, self.value = BytesToValue(bytes)
Simon Glass64349612018-09-14 04:57:16 -0600234 self.dirty = True
235
Simon Glassfa80c252018-09-14 04:57:13 -0600236 def Sync(self, auto_resize=False):
237 """Sync property changes back to the device tree
238
239 This updates the device tree blob with any changes to this property
240 since the last sync.
241
242 Args:
243 auto_resize: Resize the device tree automatically if it does not
244 have enough space for the update
245
246 Raises:
247 FdtException if auto_resize is False and there is not enough space
248 """
Simon Glass37ba9842021-03-21 18:24:35 +1300249 if self.dirty:
Simon Glassfa80c252018-09-14 04:57:13 -0600250 node = self._node
251 fdt_obj = node._fdt._fdt_obj
Simon Glass5d1bec32021-03-21 18:24:39 +1300252 node_name = fdt_obj.get_name(node._offset)
253 if node_name and node_name != node.name:
254 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
255 (node.path, node_name))
256
Simon Glassfa80c252018-09-14 04:57:13 -0600257 if auto_resize:
258 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
259 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassc0639172020-07-09 18:39:44 -0600260 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
261 len(self.bytes))
Simon Glassfa80c252018-09-14 04:57:13 -0600262 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
263 else:
264 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
Simon Glass37ba9842021-03-21 18:24:35 +1300265 self.dirty = False
Simon Glassfa80c252018-09-14 04:57:13 -0600266
267
Simon Glass7b75b442017-05-27 07:38:28 -0600268class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600269 """A device tree node
270
271 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300272 parent: Parent Node
273 offset: Integer offset in the device tree (None if to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600274 name: Device tree node tname
275 path: Full path to node, along with the node name itself
276 _fdt: Device tree object
277 subnodes: A list of subnodes for this node, each a Node object
278 props: A dict of properties for this node, each a Prop object.
279 Keyed by property name
280 """
Simon Glass979ab022017-08-29 14:15:47 -0600281 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600282 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600283 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600284 self._offset = offset
285 self.name = name
286 self.path = path
287 self.subnodes = []
288 self.props = {}
289
Simon Glass94a7c602018-07-17 13:25:46 -0600290 def GetFdt(self):
291 """Get the Fdt object for this node
292
293 Returns:
294 Fdt object
295 """
296 return self._fdt
297
Simon Glass1d858882018-07-17 13:25:41 -0600298 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600299 """Find a node given its name
300
301 Args:
302 name: Node name to look for
303 Returns:
304 Node object if found, else None
305 """
306 for subnode in self.subnodes:
307 if subnode.name == name:
308 return subnode
309 return None
310
Simon Glass7b75b442017-05-27 07:38:28 -0600311 def Offset(self):
312 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600313
Simon Glass7b75b442017-05-27 07:38:28 -0600314 This should be used instead of self._offset directly, to ensure that
315 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600316 """
Simon Glass7b75b442017-05-27 07:38:28 -0600317 self._fdt.CheckCache()
318 return self._offset
319
320 def Scan(self):
321 """Scan a node's properties and subnodes
322
323 This fills in the props and subnodes properties, recursively
324 searching into subnodes so that the entire tree is built.
325 """
Simon Glass117f57b2018-07-06 10:27:26 -0600326 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600327 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600328 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600329 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600330 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600331
Simon Glass117f57b2018-07-06 10:27:26 -0600332 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600333 while offset >= 0:
334 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600335 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600336 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600337 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600338 self.subnodes.append(node)
339
340 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600341 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600342
343 def Refresh(self, my_offset):
344 """Fix up the _offset for each node, recursively
345
346 Note: This does not take account of property offsets - these will not
347 be updated.
348 """
Simon Glass96066242018-07-06 10:27:27 -0600349 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600350 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600351 self._offset = my_offset
Simon Glass5d1bec32021-03-21 18:24:39 +1300352 name = fdt_obj.get_name(self._offset)
353 if name and self.name != name:
354 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
355 (self.path, name))
356
Simon Glass96066242018-07-06 10:27:27 -0600357 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600358 for subnode in self.subnodes:
Simon Glassf9b88b32018-07-06 10:27:29 -0600359 if subnode.name != fdt_obj.get_name(offset):
360 raise ValueError('Internal error, node name mismatch %s != %s' %
361 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600362 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600363 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600364 if offset != -libfdt.FDT_ERR_NOTFOUND:
365 raise ValueError('Internal error, offset == %d' % offset)
366
367 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
368 while poffset >= 0:
369 p = fdt_obj.get_property_by_offset(poffset)
370 prop = self.props.get(p.name)
371 if not prop:
Simon Glassacd98612021-03-21 18:24:34 +1300372 raise ValueError("Internal error, node '%s' property '%s' missing, "
373 'offset %d' % (self.path, p.name, poffset))
Simon Glassf9b88b32018-07-06 10:27:29 -0600374 prop.RefreshOffset(poffset)
375 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600376
Simon Glass2a70d892016-07-25 18:59:14 -0600377 def DeleteProp(self, prop_name):
378 """Delete a property of a node
379
Simon Glass7b75b442017-05-27 07:38:28 -0600380 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600381
382 Args:
383 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600384 Raises:
385 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600386 """
Simon Glass96066242018-07-06 10:27:27 -0600387 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600388 "Node '%s': delete property: '%s'" % (self.path, prop_name))
389 del self.props[prop_name]
390 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600391
Simon Glass116adec2018-07-06 10:27:38 -0600392 def AddZeroProp(self, prop_name):
393 """Add a new property to the device tree with an integer value of 0.
394
395 Args:
396 prop_name: Name of property
397 """
Simon Glass194b8d52019-05-17 22:00:33 -0600398 self.props[prop_name] = Prop(self, None, prop_name,
Simon Glassc1aa66e2022-01-29 14:14:04 -0700399 tools.get_bytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600400
Simon Glass64349612018-09-14 04:57:16 -0600401 def AddEmptyProp(self, prop_name, len):
402 """Add a property with a fixed data size, for filling in later
403
404 The device tree is marked dirty so that the value will be written to
405 the blob on the next sync.
406
407 Args:
408 prop_name: Name of property
409 len: Length of data in property
410 """
Simon Glassc1aa66e2022-01-29 14:14:04 -0700411 value = tools.get_bytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600412 self.props[prop_name] = Prop(self, None, prop_name, value)
413
Simon Glassd9dad102019-07-20 12:23:37 -0600414 def _CheckProp(self, prop_name):
415 """Check if a property is present
416
417 Args:
418 prop_name: Name of property
419
420 Returns:
421 self
422
423 Raises:
424 ValueError if the property is missing
425 """
426 if prop_name not in self.props:
427 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
428 (self._fdt._fname, self.path, prop_name))
429 return self
430
Simon Glass116adec2018-07-06 10:27:38 -0600431 def SetInt(self, prop_name, val):
432 """Update an integer property int the device tree.
433
434 This is not allowed to change the size of the FDT.
435
Simon Glass64349612018-09-14 04:57:16 -0600436 The device tree is marked dirty so that the value will be written to
437 the blob on the next sync.
438
Simon Glass116adec2018-07-06 10:27:38 -0600439 Args:
440 prop_name: Name of property
441 val: Value to set
442 """
Simon Glassd9dad102019-07-20 12:23:37 -0600443 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600444
Simon Glass64349612018-09-14 04:57:16 -0600445 def SetData(self, prop_name, val):
446 """Set the data value of a property
447
448 The device tree is marked dirty so that the value will be written to
449 the blob on the next sync.
450
451 Args:
452 prop_name: Name of property to set
453 val: Data value to set
454 """
Simon Glassd9dad102019-07-20 12:23:37 -0600455 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600456
457 def SetString(self, prop_name, val):
458 """Set the string value of a property
459
460 The device tree is marked dirty so that the value will be written to
461 the blob on the next sync.
462
463 Args:
464 prop_name: Name of property to set
465 val: String value to set (will be \0-terminated in DT)
466 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600467 if type(val) == str:
468 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600469 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600470
Simon Glassc0639172020-07-09 18:39:44 -0600471 def AddData(self, prop_name, val):
472 """Add a new property to a node
473
474 The device tree is marked dirty so that the value will be written to
475 the blob on the next sync.
476
477 Args:
478 prop_name: Name of property to add
479 val: Bytes value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300480
481 Returns:
482 Prop added
Simon Glassc0639172020-07-09 18:39:44 -0600483 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300484 prop = Prop(self, None, prop_name, val)
485 self.props[prop_name] = prop
486 return prop
Simon Glassc0639172020-07-09 18:39:44 -0600487
Simon Glass64349612018-09-14 04:57:16 -0600488 def AddString(self, prop_name, val):
489 """Add a new string property to a node
490
491 The device tree is marked dirty so that the value will be written to
492 the blob on the next sync.
493
494 Args:
495 prop_name: Name of property to add
496 val: String value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300497
498 Returns:
499 Prop added
Simon Glass64349612018-09-14 04:57:16 -0600500 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700501 val = bytes(val, 'utf-8')
Simon Glass5d1bec32021-03-21 18:24:39 +1300502 return self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600503
Simon Glassbc116022022-02-08 11:49:50 -0700504 def AddStringList(self, prop_name, val):
505 """Add a new string-list property to a node
506
507 The device tree is marked dirty so that the value will be written to
508 the blob on the next sync.
509
510 Args:
511 prop_name: Name of property to add
512 val (list of str): List of strings to add
513
514 Returns:
515 Prop added
516 """
517 out = b''
518 for string in val:
519 out += bytes(string, 'utf-8') + b'\0'
520 return self.AddData(prop_name, out)
521
Simon Glass6eb99322021-01-06 21:35:18 -0700522 def AddInt(self, prop_name, val):
523 """Add a new integer property to a node
524
525 The device tree is marked dirty so that the value will be written to
526 the blob on the next sync.
527
528 Args:
529 prop_name: Name of property to add
530 val: Integer value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300531
532 Returns:
533 Prop added
Simon Glass6eb99322021-01-06 21:35:18 -0700534 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300535 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glass6eb99322021-01-06 21:35:18 -0700536
Simon Glasse21c27a2018-09-14 04:57:15 -0600537 def AddSubnode(self, name):
Simon Glass64349612018-09-14 04:57:16 -0600538 """Add a new subnode to the node
539
540 Args:
541 name: name of node to add
542
543 Returns:
544 New subnode that was created
545 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600546 path = self.path + '/' + name
547 subnode = Node(self._fdt, self, None, name, path)
548 self.subnodes.append(subnode)
549 return subnode
550
Simon Glassfa80c252018-09-14 04:57:13 -0600551 def Sync(self, auto_resize=False):
552 """Sync node changes back to the device tree
553
554 This updates the device tree blob with any changes to this node and its
555 subnodes since the last sync.
556
557 Args:
558 auto_resize: Resize the device tree automatically if it does not
559 have enough space for the update
560
Simon Glassf6176652021-03-21 18:24:38 +1300561 Returns:
562 True if the node had to be added, False if it already existed
563
Simon Glassfa80c252018-09-14 04:57:13 -0600564 Raises:
565 FdtException if auto_resize is False and there is not enough space
566 """
Simon Glassf6176652021-03-21 18:24:38 +1300567 added = False
Simon Glasse21c27a2018-09-14 04:57:15 -0600568 if self._offset is None:
569 # The subnode doesn't exist yet, so add it
570 fdt_obj = self._fdt._fdt_obj
571 if auto_resize:
572 while True:
573 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
574 (libfdt.NOSPACE,))
575 if offset != -libfdt.NOSPACE:
576 break
577 fdt_obj.resize(fdt_obj.totalsize() + 1024)
578 else:
579 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
580 self._offset = offset
Simon Glassf6176652021-03-21 18:24:38 +1300581 added = True
Simon Glasse21c27a2018-09-14 04:57:15 -0600582
Simon Glassf6176652021-03-21 18:24:38 +1300583 # Sync the existing subnodes first, so that we can rely on the offsets
584 # being correct. As soon as we add new subnodes, it pushes all the
585 # existing subnodes up.
Simon Glassfa80c252018-09-14 04:57:13 -0600586 for node in reversed(self.subnodes):
Simon Glassf6176652021-03-21 18:24:38 +1300587 if node._offset is not None:
588 node.Sync(auto_resize)
Simon Glassfa80c252018-09-14 04:57:13 -0600589
Simon Glassf6176652021-03-21 18:24:38 +1300590 # Sync subnodes in reverse so that we get the expected order. Each
591 # new node goes at the start of the subnode list. This avoids an O(n^2)
592 # rescan of node offsets.
593 num_added = 0
594 for node in reversed(self.subnodes):
595 if node.Sync(auto_resize):
596 num_added += 1
597 if num_added:
598 # Reorder our list of nodes to put the new ones first, since that's
599 # what libfdt does
600 old_count = len(self.subnodes) - num_added
601 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
602 self.subnodes = subnodes
603
604 # Sync properties now, whose offsets should not have been disturbed,
605 # since properties come before subnodes. This is done after all the
606 # subnode processing above, since updating properties can disturb the
607 # offsets of those subnodes.
608 # Properties are synced in reverse order, with new properties added
609 # before existing properties are synced. This ensures that the offsets
610 # of earlier properties are not disturbed.
611 # Note that new properties will have an offset of None here, which
612 # Python cannot sort against int. So use a large value instead so that
613 # new properties are added first.
Simon Glass63518052019-05-17 22:00:38 -0600614 prop_list = sorted(self.props.values(),
615 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600616 reverse=True)
617 for prop in prop_list:
618 prop.Sync(auto_resize)
Simon Glassf6176652021-03-21 18:24:38 +1300619 return added
Simon Glass116adec2018-07-06 10:27:38 -0600620
621
Simon Glassa06a34b2016-07-25 18:59:04 -0600622class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600623 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600624
625 Properties:
626 fname: Filename of fdt
627 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600628 name: Helpful name for this Fdt for the user (useful when creating the
629 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600630 """
631 def __init__(self, fname):
632 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600633 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600634 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600635 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600636 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600637 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600638 self._fname = fdt_util.EnsureCompiled(self._fname)
639
Simon Glass3e4b51e2019-05-14 15:53:43 -0600640 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600641 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600642
Simon Glass746aee32018-09-14 04:57:17 -0600643 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600644 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600645 """Create a new Fdt object from the given data
646
647 Args:
648 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600649 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600650
651 Returns:
652 Fdt object containing the data
653 """
654 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600655 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600656 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600657 return fdt
658
Simon Glass94a7c602018-07-17 13:25:46 -0600659 def LookupPhandle(self, phandle):
660 """Look up a phandle
661
662 Args:
663 phandle: Phandle to look up (int)
664
665 Returns:
666 Node object the phandle points to
667 """
668 return self.phandle_to_node.get(phandle)
669
Simon Glassf7a2aee2016-07-25 18:59:07 -0600670 def Scan(self, root='/'):
671 """Scan a device tree, building up a tree of Node objects
672
673 This fills in the self._root property
674
675 Args:
676 root: Ignored
677
678 TODO(sjg@chromium.org): Implement the 'root' parameter
679 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600680 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600681 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600682 self._root.Scan()
683
684 def GetRoot(self):
685 """Get the root Node of the device tree
686
687 Returns:
688 The root Node object
689 """
690 return self._root
691
692 def GetNode(self, path):
693 """Look up a node from its path
694
695 Args:
696 path: Path to look up, e.g. '/microcode/update@0'
697 Returns:
698 Node object, or None if not found
699 """
700 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600701 parts = path.split('/')
702 if len(parts) < 2:
703 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600704 if len(parts) == 2 and parts[1] == '':
705 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600706 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600707 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600708 if not node:
709 return None
710 return node
711
Simon Glassda5f7492016-07-25 18:59:15 -0600712 def Flush(self):
713 """Flush device tree changes back to the file
714
715 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600716 """
Simon Glass7b75b442017-05-27 07:38:28 -0600717 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600718 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600719
Simon Glassfa80c252018-09-14 04:57:13 -0600720 def Sync(self, auto_resize=False):
721 """Make sure any DT changes are written to the blob
722
723 Args:
724 auto_resize: Resize the device tree automatically if it does not
725 have enough space for the update
726
727 Raises:
728 FdtException if auto_resize is False and there is not enough space
729 """
Simon Glass71719e12021-03-21 18:24:36 +1300730 self.CheckCache()
Simon Glassfa80c252018-09-14 04:57:13 -0600731 self._root.Sync(auto_resize)
Simon Glass71719e12021-03-21 18:24:36 +1300732 self.Refresh()
Simon Glassfa80c252018-09-14 04:57:13 -0600733
Simon Glassda5f7492016-07-25 18:59:15 -0600734 def Pack(self):
735 """Pack the device tree down to its minimum size
736
737 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600738 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600739 """
Simon Glass117f57b2018-07-06 10:27:26 -0600740 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glass71719e12021-03-21 18:24:36 +1300741 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600742
Simon Glass96066242018-07-06 10:27:27 -0600743 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600744 """Get the contents of the FDT
745
746 Returns:
747 The FDT contents as a string of bytes
748 """
Simon Glassf6b64812019-05-17 22:00:36 -0600749 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600750
Simon Glass2ba98752018-07-06 10:27:24 -0600751 def GetFdtObj(self):
752 """Get the contents of the FDT
753
754 Returns:
755 The FDT contents as a libfdt.Fdt object
756 """
757 return self._fdt_obj
758
Simon Glass7b75b442017-05-27 07:38:28 -0600759 def GetProps(self, node):
760 """Get all properties from a node.
761
762 Args:
763 node: Full path to node name to look in.
764
765 Returns:
766 A dictionary containing all the properties, indexed by node name.
767 The entries are Prop objects.
768
769 Raises:
770 ValueError: if the node does not exist.
771 """
772 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600773 poffset = self._fdt_obj.first_property_offset(node._offset,
774 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600775 while poffset >= 0:
776 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600777 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600778 props_dict[prop.name] = prop
779
Simon Glass117f57b2018-07-06 10:27:26 -0600780 poffset = self._fdt_obj.next_property_offset(poffset,
781 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600782 return props_dict
783
784 def Invalidate(self):
785 """Mark our offset cache as invalid"""
786 self._cached_offsets = False
787
788 def CheckCache(self):
789 """Refresh the offset cache if needed"""
790 if self._cached_offsets:
791 return
792 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600793
794 def Refresh(self):
795 """Refresh the offset cache"""
796 self._root.Refresh(0)
Simon Glass71719e12021-03-21 18:24:36 +1300797 self._cached_offsets = True
Simon Glass7b75b442017-05-27 07:38:28 -0600798
799 def GetStructOffset(self, offset):
800 """Get the file offset of a given struct offset
801
802 Args:
803 offset: Offset within the 'struct' region of the device tree
804 Returns:
805 Position of @offset within the device tree binary
806 """
Simon Glass117f57b2018-07-06 10:27:26 -0600807 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600808
809 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600810 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600811 """Create a new node
812
813 This is used by Fdt.Scan() to create a new node using the correct
814 class.
815
816 Args:
817 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600818 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600819 offset: Offset of node
820 name: Node name
821 path: Full path to node
822 """
Simon Glass979ab022017-08-29 14:15:47 -0600823 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600824 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600825
Simon Glassf6e02492019-07-20 12:24:08 -0600826 def GetFilename(self):
827 """Get the filename of the device tree
828
829 Returns:
830 String filename
831 """
832 return self._fname
833
Simon Glass99ed4a22017-05-27 07:38:30 -0600834def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600835 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600836 dtb = Fdt(fname)
837 dtb.Scan()
838 return dtb