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