]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
dtoc: Add a 64-bit type and a way to convert cells into 64 bits
[u-boot] / tools / dtoc / fdt.py
1 #!/usr/bin/python
2 #
3 # Copyright (C) 2016 Google, Inc
4 # Written by Simon Glass <sjg@chromium.org>
5 #
6 # SPDX-License-Identifier:      GPL-2.0+
7 #
8
9 import struct
10 import sys
11
12 import fdt_util
13 import libfdt
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 GetPhandle(self):
53         """Get a (single) phandle value from a property
54
55         Gets the phandle valuie from a property and returns it as an integer
56         """
57         return fdt_util.fdt32_to_cpu(self.value[:4])
58
59     def Widen(self, newprop):
60         """Figure out which property type is more general
61
62         Given a current property and a new property, this function returns the
63         one that is less specific as to type. The less specific property will
64         be ble to represent the data in the more specific property. This is
65         used for things like:
66
67             node1 {
68                 compatible = "fred";
69                 value = <1>;
70             };
71             node1 {
72                 compatible = "fred";
73                 value = <1 2>;
74             };
75
76         He we want to use an int array for 'value'. The first property
77         suggests that a single int is enough, but the second one shows that
78         it is not. Calling this function with these two propertes would
79         update the current property to be like the second, since it is less
80         specific.
81         """
82         if newprop.type < self.type:
83             self.type = newprop.type
84
85         if type(newprop.value) == list and type(self.value) != list:
86             self.value = [self.value]
87
88         if type(self.value) == list and len(newprop.value) > len(self.value):
89             val = self.GetEmpty(self.type)
90             while len(self.value) < len(newprop.value):
91                 self.value.append(val)
92
93     def BytesToValue(self, bytes):
94         """Converts a string of bytes into a type and value
95
96         Args:
97             A string containing bytes
98
99         Return:
100             A tuple:
101                 Type of data
102                 Data, either a single element or a list of elements. Each element
103                 is one of:
104                     TYPE_STRING: string value from the property
105                     TYPE_INT: a byte-swapped integer stored as a 4-byte string
106                     TYPE_BYTE: a byte stored as a single-byte string
107         """
108         bytes = str(bytes)
109         size = len(bytes)
110         strings = bytes.split('\0')
111         is_string = True
112         count = len(strings) - 1
113         if count > 0 and not strings[-1]:
114             for string in strings[:-1]:
115                 if not string:
116                     is_string = False
117                     break
118                 for ch in string:
119                     if ch < ' ' or ch > '~':
120                         is_string = False
121                         break
122         else:
123             is_string = False
124         if is_string:
125             if count == 1:
126                 return TYPE_STRING, strings[0]
127             else:
128                 return TYPE_STRING, strings[:-1]
129         if size % 4:
130             if size == 1:
131                 return TYPE_BYTE, bytes[0]
132             else:
133                 return TYPE_BYTE, list(bytes)
134         val = []
135         for i in range(0, size, 4):
136             val.append(bytes[i:i + 4])
137         if size == 4:
138             return TYPE_INT, val[0]
139         else:
140             return TYPE_INT, val
141
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
216         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
217         while offset >= 0:
218             sep = '' if self.path[-1] == '/' else '/'
219             name = self._fdt._fdt_obj.get_name(offset)
220             path = self.path + sep + name
221             node = Node(self._fdt, self, offset, name, path)
222             self.subnodes.append(node)
223
224             node.Scan()
225             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
226
227     def Refresh(self, my_offset):
228         """Fix up the _offset for each node, recursively
229
230         Note: This does not take account of property offsets - these will not
231         be updated.
232         """
233         if self._offset != my_offset:
234             #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
235             self._offset = my_offset
236         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
237         for subnode in self.subnodes:
238             subnode.Refresh(offset)
239             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
240
241     def DeleteProp(self, prop_name):
242         """Delete a property of a node
243
244         The property is deleted and the offset cache is invalidated.
245
246         Args:
247             prop_name: Name of the property to delete
248         Raises:
249             ValueError if the property does not exist
250         """
251         CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
252                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
253         del self.props[prop_name]
254         self._fdt.Invalidate()
255
256 class Fdt:
257     """Provides simple access to a flat device tree blob using libfdts.
258
259     Properties:
260       fname: Filename of fdt
261       _root: Root of device tree (a Node object)
262     """
263     def __init__(self, fname):
264         self._fname = fname
265         self._cached_offsets = False
266         if self._fname:
267             self._fname = fdt_util.EnsureCompiled(self._fname)
268
269             with open(self._fname) as fd:
270                 self._fdt = bytearray(fd.read())
271                 self._fdt_obj = libfdt.Fdt(self._fdt)
272
273     def Scan(self, root='/'):
274         """Scan a device tree, building up a tree of Node objects
275
276         This fills in the self._root property
277
278         Args:
279             root: Ignored
280
281         TODO(sjg@chromium.org): Implement the 'root' parameter
282         """
283         self._root = self.Node(self, None, 0, '/', '/')
284         self._root.Scan()
285
286     def GetRoot(self):
287         """Get the root Node of the device tree
288
289         Returns:
290             The root Node object
291         """
292         return self._root
293
294     def GetNode(self, path):
295         """Look up a node from its path
296
297         Args:
298             path: Path to look up, e.g. '/microcode/update@0'
299         Returns:
300             Node object, or None if not found
301         """
302         node = self._root
303         for part in path.split('/')[1:]:
304             node = node._FindNode(part)
305             if not node:
306                 return None
307         return node
308
309     def Flush(self):
310         """Flush device tree changes back to the file
311
312         If the device tree has changed in memory, write it back to the file.
313         """
314         with open(self._fname, 'wb') as fd:
315             fd.write(self._fdt)
316
317     def Pack(self):
318         """Pack the device tree down to its minimum size
319
320         When nodes and properties shrink or are deleted, wasted space can
321         build up in the device tree binary.
322         """
323         CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
324         fdt_len = libfdt.fdt_totalsize(self._fdt)
325         del self._fdt[fdt_len:]
326
327     def GetFdt(self):
328         """Get the contents of the FDT
329
330         Returns:
331             The FDT contents as a string of bytes
332         """
333         return self._fdt
334
335     def CheckErr(errnum, msg):
336         if errnum:
337             raise ValueError('Error %d: %s: %s' %
338                 (errnum, libfdt.fdt_strerror(errnum), msg))
339
340
341     def GetProps(self, node):
342         """Get all properties from a node.
343
344         Args:
345             node: Full path to node name to look in.
346
347         Returns:
348             A dictionary containing all the properties, indexed by node name.
349             The entries are Prop objects.
350
351         Raises:
352             ValueError: if the node does not exist.
353         """
354         props_dict = {}
355         poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
356         while poffset >= 0:
357             p = self._fdt_obj.get_property_by_offset(poffset)
358             prop = Prop(node, poffset, p.name, p.value)
359             props_dict[prop.name] = prop
360
361             poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
362         return props_dict
363
364     def Invalidate(self):
365         """Mark our offset cache as invalid"""
366         self._cached_offsets = False
367
368     def CheckCache(self):
369         """Refresh the offset cache if needed"""
370         if self._cached_offsets:
371             return
372         self.Refresh()
373         self._cached_offsets = True
374
375     def Refresh(self):
376         """Refresh the offset cache"""
377         self._root.Refresh(0)
378
379     def GetStructOffset(self, offset):
380         """Get the file offset of a given struct offset
381
382         Args:
383             offset: Offset within the 'struct' region of the device tree
384         Returns:
385             Position of @offset within the device tree binary
386         """
387         return libfdt.fdt_off_dt_struct(self._fdt) + offset
388
389     @classmethod
390     def Node(self, fdt, parent, offset, name, path):
391         """Create a new node
392
393         This is used by Fdt.Scan() to create a new node using the correct
394         class.
395
396         Args:
397             fdt: Fdt object
398             parent: Parent node, or None if this is the root node
399             offset: Offset of node
400             name: Node name
401             path: Full path to node
402         """
403         node = Node(fdt, parent, offset, name, path)
404         return node
405
406 def FdtScan(fname):
407     """Returns a new Fdt object from the implementation we are using"""
408     dtb = Fdt(fname)
409     dtb.Scan()
410     return dtb