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