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