dtoc: Convert the Fdt.Prop class to 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 SetInt(self, prop_name, val):
366         """Update an integer property int the device tree.
367
368         This is not allowed to change the size of the FDT.
369
370         The device tree is marked dirty so that the value will be written to
371         the blob on the next sync.
372
373         Args:
374             prop_name: Name of property
375             val: Value to set
376         """
377         self.props[prop_name].SetInt(val)
378
379     def SetData(self, prop_name, val):
380         """Set the data value of a property
381
382         The device tree is marked dirty so that the value will be written to
383         the blob on the next sync.
384
385         Args:
386             prop_name: Name of property to set
387             val: Data value to set
388         """
389         self.props[prop_name].SetData(val)
390
391     def SetString(self, prop_name, val):
392         """Set the string value of a property
393
394         The device tree is marked dirty so that the value will be written to
395         the blob on the next sync.
396
397         Args:
398             prop_name: Name of property to set
399             val: String value to set (will be \0-terminated in DT)
400         """
401         if sys.version_info[0] >= 3:  # pragma: no cover
402             val = bytes(val, 'utf-8')
403         self.props[prop_name].SetData(val + b'\0')
404
405     def AddString(self, prop_name, val):
406         """Add a new string property to a node
407
408         The device tree is marked dirty so that the value will be written to
409         the blob on the next sync.
410
411         Args:
412             prop_name: Name of property to add
413             val: String value of property
414         """
415         if sys.version_info[0] >= 3:  # pragma: no cover
416             val = bytes(val, 'utf-8')
417         self.props[prop_name] = Prop(self, None, prop_name, val + b'\0')
418
419     def AddSubnode(self, name):
420         """Add a new subnode to the node
421
422         Args:
423             name: name of node to add
424
425         Returns:
426             New subnode that was created
427         """
428         path = self.path + '/' + name
429         subnode = Node(self._fdt, self, None, name, path)
430         self.subnodes.append(subnode)
431         return subnode
432
433     def Sync(self, auto_resize=False):
434         """Sync node changes back to the device tree
435
436         This updates the device tree blob with any changes to this node and its
437         subnodes since the last sync.
438
439         Args:
440             auto_resize: Resize the device tree automatically if it does not
441                 have enough space for the update
442
443         Raises:
444             FdtException if auto_resize is False and there is not enough space
445         """
446         if self._offset is None:
447             # The subnode doesn't exist yet, so add it
448             fdt_obj = self._fdt._fdt_obj
449             if auto_resize:
450                 while True:
451                     offset = fdt_obj.add_subnode(self.parent._offset, self.name,
452                                                 (libfdt.NOSPACE,))
453                     if offset != -libfdt.NOSPACE:
454                         break
455                     fdt_obj.resize(fdt_obj.totalsize() + 1024)
456             else:
457                 offset = fdt_obj.add_subnode(self.parent._offset, self.name)
458             self._offset = offset
459
460         # Sync subnodes in reverse so that we don't disturb node offsets for
461         # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
462         # node offsets.
463         for node in reversed(self.subnodes):
464             node.Sync(auto_resize)
465
466         # Sync properties now, whose offsets should not have been disturbed.
467         # We do this after subnodes, since this disturbs the offsets of these
468         # properties.
469         prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
470                            reverse=True)
471         for prop in prop_list:
472             prop.Sync(auto_resize)
473
474
475 class Fdt:
476     """Provides simple access to a flat device tree blob using libfdts.
477
478     Properties:
479       fname: Filename of fdt
480       _root: Root of device tree (a Node object)
481     """
482     def __init__(self, fname):
483         self._fname = fname
484         self._cached_offsets = False
485         self.phandle_to_node = {}
486         if self._fname:
487             self._fname = fdt_util.EnsureCompiled(self._fname)
488
489             with open(self._fname, 'rb') as fd:
490                 self._fdt_obj = libfdt.Fdt(fd.read())
491
492     @staticmethod
493     def FromData(data):
494         """Create a new Fdt object from the given data
495
496         Args:
497             data: Device-tree data blob
498
499         Returns:
500             Fdt object containing the data
501         """
502         fdt = Fdt(None)
503         fdt._fdt_obj = libfdt.Fdt(bytes(data))
504         return fdt
505
506     def LookupPhandle(self, phandle):
507         """Look up a phandle
508
509         Args:
510             phandle: Phandle to look up (int)
511
512         Returns:
513             Node object the phandle points to
514         """
515         return self.phandle_to_node.get(phandle)
516
517     def Scan(self, root='/'):
518         """Scan a device tree, building up a tree of Node objects
519
520         This fills in the self._root property
521
522         Args:
523             root: Ignored
524
525         TODO(sjg@chromium.org): Implement the 'root' parameter
526         """
527         self._cached_offsets = True
528         self._root = self.Node(self, None, 0, '/', '/')
529         self._root.Scan()
530
531     def GetRoot(self):
532         """Get the root Node of the device tree
533
534         Returns:
535             The root Node object
536         """
537         return self._root
538
539     def GetNode(self, path):
540         """Look up a node from its path
541
542         Args:
543             path: Path to look up, e.g. '/microcode/update@0'
544         Returns:
545             Node object, or None if not found
546         """
547         node = self._root
548         parts = path.split('/')
549         if len(parts) < 2:
550             return None
551         for part in parts[1:]:
552             node = node.FindNode(part)
553             if not node:
554                 return None
555         return node
556
557     def Flush(self):
558         """Flush device tree changes back to the file
559
560         If the device tree has changed in memory, write it back to the file.
561         """
562         with open(self._fname, 'wb') as fd:
563             fd.write(self._fdt_obj.as_bytearray())
564
565     def Sync(self, auto_resize=False):
566         """Make sure any DT changes are written to the blob
567
568         Args:
569             auto_resize: Resize the device tree automatically if it does not
570                 have enough space for the update
571
572         Raises:
573             FdtException if auto_resize is False and there is not enough space
574         """
575         self._root.Sync(auto_resize)
576         self.Invalidate()
577
578     def Pack(self):
579         """Pack the device tree down to its minimum size
580
581         When nodes and properties shrink or are deleted, wasted space can
582         build up in the device tree binary.
583         """
584         CheckErr(self._fdt_obj.pack(), 'pack')
585         self.Invalidate()
586
587     def GetContents(self):
588         """Get the contents of the FDT
589
590         Returns:
591             The FDT contents as a string of bytes
592         """
593         return bytes(self._fdt_obj.as_bytearray())
594
595     def GetFdtObj(self):
596         """Get the contents of the FDT
597
598         Returns:
599             The FDT contents as a libfdt.Fdt object
600         """
601         return self._fdt_obj
602
603     def GetProps(self, node):
604         """Get all properties from a node.
605
606         Args:
607             node: Full path to node name to look in.
608
609         Returns:
610             A dictionary containing all the properties, indexed by node name.
611             The entries are Prop objects.
612
613         Raises:
614             ValueError: if the node does not exist.
615         """
616         props_dict = {}
617         poffset = self._fdt_obj.first_property_offset(node._offset,
618                                                       QUIET_NOTFOUND)
619         while poffset >= 0:
620             p = self._fdt_obj.get_property_by_offset(poffset)
621             prop = Prop(node, poffset, p.name, p)
622             props_dict[prop.name] = prop
623
624             poffset = self._fdt_obj.next_property_offset(poffset,
625                                                          QUIET_NOTFOUND)
626         return props_dict
627
628     def Invalidate(self):
629         """Mark our offset cache as invalid"""
630         self._cached_offsets = False
631
632     def CheckCache(self):
633         """Refresh the offset cache if needed"""
634         if self._cached_offsets:
635             return
636         self.Refresh()
637         self._cached_offsets = True
638
639     def Refresh(self):
640         """Refresh the offset cache"""
641         self._root.Refresh(0)
642
643     def GetStructOffset(self, offset):
644         """Get the file offset of a given struct offset
645
646         Args:
647             offset: Offset within the 'struct' region of the device tree
648         Returns:
649             Position of @offset within the device tree binary
650         """
651         return self._fdt_obj.off_dt_struct() + offset
652
653     @classmethod
654     def Node(self, fdt, parent, offset, name, path):
655         """Create a new node
656
657         This is used by Fdt.Scan() to create a new node using the correct
658         class.
659
660         Args:
661             fdt: Fdt object
662             parent: Parent node, or None if this is the root node
663             offset: Offset of node
664             name: Node name
665             path: Full path to node
666         """
667         node = Node(fdt, parent, offset, name, path)
668         return node
669
670 def FdtScan(fname):
671     """Returns a new Fdt object"""
672     dtb = Fdt(fname)
673     dtb.Scan()
674     return dtb