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