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