xref: /openbmc/u-boot/tools/dtoc/fdt.py (revision ae07d609)
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, 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        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, parent, offset, name, path):
178        self._fdt = fdt
179        self.parent = parent
180        self._offset = offset
181        self.name = name
182        self.path = path
183        self.subnodes = []
184        self.props = {}
185
186    def _FindNode(self, name):
187        """Find a node given its name
188
189        Args:
190            name: Node name to look for
191        Returns:
192            Node object if found, else None
193        """
194        for subnode in self.subnodes:
195            if subnode.name == name:
196                return subnode
197        return None
198
199    def Offset(self):
200        """Returns the offset of a node, after checking the cache
201
202        This should be used instead of self._offset directly, to ensure that
203        the cache does not contain invalid offsets.
204        """
205        self._fdt.CheckCache()
206        return self._offset
207
208    def Scan(self):
209        """Scan a node's properties and subnodes
210
211        This fills in the props and subnodes properties, recursively
212        searching into subnodes so that the entire tree is built.
213        """
214        self.props = self._fdt.GetProps(self)
215        phandle = self.props.get('phandle')
216        if phandle:
217            val = fdt_util.fdt32_to_cpu(phandle.value)
218            self._fdt.phandle_to_node[val] = self
219
220        offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
221        while offset >= 0:
222            sep = '' if self.path[-1] == '/' else '/'
223            name = self._fdt._fdt_obj.get_name(offset)
224            path = self.path + sep + name
225            node = Node(self._fdt, self, offset, name, path)
226            self.subnodes.append(node)
227
228            node.Scan()
229            offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
230
231    def Refresh(self, my_offset):
232        """Fix up the _offset for each node, recursively
233
234        Note: This does not take account of property offsets - these will not
235        be updated.
236        """
237        if self._offset != my_offset:
238            #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
239            self._offset = my_offset
240        offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
241        for subnode in self.subnodes:
242            subnode.Refresh(offset)
243            offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
244
245    def DeleteProp(self, prop_name):
246        """Delete a property of a node
247
248        The property is deleted and the offset cache is invalidated.
249
250        Args:
251            prop_name: Name of the property to delete
252        Raises:
253            ValueError if the property does not exist
254        """
255        CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
256                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
257        del self.props[prop_name]
258        self._fdt.Invalidate()
259
260class Fdt:
261    """Provides simple access to a flat device tree blob using libfdts.
262
263    Properties:
264      fname: Filename of fdt
265      _root: Root of device tree (a Node object)
266    """
267    def __init__(self, fname):
268        self._fname = fname
269        self._cached_offsets = False
270        self.phandle_to_node = {}
271        if self._fname:
272            self._fname = fdt_util.EnsureCompiled(self._fname)
273
274            with open(self._fname) as fd:
275                self._fdt = bytearray(fd.read())
276                self._fdt_obj = libfdt.Fdt(self._fdt)
277
278    def Scan(self, root='/'):
279        """Scan a device tree, building up a tree of Node objects
280
281        This fills in the self._root property
282
283        Args:
284            root: Ignored
285
286        TODO(sjg@chromium.org): Implement the 'root' parameter
287        """
288        self._root = self.Node(self, None, 0, '/', '/')
289        self._root.Scan()
290
291    def GetRoot(self):
292        """Get the root Node of the device tree
293
294        Returns:
295            The root Node object
296        """
297        return self._root
298
299    def GetNode(self, path):
300        """Look up a node from its path
301
302        Args:
303            path: Path to look up, e.g. '/microcode/update@0'
304        Returns:
305            Node object, or None if not found
306        """
307        node = self._root
308        for part in path.split('/')[1:]:
309            node = node._FindNode(part)
310            if not node:
311                return None
312        return node
313
314    def Flush(self):
315        """Flush device tree changes back to the file
316
317        If the device tree has changed in memory, write it back to the file.
318        """
319        with open(self._fname, 'wb') as fd:
320            fd.write(self._fdt)
321
322    def Pack(self):
323        """Pack the device tree down to its minimum size
324
325        When nodes and properties shrink or are deleted, wasted space can
326        build up in the device tree binary.
327        """
328        CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
329        fdt_len = libfdt.fdt_totalsize(self._fdt)
330        del self._fdt[fdt_len:]
331
332    def GetFdt(self):
333        """Get the contents of the FDT
334
335        Returns:
336            The FDT contents as a string of bytes
337        """
338        return self._fdt
339
340    def CheckErr(errnum, msg):
341        if errnum:
342            raise ValueError('Error %d: %s: %s' %
343                (errnum, libfdt.fdt_strerror(errnum), msg))
344
345
346    def GetProps(self, node):
347        """Get all properties from a node.
348
349        Args:
350            node: Full path to node name to look in.
351
352        Returns:
353            A dictionary containing all the properties, indexed by node name.
354            The entries are Prop objects.
355
356        Raises:
357            ValueError: if the node does not exist.
358        """
359        props_dict = {}
360        poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
361        while poffset >= 0:
362            p = self._fdt_obj.get_property_by_offset(poffset)
363            prop = Prop(node, poffset, p.name, p.value)
364            props_dict[prop.name] = prop
365
366            poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
367        return props_dict
368
369    def Invalidate(self):
370        """Mark our offset cache as invalid"""
371        self._cached_offsets = False
372
373    def CheckCache(self):
374        """Refresh the offset cache if needed"""
375        if self._cached_offsets:
376            return
377        self.Refresh()
378        self._cached_offsets = True
379
380    def Refresh(self):
381        """Refresh the offset cache"""
382        self._root.Refresh(0)
383
384    def GetStructOffset(self, offset):
385        """Get the file offset of a given struct offset
386
387        Args:
388            offset: Offset within the 'struct' region of the device tree
389        Returns:
390            Position of @offset within the device tree binary
391        """
392        return libfdt.fdt_off_dt_struct(self._fdt) + offset
393
394    @classmethod
395    def Node(self, fdt, parent, offset, name, path):
396        """Create a new node
397
398        This is used by Fdt.Scan() to create a new node using the correct
399        class.
400
401        Args:
402            fdt: Fdt object
403            parent: Parent node, or None if this is the root node
404            offset: Offset of node
405            name: Node name
406            path: Full path to node
407        """
408        node = Node(fdt, parent, offset, name, path)
409        return node
410
411def FdtScan(fname):
412    """Returns a new Fdt object from the implementation we are using"""
413    dtb = Fdt(fname)
414    dtb.Scan()
415    return dtb
416