]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
dtoc: Add some tests for the fdt module
[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     @classmethod
142     def GetEmpty(self, type):
143         """Get an empty / zero value of the given type
144
145         Returns:
146             A single value of the given type
147         """
148         if type == TYPE_BYTE:
149             return chr(0)
150         elif type == TYPE_INT:
151             return struct.pack('<I', 0);
152         elif type == TYPE_STRING:
153             return ''
154         else:
155             return True
156
157     def GetOffset(self):
158         """Get the offset of a property
159
160         Returns:
161             The offset of the property (struct fdt_property) within the file
162         """
163         return self._node._fdt.GetStructOffset(self._offset)
164
165 class Node:
166     """A device tree node
167
168     Properties:
169         offset: Integer offset in the device tree
170         name: Device tree node tname
171         path: Full path to node, along with the node name itself
172         _fdt: Device tree object
173         subnodes: A list of subnodes for this node, each a Node object
174         props: A dict of properties for this node, each a Prop object.
175             Keyed by property name
176     """
177     def __init__(self, fdt, parent, offset, name, path):
178         self._fdt = fdt
179         self.parent = parent
180         self._offset = offset
181         self.name = name
182         self.path = path
183         self.subnodes = []
184         self.props = {}
185
186     def _FindNode(self, name):
187         """Find a node given its name
188
189         Args:
190             name: Node name to look for
191         Returns:
192             Node object if found, else None
193         """
194         for subnode in self.subnodes:
195             if subnode.name == name:
196                 return subnode
197         return None
198
199     def Offset(self):
200         """Returns the offset of a node, after checking the cache
201
202         This should be used instead of self._offset directly, to ensure that
203         the cache does not contain invalid offsets.
204         """
205         self._fdt.CheckCache()
206         return self._offset
207
208     def Scan(self):
209         """Scan a node's properties and subnodes
210
211         This fills in the props and subnodes properties, recursively
212         searching into subnodes so that the entire tree is built.
213         """
214         self.props = self._fdt.GetProps(self)
215         phandle = self.props.get('phandle')
216         if phandle:
217             val = fdt_util.fdt32_to_cpu(phandle.value)
218             self._fdt.phandle_to_node[val] = self
219
220         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
221         while offset >= 0:
222             sep = '' if self.path[-1] == '/' else '/'
223             name = self._fdt._fdt_obj.get_name(offset)
224             path = self.path + sep + name
225             node = Node(self._fdt, self, offset, name, path)
226             self.subnodes.append(node)
227
228             node.Scan()
229             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
230
231     def Refresh(self, my_offset):
232         """Fix up the _offset for each node, recursively
233
234         Note: This does not take account of property offsets - these will not
235         be updated.
236         """
237         if 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 GetFdtObj(self):
340         """Get the contents of the FDT
341
342         Returns:
343             The FDT contents as a libfdt.Fdt object
344         """
345         return self._fdt_obj
346
347     def CheckErr(self, errnum, msg):
348         if errnum:
349             raise ValueError('Error %d: %s: %s' %
350                 (errnum, libfdt.fdt_strerror(errnum), msg))
351
352     def GetProps(self, node):
353         """Get all properties from a node.
354
355         Args:
356             node: Full path to node name to look in.
357
358         Returns:
359             A dictionary containing all the properties, indexed by node name.
360             The entries are Prop objects.
361
362         Raises:
363             ValueError: if the node does not exist.
364         """
365         props_dict = {}
366         poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
367         while poffset >= 0:
368             p = self._fdt_obj.get_property_by_offset(poffset)
369             prop = Prop(node, poffset, p.name, p)
370             props_dict[prop.name] = prop
371
372             poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
373         return props_dict
374
375     def Invalidate(self):
376         """Mark our offset cache as invalid"""
377         self._cached_offsets = False
378
379     def CheckCache(self):
380         """Refresh the offset cache if needed"""
381         if self._cached_offsets:
382             return
383         self.Refresh()
384         self._cached_offsets = True
385
386     def Refresh(self):
387         """Refresh the offset cache"""
388         self._root.Refresh(0)
389
390     def GetStructOffset(self, offset):
391         """Get the file offset of a given struct offset
392
393         Args:
394             offset: Offset within the 'struct' region of the device tree
395         Returns:
396             Position of @offset within the device tree binary
397         """
398         return libfdt.fdt_off_dt_struct(self._fdt) + offset
399
400     @classmethod
401     def Node(self, fdt, parent, offset, name, path):
402         """Create a new node
403
404         This is used by Fdt.Scan() to create a new node using the correct
405         class.
406
407         Args:
408             fdt: Fdt object
409             parent: Parent node, or None if this is the root node
410             offset: Offset of node
411             name: Node name
412             path: Full path to node
413         """
414         node = Node(fdt, parent, offset, name, path)
415         return node
416
417 def FdtScan(fname):
418     """Returns a new Fdt object from the implementation we are using"""
419     dtb = Fdt(fname)
420     dtb.Scan()
421     return dtb