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