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