]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
dtoc: Drop use of a local dtb buffer
[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 from libfdt import QUIET_NOTFOUND
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     @classmethod
143     def GetEmpty(self, type):
144         """Get an empty / zero value of the given type
145
146         Returns:
147             A single value of the given type
148         """
149         if type == TYPE_BYTE:
150             return chr(0)
151         elif type == TYPE_INT:
152             return struct.pack('<I', 0);
153         elif type == TYPE_STRING:
154             return ''
155         else:
156             return True
157
158     def GetOffset(self):
159         """Get the offset of a property
160
161         Returns:
162             The offset of the property (struct fdt_property) within the file
163         """
164         return self._node._fdt.GetStructOffset(self._offset)
165
166 class Node:
167     """A device tree node
168
169     Properties:
170         offset: Integer offset in the device tree
171         name: Device tree node tname
172         path: Full path to node, along with the node name itself
173         _fdt: Device tree object
174         subnodes: A list of subnodes for this node, each a Node object
175         props: A dict of properties for this node, each a Prop object.
176             Keyed by property name
177     """
178     def __init__(self, fdt, parent, offset, name, path):
179         self._fdt = fdt
180         self.parent = parent
181         self._offset = offset
182         self.name = name
183         self.path = path
184         self.subnodes = []
185         self.props = {}
186
187     def _FindNode(self, name):
188         """Find a node given its name
189
190         Args:
191             name: Node name to look for
192         Returns:
193             Node object if found, else None
194         """
195         for subnode in self.subnodes:
196             if subnode.name == name:
197                 return subnode
198         return None
199
200     def Offset(self):
201         """Returns the offset of a node, after checking the cache
202
203         This should be used instead of self._offset directly, to ensure that
204         the cache does not contain invalid offsets.
205         """
206         self._fdt.CheckCache()
207         return self._offset
208
209     def Scan(self):
210         """Scan a node's properties and subnodes
211
212         This fills in the props and subnodes properties, recursively
213         searching into subnodes so that the entire tree is built.
214         """
215         fdt_obj = self._fdt._fdt_obj
216         self.props = self._fdt.GetProps(self)
217         phandle = fdt_obj.get_phandle(self.Offset())
218         if phandle:
219             self._fdt.phandle_to_node[phandle] = self
220
221         offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
222         while offset >= 0:
223             sep = '' if self.path[-1] == '/' else '/'
224             name = fdt_obj.get_name(offset)
225             path = self.path + sep + name
226             node = Node(self._fdt, self, offset, name, path)
227             self.subnodes.append(node)
228
229             node.Scan()
230             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
231
232     def Refresh(self, my_offset):
233         """Fix up the _offset for each node, recursively
234
235         Note: This does not take account of property offsets - these will not
236         be updated.
237         """
238         fdt_obj = self._fdt._fdt_obj
239         if self._offset != my_offset:
240             self._offset = my_offset
241         offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
242         for subnode in self.subnodes:
243             subnode.Refresh(offset)
244             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
245
246     def DeleteProp(self, prop_name):
247         """Delete a property of a node
248
249         The property is deleted and the offset cache is invalidated.
250
251         Args:
252             prop_name: Name of the property to delete
253         Raises:
254             ValueError if the property does not exist
255         """
256         CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
257                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
258         del self.props[prop_name]
259         self._fdt.Invalidate()
260
261 class Fdt:
262     """Provides simple access to a flat device tree blob using libfdts.
263
264     Properties:
265       fname: Filename of fdt
266       _root: Root of device tree (a Node object)
267     """
268     def __init__(self, fname):
269         self._fname = fname
270         self._cached_offsets = False
271         self.phandle_to_node = {}
272         if self._fname:
273             self._fname = fdt_util.EnsureCompiled(self._fname)
274
275             with open(self._fname) as fd:
276                 self._fdt_obj = libfdt.Fdt(fd.read())
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_obj.as_bytearray())
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(self._fdt_obj.pack(), 'pack')
329         self.Invalidate()
330
331     def GetContents(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_obj.as_bytearray()
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 = self._fdt_obj.first_property_offset(node._offset,
367                                                       QUIET_NOTFOUND)
368         while poffset >= 0:
369             p = self._fdt_obj.get_property_by_offset(poffset)
370             prop = Prop(node, poffset, p.name, p)
371             props_dict[prop.name] = prop
372
373             poffset = self._fdt_obj.next_property_offset(poffset,
374                                                          QUIET_NOTFOUND)
375         return props_dict
376
377     def Invalidate(self):
378         """Mark our offset cache as invalid"""
379         self._cached_offsets = False
380
381     def CheckCache(self):
382         """Refresh the offset cache if needed"""
383         if self._cached_offsets:
384             return
385         self.Refresh()
386         self._cached_offsets = True
387
388     def Refresh(self):
389         """Refresh the offset cache"""
390         self._root.Refresh(0)
391
392     def GetStructOffset(self, offset):
393         """Get the file offset of a given struct offset
394
395         Args:
396             offset: Offset within the 'struct' region of the device tree
397         Returns:
398             Position of @offset within the device tree binary
399         """
400         return self._fdt_obj.off_dt_struct() + offset
401
402     @classmethod
403     def Node(self, fdt, parent, offset, name, path):
404         """Create a new node
405
406         This is used by Fdt.Scan() to create a new node using the correct
407         class.
408
409         Args:
410             fdt: Fdt object
411             parent: Parent node, or None if this is the root node
412             offset: Offset of node
413             name: Node name
414             path: Full path to node
415         """
416         node = Node(fdt, parent, offset, name, path)
417         return node
418
419 def FdtScan(fname):
420     """Returns a new Fdt object from the implementation we are using"""
421     dtb = Fdt(fname)
422     dtb.Scan()
423     return dtb