3870eb1fa1b97b95fef5357c07b3536fb2613c51
[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, 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     """
502     def __init__(self, fname):
503         self._fname = fname
504         self._cached_offsets = False
505         self.phandle_to_node = {}
506         if self._fname:
507             self._fname = fdt_util.EnsureCompiled(self._fname)
508
509             with open(self._fname, 'rb') as fd:
510                 self._fdt_obj = libfdt.Fdt(fd.read())
511
512     @staticmethod
513     def FromData(data):
514         """Create a new Fdt object from the given data
515
516         Args:
517             data: Device-tree data blob
518
519         Returns:
520             Fdt object containing the data
521         """
522         fdt = Fdt(None)
523         fdt._fdt_obj = libfdt.Fdt(bytes(data))
524         return fdt
525
526     def LookupPhandle(self, phandle):
527         """Look up a phandle
528
529         Args:
530             phandle: Phandle to look up (int)
531
532         Returns:
533             Node object the phandle points to
534         """
535         return self.phandle_to_node.get(phandle)
536
537     def Scan(self, root='/'):
538         """Scan a device tree, building up a tree of Node objects
539
540         This fills in the self._root property
541
542         Args:
543             root: Ignored
544
545         TODO(sjg@chromium.org): Implement the 'root' parameter
546         """
547         self._cached_offsets = True
548         self._root = self.Node(self, None, 0, '/', '/')
549         self._root.Scan()
550
551     def GetRoot(self):
552         """Get the root Node of the device tree
553
554         Returns:
555             The root Node object
556         """
557         return self._root
558
559     def GetNode(self, path):
560         """Look up a node from its path
561
562         Args:
563             path: Path to look up, e.g. '/microcode/update@0'
564         Returns:
565             Node object, or None if not found
566         """
567         node = self._root
568         parts = path.split('/')
569         if len(parts) < 2:
570             return None
571         for part in parts[1:]:
572             node = node.FindNode(part)
573             if not node:
574                 return None
575         return node
576
577     def Flush(self):
578         """Flush device tree changes back to the file
579
580         If the device tree has changed in memory, write it back to the file.
581         """
582         with open(self._fname, 'wb') as fd:
583             fd.write(self._fdt_obj.as_bytearray())
584
585     def Sync(self, auto_resize=False):
586         """Make sure any DT changes are written to the blob
587
588         Args:
589             auto_resize: Resize the device tree automatically if it does not
590                 have enough space for the update
591
592         Raises:
593             FdtException if auto_resize is False and there is not enough space
594         """
595         self._root.Sync(auto_resize)
596         self.Invalidate()
597
598     def Pack(self):
599         """Pack the device tree down to its minimum size
600
601         When nodes and properties shrink or are deleted, wasted space can
602         build up in the device tree binary.
603         """
604         CheckErr(self._fdt_obj.pack(), 'pack')
605         self.Invalidate()
606
607     def GetContents(self):
608         """Get the contents of the FDT
609
610         Returns:
611             The FDT contents as a string of bytes
612         """
613         return bytes(self._fdt_obj.as_bytearray())
614
615     def GetFdtObj(self):
616         """Get the contents of the FDT
617
618         Returns:
619             The FDT contents as a libfdt.Fdt object
620         """
621         return self._fdt_obj
622
623     def GetProps(self, node):
624         """Get all properties from a node.
625
626         Args:
627             node: Full path to node name to look in.
628
629         Returns:
630             A dictionary containing all the properties, indexed by node name.
631             The entries are Prop objects.
632
633         Raises:
634             ValueError: if the node does not exist.
635         """
636         props_dict = {}
637         poffset = self._fdt_obj.first_property_offset(node._offset,
638                                                       QUIET_NOTFOUND)
639         while poffset >= 0:
640             p = self._fdt_obj.get_property_by_offset(poffset)
641             prop = Prop(node, poffset, p.name, p)
642             props_dict[prop.name] = prop
643
644             poffset = self._fdt_obj.next_property_offset(poffset,
645                                                          QUIET_NOTFOUND)
646         return props_dict
647
648     def Invalidate(self):
649         """Mark our offset cache as invalid"""
650         self._cached_offsets = False
651
652     def CheckCache(self):
653         """Refresh the offset cache if needed"""
654         if self._cached_offsets:
655             return
656         self.Refresh()
657         self._cached_offsets = True
658
659     def Refresh(self):
660         """Refresh the offset cache"""
661         self._root.Refresh(0)
662
663     def GetStructOffset(self, offset):
664         """Get the file offset of a given struct offset
665
666         Args:
667             offset: Offset within the 'struct' region of the device tree
668         Returns:
669             Position of @offset within the device tree binary
670         """
671         return self._fdt_obj.off_dt_struct() + offset
672
673     @classmethod
674     def Node(self, fdt, parent, offset, name, path):
675         """Create a new node
676
677         This is used by Fdt.Scan() to create a new node using the correct
678         class.
679
680         Args:
681             fdt: Fdt object
682             parent: Parent node, or None if this is the root node
683             offset: Offset of node
684             name: Node name
685             path: Full path to node
686         """
687         node = Node(fdt, parent, offset, name, path)
688         return node
689
690 def FdtScan(fname):
691     """Returns a new Fdt object"""
692     dtb = Fdt(fname)
693     dtb.Scan()
694     return dtb