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