xref: /openbmc/u-boot/tools/dtoc/fdt.py (revision 8d773c4a)
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 = self.bytes
175        self.type = TYPE_INT
176        self.dirty = True
177
178    def SetData(self, bytes):
179        """Set the value of a property as bytes
180
181        Args:
182            bytes: New property value to set
183        """
184        self.bytes = str(bytes)
185        self.type, self.value = self.BytesToValue(bytes)
186        self.dirty = True
187
188    def Sync(self, auto_resize=False):
189        """Sync property changes back to the device tree
190
191        This updates the device tree blob with any changes to this property
192        since the last sync.
193
194        Args:
195            auto_resize: Resize the device tree automatically if it does not
196                have enough space for the update
197
198        Raises:
199            FdtException if auto_resize is False and there is not enough space
200        """
201        if self._offset is None or self.dirty:
202            node = self._node
203            fdt_obj = node._fdt._fdt_obj
204            if auto_resize:
205                while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
206                                    (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
207                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
208                    fdt_obj.setprop(node.Offset(), self.name, self.bytes)
209            else:
210                fdt_obj.setprop(node.Offset(), self.name, self.bytes)
211
212
213class Node:
214    """A device tree node
215
216    Properties:
217        offset: Integer offset in the device tree
218        name: Device tree node tname
219        path: Full path to node, along with the node name itself
220        _fdt: Device tree object
221        subnodes: A list of subnodes for this node, each a Node object
222        props: A dict of properties for this node, each a Prop object.
223            Keyed by property name
224    """
225    def __init__(self, fdt, parent, offset, name, path):
226        self._fdt = fdt
227        self.parent = parent
228        self._offset = offset
229        self.name = name
230        self.path = path
231        self.subnodes = []
232        self.props = {}
233
234    def GetFdt(self):
235        """Get the Fdt object for this node
236
237        Returns:
238            Fdt object
239        """
240        return self._fdt
241
242    def FindNode(self, name):
243        """Find a node given its name
244
245        Args:
246            name: Node name to look for
247        Returns:
248            Node object if found, else None
249        """
250        for subnode in self.subnodes:
251            if subnode.name == name:
252                return subnode
253        return None
254
255    def Offset(self):
256        """Returns the offset of a node, after checking the cache
257
258        This should be used instead of self._offset directly, to ensure that
259        the cache does not contain invalid offsets.
260        """
261        self._fdt.CheckCache()
262        return self._offset
263
264    def Scan(self):
265        """Scan a node's properties and subnodes
266
267        This fills in the props and subnodes properties, recursively
268        searching into subnodes so that the entire tree is built.
269        """
270        fdt_obj = self._fdt._fdt_obj
271        self.props = self._fdt.GetProps(self)
272        phandle = fdt_obj.get_phandle(self.Offset())
273        if phandle:
274            self._fdt.phandle_to_node[phandle] = self
275
276        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
277        while offset >= 0:
278            sep = '' if self.path[-1] == '/' else '/'
279            name = fdt_obj.get_name(offset)
280            path = self.path + sep + name
281            node = Node(self._fdt, self, offset, name, path)
282            self.subnodes.append(node)
283
284            node.Scan()
285            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
286
287    def Refresh(self, my_offset):
288        """Fix up the _offset for each node, recursively
289
290        Note: This does not take account of property offsets - these will not
291        be updated.
292        """
293        fdt_obj = self._fdt._fdt_obj
294        if self._offset != my_offset:
295            self._offset = my_offset
296        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
297        for subnode in self.subnodes:
298            if subnode.name != fdt_obj.get_name(offset):
299                raise ValueError('Internal error, node name mismatch %s != %s' %
300                                 (subnode.name, fdt_obj.get_name(offset)))
301            subnode.Refresh(offset)
302            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
303        if offset != -libfdt.FDT_ERR_NOTFOUND:
304            raise ValueError('Internal error, offset == %d' % offset)
305
306        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
307        while poffset >= 0:
308            p = fdt_obj.get_property_by_offset(poffset)
309            prop = self.props.get(p.name)
310            if not prop:
311                raise ValueError("Internal error, property '%s' missing, "
312                                 'offset %d' % (p.name, poffset))
313            prop.RefreshOffset(poffset)
314            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
315
316    def DeleteProp(self, prop_name):
317        """Delete a property of a node
318
319        The property is deleted and the offset cache is invalidated.
320
321        Args:
322            prop_name: Name of the property to delete
323        Raises:
324            ValueError if the property does not exist
325        """
326        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
327                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
328        del self.props[prop_name]
329        self._fdt.Invalidate()
330
331    def AddZeroProp(self, prop_name):
332        """Add a new property to the device tree with an integer value of 0.
333
334        Args:
335            prop_name: Name of property
336        """
337        self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4)
338
339    def AddEmptyProp(self, prop_name, len):
340        """Add a property with a fixed data size, for filling in later
341
342        The device tree is marked dirty so that the value will be written to
343        the blob on the next sync.
344
345        Args:
346            prop_name: Name of property
347            len: Length of data in property
348        """
349        value = chr(0) * len
350        self.props[prop_name] = Prop(self, None, prop_name, value)
351
352    def SetInt(self, prop_name, val):
353        """Update an integer property int the device tree.
354
355        This is not allowed to change the size of the FDT.
356
357        The device tree is marked dirty so that the value will be written to
358        the blob on the next sync.
359
360        Args:
361            prop_name: Name of property
362            val: Value to set
363        """
364        self.props[prop_name].SetInt(val)
365
366    def SetData(self, prop_name, val):
367        """Set the data value of a property
368
369        The device tree is marked dirty so that the value will be written to
370        the blob on the next sync.
371
372        Args:
373            prop_name: Name of property to set
374            val: Data value to set
375        """
376        self.props[prop_name].SetData(val)
377
378    def SetString(self, prop_name, val):
379        """Set the string value of a property
380
381        The device tree is marked dirty so that the value will be written to
382        the blob on the next sync.
383
384        Args:
385            prop_name: Name of property to set
386            val: String value to set (will be \0-terminated in DT)
387        """
388        self.props[prop_name].SetData(val + chr(0))
389
390    def AddString(self, prop_name, val):
391        """Add a new string property to a node
392
393        The device tree is marked dirty so that the value will be written to
394        the blob on the next sync.
395
396        Args:
397            prop_name: Name of property to add
398            val: String value of property
399        """
400        self.props[prop_name] = Prop(self, None, prop_name, val + chr(0))
401
402    def AddSubnode(self, name):
403        """Add a new subnode to the node
404
405        Args:
406            name: name of node to add
407
408        Returns:
409            New subnode that was created
410        """
411        path = self.path + '/' + name
412        subnode = Node(self._fdt, self, None, name, path)
413        self.subnodes.append(subnode)
414        return subnode
415
416    def Sync(self, auto_resize=False):
417        """Sync node changes back to the device tree
418
419        This updates the device tree blob with any changes to this node and its
420        subnodes since the last sync.
421
422        Args:
423            auto_resize: Resize the device tree automatically if it does not
424                have enough space for the update
425
426        Raises:
427            FdtException if auto_resize is False and there is not enough space
428        """
429        if self._offset is None:
430            # The subnode doesn't exist yet, so add it
431            fdt_obj = self._fdt._fdt_obj
432            if auto_resize:
433                while True:
434                    offset = fdt_obj.add_subnode(self.parent._offset, self.name,
435                                                (libfdt.NOSPACE,))
436                    if offset != -libfdt.NOSPACE:
437                        break
438                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
439            else:
440                offset = fdt_obj.add_subnode(self.parent._offset, self.name)
441            self._offset = offset
442
443        # Sync subnodes in reverse so that we don't disturb node offsets for
444        # nodes that are earlier in the DT. This avoids an O(n^2) rescan of
445        # node offsets.
446        for node in reversed(self.subnodes):
447            node.Sync(auto_resize)
448
449        # Sync properties now, whose offsets should not have been disturbed.
450        # We do this after subnodes, since this disturbs the offsets of these
451        # properties.
452        prop_list = sorted(self.props.values(), key=lambda prop: prop._offset,
453                           reverse=True)
454        for prop in prop_list:
455            prop.Sync(auto_resize)
456
457
458class Fdt:
459    """Provides simple access to a flat device tree blob using libfdts.
460
461    Properties:
462      fname: Filename of fdt
463      _root: Root of device tree (a Node object)
464    """
465    def __init__(self, fname):
466        self._fname = fname
467        self._cached_offsets = False
468        self.phandle_to_node = {}
469        if self._fname:
470            self._fname = fdt_util.EnsureCompiled(self._fname)
471
472            with open(self._fname) as fd:
473                self._fdt_obj = libfdt.Fdt(fd.read())
474
475    @staticmethod
476    def FromData(data):
477        """Create a new Fdt object from the given data
478
479        Args:
480            data: Device-tree data blob
481
482        Returns:
483            Fdt object containing the data
484        """
485        fdt = Fdt(None)
486        fdt._fdt_obj = libfdt.Fdt(bytearray(data))
487        return fdt
488
489    def LookupPhandle(self, phandle):
490        """Look up a phandle
491
492        Args:
493            phandle: Phandle to look up (int)
494
495        Returns:
496            Node object the phandle points to
497        """
498        return self.phandle_to_node.get(phandle)
499
500    def Scan(self, root='/'):
501        """Scan a device tree, building up a tree of Node objects
502
503        This fills in the self._root property
504
505        Args:
506            root: Ignored
507
508        TODO(sjg@chromium.org): Implement the 'root' parameter
509        """
510        self._cached_offsets = True
511        self._root = self.Node(self, None, 0, '/', '/')
512        self._root.Scan()
513
514    def GetRoot(self):
515        """Get the root Node of the device tree
516
517        Returns:
518            The root Node object
519        """
520        return self._root
521
522    def GetNode(self, path):
523        """Look up a node from its path
524
525        Args:
526            path: Path to look up, e.g. '/microcode/update@0'
527        Returns:
528            Node object, or None if not found
529        """
530        node = self._root
531        parts = path.split('/')
532        if len(parts) < 2:
533            return None
534        for part in parts[1:]:
535            node = node.FindNode(part)
536            if not node:
537                return None
538        return node
539
540    def Flush(self):
541        """Flush device tree changes back to the file
542
543        If the device tree has changed in memory, write it back to the file.
544        """
545        with open(self._fname, 'wb') as fd:
546            fd.write(self._fdt_obj.as_bytearray())
547
548    def Sync(self, auto_resize=False):
549        """Make sure any DT changes are written to the blob
550
551        Args:
552            auto_resize: Resize the device tree automatically if it does not
553                have enough space for the update
554
555        Raises:
556            FdtException if auto_resize is False and there is not enough space
557        """
558        self._root.Sync(auto_resize)
559        self.Invalidate()
560
561    def Pack(self):
562        """Pack the device tree down to its minimum size
563
564        When nodes and properties shrink or are deleted, wasted space can
565        build up in the device tree binary.
566        """
567        CheckErr(self._fdt_obj.pack(), 'pack')
568        self.Invalidate()
569
570    def GetContents(self):
571        """Get the contents of the FDT
572
573        Returns:
574            The FDT contents as a string of bytes
575        """
576        return self._fdt_obj.as_bytearray()
577
578    def GetFdtObj(self):
579        """Get the contents of the FDT
580
581        Returns:
582            The FDT contents as a libfdt.Fdt object
583        """
584        return self._fdt_obj
585
586    def GetProps(self, node):
587        """Get all properties from a node.
588
589        Args:
590            node: Full path to node name to look in.
591
592        Returns:
593            A dictionary containing all the properties, indexed by node name.
594            The entries are Prop objects.
595
596        Raises:
597            ValueError: if the node does not exist.
598        """
599        props_dict = {}
600        poffset = self._fdt_obj.first_property_offset(node._offset,
601                                                      QUIET_NOTFOUND)
602        while poffset >= 0:
603            p = self._fdt_obj.get_property_by_offset(poffset)
604            prop = Prop(node, poffset, p.name, p)
605            props_dict[prop.name] = prop
606
607            poffset = self._fdt_obj.next_property_offset(poffset,
608                                                         QUIET_NOTFOUND)
609        return props_dict
610
611    def Invalidate(self):
612        """Mark our offset cache as invalid"""
613        self._cached_offsets = False
614
615    def CheckCache(self):
616        """Refresh the offset cache if needed"""
617        if self._cached_offsets:
618            return
619        self.Refresh()
620        self._cached_offsets = True
621
622    def Refresh(self):
623        """Refresh the offset cache"""
624        self._root.Refresh(0)
625
626    def GetStructOffset(self, offset):
627        """Get the file offset of a given struct offset
628
629        Args:
630            offset: Offset within the 'struct' region of the device tree
631        Returns:
632            Position of @offset within the device tree binary
633        """
634        return self._fdt_obj.off_dt_struct() + offset
635
636    @classmethod
637    def Node(self, fdt, parent, offset, name, path):
638        """Create a new node
639
640        This is used by Fdt.Scan() to create a new node using the correct
641        class.
642
643        Args:
644            fdt: Fdt object
645            parent: Parent node, or None if this is the root node
646            offset: Offset of node
647            name: Node name
648            path: Full path to node
649        """
650        node = Node(fdt, parent, offset, name, path)
651        return node
652
653def FdtScan(fname):
654    """Returns a new Fdt object"""
655    dtb = Fdt(fname)
656    dtb.Scan()
657    return dtb
658