]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
libfdt: Bring in proposed pylibfdt changes
[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             self._offset = my_offset
238         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
239         for subnode in self.subnodes:
240             subnode.Refresh(offset)
241             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
242
243     def DeleteProp(self, prop_name):
244         """Delete a property of a node
245
246         The property is deleted and the offset cache is invalidated.
247
248         Args:
249             prop_name: Name of the property to delete
250         Raises:
251             ValueError if the property does not exist
252         """
253         CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
254                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
255         del self.props[prop_name]
256         self._fdt.Invalidate()
257
258 class Fdt:
259     """Provides simple access to a flat device tree blob using libfdts.
260
261     Properties:
262       fname: Filename of fdt
263       _root: Root of device tree (a Node object)
264     """
265     def __init__(self, fname):
266         self._fname = fname
267         self._cached_offsets = False
268         self.phandle_to_node = {}
269         if self._fname:
270             self._fname = fdt_util.EnsureCompiled(self._fname)
271
272             with open(self._fname) as fd:
273                 self._fdt = bytearray(fd.read())
274                 self._fdt_obj = libfdt.Fdt(self._fdt)
275
276     def Scan(self, root='/'):
277         """Scan a device tree, building up a tree of Node objects
278
279         This fills in the self._root property
280
281         Args:
282             root: Ignored
283
284         TODO(sjg@chromium.org): Implement the 'root' parameter
285         """
286         self._root = self.Node(self, None, 0, '/', '/')
287         self._root.Scan()
288
289     def GetRoot(self):
290         """Get the root Node of the device tree
291
292         Returns:
293             The root Node object
294         """
295         return self._root
296
297     def GetNode(self, path):
298         """Look up a node from its path
299
300         Args:
301             path: Path to look up, e.g. '/microcode/update@0'
302         Returns:
303             Node object, or None if not found
304         """
305         node = self._root
306         for part in path.split('/')[1:]:
307             node = node._FindNode(part)
308             if not node:
309                 return None
310         return node
311
312     def Flush(self):
313         """Flush device tree changes back to the file
314
315         If the device tree has changed in memory, write it back to the file.
316         """
317         with open(self._fname, 'wb') as fd:
318             fd.write(self._fdt)
319
320     def Pack(self):
321         """Pack the device tree down to its minimum size
322
323         When nodes and properties shrink or are deleted, wasted space can
324         build up in the device tree binary.
325         """
326         CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
327         fdt_len = libfdt.fdt_totalsize(self._fdt)
328         del self._fdt[fdt_len:]
329
330     def GetFdt(self):
331         """Get the contents of the FDT
332
333         Returns:
334             The FDT contents as a string of bytes
335         """
336         return self._fdt
337
338     def CheckErr(errnum, msg):
339         if errnum:
340             raise ValueError('Error %d: %s: %s' %
341                 (errnum, libfdt.fdt_strerror(errnum), msg))
342
343
344     def GetProps(self, node):
345         """Get all properties from a node.
346
347         Args:
348             node: Full path to node name to look in.
349
350         Returns:
351             A dictionary containing all the properties, indexed by node name.
352             The entries are Prop objects.
353
354         Raises:
355             ValueError: if the node does not exist.
356         """
357         props_dict = {}
358         poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
359         while poffset >= 0:
360             p = self._fdt_obj.get_property_by_offset(poffset)
361             prop = Prop(node, poffset, p.name, p)
362             props_dict[prop.name] = prop
363
364             poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
365         return props_dict
366
367     def Invalidate(self):
368         """Mark our offset cache as invalid"""
369         self._cached_offsets = False
370
371     def CheckCache(self):
372         """Refresh the offset cache if needed"""
373         if self._cached_offsets:
374             return
375         self.Refresh()
376         self._cached_offsets = True
377
378     def Refresh(self):
379         """Refresh the offset cache"""
380         self._root.Refresh(0)
381
382     def GetStructOffset(self, offset):
383         """Get the file offset of a given struct offset
384
385         Args:
386             offset: Offset within the 'struct' region of the device tree
387         Returns:
388             Position of @offset within the device tree binary
389         """
390         return libfdt.fdt_off_dt_struct(self._fdt) + offset
391
392     @classmethod
393     def Node(self, fdt, parent, offset, name, path):
394         """Create a new node
395
396         This is used by Fdt.Scan() to create a new node using the correct
397         class.
398
399         Args:
400             fdt: Fdt object
401             parent: Parent node, or None if this is the root node
402             offset: Offset of node
403             name: Node name
404             path: Full path to node
405         """
406         node = Node(fdt, parent, offset, name, path)
407         return node
408
409 def FdtScan(fname):
410     """Returns a new Fdt object from the implementation we are using"""
411     dtb = Fdt(fname)
412     dtb.Scan()
413     return dtb