xref: /openbmc/u-boot/tools/dtoc/fdt.py (revision e21c27af)
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
8import struct
9import sys
10
11import fdt_util
12import libfdt
13from 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
26def CheckErr(errnum, msg):
27    if errnum:
28        raise ValueError('Error %d: %s: %s' %
29            (errnum, libfdt.fdt_strerror(errnum), msg))
30
31class 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        self.dirty = False
47        if not bytes:
48            self.type = TYPE_BOOL
49            self.value = True
50            return
51        self.type, self.value = self.BytesToValue(bytes)
52
53    def RefreshOffset(self, poffset):
54        self._offset = poffset
55
56    def Widen(self, newprop):
57        """Figure out which property type is more general
58
59        Given a current property and a new property, this function returns the
60        one that is less specific as to type. The less specific property will
61        be ble to represent the data in the more specific property. This is
62        used for things like:
63
64            node1 {
65                compatible = "fred";
66                value = <1>;
67            };
68            node1 {
69                compatible = "fred";
70                value = <1 2>;
71            };
72
73        He we want to use an int array for 'value'. The first property
74        suggests that a single int is enough, but the second one shows that
75        it is not. Calling this function with these two propertes would
76        update the current property to be like the second, since it is less
77        specific.
78        """
79        if newprop.type < self.type:
80            self.type = newprop.type
81
82        if type(newprop.value) == list and type(self.value) != list:
83            self.value = [self.value]
84
85        if type(self.value) == list and len(newprop.value) > len(self.value):
86            val = self.GetEmpty(self.type)
87            while len(self.value) < len(newprop.value):
88                self.value.append(val)
89
90    def BytesToValue(self, bytes):
91        """Converts a string of bytes into a type and value
92
93        Args:
94            A string containing bytes
95
96        Return:
97            A tuple:
98                Type of data
99                Data, either a single element or a list of elements. Each element
100                is one of:
101                    TYPE_STRING: string value from the property
102                    TYPE_INT: a byte-swapped integer stored as a 4-byte string
103                    TYPE_BYTE: a byte stored as a single-byte string
104        """
105        bytes = str(bytes)
106        size = len(bytes)
107        strings = bytes.split('\0')
108        is_string = True
109        count = len(strings) - 1
110        if count > 0 and not strings[-1]:
111            for string in strings[:-1]:
112                if not string:
113                    is_string = False
114                    break
115                for ch in string:
116                    if ch < ' ' or ch > '~':
117                        is_string = False
118                        break
119        else:
120            is_string = False
121        if is_string:
122            if count == 1:
123                return TYPE_STRING, strings[0]
124            else:
125                return TYPE_STRING, strings[:-1]
126        if size % 4:
127            if size == 1:
128                return TYPE_BYTE, bytes[0]
129            else:
130                return TYPE_BYTE, list(bytes)
131        val = []
132        for i in range(0, size, 4):
133            val.append(bytes[i:i + 4])
134        if size == 4:
135            return TYPE_INT, val[0]
136        else:
137            return TYPE_INT, val
138
139    @classmethod
140    def GetEmpty(self, type):
141        """Get an empty / zero value of the given type
142
143        Returns:
144            A single value of the given type
145        """
146        if type == TYPE_BYTE:
147            return chr(0)
148        elif type == TYPE_INT:
149            return struct.pack('>I', 0);
150        elif type == TYPE_STRING:
151            return ''
152        else:
153            return True
154
155    def GetOffset(self):
156        """Get the offset of a property
157
158        Returns:
159            The offset of the property (struct fdt_property) within the file
160        """
161        self._node._fdt.CheckCache()
162        return self._node._fdt.GetStructOffset(self._offset)
163
164    def SetInt(self, val):
165        """Set the integer value of the property
166
167        The device tree is marked dirty so that the value will be written to
168        the block on the next sync.
169
170        Args:
171            val: Integer value (32-bit, single cell)
172        """
173        self.bytes = struct.pack('>I', val);
174        self.value = val
175        self.type = TYPE_INT
176        self.dirty = True
177
178    def Sync(self, auto_resize=False):
179        """Sync property changes back to the device tree
180
181        This updates the device tree blob with any changes to this property
182        since the last sync.
183
184        Args:
185            auto_resize: Resize the device tree automatically if it does not
186                have enough space for the update
187
188        Raises:
189            FdtException if auto_resize is False and there is not enough space
190        """
191        if self._offset is None or self.dirty:
192            node = self._node
193            fdt_obj = node._fdt._fdt_obj
194            if auto_resize:
195                while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
196                                    (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
197                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
198                    fdt_obj.setprop(node.Offset(), self.name, self.bytes)
199            else:
200                fdt_obj.setprop(node.Offset(), self.name, self.bytes)
201
202
203class Node:
204    """A device tree node
205
206    Properties:
207        offset: Integer offset in the device tree
208        name: Device tree node tname
209        path: Full path to node, along with the node name itself
210        _fdt: Device tree object
211        subnodes: A list of subnodes for this node, each a Node object
212        props: A dict of properties for this node, each a Prop object.
213            Keyed by property name
214    """
215    def __init__(self, fdt, parent, offset, name, path):
216        self._fdt = fdt
217        self.parent = parent
218        self._offset = offset
219        self.name = name
220        self.path = path
221        self.subnodes = []
222        self.props = {}
223
224    def GetFdt(self):
225        """Get the Fdt object for this node
226
227        Returns:
228            Fdt object
229        """
230        return self._fdt
231
232    def FindNode(self, name):
233        """Find a node given its name
234
235        Args:
236            name: Node name to look for
237        Returns:
238            Node object if found, else None
239        """
240        for subnode in self.subnodes:
241            if subnode.name == name:
242                return subnode
243        return None
244
245    def Offset(self):
246        """Returns the offset of a node, after checking the cache
247
248        This should be used instead of self._offset directly, to ensure that
249        the cache does not contain invalid offsets.
250        """
251        self._fdt.CheckCache()
252        return self._offset
253
254    def Scan(self):
255        """Scan a node's properties and subnodes
256
257        This fills in the props and subnodes properties, recursively
258        searching into subnodes so that the entire tree is built.
259        """
260        fdt_obj = self._fdt._fdt_obj
261        self.props = self._fdt.GetProps(self)
262        phandle = fdt_obj.get_phandle(self.Offset())
263        if phandle:
264            self._fdt.phandle_to_node[phandle] = self
265
266        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
267        while offset >= 0:
268            sep = '' if self.path[-1] == '/' else '/'
269            name = fdt_obj.get_name(offset)
270            path = self.path + sep + name
271            node = Node(self._fdt, self, offset, name, path)
272            self.subnodes.append(node)
273
274            node.Scan()
275            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
276
277    def Refresh(self, my_offset):
278        """Fix up the _offset for each node, recursively
279
280        Note: This does not take account of property offsets - these will not
281        be updated.
282        """
283        fdt_obj = self._fdt._fdt_obj
284        if self._offset != my_offset:
285            self._offset = my_offset
286        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
287        for subnode in self.subnodes:
288            if subnode.name != fdt_obj.get_name(offset):
289                raise ValueError('Internal error, node name mismatch %s != %s' %
290                                 (subnode.name, fdt_obj.get_name(offset)))
291            subnode.Refresh(offset)
292            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
293        if offset != -libfdt.FDT_ERR_NOTFOUND:
294            raise ValueError('Internal error, offset == %d' % offset)
295
296        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
297        while poffset >= 0:
298            p = fdt_obj.get_property_by_offset(poffset)
299            prop = self.props.get(p.name)
300            if not prop:
301                raise ValueError("Internal error, property '%s' missing, "
302                                 'offset %d' % (p.name, poffset))
303            prop.RefreshOffset(poffset)
304            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
305
306    def DeleteProp(self, prop_name):
307        """Delete a property of a node
308
309        The property is deleted and the offset cache is invalidated.
310
311        Args:
312            prop_name: Name of the property to delete
313        Raises:
314            ValueError if the property does not exist
315        """
316        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
317                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
318        del self.props[prop_name]
319        self._fdt.Invalidate()
320
321    def AddZeroProp(self, prop_name):
322        """Add a new property to the device tree with an integer value of 0.
323
324        Args:
325            prop_name: Name of property
326        """
327        self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4)
328
329    def SetInt(self, prop_name, val):
330        """Update an integer property int the device tree.
331
332        This is not allowed to change the size of the FDT.
333
334        Args:
335            prop_name: Name of property
336            val: Value to set
337        """
338        self.props[prop_name].SetInt(val)
339
340    def AddSubnode(self, name):
341        path = self.path + '/' + name
342        subnode = Node(self._fdt, self, None, name, path)
343        self.subnodes.append(subnode)
344        return subnode
345
346    def Sync(self, auto_resize=False):
347        """Sync node changes back to the device tree
348
349        This updates the device tree blob with any changes to this node and its
350        subnodes since the last sync.
351
352        Args:
353            auto_resize: Resize the device tree automatically if it does not
354                have enough space for the update
355
356        Raises:
357            FdtException if auto_resize is False and there is not enough space
358        """
359        if self._offset is None:
360            # The subnode doesn't exist yet, so add it
361            fdt_obj = self._fdt._fdt_obj
362            if auto_resize:
363                while True:
364                    offset = fdt_obj.add_subnode(self.parent._offset, self.name,
365                                                (libfdt.NOSPACE,))
366                    if offset != -libfdt.NOSPACE:
367                        break
368                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
369            else:
370                offset = fdt_obj.add_subnode(self.parent._offset, self.name)
371            self._offset = offset
372
373        # Sync subnodes in reverse so that we don't disturb node offsets for
374        # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
375        # node offsets.
376        for node in reversed(self.subnodes):
377            node.Sync(auto_resize)
378
379        # Sync properties now, whose offsets should not have been disturbed.
380        # We do this after subnodes, since this disturbs the offsets of these
381        # properties.
382        prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
383                           reverse=True)
384        for prop in prop_list:
385            prop.Sync(auto_resize)
386
387
388class Fdt:
389    """Provides simple access to a flat device tree blob using libfdts.
390
391    Properties:
392      fname: Filename of fdt
393      _root: Root of device tree (a Node object)
394    """
395    def __init__(self, fname):
396        self._fname = fname
397        self._cached_offsets = False
398        self.phandle_to_node = {}
399        if self._fname:
400            self._fname = fdt_util.EnsureCompiled(self._fname)
401
402            with open(self._fname) as fd:
403                self._fdt_obj = libfdt.Fdt(fd.read())
404
405    def LookupPhandle(self, phandle):
406        """Look up a phandle
407
408        Args:
409            phandle: Phandle to look up (int)
410
411        Returns:
412            Node object the phandle points to
413        """
414        return self.phandle_to_node.get(phandle)
415
416    def Scan(self, root='/'):
417        """Scan a device tree, building up a tree of Node objects
418
419        This fills in the self._root property
420
421        Args:
422            root: Ignored
423
424        TODO(sjg@chromium.org): Implement the 'root' parameter
425        """
426        self._cached_offsets = True
427        self._root = self.Node(self, None, 0, '/', '/')
428        self._root.Scan()
429
430    def GetRoot(self):
431        """Get the root Node of the device tree
432
433        Returns:
434            The root Node object
435        """
436        return self._root
437
438    def GetNode(self, path):
439        """Look up a node from its path
440
441        Args:
442            path: Path to look up, e.g. '/microcode/update@0'
443        Returns:
444            Node object, or None if not found
445        """
446        node = self._root
447        parts = path.split('/')
448        if len(parts) < 2:
449            return None
450        for part in parts[1:]:
451            node = node.FindNode(part)
452            if not node:
453                return None
454        return node
455
456    def Flush(self):
457        """Flush device tree changes back to the file
458
459        If the device tree has changed in memory, write it back to the file.
460        """
461        with open(self._fname, 'wb') as fd:
462            fd.write(self._fdt_obj.as_bytearray())
463
464    def Sync(self, auto_resize=False):
465        """Make sure any DT changes are written to the blob
466
467        Args:
468            auto_resize: Resize the device tree automatically if it does not
469                have enough space for the update
470
471        Raises:
472            FdtException if auto_resize is False and there is not enough space
473        """
474        self._root.Sync(auto_resize)
475        self.Invalidate()
476
477    def Pack(self):
478        """Pack the device tree down to its minimum size
479
480        When nodes and properties shrink or are deleted, wasted space can
481        build up in the device tree binary.
482        """
483        CheckErr(self._fdt_obj.pack(), 'pack')
484        self.Invalidate()
485
486    def GetContents(self):
487        """Get the contents of the FDT
488
489        Returns:
490            The FDT contents as a string of bytes
491        """
492        return self._fdt_obj.as_bytearray()
493
494    def GetFdtObj(self):
495        """Get the contents of the FDT
496
497        Returns:
498            The FDT contents as a libfdt.Fdt object
499        """
500        return self._fdt_obj
501
502    def GetProps(self, node):
503        """Get all properties from a node.
504
505        Args:
506            node: Full path to node name to look in.
507
508        Returns:
509            A dictionary containing all the properties, indexed by node name.
510            The entries are Prop objects.
511
512        Raises:
513            ValueError: if the node does not exist.
514        """
515        props_dict = {}
516        poffset = self._fdt_obj.first_property_offset(node._offset,
517                                                      QUIET_NOTFOUND)
518        while poffset >= 0:
519            p = self._fdt_obj.get_property_by_offset(poffset)
520            prop = Prop(node, poffset, p.name, p)
521            props_dict[prop.name] = prop
522
523            poffset = self._fdt_obj.next_property_offset(poffset,
524                                                         QUIET_NOTFOUND)
525        return props_dict
526
527    def Invalidate(self):
528        """Mark our offset cache as invalid"""
529        self._cached_offsets = False
530
531    def CheckCache(self):
532        """Refresh the offset cache if needed"""
533        if self._cached_offsets:
534            return
535        self.Refresh()
536        self._cached_offsets = True
537
538    def Refresh(self):
539        """Refresh the offset cache"""
540        self._root.Refresh(0)
541
542    def GetStructOffset(self, offset):
543        """Get the file offset of a given struct offset
544
545        Args:
546            offset: Offset within the 'struct' region of the device tree
547        Returns:
548            Position of @offset within the device tree binary
549        """
550        return self._fdt_obj.off_dt_struct() + offset
551
552    @classmethod
553    def Node(self, fdt, parent, offset, name, path):
554        """Create a new node
555
556        This is used by Fdt.Scan() to create a new node using the correct
557        class.
558
559        Args:
560            fdt: Fdt object
561            parent: Parent node, or None if this is the root node
562            offset: Offset of node
563            name: Node name
564            path: Full path to node
565        """
566        node = Node(fdt, parent, offset, name, path)
567        return node
568
569def FdtScan(fname):
570    """Returns a new Fdt object"""
571    dtb = Fdt(fname)
572    dtb.Scan()
573    return dtb
574