blob: 9749966d5fb91859cac04278ad39a5fa9a9ce0a4 [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 Glass5ea9dcc2020-11-08 20:36:17 -0700156 if self.type == Type.INT and newprop.type == Type.BYTE:
Simon Glasse144caf2020-10-03 11:31:27 -0600157 if type(self.value) == list:
158 new_value = []
159 for val in self.value:
Simon Glass479dd302020-11-08 20:36:20 -0700160 new_value += [chr(by) for by in val]
Simon Glasse144caf2020-10-03 11:31:27 -0600161 else:
Simon Glass479dd302020-11-08 20:36:20 -0700162 new_value = [chr(by) for by in self.value]
Simon Glasse144caf2020-10-03 11:31:27 -0600163 self.value = new_value
Simon Glassc322a852016-07-25 18:59:06 -0600164 self.type = newprop.type
165
166 if type(newprop.value) == list and type(self.value) != list:
167 self.value = [self.value]
168
169 if type(self.value) == list and len(newprop.value) > len(self.value):
170 val = self.GetEmpty(self.type)
171 while len(self.value) < len(newprop.value):
172 self.value.append(val)
173
Simon Glass2ba98752018-07-06 10:27:24 -0600174 @classmethod
Simon Glassbc1dea32016-07-25 18:59:05 -0600175 def GetEmpty(self, type):
176 """Get an empty / zero value of the given type
177
178 Returns:
179 A single value of the given type
180 """
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700181 if type == Type.BYTE:
Simon Glassbc1dea32016-07-25 18:59:05 -0600182 return chr(0)
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700183 elif type == Type.INT:
Simon Glassaf53f5a2018-09-14 04:57:14 -0600184 return struct.pack('>I', 0);
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700185 elif type == Type.STRING:
Simon Glassbc1dea32016-07-25 18:59:05 -0600186 return ''
187 else:
188 return True
189
Simon Glassbabdbde2016-07-25 18:59:16 -0600190 def GetOffset(self):
191 """Get the offset of a property
192
Simon Glassbabdbde2016-07-25 18:59:16 -0600193 Returns:
Simon Glass7b75b442017-05-27 07:38:28 -0600194 The offset of the property (struct fdt_property) within the file
Simon Glassbabdbde2016-07-25 18:59:16 -0600195 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600196 self._node._fdt.CheckCache()
Simon Glass7b75b442017-05-27 07:38:28 -0600197 return self._node._fdt.GetStructOffset(self._offset)
Simon Glassbabdbde2016-07-25 18:59:16 -0600198
Simon Glassfa80c252018-09-14 04:57:13 -0600199 def SetInt(self, val):
200 """Set the integer value of the property
201
202 The device tree is marked dirty so that the value will be written to
203 the block on the next sync.
204
205 Args:
206 val: Integer value (32-bit, single cell)
207 """
208 self.bytes = struct.pack('>I', val);
Simon Glass41b781d2018-10-01 12:22:49 -0600209 self.value = self.bytes
Simon Glass5ea9dcc2020-11-08 20:36:17 -0700210 self.type = Type.INT
Simon Glassfa80c252018-09-14 04:57:13 -0600211 self.dirty = True
212
Simon Glass64349612018-09-14 04:57:16 -0600213 def SetData(self, bytes):
214 """Set the value of a property as bytes
215
216 Args:
217 bytes: New property value to set
218 """
Simon Glassf6b64812019-05-17 22:00:36 -0600219 self.bytes = bytes
Simon Glass7e6952d2019-05-17 22:00:34 -0600220 self.type, self.value = BytesToValue(bytes)
Simon Glass64349612018-09-14 04:57:16 -0600221 self.dirty = True
222
Simon Glassfa80c252018-09-14 04:57:13 -0600223 def Sync(self, auto_resize=False):
224 """Sync property changes back to the device tree
225
226 This updates the device tree blob with any changes to this property
227 since the last sync.
228
229 Args:
230 auto_resize: Resize the device tree automatically if it does not
231 have enough space for the update
232
233 Raises:
234 FdtException if auto_resize is False and there is not enough space
235 """
Simon Glass37ba9842021-03-21 18:24:35 +1300236 if self.dirty:
Simon Glassfa80c252018-09-14 04:57:13 -0600237 node = self._node
238 fdt_obj = node._fdt._fdt_obj
Simon Glass5d1bec32021-03-21 18:24:39 +1300239 node_name = fdt_obj.get_name(node._offset)
240 if node_name and node_name != node.name:
241 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
242 (node.path, node_name))
243
Simon Glassfa80c252018-09-14 04:57:13 -0600244 if auto_resize:
245 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
246 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
Simon Glassc0639172020-07-09 18:39:44 -0600247 fdt_obj.resize(fdt_obj.totalsize() + 1024 +
248 len(self.bytes))
Simon Glassfa80c252018-09-14 04:57:13 -0600249 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
250 else:
251 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
Simon Glass37ba9842021-03-21 18:24:35 +1300252 self.dirty = False
Simon Glassfa80c252018-09-14 04:57:13 -0600253
254
Simon Glass7b75b442017-05-27 07:38:28 -0600255class Node:
Simon Glassa06a34b2016-07-25 18:59:04 -0600256 """A device tree node
257
258 Properties:
Simon Glass37ba9842021-03-21 18:24:35 +1300259 parent: Parent Node
260 offset: Integer offset in the device tree (None if to be synced)
Simon Glassa06a34b2016-07-25 18:59:04 -0600261 name: Device tree node tname
262 path: Full path to node, along with the node name itself
263 _fdt: Device tree object
264 subnodes: A list of subnodes for this node, each a Node object
265 props: A dict of properties for this node, each a Prop object.
266 Keyed by property name
267 """
Simon Glass979ab022017-08-29 14:15:47 -0600268 def __init__(self, fdt, parent, offset, name, path):
Simon Glassa06a34b2016-07-25 18:59:04 -0600269 self._fdt = fdt
Simon Glass979ab022017-08-29 14:15:47 -0600270 self.parent = parent
Simon Glassa06a34b2016-07-25 18:59:04 -0600271 self._offset = offset
272 self.name = name
273 self.path = path
274 self.subnodes = []
275 self.props = {}
276
Simon Glass94a7c602018-07-17 13:25:46 -0600277 def GetFdt(self):
278 """Get the Fdt object for this node
279
280 Returns:
281 Fdt object
282 """
283 return self._fdt
284
Simon Glass1d858882018-07-17 13:25:41 -0600285 def FindNode(self, name):
Simon Glassf7a2aee2016-07-25 18:59:07 -0600286 """Find a node given its name
287
288 Args:
289 name: Node name to look for
290 Returns:
291 Node object if found, else None
292 """
293 for subnode in self.subnodes:
294 if subnode.name == name:
295 return subnode
296 return None
297
Simon Glass7b75b442017-05-27 07:38:28 -0600298 def Offset(self):
299 """Returns the offset of a node, after checking the cache
Simon Glassf7a2aee2016-07-25 18:59:07 -0600300
Simon Glass7b75b442017-05-27 07:38:28 -0600301 This should be used instead of self._offset directly, to ensure that
302 the cache does not contain invalid offsets.
Simon Glassf7a2aee2016-07-25 18:59:07 -0600303 """
Simon Glass7b75b442017-05-27 07:38:28 -0600304 self._fdt.CheckCache()
305 return self._offset
306
307 def Scan(self):
308 """Scan a node's properties and subnodes
309
310 This fills in the props and subnodes properties, recursively
311 searching into subnodes so that the entire tree is built.
312 """
Simon Glass117f57b2018-07-06 10:27:26 -0600313 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600314 self.props = self._fdt.GetProps(self)
Simon Glass117f57b2018-07-06 10:27:26 -0600315 phandle = fdt_obj.get_phandle(self.Offset())
Simon Glass09264e02017-08-29 14:15:52 -0600316 if phandle:
Simon Glass117f57b2018-07-06 10:27:26 -0600317 self._fdt.phandle_to_node[phandle] = self
Simon Glass7b75b442017-05-27 07:38:28 -0600318
Simon Glass117f57b2018-07-06 10:27:26 -0600319 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600320 while offset >= 0:
321 sep = '' if self.path[-1] == '/' else '/'
Simon Glass117f57b2018-07-06 10:27:26 -0600322 name = fdt_obj.get_name(offset)
Simon Glass7b75b442017-05-27 07:38:28 -0600323 path = self.path + sep + name
Simon Glass979ab022017-08-29 14:15:47 -0600324 node = Node(self._fdt, self, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600325 self.subnodes.append(node)
326
327 node.Scan()
Simon Glass117f57b2018-07-06 10:27:26 -0600328 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600329
330 def Refresh(self, my_offset):
331 """Fix up the _offset for each node, recursively
332
333 Note: This does not take account of property offsets - these will not
334 be updated.
335 """
Simon Glass96066242018-07-06 10:27:27 -0600336 fdt_obj = self._fdt._fdt_obj
Simon Glass7b75b442017-05-27 07:38:28 -0600337 if self._offset != my_offset:
Simon Glass7b75b442017-05-27 07:38:28 -0600338 self._offset = my_offset
Simon Glass5d1bec32021-03-21 18:24:39 +1300339 name = fdt_obj.get_name(self._offset)
340 if name and self.name != name:
341 raise ValueError("Internal error, node '%s' name mismatch '%s'" %
342 (self.path, name))
343
Simon Glass96066242018-07-06 10:27:27 -0600344 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600345 for subnode in self.subnodes:
Simon Glassf9b88b32018-07-06 10:27:29 -0600346 if subnode.name != fdt_obj.get_name(offset):
347 raise ValueError('Internal error, node name mismatch %s != %s' %
348 (subnode.name, fdt_obj.get_name(offset)))
Simon Glass7b75b442017-05-27 07:38:28 -0600349 subnode.Refresh(offset)
Simon Glass96066242018-07-06 10:27:27 -0600350 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
Simon Glassf9b88b32018-07-06 10:27:29 -0600351 if offset != -libfdt.FDT_ERR_NOTFOUND:
352 raise ValueError('Internal error, offset == %d' % offset)
353
354 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
355 while poffset >= 0:
356 p = fdt_obj.get_property_by_offset(poffset)
357 prop = self.props.get(p.name)
358 if not prop:
Simon Glassacd98612021-03-21 18:24:34 +1300359 raise ValueError("Internal error, node '%s' property '%s' missing, "
360 'offset %d' % (self.path, p.name, poffset))
Simon Glassf9b88b32018-07-06 10:27:29 -0600361 prop.RefreshOffset(poffset)
362 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600363
Simon Glass2a70d892016-07-25 18:59:14 -0600364 def DeleteProp(self, prop_name):
365 """Delete a property of a node
366
Simon Glass7b75b442017-05-27 07:38:28 -0600367 The property is deleted and the offset cache is invalidated.
Simon Glass2a70d892016-07-25 18:59:14 -0600368
369 Args:
370 prop_name: Name of the property to delete
Simon Glass7b75b442017-05-27 07:38:28 -0600371 Raises:
372 ValueError if the property does not exist
Simon Glass2a70d892016-07-25 18:59:14 -0600373 """
Simon Glass96066242018-07-06 10:27:27 -0600374 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
Simon Glass7b75b442017-05-27 07:38:28 -0600375 "Node '%s': delete property: '%s'" % (self.path, prop_name))
376 del self.props[prop_name]
377 self._fdt.Invalidate()
Simon Glass2a70d892016-07-25 18:59:14 -0600378
Simon Glass116adec2018-07-06 10:27:38 -0600379 def AddZeroProp(self, prop_name):
380 """Add a new property to the device tree with an integer value of 0.
381
382 Args:
383 prop_name: Name of property
384 """
Simon Glass194b8d52019-05-17 22:00:33 -0600385 self.props[prop_name] = Prop(self, None, prop_name,
386 tools.GetBytes(0, 4))
Simon Glass116adec2018-07-06 10:27:38 -0600387
Simon Glass64349612018-09-14 04:57:16 -0600388 def AddEmptyProp(self, prop_name, len):
389 """Add a property with a fixed data size, for filling in later
390
391 The device tree is marked dirty so that the value will be written to
392 the blob on the next sync.
393
394 Args:
395 prop_name: Name of property
396 len: Length of data in property
397 """
Simon Glass194b8d52019-05-17 22:00:33 -0600398 value = tools.GetBytes(0, len)
Simon Glass64349612018-09-14 04:57:16 -0600399 self.props[prop_name] = Prop(self, None, prop_name, value)
400
Simon Glassd9dad102019-07-20 12:23:37 -0600401 def _CheckProp(self, prop_name):
402 """Check if a property is present
403
404 Args:
405 prop_name: Name of property
406
407 Returns:
408 self
409
410 Raises:
411 ValueError if the property is missing
412 """
413 if prop_name not in self.props:
414 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
415 (self._fdt._fname, self.path, prop_name))
416 return self
417
Simon Glass116adec2018-07-06 10:27:38 -0600418 def SetInt(self, prop_name, val):
419 """Update an integer property int the device tree.
420
421 This is not allowed to change the size of the FDT.
422
Simon Glass64349612018-09-14 04:57:16 -0600423 The device tree is marked dirty so that the value will be written to
424 the blob on the next sync.
425
Simon Glass116adec2018-07-06 10:27:38 -0600426 Args:
427 prop_name: Name of property
428 val: Value to set
429 """
Simon Glassd9dad102019-07-20 12:23:37 -0600430 self._CheckProp(prop_name).props[prop_name].SetInt(val)
Simon Glassfa80c252018-09-14 04:57:13 -0600431
Simon Glass64349612018-09-14 04:57:16 -0600432 def SetData(self, prop_name, val):
433 """Set the data value of a property
434
435 The device tree is marked dirty so that the value will be written to
436 the blob on the next sync.
437
438 Args:
439 prop_name: Name of property to set
440 val: Data value to set
441 """
Simon Glassd9dad102019-07-20 12:23:37 -0600442 self._CheckProp(prop_name).props[prop_name].SetData(val)
Simon Glass64349612018-09-14 04:57:16 -0600443
444 def SetString(self, prop_name, val):
445 """Set the string value of a property
446
447 The device tree is marked dirty so that the value will be written to
448 the blob on the next sync.
449
450 Args:
451 prop_name: Name of property to set
452 val: String value to set (will be \0-terminated in DT)
453 """
Simon Glassa90df2b2019-10-31 07:43:04 -0600454 if type(val) == str:
455 val = val.encode('utf-8')
Simon Glassd9dad102019-07-20 12:23:37 -0600456 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600457
Simon Glassc0639172020-07-09 18:39:44 -0600458 def AddData(self, prop_name, val):
459 """Add a new property to a node
460
461 The device tree is marked dirty so that the value will be written to
462 the blob on the next sync.
463
464 Args:
465 prop_name: Name of property to add
466 val: Bytes value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300467
468 Returns:
469 Prop added
Simon Glassc0639172020-07-09 18:39:44 -0600470 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300471 prop = Prop(self, None, prop_name, val)
472 self.props[prop_name] = prop
473 return prop
Simon Glassc0639172020-07-09 18:39:44 -0600474
Simon Glass64349612018-09-14 04:57:16 -0600475 def AddString(self, prop_name, val):
476 """Add a new string property to a node
477
478 The device tree is marked dirty so that the value will be written to
479 the blob on the next sync.
480
481 Args:
482 prop_name: Name of property to add
483 val: String value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300484
485 Returns:
486 Prop added
Simon Glass64349612018-09-14 04:57:16 -0600487 """
Simon Glass9fc6ebd2021-01-06 21:35:10 -0700488 val = bytes(val, 'utf-8')
Simon Glass5d1bec32021-03-21 18:24:39 +1300489 return self.AddData(prop_name, val + b'\0')
Simon Glass64349612018-09-14 04:57:16 -0600490
Simon Glass6eb99322021-01-06 21:35:18 -0700491 def AddInt(self, prop_name, val):
492 """Add a new integer property to a node
493
494 The device tree is marked dirty so that the value will be written to
495 the blob on the next sync.
496
497 Args:
498 prop_name: Name of property to add
499 val: Integer value of property
Simon Glass5d1bec32021-03-21 18:24:39 +1300500
501 Returns:
502 Prop added
Simon Glass6eb99322021-01-06 21:35:18 -0700503 """
Simon Glass5d1bec32021-03-21 18:24:39 +1300504 return self.AddData(prop_name, struct.pack('>I', val))
Simon Glass6eb99322021-01-06 21:35:18 -0700505
Simon Glasse21c27a2018-09-14 04:57:15 -0600506 def AddSubnode(self, name):
Simon Glass64349612018-09-14 04:57:16 -0600507 """Add a new subnode to the node
508
509 Args:
510 name: name of node to add
511
512 Returns:
513 New subnode that was created
514 """
Simon Glasse21c27a2018-09-14 04:57:15 -0600515 path = self.path + '/' + name
516 subnode = Node(self._fdt, self, None, name, path)
517 self.subnodes.append(subnode)
518 return subnode
519
Simon Glassfa80c252018-09-14 04:57:13 -0600520 def Sync(self, auto_resize=False):
521 """Sync node changes back to the device tree
522
523 This updates the device tree blob with any changes to this node and its
524 subnodes since the last sync.
525
526 Args:
527 auto_resize: Resize the device tree automatically if it does not
528 have enough space for the update
529
Simon Glassf6176652021-03-21 18:24:38 +1300530 Returns:
531 True if the node had to be added, False if it already existed
532
Simon Glassfa80c252018-09-14 04:57:13 -0600533 Raises:
534 FdtException if auto_resize is False and there is not enough space
535 """
Simon Glassf6176652021-03-21 18:24:38 +1300536 added = False
Simon Glasse21c27a2018-09-14 04:57:15 -0600537 if self._offset is None:
538 # The subnode doesn't exist yet, so add it
539 fdt_obj = self._fdt._fdt_obj
540 if auto_resize:
541 while True:
542 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
543 (libfdt.NOSPACE,))
544 if offset != -libfdt.NOSPACE:
545 break
546 fdt_obj.resize(fdt_obj.totalsize() + 1024)
547 else:
548 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
549 self._offset = offset
Simon Glassf6176652021-03-21 18:24:38 +1300550 added = True
Simon Glasse21c27a2018-09-14 04:57:15 -0600551
Simon Glassf6176652021-03-21 18:24:38 +1300552 # Sync the existing subnodes first, so that we can rely on the offsets
553 # being correct. As soon as we add new subnodes, it pushes all the
554 # existing subnodes up.
Simon Glassfa80c252018-09-14 04:57:13 -0600555 for node in reversed(self.subnodes):
Simon Glassf6176652021-03-21 18:24:38 +1300556 if node._offset is not None:
557 node.Sync(auto_resize)
Simon Glassfa80c252018-09-14 04:57:13 -0600558
Simon Glassf6176652021-03-21 18:24:38 +1300559 # Sync subnodes in reverse so that we get the expected order. Each
560 # new node goes at the start of the subnode list. This avoids an O(n^2)
561 # rescan of node offsets.
562 num_added = 0
563 for node in reversed(self.subnodes):
564 if node.Sync(auto_resize):
565 num_added += 1
566 if num_added:
567 # Reorder our list of nodes to put the new ones first, since that's
568 # what libfdt does
569 old_count = len(self.subnodes) - num_added
570 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
571 self.subnodes = subnodes
572
573 # Sync properties now, whose offsets should not have been disturbed,
574 # since properties come before subnodes. This is done after all the
575 # subnode processing above, since updating properties can disturb the
576 # offsets of those subnodes.
577 # Properties are synced in reverse order, with new properties added
578 # before existing properties are synced. This ensures that the offsets
579 # of earlier properties are not disturbed.
580 # Note that new properties will have an offset of None here, which
581 # Python cannot sort against int. So use a large value instead so that
582 # new properties are added first.
Simon Glass63518052019-05-17 22:00:38 -0600583 prop_list = sorted(self.props.values(),
584 key=lambda prop: prop._offset or 1 << 31,
Simon Glassfa80c252018-09-14 04:57:13 -0600585 reverse=True)
586 for prop in prop_list:
587 prop.Sync(auto_resize)
Simon Glassf6176652021-03-21 18:24:38 +1300588 return added
Simon Glass116adec2018-07-06 10:27:38 -0600589
590
Simon Glassa06a34b2016-07-25 18:59:04 -0600591class Fdt:
Simon Glass7b75b442017-05-27 07:38:28 -0600592 """Provides simple access to a flat device tree blob using libfdts.
Simon Glassa06a34b2016-07-25 18:59:04 -0600593
594 Properties:
595 fname: Filename of fdt
596 _root: Root of device tree (a Node object)
Simon Glass880e9ee2019-07-20 12:23:38 -0600597 name: Helpful name for this Fdt for the user (useful when creating the
598 DT from data rather than a file)
Simon Glassa06a34b2016-07-25 18:59:04 -0600599 """
600 def __init__(self, fname):
601 self._fname = fname
Simon Glass7b75b442017-05-27 07:38:28 -0600602 self._cached_offsets = False
Simon Glass09264e02017-08-29 14:15:52 -0600603 self.phandle_to_node = {}
Simon Glass880e9ee2019-07-20 12:23:38 -0600604 self.name = ''
Simon Glass7b75b442017-05-27 07:38:28 -0600605 if self._fname:
Simon Glass880e9ee2019-07-20 12:23:38 -0600606 self.name = self._fname
Simon Glass7b75b442017-05-27 07:38:28 -0600607 self._fname = fdt_util.EnsureCompiled(self._fname)
608
Simon Glass3e4b51e2019-05-14 15:53:43 -0600609 with open(self._fname, 'rb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600610 self._fdt_obj = libfdt.Fdt(fd.read())
Simon Glassf7a2aee2016-07-25 18:59:07 -0600611
Simon Glass746aee32018-09-14 04:57:17 -0600612 @staticmethod
Simon Glass880e9ee2019-07-20 12:23:38 -0600613 def FromData(data, name=''):
Simon Glass746aee32018-09-14 04:57:17 -0600614 """Create a new Fdt object from the given data
615
616 Args:
617 data: Device-tree data blob
Simon Glass880e9ee2019-07-20 12:23:38 -0600618 name: Helpful name for this Fdt for the user
Simon Glass746aee32018-09-14 04:57:17 -0600619
620 Returns:
621 Fdt object containing the data
622 """
623 fdt = Fdt(None)
Simon Glassf6b64812019-05-17 22:00:36 -0600624 fdt._fdt_obj = libfdt.Fdt(bytes(data))
Simon Glass880e9ee2019-07-20 12:23:38 -0600625 fdt.name = name
Simon Glass746aee32018-09-14 04:57:17 -0600626 return fdt
627
Simon Glass94a7c602018-07-17 13:25:46 -0600628 def LookupPhandle(self, phandle):
629 """Look up a phandle
630
631 Args:
632 phandle: Phandle to look up (int)
633
634 Returns:
635 Node object the phandle points to
636 """
637 return self.phandle_to_node.get(phandle)
638
Simon Glassf7a2aee2016-07-25 18:59:07 -0600639 def Scan(self, root='/'):
640 """Scan a device tree, building up a tree of Node objects
641
642 This fills in the self._root property
643
644 Args:
645 root: Ignored
646
647 TODO(sjg@chromium.org): Implement the 'root' parameter
648 """
Simon Glassf9b88b32018-07-06 10:27:29 -0600649 self._cached_offsets = True
Simon Glass979ab022017-08-29 14:15:47 -0600650 self._root = self.Node(self, None, 0, '/', '/')
Simon Glassf7a2aee2016-07-25 18:59:07 -0600651 self._root.Scan()
652
653 def GetRoot(self):
654 """Get the root Node of the device tree
655
656 Returns:
657 The root Node object
658 """
659 return self._root
660
661 def GetNode(self, path):
662 """Look up a node from its path
663
664 Args:
665 path: Path to look up, e.g. '/microcode/update@0'
666 Returns:
667 Node object, or None if not found
668 """
669 node = self._root
Simon Glassb9066ff2018-07-06 10:27:30 -0600670 parts = path.split('/')
671 if len(parts) < 2:
672 return None
Simon Glasse44bc832019-07-20 12:23:39 -0600673 if len(parts) == 2 and parts[1] == '':
674 return node
Simon Glassb9066ff2018-07-06 10:27:30 -0600675 for part in parts[1:]:
Simon Glass1d858882018-07-17 13:25:41 -0600676 node = node.FindNode(part)
Simon Glassf7a2aee2016-07-25 18:59:07 -0600677 if not node:
678 return None
679 return node
680
Simon Glassda5f7492016-07-25 18:59:15 -0600681 def Flush(self):
682 """Flush device tree changes back to the file
683
684 If the device tree has changed in memory, write it back to the file.
Simon Glassda5f7492016-07-25 18:59:15 -0600685 """
Simon Glass7b75b442017-05-27 07:38:28 -0600686 with open(self._fname, 'wb') as fd:
Simon Glass96066242018-07-06 10:27:27 -0600687 fd.write(self._fdt_obj.as_bytearray())
Simon Glassda5f7492016-07-25 18:59:15 -0600688
Simon Glassfa80c252018-09-14 04:57:13 -0600689 def Sync(self, auto_resize=False):
690 """Make sure any DT changes are written to the blob
691
692 Args:
693 auto_resize: Resize the device tree automatically if it does not
694 have enough space for the update
695
696 Raises:
697 FdtException if auto_resize is False and there is not enough space
698 """
Simon Glass71719e12021-03-21 18:24:36 +1300699 self.CheckCache()
Simon Glassfa80c252018-09-14 04:57:13 -0600700 self._root.Sync(auto_resize)
Simon Glass71719e12021-03-21 18:24:36 +1300701 self.Refresh()
Simon Glassfa80c252018-09-14 04:57:13 -0600702
Simon Glassda5f7492016-07-25 18:59:15 -0600703 def Pack(self):
704 """Pack the device tree down to its minimum size
705
706 When nodes and properties shrink or are deleted, wasted space can
Simon Glass7b75b442017-05-27 07:38:28 -0600707 build up in the device tree binary.
Simon Glassda5f7492016-07-25 18:59:15 -0600708 """
Simon Glass117f57b2018-07-06 10:27:26 -0600709 CheckErr(self._fdt_obj.pack(), 'pack')
Simon Glass71719e12021-03-21 18:24:36 +1300710 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600711
Simon Glass96066242018-07-06 10:27:27 -0600712 def GetContents(self):
Simon Glass7b75b442017-05-27 07:38:28 -0600713 """Get the contents of the FDT
714
715 Returns:
716 The FDT contents as a string of bytes
717 """
Simon Glassf6b64812019-05-17 22:00:36 -0600718 return bytes(self._fdt_obj.as_bytearray())
Simon Glass7b75b442017-05-27 07:38:28 -0600719
Simon Glass2ba98752018-07-06 10:27:24 -0600720 def GetFdtObj(self):
721 """Get the contents of the FDT
722
723 Returns:
724 The FDT contents as a libfdt.Fdt object
725 """
726 return self._fdt_obj
727
Simon Glass7b75b442017-05-27 07:38:28 -0600728 def GetProps(self, node):
729 """Get all properties from a node.
730
731 Args:
732 node: Full path to node name to look in.
733
734 Returns:
735 A dictionary containing all the properties, indexed by node name.
736 The entries are Prop objects.
737
738 Raises:
739 ValueError: if the node does not exist.
740 """
741 props_dict = {}
Simon Glass117f57b2018-07-06 10:27:26 -0600742 poffset = self._fdt_obj.first_property_offset(node._offset,
743 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600744 while poffset >= 0:
745 p = self._fdt_obj.get_property_by_offset(poffset)
Simon Glass3def0cf2018-07-06 10:27:20 -0600746 prop = Prop(node, poffset, p.name, p)
Simon Glass7b75b442017-05-27 07:38:28 -0600747 props_dict[prop.name] = prop
748
Simon Glass117f57b2018-07-06 10:27:26 -0600749 poffset = self._fdt_obj.next_property_offset(poffset,
750 QUIET_NOTFOUND)
Simon Glass7b75b442017-05-27 07:38:28 -0600751 return props_dict
752
753 def Invalidate(self):
754 """Mark our offset cache as invalid"""
755 self._cached_offsets = False
756
757 def CheckCache(self):
758 """Refresh the offset cache if needed"""
759 if self._cached_offsets:
760 return
761 self.Refresh()
Simon Glass7b75b442017-05-27 07:38:28 -0600762
763 def Refresh(self):
764 """Refresh the offset cache"""
765 self._root.Refresh(0)
Simon Glass71719e12021-03-21 18:24:36 +1300766 self._cached_offsets = True
Simon Glass7b75b442017-05-27 07:38:28 -0600767
768 def GetStructOffset(self, offset):
769 """Get the file offset of a given struct offset
770
771 Args:
772 offset: Offset within the 'struct' region of the device tree
773 Returns:
774 Position of @offset within the device tree binary
775 """
Simon Glass117f57b2018-07-06 10:27:26 -0600776 return self._fdt_obj.off_dt_struct() + offset
Simon Glass7b75b442017-05-27 07:38:28 -0600777
778 @classmethod
Simon Glass979ab022017-08-29 14:15:47 -0600779 def Node(self, fdt, parent, offset, name, path):
Simon Glass7b75b442017-05-27 07:38:28 -0600780 """Create a new node
781
782 This is used by Fdt.Scan() to create a new node using the correct
783 class.
784
785 Args:
786 fdt: Fdt object
Simon Glass979ab022017-08-29 14:15:47 -0600787 parent: Parent node, or None if this is the root node
Simon Glass7b75b442017-05-27 07:38:28 -0600788 offset: Offset of node
789 name: Node name
790 path: Full path to node
791 """
Simon Glass979ab022017-08-29 14:15:47 -0600792 node = Node(fdt, parent, offset, name, path)
Simon Glass7b75b442017-05-27 07:38:28 -0600793 return node
Simon Glass99ed4a22017-05-27 07:38:30 -0600794
Simon Glassf6e02492019-07-20 12:24:08 -0600795 def GetFilename(self):
796 """Get the filename of the device tree
797
798 Returns:
799 String filename
800 """
801 return self._fname
802
Simon Glass99ed4a22017-05-27 07:38:30 -0600803def FdtScan(fname):
Simon Glassdfe5f5b2018-07-06 10:27:32 -0600804 """Returns a new Fdt object"""
Simon Glass99ed4a22017-05-27 07:38:30 -0600805 dtb = Fdt(fname)
806 dtb.Scan()
807 return dtb