dtoc: Add a test for code coverage
[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
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 _FindNode(self, name):
185         """Find a node given its name
186
187         Args:
188             name: Node name to look for
189         Returns:
190             Node object if found, else None
191         """
192         for subnode in self.subnodes:
193             if subnode.name == name:
194                 return subnode
195         return None
196
197     def Offset(self):
198         """Returns the offset of a node, after checking the cache
199
200         This should be used instead of self._offset directly, to ensure that
201         the cache does not contain invalid offsets.
202         """
203         self._fdt.CheckCache()
204         return self._offset
205
206     def Scan(self):
207         """Scan a node's properties and subnodes
208
209         This fills in the props and subnodes properties, recursively
210         searching into subnodes so that the entire tree is built.
211         """
212         fdt_obj = self._fdt._fdt_obj
213         self.props = self._fdt.GetProps(self)
214         phandle = fdt_obj.get_phandle(self.Offset())
215         if phandle:
216             self._fdt.phandle_to_node[phandle] = self
217
218         offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
219         while offset >= 0:
220             sep = '' if self.path[-1] == '/' else '/'
221             name = fdt_obj.get_name(offset)
222             path = self.path + sep + name
223             node = Node(self._fdt, self, offset, name, path)
224             self.subnodes.append(node)
225
226             node.Scan()
227             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
228
229     def Refresh(self, my_offset):
230         """Fix up the _offset for each node, recursively
231
232         Note: This does not take account of property offsets - these will not
233         be updated.
234         """
235         fdt_obj = self._fdt._fdt_obj
236         if self._offset != my_offset:
237             self._offset = my_offset
238         offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
239         for subnode in self.subnodes:
240             if subnode.name != fdt_obj.get_name(offset):
241                 raise ValueError('Internal error, node name mismatch %s != %s' %
242                                  (subnode.name, fdt_obj.get_name(offset)))
243             subnode.Refresh(offset)
244             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
245         if offset != -libfdt.FDT_ERR_NOTFOUND:
246             raise ValueError('Internal error, offset == %d' % offset)
247
248         poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
249         while poffset >= 0:
250             p = fdt_obj.get_property_by_offset(poffset)
251             prop = self.props.get(p.name)
252             if not prop:
253                 raise ValueError("Internal error, property '%s' missing, "
254                                  'offset %d' % (p.name, poffset))
255             prop.RefreshOffset(poffset)
256             poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
257
258     def DeleteProp(self, prop_name):
259         """Delete a property of a node
260
261         The property is deleted and the offset cache is invalidated.
262
263         Args:
264             prop_name: Name of the property to delete
265         Raises:
266             ValueError if the property does not exist
267         """
268         CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
269                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
270         del self.props[prop_name]
271         self._fdt.Invalidate()
272
273 class Fdt:
274     """Provides simple access to a flat device tree blob using libfdts.
275
276     Properties:
277       fname: Filename of fdt
278       _root: Root of device tree (a Node object)
279     """
280     def __init__(self, fname):
281         self._fname = fname
282         self._cached_offsets = False
283         self.phandle_to_node = {}
284         if self._fname:
285             self._fname = fdt_util.EnsureCompiled(self._fname)
286
287             with open(self._fname) as fd:
288                 self._fdt_obj = libfdt.Fdt(fd.read())
289
290     def Scan(self, root='/'):
291         """Scan a device tree, building up a tree of Node objects
292
293         This fills in the self._root property
294
295         Args:
296             root: Ignored
297
298         TODO(sjg@chromium.org): Implement the 'root' parameter
299         """
300         self._cached_offsets = True
301         self._root = self.Node(self, None, 0, '/', '/')
302         self._root.Scan()
303
304     def GetRoot(self):
305         """Get the root Node of the device tree
306
307         Returns:
308             The root Node object
309         """
310         return self._root
311
312     def GetNode(self, path):
313         """Look up a node from its path
314
315         Args:
316             path: Path to look up, e.g. '/microcode/update@0'
317         Returns:
318             Node object, or None if not found
319         """
320         node = self._root
321         parts = path.split('/')
322         if len(parts) < 2:
323             return None
324         for part in parts[1:]:
325             node = node._FindNode(part)
326             if not node:
327                 return None
328         return node
329
330     def Flush(self):
331         """Flush device tree changes back to the file
332
333         If the device tree has changed in memory, write it back to the file.
334         """
335         with open(self._fname, 'wb') as fd:
336             fd.write(self._fdt_obj.as_bytearray())
337
338     def Pack(self):
339         """Pack the device tree down to its minimum size
340
341         When nodes and properties shrink or are deleted, wasted space can
342         build up in the device tree binary.
343         """
344         CheckErr(self._fdt_obj.pack(), 'pack')
345         self.Invalidate()
346
347     def GetContents(self):
348         """Get the contents of the FDT
349
350         Returns:
351             The FDT contents as a string of bytes
352         """
353         return self._fdt_obj.as_bytearray()
354
355     def GetFdtObj(self):
356         """Get the contents of the FDT
357
358         Returns:
359             The FDT contents as a libfdt.Fdt object
360         """
361         return self._fdt_obj
362
363     def GetProps(self, node):
364         """Get all properties from a node.
365
366         Args:
367             node: Full path to node name to look in.
368
369         Returns:
370             A dictionary containing all the properties, indexed by node name.
371             The entries are Prop objects.
372
373         Raises:
374             ValueError: if the node does not exist.
375         """
376         props_dict = {}
377         poffset = self._fdt_obj.first_property_offset(node._offset,
378                                                       QUIET_NOTFOUND)
379         while poffset >= 0:
380             p = self._fdt_obj.get_property_by_offset(poffset)
381             prop = Prop(node, poffset, p.name, p)
382             props_dict[prop.name] = prop
383
384             poffset = self._fdt_obj.next_property_offset(poffset,
385                                                          QUIET_NOTFOUND)
386         return props_dict
387
388     def Invalidate(self):
389         """Mark our offset cache as invalid"""
390         self._cached_offsets = False
391
392     def CheckCache(self):
393         """Refresh the offset cache if needed"""
394         if self._cached_offsets:
395             return
396         self.Refresh()
397         self._cached_offsets = True
398
399     def Refresh(self):
400         """Refresh the offset cache"""
401         self._root.Refresh(0)
402
403     def GetStructOffset(self, offset):
404         """Get the file offset of a given struct offset
405
406         Args:
407             offset: Offset within the 'struct' region of the device tree
408         Returns:
409             Position of @offset within the device tree binary
410         """
411         return self._fdt_obj.off_dt_struct() + offset
412
413     @classmethod
414     def Node(self, fdt, parent, offset, name, path):
415         """Create a new node
416
417         This is used by Fdt.Scan() to create a new node using the correct
418         class.
419
420         Args:
421             fdt: Fdt object
422             parent: Parent node, or None if this is the root node
423             offset: Offset of node
424             name: Node name
425             path: Full path to node
426         """
427         node = Node(fdt, parent, offset, name, path)
428         return node
429
430 def FdtScan(fname):
431     """Returns a new Fdt object"""
432     dtb = Fdt(fname)
433     dtb.Scan()
434     return dtb