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