]> git.ipfire.org Git - thirdparty/u-boot.git/blame - tools/dtoc/fdt.py
configs: Migrate the various SPL_BOOT_xxx choices for PowerPC
[thirdparty/u-boot.git] / tools / dtoc / fdt.py
CommitLineData
a06a34b2 1#!/usr/bin/python
83d290c5 2# SPDX-License-Identifier: GPL-2.0+
a06a34b2
SG
3#
4# Copyright (C) 2016 Google, Inc
5# Written by Simon Glass <sjg@chromium.org>
6#
a06a34b2
SG
7
8import struct
9import sys
10
11import fdt_util
7b75b448 12import libfdt
117f57b7 13from libfdt import QUIET_NOTFOUND
a06a34b2
SG
14
15# This deals with a device tree, presenting it as an assortment of Node and
16# Prop objects, representing nodes and properties, respectively. This file
99ed4a2e
SG
17# contains the base classes and defines the high-level API. You can use
18# FdtScan() as a convenience function to create and scan an Fdt.
7b75b448
SG
19
20# This implementation uses a libfdt Python library to access the device tree,
21# so it is fairly efficient.
a06a34b2 22
bc1dea36 23# A list of types we support
fbdfd228 24(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
bc1dea36 25
a06a34b2
SG
26def CheckErr(errnum, msg):
27 if errnum:
28 raise ValueError('Error %d: %s: %s' %
29 (errnum, libfdt.fdt_strerror(errnum), msg))
30
7b75b448 31class Prop:
a06a34b2
SG
32 """A device tree property
33
34 Properties:
35 name: Property name (as per the device tree)
36 value: Property value as a string of bytes, or a list of strings of
37 bytes
38 type: Value type
39 """
7b75b448 40 def __init__(self, node, offset, name, bytes):
a06a34b2
SG
41 self._node = node
42 self._offset = offset
43 self.name = name
44 self.value = None
7b75b448 45 self.bytes = str(bytes)
fa80c25c 46 self.dirty = False
7b75b448
SG
47 if not bytes:
48 self.type = TYPE_BOOL
49 self.value = True
50 return
51 self.type, self.value = self.BytesToValue(bytes)
a06a34b2 52
f9b88b3a
SG
53 def RefreshOffset(self, poffset):
54 self._offset = poffset
55
c322a850
SG
56 def Widen(self, newprop):
57 """Figure out which property type is more general
58
59 Given a current property and a new property, this function returns the
60 one that is less specific as to type. The less specific property will
61 be ble to represent the data in the more specific property. This is
62 used for things like:
63
64 node1 {
65 compatible = "fred";
66 value = <1>;
67 };
68 node1 {
69 compatible = "fred";
70 value = <1 2>;
71 };
72
73 He we want to use an int array for 'value'. The first property
74 suggests that a single int is enough, but the second one shows that
75 it is not. Calling this function with these two propertes would
76 update the current property to be like the second, since it is less
77 specific.
78 """
79 if newprop.type < self.type:
80 self.type = newprop.type
81
82 if type(newprop.value) == list and type(self.value) != list:
83 self.value = [self.value]
84
85 if type(self.value) == list and len(newprop.value) > len(self.value):
86 val = self.GetEmpty(self.type)
87 while len(self.value) < len(newprop.value):
88 self.value.append(val)
89
bc1dea36
SG
90 def BytesToValue(self, bytes):
91 """Converts a string of bytes into a type and value
92
93 Args:
94 A string containing bytes
95
96 Return:
97 A tuple:
98 Type of data
99 Data, either a single element or a list of elements. Each element
100 is one of:
101 TYPE_STRING: string value from the property
102 TYPE_INT: a byte-swapped integer stored as a 4-byte string
103 TYPE_BYTE: a byte stored as a single-byte string
104 """
b4360206 105 bytes = str(bytes)
bc1dea36
SG
106 size = len(bytes)
107 strings = bytes.split('\0')
108 is_string = True
109 count = len(strings) - 1
110 if count > 0 and not strings[-1]:
111 for string in strings[:-1]:
112 if not string:
113 is_string = False
114 break
115 for ch in string:
116 if ch < ' ' or ch > '~':
117 is_string = False
118 break
119 else:
120 is_string = False
121 if is_string:
122 if count == 1:
123 return TYPE_STRING, strings[0]
124 else:
125 return TYPE_STRING, strings[:-1]
126 if size % 4:
127 if size == 1:
128 return TYPE_BYTE, bytes[0]
129 else:
130 return TYPE_BYTE, list(bytes)
131 val = []
132 for i in range(0, size, 4):
133 val.append(bytes[i:i + 4])
134 if size == 4:
135 return TYPE_INT, val[0]
136 else:
137 return TYPE_INT, val
138
2ba98753 139 @classmethod
bc1dea36
SG
140 def GetEmpty(self, type):
141 """Get an empty / zero value of the given type
142
143 Returns:
144 A single value of the given type
145 """
146 if type == TYPE_BYTE:
147 return chr(0)
148 elif type == TYPE_INT:
af53f5aa 149 return struct.pack('>I', 0);
bc1dea36
SG
150 elif type == TYPE_STRING:
151 return ''
152 else:
153 return True
154
babdbde6
SG
155 def GetOffset(self):
156 """Get the offset of a property
157
babdbde6 158 Returns:
7b75b448 159 The offset of the property (struct fdt_property) within the file
babdbde6 160 """
f9b88b3a 161 self._node._fdt.CheckCache()
7b75b448 162 return self._node._fdt.GetStructOffset(self._offset)
babdbde6 163
fa80c25c
SG
164 def SetInt(self, val):
165 """Set the integer value of the property
166
167 The device tree is marked dirty so that the value will be written to
168 the block on the next sync.
169
170 Args:
171 val: Integer value (32-bit, single cell)
172 """
173 self.bytes = struct.pack('>I', val);
41b781dd 174 self.value = self.bytes
fa80c25c
SG
175 self.type = TYPE_INT
176 self.dirty = True
177
6434961b
SG
178 def SetData(self, bytes):
179 """Set the value of a property as bytes
180
181 Args:
182 bytes: New property value to set
183 """
184 self.bytes = str(bytes)
185 self.type, self.value = self.BytesToValue(bytes)
186 self.dirty = True
187
fa80c25c
SG
188 def Sync(self, auto_resize=False):
189 """Sync property changes back to the device tree
190
191 This updates the device tree blob with any changes to this property
192 since the last sync.
193
194 Args:
195 auto_resize: Resize the device tree automatically if it does not
196 have enough space for the update
197
198 Raises:
199 FdtException if auto_resize is False and there is not enough space
200 """
201 if self._offset is None or self.dirty:
202 node = self._node
203 fdt_obj = node._fdt._fdt_obj
204 if auto_resize:
205 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
206 (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
207 fdt_obj.resize(fdt_obj.totalsize() + 1024)
208 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
209 else:
210 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
211
212
7b75b448 213class Node:
a06a34b2
SG
214 """A device tree node
215
216 Properties:
217 offset: Integer offset in the device tree
218 name: Device tree node tname
219 path: Full path to node, along with the node name itself
220 _fdt: Device tree object
221 subnodes: A list of subnodes for this node, each a Node object
222 props: A dict of properties for this node, each a Prop object.
223 Keyed by property name
224 """
979ab024 225 def __init__(self, fdt, parent, offset, name, path):
a06a34b2 226 self._fdt = fdt
979ab024 227 self.parent = parent
a06a34b2
SG
228 self._offset = offset
229 self.name = name
230 self.path = path
231 self.subnodes = []
232 self.props = {}
233
94a7c603
SG
234 def GetFdt(self):
235 """Get the Fdt object for this node
236
237 Returns:
238 Fdt object
239 """
240 return self._fdt
241
1d85888c 242 def FindNode(self, name):
f7a2aeee
SG
243 """Find a node given its name
244
245 Args:
246 name: Node name to look for
247 Returns:
248 Node object if found, else None
249 """
250 for subnode in self.subnodes:
251 if subnode.name == name:
252 return subnode
253 return None
254
7b75b448
SG
255 def Offset(self):
256 """Returns the offset of a node, after checking the cache
257
258 This should be used instead of self._offset directly, to ensure that
259 the cache does not contain invalid offsets.
260 """
261 self._fdt.CheckCache()
262 return self._offset
263
f7a2aeee 264 def Scan(self):
7b75b448
SG
265 """Scan a node's properties and subnodes
266
267 This fills in the props and subnodes properties, recursively
268 searching into subnodes so that the entire tree is built.
269 """
117f57b7 270 fdt_obj = self._fdt._fdt_obj
7b75b448 271 self.props = self._fdt.GetProps(self)
117f57b7 272 phandle = fdt_obj.get_phandle(self.Offset())
09264e04 273 if phandle:
117f57b7 274 self._fdt.phandle_to_node[phandle] = self
7b75b448 275
117f57b7 276 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
7b75b448
SG
277 while offset >= 0:
278 sep = '' if self.path[-1] == '/' else '/'
117f57b7 279 name = fdt_obj.get_name(offset)
7b75b448 280 path = self.path + sep + name
979ab024 281 node = Node(self._fdt, self, offset, name, path)
7b75b448 282 self.subnodes.append(node)
f7a2aeee 283
7b75b448 284 node.Scan()
117f57b7 285 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
7b75b448
SG
286
287 def Refresh(self, my_offset):
288 """Fix up the _offset for each node, recursively
289
290 Note: This does not take account of property offsets - these will not
291 be updated.
f7a2aeee 292 """
96066240 293 fdt_obj = self._fdt._fdt_obj
7b75b448 294 if self._offset != my_offset:
7b75b448 295 self._offset = my_offset
96066240 296 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
7b75b448 297 for subnode in self.subnodes:
f9b88b3a
SG
298 if subnode.name != fdt_obj.get_name(offset):
299 raise ValueError('Internal error, node name mismatch %s != %s' %
300 (subnode.name, fdt_obj.get_name(offset)))
7b75b448 301 subnode.Refresh(offset)
96066240 302 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
f9b88b3a
SG
303 if offset != -libfdt.FDT_ERR_NOTFOUND:
304 raise ValueError('Internal error, offset == %d' % offset)
305
306 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
307 while poffset >= 0:
308 p = fdt_obj.get_property_by_offset(poffset)
309 prop = self.props.get(p.name)
310 if not prop:
311 raise ValueError("Internal error, property '%s' missing, "
312 'offset %d' % (p.name, poffset))
313 prop.RefreshOffset(poffset)
314 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
f7a2aeee 315
2a70d897
SG
316 def DeleteProp(self, prop_name):
317 """Delete a property of a node
318
7b75b448 319 The property is deleted and the offset cache is invalidated.
2a70d897
SG
320
321 Args:
322 prop_name: Name of the property to delete
7b75b448
SG
323 Raises:
324 ValueError if the property does not exist
2a70d897 325 """
96066240 326 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
7b75b448
SG
327 "Node '%s': delete property: '%s'" % (self.path, prop_name))
328 del self.props[prop_name]
329 self._fdt.Invalidate()
2a70d897 330
116adecb
SG
331 def AddZeroProp(self, prop_name):
332 """Add a new property to the device tree with an integer value of 0.
333
334 Args:
335 prop_name: Name of property
336 """
fa80c25c 337 self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4)
116adecb 338
6434961b
SG
339 def AddEmptyProp(self, prop_name, len):
340 """Add a property with a fixed data size, for filling in later
341
342 The device tree is marked dirty so that the value will be written to
343 the blob on the next sync.
344
345 Args:
346 prop_name: Name of property
347 len: Length of data in property
348 """
349 value = chr(0) * len
350 self.props[prop_name] = Prop(self, None, prop_name, value)
351
116adecb
SG
352 def SetInt(self, prop_name, val):
353 """Update an integer property int the device tree.
354
355 This is not allowed to change the size of the FDT.
356
6434961b
SG
357 The device tree is marked dirty so that the value will be written to
358 the blob on the next sync.
359
116adecb
SG
360 Args:
361 prop_name: Name of property
362 val: Value to set
363 """
fa80c25c
SG
364 self.props[prop_name].SetInt(val)
365
6434961b
SG
366 def SetData(self, prop_name, val):
367 """Set the data value of a property
368
369 The device tree is marked dirty so that the value will be written to
370 the blob on the next sync.
371
372 Args:
373 prop_name: Name of property to set
374 val: Data value to set
375 """
376 self.props[prop_name].SetData(val)
377
378 def SetString(self, prop_name, val):
379 """Set the string value of a property
380
381 The device tree is marked dirty so that the value will be written to
382 the blob on the next sync.
383
384 Args:
385 prop_name: Name of property to set
386 val: String value to set (will be \0-terminated in DT)
387 """
388 self.props[prop_name].SetData(val + chr(0))
389
390 def AddString(self, prop_name, val):
391 """Add a new string property to a node
392
393 The device tree is marked dirty so that the value will be written to
394 the blob on the next sync.
395
396 Args:
397 prop_name: Name of property to add
398 val: String value of property
399 """
400 self.props[prop_name] = Prop(self, None, prop_name, val + chr(0))
401
e21c27af 402 def AddSubnode(self, name):
6434961b
SG
403 """Add a new subnode to the node
404
405 Args:
406 name: name of node to add
407
408 Returns:
409 New subnode that was created
410 """
e21c27af
SG
411 path = self.path + '/' + name
412 subnode = Node(self._fdt, self, None, name, path)
413 self.subnodes.append(subnode)
414 return subnode
415
fa80c25c
SG
416 def Sync(self, auto_resize=False):
417 """Sync node changes back to the device tree
418
419 This updates the device tree blob with any changes to this node and its
420 subnodes since the last sync.
421
422 Args:
423 auto_resize: Resize the device tree automatically if it does not
424 have enough space for the update
425
426 Raises:
427 FdtException if auto_resize is False and there is not enough space
428 """
e21c27af
SG
429 if self._offset is None:
430 # The subnode doesn't exist yet, so add it
431 fdt_obj = self._fdt._fdt_obj
432 if auto_resize:
433 while True:
434 offset = fdt_obj.add_subnode(self.parent._offset, self.name,
435 (libfdt.NOSPACE,))
436 if offset != -libfdt.NOSPACE:
437 break
438 fdt_obj.resize(fdt_obj.totalsize() + 1024)
439 else:
440 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
441 self._offset = offset
442
fa80c25c
SG
443 # Sync subnodes in reverse so that we don't disturb node offsets for
444 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
445 # node offsets.
446 for node in reversed(self.subnodes):
447 node.Sync(auto_resize)
448
449 # Sync properties now, whose offsets should not have been disturbed.
450 # We do this after subnodes, since this disturbs the offsets of these
451 # properties.
452 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
453 reverse=True)
454 for prop in prop_list:
455 prop.Sync(auto_resize)
116adecb
SG
456
457
a06a34b2 458class Fdt:
7b75b448 459 """Provides simple access to a flat device tree blob using libfdts.
a06a34b2
SG
460
461 Properties:
462 fname: Filename of fdt
463 _root: Root of device tree (a Node object)
464 """
465 def __init__(self, fname):
466 self._fname = fname
7b75b448 467 self._cached_offsets = False
09264e04 468 self.phandle_to_node = {}
7b75b448
SG
469 if self._fname:
470 self._fname = fdt_util.EnsureCompiled(self._fname)
471
472 with open(self._fname) as fd:
96066240 473 self._fdt_obj = libfdt.Fdt(fd.read())
f7a2aeee 474
746aee3f
SG
475 @staticmethod
476 def FromData(data):
477 """Create a new Fdt object from the given data
478
479 Args:
480 data: Device-tree data blob
481
482 Returns:
483 Fdt object containing the data
484 """
485 fdt = Fdt(None)
486 fdt._fdt_obj = libfdt.Fdt(bytearray(data))
487 return fdt
488
94a7c603
SG
489 def LookupPhandle(self, phandle):
490 """Look up a phandle
491
492 Args:
493 phandle: Phandle to look up (int)
494
495 Returns:
496 Node object the phandle points to
497 """
498 return self.phandle_to_node.get(phandle)
499
f7a2aeee
SG
500 def Scan(self, root='/'):
501 """Scan a device tree, building up a tree of Node objects
502
503 This fills in the self._root property
504
505 Args:
506 root: Ignored
507
508 TODO(sjg@chromium.org): Implement the 'root' parameter
509 """
f9b88b3a 510 self._cached_offsets = True
979ab024 511 self._root = self.Node(self, None, 0, '/', '/')
f7a2aeee
SG
512 self._root.Scan()
513
514 def GetRoot(self):
515 """Get the root Node of the device tree
516
517 Returns:
518 The root Node object
519 """
520 return self._root
521
522 def GetNode(self, path):
523 """Look up a node from its path
524
525 Args:
526 path: Path to look up, e.g. '/microcode/update@0'
527 Returns:
528 Node object, or None if not found
529 """
530 node = self._root
b9066ffc
SG
531 parts = path.split('/')
532 if len(parts) < 2:
533 return None
534 for part in parts[1:]:
1d85888c 535 node = node.FindNode(part)
f7a2aeee
SG
536 if not node:
537 return None
538 return node
539
da5f7499
SG
540 def Flush(self):
541 """Flush device tree changes back to the file
542
543 If the device tree has changed in memory, write it back to the file.
da5f7499 544 """
7b75b448 545 with open(self._fname, 'wb') as fd:
96066240 546 fd.write(self._fdt_obj.as_bytearray())
da5f7499 547
fa80c25c
SG
548 def Sync(self, auto_resize=False):
549 """Make sure any DT changes are written to the blob
550
551 Args:
552 auto_resize: Resize the device tree automatically if it does not
553 have enough space for the update
554
555 Raises:
556 FdtException if auto_resize is False and there is not enough space
557 """
558 self._root.Sync(auto_resize)
559 self.Invalidate()
560
da5f7499
SG
561 def Pack(self):
562 """Pack the device tree down to its minimum size
563
564 When nodes and properties shrink or are deleted, wasted space can
7b75b448
SG
565 build up in the device tree binary.
566 """
117f57b7
SG
567 CheckErr(self._fdt_obj.pack(), 'pack')
568 self.Invalidate()
7b75b448 569
96066240 570 def GetContents(self):
7b75b448
SG
571 """Get the contents of the FDT
572
573 Returns:
574 The FDT contents as a string of bytes
575 """
96066240 576 return self._fdt_obj.as_bytearray()
7b75b448 577
2ba98753
SG
578 def GetFdtObj(self):
579 """Get the contents of the FDT
580
581 Returns:
582 The FDT contents as a libfdt.Fdt object
583 """
584 return self._fdt_obj
585
7b75b448
SG
586 def GetProps(self, node):
587 """Get all properties from a node.
588
589 Args:
590 node: Full path to node name to look in.
591
592 Returns:
593 A dictionary containing all the properties, indexed by node name.
594 The entries are Prop objects.
595
596 Raises:
597 ValueError: if the node does not exist.
598 """
599 props_dict = {}
117f57b7
SG
600 poffset = self._fdt_obj.first_property_offset(node._offset,
601 QUIET_NOTFOUND)
7b75b448
SG
602 while poffset >= 0:
603 p = self._fdt_obj.get_property_by_offset(poffset)
3def0cf2 604 prop = Prop(node, poffset, p.name, p)
7b75b448
SG
605 props_dict[prop.name] = prop
606
117f57b7
SG
607 poffset = self._fdt_obj.next_property_offset(poffset,
608 QUIET_NOTFOUND)
7b75b448
SG
609 return props_dict
610
611 def Invalidate(self):
612 """Mark our offset cache as invalid"""
613 self._cached_offsets = False
614
615 def CheckCache(self):
616 """Refresh the offset cache if needed"""
617 if self._cached_offsets:
618 return
619 self.Refresh()
620 self._cached_offsets = True
621
622 def Refresh(self):
623 """Refresh the offset cache"""
624 self._root.Refresh(0)
625
626 def GetStructOffset(self, offset):
627 """Get the file offset of a given struct offset
628
629 Args:
630 offset: Offset within the 'struct' region of the device tree
631 Returns:
632 Position of @offset within the device tree binary
da5f7499 633 """
117f57b7 634 return self._fdt_obj.off_dt_struct() + offset
7b75b448
SG
635
636 @classmethod
979ab024 637 def Node(self, fdt, parent, offset, name, path):
7b75b448
SG
638 """Create a new node
639
640 This is used by Fdt.Scan() to create a new node using the correct
641 class.
642
643 Args:
644 fdt: Fdt object
979ab024 645 parent: Parent node, or None if this is the root node
7b75b448
SG
646 offset: Offset of node
647 name: Node name
648 path: Full path to node
649 """
979ab024 650 node = Node(fdt, parent, offset, name, path)
7b75b448 651 return node
99ed4a2e
SG
652
653def FdtScan(fname):
dfe5f5b9 654 """Returns a new Fdt object"""
99ed4a2e
SG
655 dtb = Fdt(fname)
656 dtb.Scan()
657 return dtb