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