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