]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
SPDX: Convert all of our single license tags to Linux Kernel style
[u-boot] / 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
14 # This deals with a device tree, presenting it as an assortment of Node and
15 # Prop objects, representing nodes and properties, respectively. This file
16 # contains the base classes and defines the high-level API. You can use
17 # FdtScan() as a convenience function to create and scan an Fdt.
18
19 # This implementation uses a libfdt Python library to access the device tree,
20 # so it is fairly efficient.
21
22 # A list of types we support
23 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
24
25 def CheckErr(errnum, msg):
26     if errnum:
27         raise ValueError('Error %d: %s: %s' %
28             (errnum, libfdt.fdt_strerror(errnum), msg))
29
30 class Prop:
31     """A device tree property
32
33     Properties:
34         name: Property name (as per the device tree)
35         value: Property value as a string of bytes, or a list of strings of
36             bytes
37         type: Value type
38     """
39     def __init__(self, node, offset, name, bytes):
40         self._node = node
41         self._offset = offset
42         self.name = name
43         self.value = None
44         self.bytes = str(bytes)
45         if not bytes:
46             self.type = TYPE_BOOL
47             self.value = True
48             return
49         self.type, self.value = self.BytesToValue(bytes)
50
51     def GetPhandle(self):
52         """Get a (single) phandle value from a property
53
54         Gets the phandle valuie from a property and returns it as an integer
55         """
56         return fdt_util.fdt32_to_cpu(self.value[:4])
57
58     def Widen(self, newprop):
59         """Figure out which property type is more general
60
61         Given a current property and a new property, this function returns the
62         one that is less specific as to type. The less specific property will
63         be ble to represent the data in the more specific property. This is
64         used for things like:
65
66             node1 {
67                 compatible = "fred";
68                 value = <1>;
69             };
70             node1 {
71                 compatible = "fred";
72                 value = <1 2>;
73             };
74
75         He we want to use an int array for 'value'. The first property
76         suggests that a single int is enough, but the second one shows that
77         it is not. Calling this function with these two propertes would
78         update the current property to be like the second, since it is less
79         specific.
80         """
81         if newprop.type < self.type:
82             self.type = newprop.type
83
84         if type(newprop.value) == list and type(self.value) != list:
85             self.value = [self.value]
86
87         if type(self.value) == list and len(newprop.value) > len(self.value):
88             val = self.GetEmpty(self.type)
89             while len(self.value) < len(newprop.value):
90                 self.value.append(val)
91
92     def BytesToValue(self, bytes):
93         """Converts a string of bytes into a type and value
94
95         Args:
96             A string containing bytes
97
98         Return:
99             A tuple:
100                 Type of data
101                 Data, either a single element or a list of elements. Each element
102                 is one of:
103                     TYPE_STRING: string value from the property
104                     TYPE_INT: a byte-swapped integer stored as a 4-byte string
105                     TYPE_BYTE: a byte stored as a single-byte string
106         """
107         bytes = str(bytes)
108         size = len(bytes)
109         strings = bytes.split('\0')
110         is_string = True
111         count = len(strings) - 1
112         if count > 0 and not strings[-1]:
113             for string in strings[:-1]:
114                 if not string:
115                     is_string = False
116                     break
117                 for ch in string:
118                     if ch < ' ' or ch > '~':
119                         is_string = False
120                         break
121         else:
122             is_string = False
123         if is_string:
124             if count == 1:
125                 return TYPE_STRING, strings[0]
126             else:
127                 return TYPE_STRING, strings[:-1]
128         if size % 4:
129             if size == 1:
130                 return TYPE_BYTE, bytes[0]
131             else:
132                 return TYPE_BYTE, list(bytes)
133         val = []
134         for i in range(0, size, 4):
135             val.append(bytes[i:i + 4])
136         if size == 4:
137             return TYPE_INT, val[0]
138         else:
139             return TYPE_INT, val
140
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         return self._node._fdt.GetStructOffset(self._offset)
163
164 class Node:
165     """A device tree node
166
167     Properties:
168         offset: Integer offset in the device tree
169         name: Device tree node tname
170         path: Full path to node, along with the node name itself
171         _fdt: Device tree object
172         subnodes: A list of subnodes for this node, each a Node object
173         props: A dict of properties for this node, each a Prop object.
174             Keyed by property name
175     """
176     def __init__(self, fdt, parent, offset, name, path):
177         self._fdt = fdt
178         self.parent = parent
179         self._offset = offset
180         self.name = name
181         self.path = path
182         self.subnodes = []
183         self.props = {}
184
185     def _FindNode(self, name):
186         """Find a node given its name
187
188         Args:
189             name: Node name to look for
190         Returns:
191             Node object if found, else None
192         """
193         for subnode in self.subnodes:
194             if subnode.name == name:
195                 return subnode
196         return None
197
198     def Offset(self):
199         """Returns the offset of a node, after checking the cache
200
201         This should be used instead of self._offset directly, to ensure that
202         the cache does not contain invalid offsets.
203         """
204         self._fdt.CheckCache()
205         return self._offset
206
207     def Scan(self):
208         """Scan a node's properties and subnodes
209
210         This fills in the props and subnodes properties, recursively
211         searching into subnodes so that the entire tree is built.
212         """
213         self.props = self._fdt.GetProps(self)
214         phandle = self.props.get('phandle')
215         if phandle:
216             val = fdt_util.fdt32_to_cpu(phandle.value)
217             self._fdt.phandle_to_node[val] = self
218
219         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
220         while offset >= 0:
221             sep = '' if self.path[-1] == '/' else '/'
222             name = self._fdt._fdt_obj.get_name(offset)
223             path = self.path + sep + name
224             node = Node(self._fdt, self, offset, name, path)
225             self.subnodes.append(node)
226
227             node.Scan()
228             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
229
230     def Refresh(self, my_offset):
231         """Fix up the _offset for each node, recursively
232
233         Note: This does not take account of property offsets - these will not
234         be updated.
235         """
236         if self._offset != my_offset:
237             #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
238             self._offset = my_offset
239         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
240         for subnode in self.subnodes:
241             subnode.Refresh(offset)
242             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
243
244     def DeleteProp(self, prop_name):
245         """Delete a property of a node
246
247         The property is deleted and the offset cache is invalidated.
248
249         Args:
250             prop_name: Name of the property to delete
251         Raises:
252             ValueError if the property does not exist
253         """
254         CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
255                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
256         del self.props[prop_name]
257         self._fdt.Invalidate()
258
259 class Fdt:
260     """Provides simple access to a flat device tree blob using libfdts.
261
262     Properties:
263       fname: Filename of fdt
264       _root: Root of device tree (a Node object)
265     """
266     def __init__(self, fname):
267         self._fname = fname
268         self._cached_offsets = False
269         self.phandle_to_node = {}
270         if self._fname:
271             self._fname = fdt_util.EnsureCompiled(self._fname)
272
273             with open(self._fname) as fd:
274                 self._fdt = bytearray(fd.read())
275                 self._fdt_obj = libfdt.Fdt(self._fdt)
276
277     def Scan(self, root='/'):
278         """Scan a device tree, building up a tree of Node objects
279
280         This fills in the self._root property
281
282         Args:
283             root: Ignored
284
285         TODO(sjg@chromium.org): Implement the 'root' parameter
286         """
287         self._root = self.Node(self, None, 0, '/', '/')
288         self._root.Scan()
289
290     def GetRoot(self):
291         """Get the root Node of the device tree
292
293         Returns:
294             The root Node object
295         """
296         return self._root
297
298     def GetNode(self, path):
299         """Look up a node from its path
300
301         Args:
302             path: Path to look up, e.g. '/microcode/update@0'
303         Returns:
304             Node object, or None if not found
305         """
306         node = self._root
307         for part in path.split('/')[1:]:
308             node = node._FindNode(part)
309             if not node:
310                 return None
311         return node
312
313     def Flush(self):
314         """Flush device tree changes back to the file
315
316         If the device tree has changed in memory, write it back to the file.
317         """
318         with open(self._fname, 'wb') as fd:
319             fd.write(self._fdt)
320
321     def Pack(self):
322         """Pack the device tree down to its minimum size
323
324         When nodes and properties shrink or are deleted, wasted space can
325         build up in the device tree binary.
326         """
327         CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
328         fdt_len = libfdt.fdt_totalsize(self._fdt)
329         del self._fdt[fdt_len:]
330
331     def GetFdt(self):
332         """Get the contents of the FDT
333
334         Returns:
335             The FDT contents as a string of bytes
336         """
337         return self._fdt
338
339     def CheckErr(errnum, msg):
340         if errnum:
341             raise ValueError('Error %d: %s: %s' %
342                 (errnum, libfdt.fdt_strerror(errnum), msg))
343
344
345     def GetProps(self, node):
346         """Get all properties from a node.
347
348         Args:
349             node: Full path to node name to look in.
350
351         Returns:
352             A dictionary containing all the properties, indexed by node name.
353             The entries are Prop objects.
354
355         Raises:
356             ValueError: if the node does not exist.
357         """
358         props_dict = {}
359         poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
360         while poffset >= 0:
361             p = self._fdt_obj.get_property_by_offset(poffset)
362             prop = Prop(node, poffset, p.name, p.value)
363             props_dict[prop.name] = prop
364
365             poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
366         return props_dict
367
368     def Invalidate(self):
369         """Mark our offset cache as invalid"""
370         self._cached_offsets = False
371
372     def CheckCache(self):
373         """Refresh the offset cache if needed"""
374         if self._cached_offsets:
375             return
376         self.Refresh()
377         self._cached_offsets = True
378
379     def Refresh(self):
380         """Refresh the offset cache"""
381         self._root.Refresh(0)
382
383     def GetStructOffset(self, offset):
384         """Get the file offset of a given struct offset
385
386         Args:
387             offset: Offset within the 'struct' region of the device tree
388         Returns:
389             Position of @offset within the device tree binary
390         """
391         return libfdt.fdt_off_dt_struct(self._fdt) + offset
392
393     @classmethod
394     def Node(self, fdt, parent, offset, name, path):
395         """Create a new node
396
397         This is used by Fdt.Scan() to create a new node using the correct
398         class.
399
400         Args:
401             fdt: Fdt object
402             parent: Parent node, or None if this is the root node
403             offset: Offset of node
404             name: Node name
405             path: Full path to node
406         """
407         node = Node(fdt, parent, offset, name, path)
408         return node
409
410 def FdtScan(fname):
411     """Returns a new Fdt object from the implementation we are using"""
412     dtb = Fdt(fname)
413     dtb.Scan()
414     return dtb