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