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