dtoc: binman: Drop Python 2 code
[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 AddSubnode(self, name):
467         """Add a new subnode to the node
468
469         Args:
470             name: name of node to add
471
472         Returns:
473             New subnode that was created
474         """
475         path = self.path + '/' + name
476         subnode = Node(self._fdt, self, None, name, path)
477         self.subnodes.append(subnode)
478         return subnode
479
480     def Sync(self, auto_resize=False):
481         """Sync node changes back to the device tree
482
483         This updates the device tree blob with any changes to this node and its
484         subnodes since the last sync.
485
486         Args:
487             auto_resize: Resize the device tree automatically if it does not
488                 have enough space for the update
489
490         Raises:
491             FdtException if auto_resize is False and there is not enough space
492         """
493         if self._offset is None:
494             # The subnode doesn't exist yet, so add it
495             fdt_obj = self._fdt._fdt_obj
496             if auto_resize:
497                 while True:
498                     offset = fdt_obj.add_subnode(self.parent._offset, self.name,
499                                                 (libfdt.NOSPACE,))
500                     if offset != -libfdt.NOSPACE:
501                         break
502                     fdt_obj.resize(fdt_obj.totalsize() + 1024)
503             else:
504                 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
505             self._offset = offset
506
507         # Sync subnodes in reverse so that we don't disturb node offsets for
508         # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
509         # node offsets.
510         for node in reversed(self.subnodes):
511             node.Sync(auto_resize)
512
513         # Sync properties now, whose offsets should not have been disturbed.
514         # We do this after subnodes, since this disturbs the offsets of these
515         # properties. Note that new properties will have an offset of None here,
516         # which Python 3 cannot sort against int. So use a large value instead
517         # to ensure that the new properties are added first.
518         prop_list = sorted(self.props.values(),
519                            key=lambda prop: prop._offset or 1 << 31,
520                            reverse=True)
521         for prop in prop_list:
522             prop.Sync(auto_resize)
523
524
525 class Fdt:
526     """Provides simple access to a flat device tree blob using libfdts.
527
528     Properties:
529       fname: Filename of fdt
530       _root: Root of device tree (a Node object)
531       name: Helpful name for this Fdt for the user (useful when creating the
532         DT from data rather than a file)
533     """
534     def __init__(self, fname):
535         self._fname = fname
536         self._cached_offsets = False
537         self.phandle_to_node = {}
538         self.name = ''
539         if self._fname:
540             self.name = self._fname
541             self._fname = fdt_util.EnsureCompiled(self._fname)
542
543             with open(self._fname, 'rb') as fd:
544                 self._fdt_obj = libfdt.Fdt(fd.read())
545
546     @staticmethod
547     def FromData(data, name=''):
548         """Create a new Fdt object from the given data
549
550         Args:
551             data: Device-tree data blob
552             name: Helpful name for this Fdt for the user
553
554         Returns:
555             Fdt object containing the data
556         """
557         fdt = Fdt(None)
558         fdt._fdt_obj = libfdt.Fdt(bytes(data))
559         fdt.name = name
560         return fdt
561
562     def LookupPhandle(self, phandle):
563         """Look up a phandle
564
565         Args:
566             phandle: Phandle to look up (int)
567
568         Returns:
569             Node object the phandle points to
570         """
571         return self.phandle_to_node.get(phandle)
572
573     def Scan(self, root='/'):
574         """Scan a device tree, building up a tree of Node objects
575
576         This fills in the self._root property
577
578         Args:
579             root: Ignored
580
581         TODO(sjg@chromium.org): Implement the 'root' parameter
582         """
583         self._cached_offsets = True
584         self._root = self.Node(self, None, 0, '/', '/')
585         self._root.Scan()
586
587     def GetRoot(self):
588         """Get the root Node of the device tree
589
590         Returns:
591             The root Node object
592         """
593         return self._root
594
595     def GetNode(self, path):
596         """Look up a node from its path
597
598         Args:
599             path: Path to look up, e.g. '/microcode/update@0'
600         Returns:
601             Node object, or None if not found
602         """
603         node = self._root
604         parts = path.split('/')
605         if len(parts) < 2:
606             return None
607         if len(parts) == 2 and parts[1] == '':
608             return node
609         for part in parts[1:]:
610             node = node.FindNode(part)
611             if not node:
612                 return None
613         return node
614
615     def Flush(self):
616         """Flush device tree changes back to the file
617
618         If the device tree has changed in memory, write it back to the file.
619         """
620         with open(self._fname, 'wb') as fd:
621             fd.write(self._fdt_obj.as_bytearray())
622
623     def Sync(self, auto_resize=False):
624         """Make sure any DT changes are written to the blob
625
626         Args:
627             auto_resize: Resize the device tree automatically if it does not
628                 have enough space for the update
629
630         Raises:
631             FdtException if auto_resize is False and there is not enough space
632         """
633         self._root.Sync(auto_resize)
634         self.Invalidate()
635
636     def Pack(self):
637         """Pack the device tree down to its minimum size
638
639         When nodes and properties shrink or are deleted, wasted space can
640         build up in the device tree binary.
641         """
642         CheckErr(self._fdt_obj.pack(), 'pack')
643         self.Invalidate()
644
645     def GetContents(self):
646         """Get the contents of the FDT
647
648         Returns:
649             The FDT contents as a string of bytes
650         """
651         return bytes(self._fdt_obj.as_bytearray())
652
653     def GetFdtObj(self):
654         """Get the contents of the FDT
655
656         Returns:
657             The FDT contents as a libfdt.Fdt object
658         """
659         return self._fdt_obj
660
661     def GetProps(self, node):
662         """Get all properties from a node.
663
664         Args:
665             node: Full path to node name to look in.
666
667         Returns:
668             A dictionary containing all the properties, indexed by node name.
669             The entries are Prop objects.
670
671         Raises:
672             ValueError: if the node does not exist.
673         """
674         props_dict = {}
675         poffset = self._fdt_obj.first_property_offset(node._offset,
676                                                       QUIET_NOTFOUND)
677         while poffset >= 0:
678             p = self._fdt_obj.get_property_by_offset(poffset)
679             prop = Prop(node, poffset, p.name, p)
680             props_dict[prop.name] = prop
681
682             poffset = self._fdt_obj.next_property_offset(poffset,
683                                                          QUIET_NOTFOUND)
684         return props_dict
685
686     def Invalidate(self):
687         """Mark our offset cache as invalid"""
688         self._cached_offsets = False
689
690     def CheckCache(self):
691         """Refresh the offset cache if needed"""
692         if self._cached_offsets:
693             return
694         self.Refresh()
695         self._cached_offsets = True
696
697     def Refresh(self):
698         """Refresh the offset cache"""
699         self._root.Refresh(0)
700
701     def GetStructOffset(self, offset):
702         """Get the file offset of a given struct offset
703
704         Args:
705             offset: Offset within the 'struct' region of the device tree
706         Returns:
707             Position of @offset within the device tree binary
708         """
709         return self._fdt_obj.off_dt_struct() + offset
710
711     @classmethod
712     def Node(self, fdt, parent, offset, name, path):
713         """Create a new node
714
715         This is used by Fdt.Scan() to create a new node using the correct
716         class.
717
718         Args:
719             fdt: Fdt object
720             parent: Parent node, or None if this is the root node
721             offset: Offset of node
722             name: Node name
723             path: Full path to node
724         """
725         node = Node(fdt, parent, offset, name, path)
726         return node
727
728     def GetFilename(self):
729         """Get the filename of the device tree
730
731         Returns:
732             String filename
733         """
734         return self._fname
735
736 def FdtScan(fname):
737     """Returns a new Fdt object"""
738     dtb = Fdt(fname)
739     dtb.Scan()
740     return dtb