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