]> git.sur5r.net Git - u-boot/blob - tools/dtoc/fdt.py
dtoc: Add functions to add integer properties
[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 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     def AddZeroProp(self, prop_name):
274         """Add a new property to the device tree with an integer value of 0.
275
276         Args:
277             prop_name: Name of property
278         """
279         fdt_obj = self._fdt._fdt_obj
280         if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
281                                (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
282             fdt_obj.open_into(fdt_obj.totalsize() + 1024)
283             fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
284         self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
285         self._fdt.Invalidate()
286
287     def SetInt(self, prop_name, val):
288         """Update an integer property int the device tree.
289
290         This is not allowed to change the size of the FDT.
291
292         Args:
293             prop_name: Name of property
294             val: Value to set
295         """
296         fdt_obj = self._fdt._fdt_obj
297         fdt_obj.setprop_u32(self.Offset(), prop_name, val)
298
299
300 class Fdt:
301     """Provides simple access to a flat device tree blob using libfdts.
302
303     Properties:
304       fname: Filename of fdt
305       _root: Root of device tree (a Node object)
306     """
307     def __init__(self, fname):
308         self._fname = fname
309         self._cached_offsets = False
310         self.phandle_to_node = {}
311         if self._fname:
312             self._fname = fdt_util.EnsureCompiled(self._fname)
313
314             with open(self._fname) as fd:
315                 self._fdt_obj = libfdt.Fdt(fd.read())
316
317     def Scan(self, root='/'):
318         """Scan a device tree, building up a tree of Node objects
319
320         This fills in the self._root property
321
322         Args:
323             root: Ignored
324
325         TODO(sjg@chromium.org): Implement the 'root' parameter
326         """
327         self._cached_offsets = True
328         self._root = self.Node(self, None, 0, '/', '/')
329         self._root.Scan()
330
331     def GetRoot(self):
332         """Get the root Node of the device tree
333
334         Returns:
335             The root Node object
336         """
337         return self._root
338
339     def GetNode(self, path):
340         """Look up a node from its path
341
342         Args:
343             path: Path to look up, e.g. '/microcode/update@0'
344         Returns:
345             Node object, or None if not found
346         """
347         node = self._root
348         parts = path.split('/')
349         if len(parts) < 2:
350             return None
351         for part in parts[1:]:
352             node = node._FindNode(part)
353             if not node:
354                 return None
355         return node
356
357     def Flush(self):
358         """Flush device tree changes back to the file
359
360         If the device tree has changed in memory, write it back to the file.
361         """
362         with open(self._fname, 'wb') as fd:
363             fd.write(self._fdt_obj.as_bytearray())
364
365     def Pack(self):
366         """Pack the device tree down to its minimum size
367
368         When nodes and properties shrink or are deleted, wasted space can
369         build up in the device tree binary.
370         """
371         CheckErr(self._fdt_obj.pack(), 'pack')
372         self.Invalidate()
373
374     def GetContents(self):
375         """Get the contents of the FDT
376
377         Returns:
378             The FDT contents as a string of bytes
379         """
380         return self._fdt_obj.as_bytearray()
381
382     def GetFdtObj(self):
383         """Get the contents of the FDT
384
385         Returns:
386             The FDT contents as a libfdt.Fdt object
387         """
388         return self._fdt_obj
389
390     def GetProps(self, node):
391         """Get all properties from a node.
392
393         Args:
394             node: Full path to node name to look in.
395
396         Returns:
397             A dictionary containing all the properties, indexed by node name.
398             The entries are Prop objects.
399
400         Raises:
401             ValueError: if the node does not exist.
402         """
403         props_dict = {}
404         poffset = self._fdt_obj.first_property_offset(node._offset,
405                                                       QUIET_NOTFOUND)
406         while poffset >= 0:
407             p = self._fdt_obj.get_property_by_offset(poffset)
408             prop = Prop(node, poffset, p.name, p)
409             props_dict[prop.name] = prop
410
411             poffset = self._fdt_obj.next_property_offset(poffset,
412                                                          QUIET_NOTFOUND)
413         return props_dict
414
415     def Invalidate(self):
416         """Mark our offset cache as invalid"""
417         self._cached_offsets = False
418
419     def CheckCache(self):
420         """Refresh the offset cache if needed"""
421         if self._cached_offsets:
422             return
423         self.Refresh()
424         self._cached_offsets = True
425
426     def Refresh(self):
427         """Refresh the offset cache"""
428         self._root.Refresh(0)
429
430     def GetStructOffset(self, offset):
431         """Get the file offset of a given struct offset
432
433         Args:
434             offset: Offset within the 'struct' region of the device tree
435         Returns:
436             Position of @offset within the device tree binary
437         """
438         return self._fdt_obj.off_dt_struct() + offset
439
440     @classmethod
441     def Node(self, fdt, parent, offset, name, path):
442         """Create a new node
443
444         This is used by Fdt.Scan() to create a new node using the correct
445         class.
446
447         Args:
448             fdt: Fdt object
449             parent: Parent node, or None if this is the root node
450             offset: Offset of node
451             name: Node name
452             path: Full path to node
453         """
454         node = Node(fdt, parent, offset, name, path)
455         return node
456
457 def FdtScan(fname):
458     """Returns a new Fdt object"""
459     dtb = Fdt(fname)
460     dtb.Scan()
461     return dtb