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