xref: /openbmc/u-boot/tools/dtoc/fdt.py (revision ad7061ed)
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        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 RefreshOffset(self, poffset):
53        self._offset = poffset
54
55    def Widen(self, newprop):
56        """Figure out which property type is more general
57
58        Given a current property and a new property, this function returns the
59        one that is less specific as to type. The less specific property will
60        be ble to represent the data in the more specific property. This is
61        used for things like:
62
63            node1 {
64                compatible = "fred";
65                value = <1>;
66            };
67            node1 {
68                compatible = "fred";
69                value = <1 2>;
70            };
71
72        He we want to use an int array for 'value'. The first property
73        suggests that a single int is enough, but the second one shows that
74        it is not. Calling this function with these two propertes would
75        update the current property to be like the second, since it is less
76        specific.
77        """
78        if newprop.type < self.type:
79            self.type = newprop.type
80
81        if type(newprop.value) == list and type(self.value) != list:
82            self.value = [self.value]
83
84        if type(self.value) == list and len(newprop.value) > len(self.value):
85            val = self.GetEmpty(self.type)
86            while len(self.value) < len(newprop.value):
87                self.value.append(val)
88
89    def BytesToValue(self, bytes):
90        """Converts a string of bytes into a type and value
91
92        Args:
93            A string containing bytes
94
95        Return:
96            A tuple:
97                Type of data
98                Data, either a single element or a list of elements. Each element
99                is one of:
100                    TYPE_STRING: string value from the property
101                    TYPE_INT: a byte-swapped integer stored as a 4-byte string
102                    TYPE_BYTE: a byte stored as a single-byte string
103        """
104        bytes = str(bytes)
105        size = len(bytes)
106        strings = bytes.split('\0')
107        is_string = True
108        count = len(strings) - 1
109        if count > 0 and not strings[-1]:
110            for string in strings[:-1]:
111                if not string:
112                    is_string = False
113                    break
114                for ch in string:
115                    if ch < ' ' or ch > '~':
116                        is_string = False
117                        break
118        else:
119            is_string = False
120        if is_string:
121            if count == 1:
122                return TYPE_STRING, strings[0]
123            else:
124                return TYPE_STRING, strings[:-1]
125        if size % 4:
126            if size == 1:
127                return TYPE_BYTE, bytes[0]
128            else:
129                return TYPE_BYTE, list(bytes)
130        val = []
131        for i in range(0, size, 4):
132            val.append(bytes[i:i + 4])
133        if size == 4:
134            return TYPE_INT, val[0]
135        else:
136            return TYPE_INT, val
137
138    @classmethod
139    def GetEmpty(self, type):
140        """Get an empty / zero value of the given type
141
142        Returns:
143            A single value of the given type
144        """
145        if type == TYPE_BYTE:
146            return chr(0)
147        elif type == TYPE_INT:
148            return struct.pack('<I', 0);
149        elif type == TYPE_STRING:
150            return ''
151        else:
152            return True
153
154    def GetOffset(self):
155        """Get the offset of a property
156
157        Returns:
158            The offset of the property (struct fdt_property) within the file
159        """
160        self._node._fdt.CheckCache()
161        return self._node._fdt.GetStructOffset(self._offset)
162
163class Node:
164    """A device tree node
165
166    Properties:
167        offset: Integer offset in the device tree
168        name: Device tree node tname
169        path: Full path to node, along with the node name itself
170        _fdt: Device tree object
171        subnodes: A list of subnodes for this node, each a Node object
172        props: A dict of properties for this node, each a Prop object.
173            Keyed by property name
174    """
175    def __init__(self, fdt, parent, offset, name, path):
176        self._fdt = fdt
177        self.parent = parent
178        self._offset = offset
179        self.name = name
180        self.path = path
181        self.subnodes = []
182        self.props = {}
183
184    def _FindNode(self, name):
185        """Find a node given its name
186
187        Args:
188            name: Node name to look for
189        Returns:
190            Node object if found, else None
191        """
192        for subnode in self.subnodes:
193            if subnode.name == name:
194                return subnode
195        return None
196
197    def Offset(self):
198        """Returns the offset of a node, after checking the cache
199
200        This should be used instead of self._offset directly, to ensure that
201        the cache does not contain invalid offsets.
202        """
203        self._fdt.CheckCache()
204        return self._offset
205
206    def Scan(self):
207        """Scan a node's properties and subnodes
208
209        This fills in the props and subnodes properties, recursively
210        searching into subnodes so that the entire tree is built.
211        """
212        fdt_obj = self._fdt._fdt_obj
213        self.props = self._fdt.GetProps(self)
214        phandle = fdt_obj.get_phandle(self.Offset())
215        if phandle:
216            self._fdt.phandle_to_node[phandle] = self
217
218        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
219        while offset >= 0:
220            sep = '' if self.path[-1] == '/' else '/'
221            name = fdt_obj.get_name(offset)
222            path = self.path + sep + name
223            node = Node(self._fdt, self, offset, name, path)
224            self.subnodes.append(node)
225
226            node.Scan()
227            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
228
229    def Refresh(self, my_offset):
230        """Fix up the _offset for each node, recursively
231
232        Note: This does not take account of property offsets - these will not
233        be updated.
234        """
235        fdt_obj = self._fdt._fdt_obj
236        if self._offset != my_offset:
237            self._offset = my_offset
238        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
239        for subnode in self.subnodes:
240            if subnode.name != fdt_obj.get_name(offset):
241                raise ValueError('Internal error, node name mismatch %s != %s' %
242                                 (subnode.name, fdt_obj.get_name(offset)))
243            subnode.Refresh(offset)
244            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
245        if offset != -libfdt.FDT_ERR_NOTFOUND:
246            raise ValueError('Internal error, offset == %d' % offset)
247
248        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
249        while poffset >= 0:
250            p = fdt_obj.get_property_by_offset(poffset)
251            prop = self.props.get(p.name)
252            if not prop:
253                raise ValueError("Internal error, property '%s' missing, "
254                                 'offset %d' % (p.name, poffset))
255            prop.RefreshOffset(poffset)
256            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
257
258    def DeleteProp(self, prop_name):
259        """Delete a property of a node
260
261        The property is deleted and the offset cache is invalidated.
262
263        Args:
264            prop_name: Name of the property to delete
265        Raises:
266            ValueError if the property does not exist
267        """
268        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
269                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
270        del self.props[prop_name]
271        self._fdt.Invalidate()
272
273    def AddZeroProp(self, prop_name):
274        """Add a new property to the device tree with an integer value of 0.
275
276        Args:
277            prop_name: Name of property
278        """
279        fdt_obj = self._fdt._fdt_obj
280        if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
281                               (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
282            fdt_obj.open_into(fdt_obj.totalsize() + 1024)
283            fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
284        self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
285        self._fdt.Invalidate()
286
287    def SetInt(self, prop_name, val):
288        """Update an integer property int the device tree.
289
290        This is not allowed to change the size of the FDT.
291
292        Args:
293            prop_name: Name of property
294            val: Value to set
295        """
296        fdt_obj = self._fdt._fdt_obj
297        fdt_obj.setprop_u32(self.Offset(), prop_name, val)
298
299
300class Fdt:
301    """Provides simple access to a flat device tree blob using libfdts.
302
303    Properties:
304      fname: Filename of fdt
305      _root: Root of device tree (a Node object)
306    """
307    def __init__(self, fname):
308        self._fname = fname
309        self._cached_offsets = False
310        self.phandle_to_node = {}
311        if self._fname:
312            self._fname = fdt_util.EnsureCompiled(self._fname)
313
314            with open(self._fname) as fd:
315                self._fdt_obj = libfdt.Fdt(fd.read())
316
317    def Scan(self, root='/'):
318        """Scan a device tree, building up a tree of Node objects
319
320        This fills in the self._root property
321
322        Args:
323            root: Ignored
324
325        TODO(sjg@chromium.org): Implement the 'root' parameter
326        """
327        self._cached_offsets = True
328        self._root = self.Node(self, None, 0, '/', '/')
329        self._root.Scan()
330
331    def GetRoot(self):
332        """Get the root Node of the device tree
333
334        Returns:
335            The root Node object
336        """
337        return self._root
338
339    def GetNode(self, path):
340        """Look up a node from its path
341
342        Args:
343            path: Path to look up, e.g. '/microcode/update@0'
344        Returns:
345            Node object, or None if not found
346        """
347        node = self._root
348        parts = path.split('/')
349        if len(parts) < 2:
350            return None
351        for part in parts[1:]:
352            node = node._FindNode(part)
353            if not node:
354                return None
355        return node
356
357    def Flush(self):
358        """Flush device tree changes back to the file
359
360        If the device tree has changed in memory, write it back to the file.
361        """
362        with open(self._fname, 'wb') as fd:
363            fd.write(self._fdt_obj.as_bytearray())
364
365    def Pack(self):
366        """Pack the device tree down to its minimum size
367
368        When nodes and properties shrink or are deleted, wasted space can
369        build up in the device tree binary.
370        """
371        CheckErr(self._fdt_obj.pack(), 'pack')
372        self.Invalidate()
373
374    def GetContents(self):
375        """Get the contents of the FDT
376
377        Returns:
378            The FDT contents as a string of bytes
379        """
380        return self._fdt_obj.as_bytearray()
381
382    def GetFdtObj(self):
383        """Get the contents of the FDT
384
385        Returns:
386            The FDT contents as a libfdt.Fdt object
387        """
388        return self._fdt_obj
389
390    def GetProps(self, node):
391        """Get all properties from a node.
392
393        Args:
394            node: Full path to node name to look in.
395
396        Returns:
397            A dictionary containing all the properties, indexed by node name.
398            The entries are Prop objects.
399
400        Raises:
401            ValueError: if the node does not exist.
402        """
403        props_dict = {}
404        poffset = self._fdt_obj.first_property_offset(node._offset,
405                                                      QUIET_NOTFOUND)
406        while poffset >= 0:
407            p = self._fdt_obj.get_property_by_offset(poffset)
408            prop = Prop(node, poffset, p.name, p)
409            props_dict[prop.name] = prop
410
411            poffset = self._fdt_obj.next_property_offset(poffset,
412                                                         QUIET_NOTFOUND)
413        return props_dict
414
415    def Invalidate(self):
416        """Mark our offset cache as invalid"""
417        self._cached_offsets = False
418
419    def CheckCache(self):
420        """Refresh the offset cache if needed"""
421        if self._cached_offsets:
422            return
423        self.Refresh()
424        self._cached_offsets = True
425
426    def Refresh(self):
427        """Refresh the offset cache"""
428        self._root.Refresh(0)
429
430    def GetStructOffset(self, offset):
431        """Get the file offset of a given struct offset
432
433        Args:
434            offset: Offset within the 'struct' region of the device tree
435        Returns:
436            Position of @offset within the device tree binary
437        """
438        return self._fdt_obj.off_dt_struct() + offset
439
440    @classmethod
441    def Node(self, fdt, parent, offset, name, path):
442        """Create a new node
443
444        This is used by Fdt.Scan() to create a new node using the correct
445        class.
446
447        Args:
448            fdt: Fdt object
449            parent: Parent node, or None if this is the root node
450            offset: Offset of node
451            name: Node name
452            path: Full path to node
453        """
454        node = Node(fdt, parent, offset, name, path)
455        return node
456
457def FdtScan(fname):
458    """Returns a new Fdt object"""
459    dtb = Fdt(fname)
460    dtb.Scan()
461    return dtb
462