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