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