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