dtoc: Keep track of property offsets
[oweals/u-boot.git] / 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 RefreshOffset(self, poffset):
53         self._offset = poffset
54
55     def Widen(self, newprop):
56         """Figure out which property type is more general
57
58         Given a current property and a new property, this function returns the
59         one that is less specific as to type. The less specific property will
60         be ble to represent the data in the more specific property. This is
61         used for things like:
62
63             node1 {
64                 compatible = "fred";
65                 value = <1>;
66             };
67             node1 {
68                 compatible = "fred";
69                 value = <1 2>;
70             };
71
72         He we want to use an int array for 'value'. The first property
73         suggests that a single int is enough, but the second one shows that
74         it is not. Calling this function with these two propertes would
75         update the current property to be like the second, since it is less
76         specific.
77         """
78         if newprop.type < self.type:
79             self.type = newprop.type
80
81         if type(newprop.value) == list and type(self.value) != list:
82             self.value = [self.value]
83
84         if type(self.value) == list and len(newprop.value) > len(self.value):
85             val = self.GetEmpty(self.type)
86             while len(self.value) < len(newprop.value):
87                 self.value.append(val)
88
89     def BytesToValue(self, bytes):
90         """Converts a string of bytes into a type and value
91
92         Args:
93             A string containing bytes
94
95         Return:
96             A tuple:
97                 Type of data
98                 Data, either a single element or a list of elements. Each element
99                 is one of:
100                     TYPE_STRING: string value from the property
101                     TYPE_INT: a byte-swapped integer stored as a 4-byte string
102                     TYPE_BYTE: a byte stored as a single-byte string
103         """
104         bytes = str(bytes)
105         size = len(bytes)
106         strings = bytes.split('\0')
107         is_string = True
108         count = len(strings) - 1
109         if count > 0 and not strings[-1]:
110             for string in strings[:-1]:
111                 if not string:
112                     is_string = False
113                     break
114                 for ch in string:
115                     if ch < ' ' or ch > '~':
116                         is_string = False
117                         break
118         else:
119             is_string = False
120         if is_string:
121             if count == 1:
122                 return TYPE_STRING, strings[0]
123             else:
124                 return TYPE_STRING, strings[:-1]
125         if size % 4:
126             if size == 1:
127                 return TYPE_BYTE, bytes[0]
128             else:
129                 return TYPE_BYTE, list(bytes)
130         val = []
131         for i in range(0, size, 4):
132             val.append(bytes[i:i + 4])
133         if size == 4:
134             return TYPE_INT, val[0]
135         else:
136             return TYPE_INT, val
137
138     @classmethod
139     def GetEmpty(self, type):
140         """Get an empty / zero value of the given type
141
142         Returns:
143             A single value of the given type
144         """
145         if type == TYPE_BYTE:
146             return chr(0)
147         elif type == TYPE_INT:
148             return struct.pack('<I', 0);
149         elif type == TYPE_STRING:
150             return ''
151         else:
152             return True
153
154     def GetOffset(self):
155         """Get the offset of a property
156
157         Returns:
158             The offset of the property (struct fdt_property) within the file
159         """
160         self._node._fdt.CheckCache()
161         return self._node._fdt.GetStructOffset(self._offset)
162
163 class Node:
164     """A device tree node
165
166     Properties:
167         offset: Integer offset in the device tree
168         name: Device tree node tname
169         path: Full path to node, along with the node name itself
170         _fdt: Device tree object
171         subnodes: A list of subnodes for this node, each a Node object
172         props: A dict of properties for this node, each a Prop object.
173             Keyed by property name
174     """
175     def __init__(self, fdt, parent, offset, name, path):
176         self._fdt = fdt
177         self.parent = parent
178         self._offset = offset
179         self.name = name
180         self.path = path
181         self.subnodes = []
182         self.props = {}
183
184     def _FindNode(self, name):
185         """Find a node given its name
186
187         Args:
188             name: Node name to look for
189         Returns:
190             Node object if found, else None
191         """
192         for subnode in self.subnodes:
193             if subnode.name == name:
194                 return subnode
195         return None
196
197     def Offset(self):
198         """Returns the offset of a node, after checking the cache
199
200         This should be used instead of self._offset directly, to ensure that
201         the cache does not contain invalid offsets.
202         """
203         self._fdt.CheckCache()
204         return self._offset
205
206     def Scan(self):
207         """Scan a node's properties and subnodes
208
209         This fills in the props and subnodes properties, recursively
210         searching into subnodes so that the entire tree is built.
211         """
212         fdt_obj = self._fdt._fdt_obj
213         self.props = self._fdt.GetProps(self)
214         phandle = fdt_obj.get_phandle(self.Offset())
215         if phandle:
216             self._fdt.phandle_to_node[phandle] = self
217
218         offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
219         while offset >= 0:
220             sep = '' if self.path[-1] == '/' else '/'
221             name = fdt_obj.get_name(offset)
222             path = self.path + sep + name
223             node = Node(self._fdt, self, offset, name, path)
224             self.subnodes.append(node)
225
226             node.Scan()
227             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
228
229     def Refresh(self, my_offset):
230         """Fix up the _offset for each node, recursively
231
232         Note: This does not take account of property offsets - these will not
233         be updated.
234         """
235         fdt_obj = self._fdt._fdt_obj
236         if self._offset != my_offset:
237             self._offset = my_offset
238         offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
239         for subnode in self.subnodes:
240             if subnode.name != fdt_obj.get_name(offset):
241                 raise ValueError('Internal error, node name mismatch %s != %s' %
242                                  (subnode.name, fdt_obj.get_name(offset)))
243             subnode.Refresh(offset)
244             offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
245         if offset != -libfdt.FDT_ERR_NOTFOUND:
246             raise ValueError('Internal error, offset == %d' % offset)
247
248         poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
249         while poffset >= 0:
250             p = fdt_obj.get_property_by_offset(poffset)
251             prop = self.props.get(p.name)
252             if not prop:
253                 raise ValueError("Internal error, property '%s' missing, "
254                                  'offset %d' % (p.name, poffset))
255             prop.RefreshOffset(poffset)
256             poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
257
258     def DeleteProp(self, prop_name):
259         """Delete a property of a node
260
261         The property is deleted and the offset cache is invalidated.
262
263         Args:
264             prop_name: Name of the property to delete
265         Raises:
266             ValueError if the property does not exist
267         """
268         CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
269                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
270         del self.props[prop_name]
271         self._fdt.Invalidate()
272
273 class Fdt:
274     """Provides simple access to a flat device tree blob using libfdts.
275
276     Properties:
277       fname: Filename of fdt
278       _root: Root of device tree (a Node object)
279     """
280     def __init__(self, fname):
281         self._fname = fname
282         self._cached_offsets = False
283         self.phandle_to_node = {}
284         if self._fname:
285             self._fname = fdt_util.EnsureCompiled(self._fname)
286
287             with open(self._fname) as fd:
288                 self._fdt_obj = libfdt.Fdt(fd.read())
289
290     def Scan(self, root='/'):
291         """Scan a device tree, building up a tree of Node objects
292
293         This fills in the self._root property
294
295         Args:
296             root: Ignored
297
298         TODO(sjg@chromium.org): Implement the 'root' parameter
299         """
300         self._cached_offsets = True
301         self._root = self.Node(self, None, 0, '/', '/')
302         self._root.Scan()
303
304     def GetRoot(self):
305         """Get the root Node of the device tree
306
307         Returns:
308             The root Node object
309         """
310         return self._root
311
312     def GetNode(self, path):
313         """Look up a node from its path
314
315         Args:
316             path: Path to look up, e.g. '/microcode/update@0'
317         Returns:
318             Node object, or None if not found
319         """
320         node = self._root
321         for part in path.split('/')[1:]:
322             node = node._FindNode(part)
323             if not node:
324                 return None
325         return node
326
327     def Flush(self):
328         """Flush device tree changes back to the file
329
330         If the device tree has changed in memory, write it back to the file.
331         """
332         with open(self._fname, 'wb') as fd:
333             fd.write(self._fdt_obj.as_bytearray())
334
335     def Pack(self):
336         """Pack the device tree down to its minimum size
337
338         When nodes and properties shrink or are deleted, wasted space can
339         build up in the device tree binary.
340         """
341         CheckErr(self._fdt_obj.pack(), 'pack')
342         self.Invalidate()
343
344     def GetContents(self):
345         """Get the contents of the FDT
346
347         Returns:
348             The FDT contents as a string of bytes
349         """
350         return self._fdt_obj.as_bytearray()
351
352     def GetFdtObj(self):
353         """Get the contents of the FDT
354
355         Returns:
356             The FDT contents as a libfdt.Fdt object
357         """
358         return self._fdt_obj
359
360     def GetProps(self, node):
361         """Get all properties from a node.
362
363         Args:
364             node: Full path to node name to look in.
365
366         Returns:
367             A dictionary containing all the properties, indexed by node name.
368             The entries are Prop objects.
369
370         Raises:
371             ValueError: if the node does not exist.
372         """
373         props_dict = {}
374         poffset = self._fdt_obj.first_property_offset(node._offset,
375                                                       QUIET_NOTFOUND)
376         while poffset >= 0:
377             p = self._fdt_obj.get_property_by_offset(poffset)
378             prop = Prop(node, poffset, p.name, p)
379             props_dict[prop.name] = prop
380
381             poffset = self._fdt_obj.next_property_offset(poffset,
382                                                          QUIET_NOTFOUND)
383         return props_dict
384
385     def Invalidate(self):
386         """Mark our offset cache as invalid"""
387         self._cached_offsets = False
388
389     def CheckCache(self):
390         """Refresh the offset cache if needed"""
391         if self._cached_offsets:
392             return
393         self.Refresh()
394         self._cached_offsets = True
395
396     def Refresh(self):
397         """Refresh the offset cache"""
398         self._root.Refresh(0)
399
400     def GetStructOffset(self, offset):
401         """Get the file offset of a given struct offset
402
403         Args:
404             offset: Offset within the 'struct' region of the device tree
405         Returns:
406             Position of @offset within the device tree binary
407         """
408         return self._fdt_obj.off_dt_struct() + offset
409
410     @classmethod
411     def Node(self, fdt, parent, offset, name, path):
412         """Create a new node
413
414         This is used by Fdt.Scan() to create a new node using the correct
415         class.
416
417         Args:
418             fdt: Fdt object
419             parent: Parent node, or None if this is the root node
420             offset: Offset of node
421             name: Node name
422             path: Full path to node
423         """
424         node = Node(fdt, parent, offset, name, path)
425         return node
426
427 def FdtScan(fname):
428     """Returns a new Fdt object from the implementation we are using"""
429     dtb = Fdt(fname)
430     dtb.Scan()
431     return dtb