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