Merge branch 'master' of git://git.denx.de/u-boot-samsung
[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         if not bytes:
47             self.type = TYPE_BOOL
48             self.value = True
49             return
50         self.type, self.value = self.BytesToValue(bytes)
51
52     def RefreshOffset(self, poffset):
53         self._offset = poffset
54
55     def Widen(self, newprop):
56         """Figure out which property type is more general
57
58         Given a current property and a new property, this function returns the
59         one that is less specific as to type. The less specific property will
60         be ble to represent the data in the more specific property. This is
61         used for things like:
62
63             node1 {
64                 compatible = "fred";
65                 value = <1>;
66             };
67             node1 {
68                 compatible = "fred";
69                 value = <1 2>;
70             };
71
72         He we want to use an int array for 'value'. The first property
73         suggests that a single int is enough, but the second one shows that
74         it is not. Calling this function with these two propertes would
75         update the current property to be like the second, since it is less
76         specific.
77         """
78         if newprop.type < self.type:
79             self.type = newprop.type
80
81         if type(newprop.value) == list and type(self.value) != list:
82             self.value = [self.value]
83
84         if type(self.value) == list and len(newprop.value) > len(self.value):
85             val = self.GetEmpty(self.type)
86             while len(self.value) < len(newprop.value):
87                 self.value.append(val)
88
89     def BytesToValue(self, bytes):
90         """Converts a string of bytes into a type and value
91
92         Args:
93             A string containing bytes
94
95         Return:
96             A tuple:
97                 Type of data
98                 Data, either a single element or a list of elements. Each element
99                 is one of:
100                     TYPE_STRING: string value from the property
101                     TYPE_INT: a byte-swapped integer stored as a 4-byte string
102                     TYPE_BYTE: a byte stored as a single-byte string
103         """
104         bytes = str(bytes)
105         size = len(bytes)
106         strings = bytes.split('\0')
107         is_string = True
108         count = len(strings) - 1
109         if count > 0 and not strings[-1]:
110             for string in strings[:-1]:
111                 if not string:
112                     is_string = False
113                     break
114                 for ch in string:
115                     if ch < ' ' or ch > '~':
116                         is_string = False
117                         break
118         else:
119             is_string = False
120         if is_string:
121             if count == 1:
122                 return TYPE_STRING, strings[0]
123             else:
124                 return TYPE_STRING, strings[:-1]
125         if size % 4:
126             if size == 1:
127                 return TYPE_BYTE, bytes[0]
128             else:
129                 return TYPE_BYTE, list(bytes)
130         val = []
131         for i in range(0, size, 4):
132             val.append(bytes[i:i + 4])
133         if size == 4:
134             return TYPE_INT, val[0]
135         else:
136             return TYPE_INT, val
137
138     @classmethod
139     def GetEmpty(self, type):
140         """Get an empty / zero value of the given type
141
142         Returns:
143             A single value of the given type
144         """
145         if type == TYPE_BYTE:
146             return chr(0)
147         elif type == TYPE_INT:
148             return struct.pack('<I', 0);
149         elif type == TYPE_STRING:
150             return ''
151         else:
152             return True
153
154     def GetOffset(self):
155         """Get the offset of a property
156
157         Returns:
158             The offset of the property (struct fdt_property) within the file
159         """
160         self._node._fdt.CheckCache()
161         return self._node._fdt.GetStructOffset(self._offset)
162
163 class Node:
164     """A device tree node
165
166     Properties:
167         offset: Integer offset in the device tree
168         name: Device tree node tname
169         path: Full path to node, along with the node name itself
170         _fdt: Device tree object
171         subnodes: A list of subnodes for this node, each a Node object
172         props: A dict of properties for this node, each a Prop object.
173             Keyed by property name
174     """
175     def __init__(self, fdt, parent, offset, name, path):
176         self._fdt = fdt
177         self.parent = parent
178         self._offset = offset
179         self.name = name
180         self.path = path
181         self.subnodes = []
182         self.props = {}
183
184     def GetFdt(self):
185         """Get the Fdt object for this node
186
187         Returns:
188             Fdt object
189         """
190         return self._fdt
191
192     def FindNode(self, name):
193         """Find a node given its name
194
195         Args:
196             name: Node name to look for
197         Returns:
198             Node object if found, else None
199         """
200         for subnode in self.subnodes:
201             if subnode.name == name:
202                 return subnode
203         return None
204
205     def Offset(self):
206         """Returns the offset of a node, after checking the cache
207
208         This should be used instead of self._offset directly, to ensure that
209         the cache does not contain invalid offsets.
210         """
211         self._fdt.CheckCache()
212         return self._offset
213
214     def Scan(self):
215         """Scan a node's properties and subnodes
216
217         This fills in the props and subnodes properties, recursively
218         searching into subnodes so that the entire tree is built.
219         """
220         fdt_obj = self._fdt._fdt_obj
221         self.props = self._fdt.GetProps(self)
222         phandle = fdt_obj.get_phandle(self.Offset())
223         if phandle:
224             self._fdt.phandle_to_node[phandle] = self
225
226         offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
227         while offset >= 0:
228             sep = '' if self.path[-1] == '/' else '/'
229             name = fdt_obj.get_name(offset)
230             path = self.path + sep + name
231             node = Node(self._fdt, self, offset, name, path)
232             self.subnodes.append(node)
233
234             node.Scan()
235             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
236
237     def Refresh(self, my_offset):
238         """Fix up the _offset for each node, recursively
239
240         Note: This does not take account of property offsets - these will not
241         be updated.
242         """
243         fdt_obj = self._fdt._fdt_obj
244         if self._offset != my_offset:
245             self._offset = my_offset
246         offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
247         for subnode in self.subnodes:
248             if subnode.name != fdt_obj.get_name(offset):
249                 raise ValueError('Internal error, node name mismatch %s != %s' %
250                                  (subnode.name, fdt_obj.get_name(offset)))
251             subnode.Refresh(offset)
252             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
253         if offset != -libfdt.FDT_ERR_NOTFOUND:
254             raise ValueError('Internal error, offset == %d' % offset)
255
256         poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
257         while poffset >= 0:
258             p = fdt_obj.get_property_by_offset(poffset)
259             prop = self.props.get(p.name)
260             if not prop:
261                 raise ValueError("Internal error, property '%s' missing, "
262                                  'offset %d' % (p.name, poffset))
263             prop.RefreshOffset(poffset)
264             poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
265
266     def DeleteProp(self, prop_name):
267         """Delete a property of a node
268
269         The property is deleted and the offset cache is invalidated.
270
271         Args:
272             prop_name: Name of the property to delete
273         Raises:
274             ValueError if the property does not exist
275         """
276         CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
277                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
278         del self.props[prop_name]
279         self._fdt.Invalidate()
280
281     def AddZeroProp(self, prop_name):
282         """Add a new property to the device tree with an integer value of 0.
283
284         Args:
285             prop_name: Name of property
286         """
287         fdt_obj = self._fdt._fdt_obj
288         if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
289                                (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
290             fdt_obj.open_into(fdt_obj.totalsize() + 1024)
291             fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
292         self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
293         self._fdt.Invalidate()
294
295     def SetInt(self, prop_name, val):
296         """Update an integer property int the device tree.
297
298         This is not allowed to change the size of the FDT.
299
300         Args:
301             prop_name: Name of property
302             val: Value to set
303         """
304         fdt_obj = self._fdt._fdt_obj
305         fdt_obj.setprop_u32(self.Offset(), prop_name, val)
306
307
308 class Fdt:
309     """Provides simple access to a flat device tree blob using libfdts.
310
311     Properties:
312       fname: Filename of fdt
313       _root: Root of device tree (a Node object)
314     """
315     def __init__(self, fname):
316         self._fname = fname
317         self._cached_offsets = False
318         self.phandle_to_node = {}
319         if self._fname:
320             self._fname = fdt_util.EnsureCompiled(self._fname)
321
322             with open(self._fname) as fd:
323                 self._fdt_obj = libfdt.Fdt(fd.read())
324
325     def LookupPhandle(self, phandle):
326         """Look up a phandle
327
328         Args:
329             phandle: Phandle to look up (int)
330
331         Returns:
332             Node object the phandle points to
333         """
334         return self.phandle_to_node.get(phandle)
335
336     def Scan(self, root='/'):
337         """Scan a device tree, building up a tree of Node objects
338
339         This fills in the self._root property
340
341         Args:
342             root: Ignored
343
344         TODO(sjg@chromium.org): Implement the 'root' parameter
345         """
346         self._cached_offsets = True
347         self._root = self.Node(self, None, 0, '/', '/')
348         self._root.Scan()
349
350     def GetRoot(self):
351         """Get the root Node of the device tree
352
353         Returns:
354             The root Node object
355         """
356         return self._root
357
358     def GetNode(self, path):
359         """Look up a node from its path
360
361         Args:
362             path: Path to look up, e.g. '/microcode/update@0'
363         Returns:
364             Node object, or None if not found
365         """
366         node = self._root
367         parts = path.split('/')
368         if len(parts) < 2:
369             return None
370         for part in parts[1:]:
371             node = node.FindNode(part)
372             if not node:
373                 return None
374         return node
375
376     def Flush(self):
377         """Flush device tree changes back to the file
378
379         If the device tree has changed in memory, write it back to the file.
380         """
381         with open(self._fname, 'wb') as fd:
382             fd.write(self._fdt_obj.as_bytearray())
383
384     def Pack(self):
385         """Pack the device tree down to its minimum size
386
387         When nodes and properties shrink or are deleted, wasted space can
388         build up in the device tree binary.
389         """
390         CheckErr(self._fdt_obj.pack(), 'pack')
391         self.Invalidate()
392
393     def GetContents(self):
394         """Get the contents of the FDT
395
396         Returns:
397             The FDT contents as a string of bytes
398         """
399         return self._fdt_obj.as_bytearray()
400
401     def GetFdtObj(self):
402         """Get the contents of the FDT
403
404         Returns:
405             The FDT contents as a libfdt.Fdt object
406         """
407         return self._fdt_obj
408
409     def GetProps(self, node):
410         """Get all properties from a node.
411
412         Args:
413             node: Full path to node name to look in.
414
415         Returns:
416             A dictionary containing all the properties, indexed by node name.
417             The entries are Prop objects.
418
419         Raises:
420             ValueError: if the node does not exist.
421         """
422         props_dict = {}
423         poffset = self._fdt_obj.first_property_offset(node._offset,
424                                                       QUIET_NOTFOUND)
425         while poffset >= 0:
426             p = self._fdt_obj.get_property_by_offset(poffset)
427             prop = Prop(node, poffset, p.name, p)
428             props_dict[prop.name] = prop
429
430             poffset = self._fdt_obj.next_property_offset(poffset,
431                                                          QUIET_NOTFOUND)
432         return props_dict
433
434     def Invalidate(self):
435         """Mark our offset cache as invalid"""
436         self._cached_offsets = False
437
438     def CheckCache(self):
439         """Refresh the offset cache if needed"""
440         if self._cached_offsets:
441             return
442         self.Refresh()
443         self._cached_offsets = True
444
445     def Refresh(self):
446         """Refresh the offset cache"""
447         self._root.Refresh(0)
448
449     def GetStructOffset(self, offset):
450         """Get the file offset of a given struct offset
451
452         Args:
453             offset: Offset within the 'struct' region of the device tree
454         Returns:
455             Position of @offset within the device tree binary
456         """
457         return self._fdt_obj.off_dt_struct() + offset
458
459     @classmethod
460     def Node(self, fdt, parent, offset, name, path):
461         """Create a new node
462
463         This is used by Fdt.Scan() to create a new node using the correct
464         class.
465
466         Args:
467             fdt: Fdt object
468             parent: Parent node, or None if this is the root node
469             offset: Offset of node
470             name: Node name
471             path: Full path to node
472         """
473         node = Node(fdt, parent, offset, name, path)
474         return node
475
476 def FdtScan(fname):
477     """Returns a new Fdt object"""
478     dtb = Fdt(fname)
479     dtb.Scan()
480     return dtb