test_dtoc: Move to use Python 3
[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 import struct
9 import sys
10
11 import fdt_util
12 import libfdt
13 from libfdt import QUIET_NOTFOUND
14 import tools
15
16 # This deals with a device tree, presenting it as an assortment of Node and
17 # Prop objects, representing nodes and properties, respectively. This file
18 # contains the base classes and defines the high-level API. You can use
19 # FdtScan() as a convenience function to create and scan an Fdt.
20
21 # This implementation uses a libfdt Python library to access the device tree,
22 # so it is fairly efficient.
23
24 # A list of types we support
25 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
26
27 def CheckErr(errnum, msg):
28     if errnum:
29         raise ValueError('Error %d: %s: %s' %
30             (errnum, libfdt.fdt_strerror(errnum), msg))
31
32
33 def BytesToValue(data):
34     """Converts a string of bytes into a type and value
35
36     Args:
37         A bytes value (which on Python 2 is an alias for str)
38
39     Return:
40         A tuple:
41             Type of data
42             Data, either a single element or a list of elements. Each element
43             is one of:
44                 TYPE_STRING: str/bytes value from the property
45                 TYPE_INT: a byte-swapped integer stored as a 4-byte str/bytes
46                 TYPE_BYTE: a byte stored as a single-byte str/bytes
47     """
48     data = bytes(data)
49     size = len(data)
50     strings = data.split(b'\0')
51     is_string = True
52     count = len(strings) - 1
53     if count > 0 and not len(strings[-1]):
54         for string in strings[:-1]:
55             if not string:
56                 is_string = False
57                 break
58             for ch in string:
59                 # Handle Python 2 treating bytes as str
60                 if type(ch) == str:
61                     ch = ord(ch)
62                 if ch < 32 or ch > 127:
63                     is_string = False
64                     break
65     else:
66         is_string = False
67     if is_string:
68         if count == 1: 
69             if sys.version_info[0] >= 3:  # pragma: no cover
70                 return TYPE_STRING, strings[0].decode()
71             else:
72                 return TYPE_STRING, strings[0]
73         else:
74             if sys.version_info[0] >= 3:  # pragma: no cover
75                 return TYPE_STRING, [s.decode() for s in strings[:-1]]
76             else:
77                 return TYPE_STRING, strings[:-1]
78     if size % 4:
79         if size == 1:
80             return TYPE_BYTE, tools.ToChar(data[0])
81         else:
82             return TYPE_BYTE, [tools.ToChar(ch) for ch in list(data)]
83     val = []
84     for i in range(0, size, 4):
85         val.append(data[i:i + 4])
86     if size == 4:
87         return TYPE_INT, val[0]
88     else:
89         return TYPE_INT, val
90
91
92 class Prop:
93     """A device tree property
94
95     Properties:
96         name: Property name (as per the device tree)
97         value: Property value as a string of bytes, or a list of strings of
98             bytes
99         type: Value type
100     """
101     def __init__(self, node, offset, name, data):
102         self._node = node
103         self._offset = offset
104         self.name = name
105         self.value = None
106         self.bytes = bytes(data)
107         self.dirty = False
108         if not data:
109             self.type = TYPE_BOOL
110             self.value = True
111             return
112         self.type, self.value = BytesToValue(bytes(data))
113
114     def RefreshOffset(self, poffset):
115         self._offset = poffset
116
117     def Widen(self, newprop):
118         """Figure out which property type is more general
119
120         Given a current property and a new property, this function returns the
121         one that is less specific as to type. The less specific property will
122         be ble to represent the data in the more specific property. This is
123         used for things like:
124
125             node1 {
126                 compatible = "fred";
127                 value = <1>;
128             };
129             node1 {
130                 compatible = "fred";
131                 value = <1 2>;
132             };
133
134         He we want to use an int array for 'value'. The first property
135         suggests that a single int is enough, but the second one shows that
136         it is not. Calling this function with these two propertes would
137         update the current property to be like the second, since it is less
138         specific.
139         """
140         if newprop.type < self.type:
141             self.type = newprop.type
142
143         if type(newprop.value) == list and type(self.value) != list:
144             self.value = [self.value]
145
146         if type(self.value) == list and len(newprop.value) > len(self.value):
147             val = self.GetEmpty(self.type)
148             while len(self.value) < len(newprop.value):
149                 self.value.append(val)
150
151     @classmethod
152     def GetEmpty(self, type):
153         """Get an empty / zero value of the given type
154
155         Returns:
156             A single value of the given type
157         """
158         if type == TYPE_BYTE:
159             return chr(0)
160         elif type == TYPE_INT:
161             return struct.pack('>I', 0);
162         elif type == TYPE_STRING:
163             return ''
164         else:
165             return True
166
167     def GetOffset(self):
168         """Get the offset of a property
169
170         Returns:
171             The offset of the property (struct fdt_property) within the file
172         """
173         self._node._fdt.CheckCache()
174         return self._node._fdt.GetStructOffset(self._offset)
175
176     def SetInt(self, val):
177         """Set the integer value of the property
178
179         The device tree is marked dirty so that the value will be written to
180         the block on the next sync.
181
182         Args:
183             val: Integer value (32-bit, single cell)
184         """
185         self.bytes = struct.pack('>I', val);
186         self.value = self.bytes
187         self.type = TYPE_INT
188         self.dirty = True
189
190     def SetData(self, bytes):
191         """Set the value of a property as bytes
192
193         Args:
194             bytes: New property value to set
195         """
196         self.bytes = bytes
197         self.type, self.value = BytesToValue(bytes)
198         self.dirty = True
199
200     def Sync(self, auto_resize=False):
201         """Sync property changes back to the device tree
202
203         This updates the device tree blob with any changes to this property
204         since the last sync.
205
206         Args:
207             auto_resize: Resize the device tree automatically if it does not
208                 have enough space for the update
209
210         Raises:
211             FdtException if auto_resize is False and there is not enough space
212         """
213         if self._offset is None or self.dirty:
214             node = self._node
215             fdt_obj = node._fdt._fdt_obj
216             if auto_resize:
217                 while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
218                                     (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
219                     fdt_obj.resize(fdt_obj.totalsize() + 1024)
220                     fdt_obj.setprop(node.Offset(), self.name, self.bytes)
221             else:
222                 fdt_obj.setprop(node.Offset(), self.name, self.bytes)
223
224
225 class Node:
226     """A device tree node
227
228     Properties:
229         offset: Integer offset in the device tree
230         name: Device tree node tname
231         path: Full path to node, along with the node name itself
232         _fdt: Device tree object
233         subnodes: A list of subnodes for this node, each a Node object
234         props: A dict of properties for this node, each a Prop object.
235             Keyed by property name
236     """
237     def __init__(self, fdt, parent, offset, name, path):
238         self._fdt = fdt
239         self.parent = parent
240         self._offset = offset
241         self.name = name
242         self.path = path
243         self.subnodes = []
244         self.props = {}
245
246     def GetFdt(self):
247         """Get the Fdt object for this node
248
249         Returns:
250             Fdt object
251         """
252         return self._fdt
253
254     def FindNode(self, name):
255         """Find a node given its name
256
257         Args:
258             name: Node name to look for
259         Returns:
260             Node object if found, else None
261         """
262         for subnode in self.subnodes:
263             if subnode.name == name:
264                 return subnode
265         return None
266
267     def Offset(self):
268         """Returns the offset of a node, after checking the cache
269
270         This should be used instead of self._offset directly, to ensure that
271         the cache does not contain invalid offsets.
272         """
273         self._fdt.CheckCache()
274         return self._offset
275
276     def Scan(self):
277         """Scan a node's properties and subnodes
278
279         This fills in the props and subnodes properties, recursively
280         searching into subnodes so that the entire tree is built.
281         """
282         fdt_obj = self._fdt._fdt_obj
283         self.props = self._fdt.GetProps(self)
284         phandle = fdt_obj.get_phandle(self.Offset())
285         if phandle:
286             self._fdt.phandle_to_node[phandle] = self
287
288         offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
289         while offset >= 0:
290             sep = '' if self.path[-1] == '/' else '/'
291             name = fdt_obj.get_name(offset)
292             path = self.path + sep + name
293             node = Node(self._fdt, self, offset, name, path)
294             self.subnodes.append(node)
295
296             node.Scan()
297             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
298
299     def Refresh(self, my_offset):
300         """Fix up the _offset for each node, recursively
301
302         Note: This does not take account of property offsets - these will not
303         be updated.
304         """
305         fdt_obj = self._fdt._fdt_obj
306         if self._offset != my_offset:
307             self._offset = my_offset
308         offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
309         for subnode in self.subnodes:
310             if subnode.name != fdt_obj.get_name(offset):
311                 raise ValueError('Internal error, node name mismatch %s != %s' %
312                                  (subnode.name, fdt_obj.get_name(offset)))
313             subnode.Refresh(offset)
314             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
315         if offset != -libfdt.FDT_ERR_NOTFOUND:
316             raise ValueError('Internal error, offset == %d' % offset)
317
318         poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
319         while poffset >= 0:
320             p = fdt_obj.get_property_by_offset(poffset)
321             prop = self.props.get(p.name)
322             if not prop:
323                 raise ValueError("Internal error, property '%s' missing, "
324                                  'offset %d' % (p.name, poffset))
325             prop.RefreshOffset(poffset)
326             poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
327
328     def DeleteProp(self, prop_name):
329         """Delete a property of a node
330
331         The property is deleted and the offset cache is invalidated.
332
333         Args:
334             prop_name: Name of the property to delete
335         Raises:
336             ValueError if the property does not exist
337         """
338         CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
339                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
340         del self.props[prop_name]
341         self._fdt.Invalidate()
342
343     def AddZeroProp(self, prop_name):
344         """Add a new property to the device tree with an integer value of 0.
345
346         Args:
347             prop_name: Name of property
348         """
349         self.props[prop_name] = Prop(self, None, prop_name,
350                                      tools.GetBytes(0, 4))
351
352     def AddEmptyProp(self, prop_name, len):
353         """Add a property with a fixed data size, for filling in later
354
355         The device tree is marked dirty so that the value will be written to
356         the blob on the next sync.
357
358         Args:
359             prop_name: Name of property
360             len: Length of data in property
361         """
362         value = tools.GetBytes(0, len)
363         self.props[prop_name] = Prop(self, None, prop_name, value)
364
365     def _CheckProp(self, prop_name):
366         """Check if a property is present
367
368         Args:
369             prop_name: Name of property
370
371         Returns:
372             self
373
374         Raises:
375             ValueError if the property is missing
376         """
377         if prop_name not in self.props:
378             raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
379                              (self._fdt._fname, self.path, prop_name))
380         return self
381
382     def SetInt(self, prop_name, val):
383         """Update an integer property int the device tree.
384
385         This is not allowed to change the size of the FDT.
386
387         The device tree is marked dirty so that the value will be written to
388         the blob on the next sync.
389
390         Args:
391             prop_name: Name of property
392             val: Value to set
393         """
394         self._CheckProp(prop_name).props[prop_name].SetInt(val)
395
396     def SetData(self, prop_name, val):
397         """Set the data value of a property
398
399         The device tree is marked dirty so that the value will be written to
400         the blob on the next sync.
401
402         Args:
403             prop_name: Name of property to set
404             val: Data value to set
405         """
406         self._CheckProp(prop_name).props[prop_name].SetData(val)
407
408     def SetString(self, prop_name, val):
409         """Set the string value of a property
410
411         The device tree is marked dirty so that the value will be written to
412         the blob on the next sync.
413
414         Args:
415             prop_name: Name of property to set
416             val: String value to set (will be \0-terminated in DT)
417         """
418         if sys.version_info[0] >= 3:  # pragma: no cover
419             val = bytes(val, 'utf-8')
420         self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
421
422     def AddString(self, prop_name, val):
423         """Add a new string property to a node
424
425         The device tree is marked dirty so that the value will be written to
426         the blob on the next sync.
427
428         Args:
429             prop_name: Name of property to add
430             val: String value of property
431         """
432         if sys.version_info[0] >= 3:  # pragma: no cover
433             val = bytes(val, 'utf-8')
434         self.props[prop_name] = Prop(self, None, prop_name, val + b'\0')
435
436     def AddSubnode(self, name):
437         """Add a new subnode to the node
438
439         Args:
440             name: name of node to add
441
442         Returns:
443             New subnode that was created
444         """
445         path = self.path + '/' + name
446         subnode = Node(self._fdt, self, None, name, path)
447         self.subnodes.append(subnode)
448         return subnode
449
450     def Sync(self, auto_resize=False):
451         """Sync node changes back to the device tree
452
453         This updates the device tree blob with any changes to this node and its
454         subnodes since the last sync.
455
456         Args:
457             auto_resize: Resize the device tree automatically if it does not
458                 have enough space for the update
459
460         Raises:
461             FdtException if auto_resize is False and there is not enough space
462         """
463         if self._offset is None:
464             # The subnode doesn't exist yet, so add it
465             fdt_obj = self._fdt._fdt_obj
466             if auto_resize:
467                 while True:
468                     offset = fdt_obj.add_subnode(self.parent._offset, self.name,
469                                                 (libfdt.NOSPACE,))
470                     if offset != -libfdt.NOSPACE:
471                         break
472                     fdt_obj.resize(fdt_obj.totalsize() + 1024)
473             else:
474                 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
475             self._offset = offset
476
477         # Sync subnodes in reverse so that we don't disturb node offsets for
478         # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
479         # node offsets.
480         for node in reversed(self.subnodes):
481             node.Sync(auto_resize)
482
483         # Sync properties now, whose offsets should not have been disturbed.
484         # We do this after subnodes, since this disturbs the offsets of these
485         # properties. Note that new properties will have an offset of None here,
486         # which Python 3 cannot sort against int. So use a large value instead
487         # to ensure that the new properties are added first.
488         prop_list = sorted(self.props.values(),
489                            key=lambda prop: prop._offset or 1 << 31,
490                            reverse=True)
491         for prop in prop_list:
492             prop.Sync(auto_resize)
493
494
495 class Fdt:
496     """Provides simple access to a flat device tree blob using libfdts.
497
498     Properties:
499       fname: Filename of fdt
500       _root: Root of device tree (a Node object)
501       name: Helpful name for this Fdt for the user (useful when creating the
502         DT from data rather than a file)
503     """
504     def __init__(self, fname):
505         self._fname = fname
506         self._cached_offsets = False
507         self.phandle_to_node = {}
508         self.name = ''
509         if self._fname:
510             self.name = self._fname
511             self._fname = fdt_util.EnsureCompiled(self._fname)
512
513             with open(self._fname, 'rb') as fd:
514                 self._fdt_obj = libfdt.Fdt(fd.read())
515
516     @staticmethod
517     def FromData(data, name=''):
518         """Create a new Fdt object from the given data
519
520         Args:
521             data: Device-tree data blob
522             name: Helpful name for this Fdt for the user
523
524         Returns:
525             Fdt object containing the data
526         """
527         fdt = Fdt(None)
528         fdt._fdt_obj = libfdt.Fdt(bytes(data))
529         fdt.name = name
530         return fdt
531
532     def LookupPhandle(self, phandle):
533         """Look up a phandle
534
535         Args:
536             phandle: Phandle to look up (int)
537
538         Returns:
539             Node object the phandle points to
540         """
541         return self.phandle_to_node.get(phandle)
542
543     def Scan(self, root='/'):
544         """Scan a device tree, building up a tree of Node objects
545
546         This fills in the self._root property
547
548         Args:
549             root: Ignored
550
551         TODO(sjg@chromium.org): Implement the 'root' parameter
552         """
553         self._cached_offsets = True
554         self._root = self.Node(self, None, 0, '/', '/')
555         self._root.Scan()
556
557     def GetRoot(self):
558         """Get the root Node of the device tree
559
560         Returns:
561             The root Node object
562         """
563         return self._root
564
565     def GetNode(self, path):
566         """Look up a node from its path
567
568         Args:
569             path: Path to look up, e.g. '/microcode/update@0'
570         Returns:
571             Node object, or None if not found
572         """
573         node = self._root
574         parts = path.split('/')
575         if len(parts) < 2:
576             return None
577         if len(parts) == 2 and parts[1] == '':
578             return node
579         for part in parts[1:]:
580             node = node.FindNode(part)
581             if not node:
582                 return None
583         return node
584
585     def Flush(self):
586         """Flush device tree changes back to the file
587
588         If the device tree has changed in memory, write it back to the file.
589         """
590         with open(self._fname, 'wb') as fd:
591             fd.write(self._fdt_obj.as_bytearray())
592
593     def Sync(self, auto_resize=False):
594         """Make sure any DT changes are written to the blob
595
596         Args:
597             auto_resize: Resize the device tree automatically if it does not
598                 have enough space for the update
599
600         Raises:
601             FdtException if auto_resize is False and there is not enough space
602         """
603         self._root.Sync(auto_resize)
604         self.Invalidate()
605
606     def Pack(self):
607         """Pack the device tree down to its minimum size
608
609         When nodes and properties shrink or are deleted, wasted space can
610         build up in the device tree binary.
611         """
612         CheckErr(self._fdt_obj.pack(), 'pack')
613         self.Invalidate()
614
615     def GetContents(self):
616         """Get the contents of the FDT
617
618         Returns:
619             The FDT contents as a string of bytes
620         """
621         return bytes(self._fdt_obj.as_bytearray())
622
623     def GetFdtObj(self):
624         """Get the contents of the FDT
625
626         Returns:
627             The FDT contents as a libfdt.Fdt object
628         """
629         return self._fdt_obj
630
631     def GetProps(self, node):
632         """Get all properties from a node.
633
634         Args:
635             node: Full path to node name to look in.
636
637         Returns:
638             A dictionary containing all the properties, indexed by node name.
639             The entries are Prop objects.
640
641         Raises:
642             ValueError: if the node does not exist.
643         """
644         props_dict = {}
645         poffset = self._fdt_obj.first_property_offset(node._offset,
646                                                       QUIET_NOTFOUND)
647         while poffset >= 0:
648             p = self._fdt_obj.get_property_by_offset(poffset)
649             prop = Prop(node, poffset, p.name, p)
650             props_dict[prop.name] = prop
651
652             poffset = self._fdt_obj.next_property_offset(poffset,
653                                                          QUIET_NOTFOUND)
654         return props_dict
655
656     def Invalidate(self):
657         """Mark our offset cache as invalid"""
658         self._cached_offsets = False
659
660     def CheckCache(self):
661         """Refresh the offset cache if needed"""
662         if self._cached_offsets:
663             return
664         self.Refresh()
665         self._cached_offsets = True
666
667     def Refresh(self):
668         """Refresh the offset cache"""
669         self._root.Refresh(0)
670
671     def GetStructOffset(self, offset):
672         """Get the file offset of a given struct offset
673
674         Args:
675             offset: Offset within the 'struct' region of the device tree
676         Returns:
677             Position of @offset within the device tree binary
678         """
679         return self._fdt_obj.off_dt_struct() + offset
680
681     @classmethod
682     def Node(self, fdt, parent, offset, name, path):
683         """Create a new node
684
685         This is used by Fdt.Scan() to create a new node using the correct
686         class.
687
688         Args:
689             fdt: Fdt object
690             parent: Parent node, or None if this is the root node
691             offset: Offset of node
692             name: Node name
693             path: Full path to node
694         """
695         node = Node(fdt, parent, offset, name, path)
696         return node
697
698     def GetFilename(self):
699         """Get the filename of the device tree
700
701         Returns:
702             String filename
703         """
704         return self._fname
705
706 def FdtScan(fname):
707     """Returns a new Fdt object"""
708     dtb = Fdt(fname)
709     dtb.Scan()
710     return dtb