xref: /openbmc/u-boot/tools/dtoc/fdt.py (revision e4061556)
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 GetFdt(self):
185        """Get the Fdt object for this node
186
187        Returns:
188            Fdt object
189        """
190        return self._fdt
191
192    def FindNode(self, name):
193        """Find a node given its name
194
195        Args:
196            name: Node name to look for
197        Returns:
198            Node object if found, else None
199        """
200        for subnode in self.subnodes:
201            if subnode.name == name:
202                return subnode
203        return None
204
205    def Offset(self):
206        """Returns the offset of a node, after checking the cache
207
208        This should be used instead of self._offset directly, to ensure that
209        the cache does not contain invalid offsets.
210        """
211        self._fdt.CheckCache()
212        return self._offset
213
214    def Scan(self):
215        """Scan a node's properties and subnodes
216
217        This fills in the props and subnodes properties, recursively
218        searching into subnodes so that the entire tree is built.
219        """
220        fdt_obj = self._fdt._fdt_obj
221        self.props = self._fdt.GetProps(self)
222        phandle = fdt_obj.get_phandle(self.Offset())
223        if phandle:
224            self._fdt.phandle_to_node[phandle] = self
225
226        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
227        while offset >= 0:
228            sep = '' if self.path[-1] == '/' else '/'
229            name = fdt_obj.get_name(offset)
230            path = self.path + sep + name
231            node = Node(self._fdt, self, offset, name, path)
232            self.subnodes.append(node)
233
234            node.Scan()
235            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
236
237    def Refresh(self, my_offset):
238        """Fix up the _offset for each node, recursively
239
240        Note: This does not take account of property offsets - these will not
241        be updated.
242        """
243        fdt_obj = self._fdt._fdt_obj
244        if self._offset != my_offset:
245            self._offset = my_offset
246        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
247        for subnode in self.subnodes:
248            if subnode.name != fdt_obj.get_name(offset):
249                raise ValueError('Internal error, node name mismatch %s != %s' %
250                                 (subnode.name, fdt_obj.get_name(offset)))
251            subnode.Refresh(offset)
252            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
253        if offset != -libfdt.FDT_ERR_NOTFOUND:
254            raise ValueError('Internal error, offset == %d' % offset)
255
256        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
257        while poffset >= 0:
258            p = fdt_obj.get_property_by_offset(poffset)
259            prop = self.props.get(p.name)
260            if not prop:
261                raise ValueError("Internal error, property '%s' missing, "
262                                 'offset %d' % (p.name, poffset))
263            prop.RefreshOffset(poffset)
264            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
265
266    def DeleteProp(self, prop_name):
267        """Delete a property of a node
268
269        The property is deleted and the offset cache is invalidated.
270
271        Args:
272            prop_name: Name of the property to delete
273        Raises:
274            ValueError if the property does not exist
275        """
276        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
277                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
278        del self.props[prop_name]
279        self._fdt.Invalidate()
280
281    def AddZeroProp(self, prop_name):
282        """Add a new property to the device tree with an integer value of 0.
283
284        Args:
285            prop_name: Name of property
286        """
287        fdt_obj = self._fdt._fdt_obj
288        if fdt_obj.setprop_u32(self.Offset(), prop_name, 0,
289                               (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
290            fdt_obj.resize(fdt_obj.totalsize() + 1024)
291            fdt_obj.setprop_u32(self.Offset(), prop_name, 0)
292        self.props[prop_name] = Prop(self, -1, prop_name, '\0' * 4)
293        self._fdt.Invalidate()
294
295    def SetInt(self, prop_name, val):
296        """Update an integer property int the device tree.
297
298        This is not allowed to change the size of the FDT.
299
300        Args:
301            prop_name: Name of property
302            val: Value to set
303        """
304        fdt_obj = self._fdt._fdt_obj
305        fdt_obj.setprop_u32(self.Offset(), prop_name, val)
306
307
308class Fdt:
309    """Provides simple access to a flat device tree blob using libfdts.
310
311    Properties:
312      fname: Filename of fdt
313      _root: Root of device tree (a Node object)
314    """
315    def __init__(self, fname):
316        self._fname = fname
317        self._cached_offsets = False
318        self.phandle_to_node = {}
319        if self._fname:
320            self._fname = fdt_util.EnsureCompiled(self._fname)
321
322            with open(self._fname) as fd:
323                self._fdt_obj = libfdt.Fdt(fd.read())
324
325    def LookupPhandle(self, phandle):
326        """Look up a phandle
327
328        Args:
329            phandle: Phandle to look up (int)
330
331        Returns:
332            Node object the phandle points to
333        """
334        return self.phandle_to_node.get(phandle)
335
336    def Scan(self, root='/'):
337        """Scan a device tree, building up a tree of Node objects
338
339        This fills in the self._root property
340
341        Args:
342            root: Ignored
343
344        TODO(sjg@chromium.org): Implement the 'root' parameter
345        """
346        self._cached_offsets = True
347        self._root = self.Node(self, None, 0, '/', '/')
348        self._root.Scan()
349
350    def GetRoot(self):
351        """Get the root Node of the device tree
352
353        Returns:
354            The root Node object
355        """
356        return self._root
357
358    def GetNode(self, path):
359        """Look up a node from its path
360
361        Args:
362            path: Path to look up, e.g. '/microcode/update@0'
363        Returns:
364            Node object, or None if not found
365        """
366        node = self._root
367        parts = path.split('/')
368        if len(parts) < 2:
369            return None
370        for part in parts[1:]:
371            node = node.FindNode(part)
372            if not node:
373                return None
374        return node
375
376    def Flush(self):
377        """Flush device tree changes back to the file
378
379        If the device tree has changed in memory, write it back to the file.
380        """
381        with open(self._fname, 'wb') as fd:
382            fd.write(self._fdt_obj.as_bytearray())
383
384    def Pack(self):
385        """Pack the device tree down to its minimum size
386
387        When nodes and properties shrink or are deleted, wasted space can
388        build up in the device tree binary.
389        """
390        CheckErr(self._fdt_obj.pack(), 'pack')
391        self.Invalidate()
392
393    def GetContents(self):
394        """Get the contents of the FDT
395
396        Returns:
397            The FDT contents as a string of bytes
398        """
399        return self._fdt_obj.as_bytearray()
400
401    def GetFdtObj(self):
402        """Get the contents of the FDT
403
404        Returns:
405            The FDT contents as a libfdt.Fdt object
406        """
407        return self._fdt_obj
408
409    def GetProps(self, node):
410        """Get all properties from a node.
411
412        Args:
413            node: Full path to node name to look in.
414
415        Returns:
416            A dictionary containing all the properties, indexed by node name.
417            The entries are Prop objects.
418
419        Raises:
420            ValueError: if the node does not exist.
421        """
422        props_dict = {}
423        poffset = self._fdt_obj.first_property_offset(node._offset,
424                                                      QUIET_NOTFOUND)
425        while poffset >= 0:
426            p = self._fdt_obj.get_property_by_offset(poffset)
427            prop = Prop(node, poffset, p.name, p)
428            props_dict[prop.name] = prop
429
430            poffset = self._fdt_obj.next_property_offset(poffset,
431                                                         QUIET_NOTFOUND)
432        return props_dict
433
434    def Invalidate(self):
435        """Mark our offset cache as invalid"""
436        self._cached_offsets = False
437
438    def CheckCache(self):
439        """Refresh the offset cache if needed"""
440        if self._cached_offsets:
441            return
442        self.Refresh()
443        self._cached_offsets = True
444
445    def Refresh(self):
446        """Refresh the offset cache"""
447        self._root.Refresh(0)
448
449    def GetStructOffset(self, offset):
450        """Get the file offset of a given struct offset
451
452        Args:
453            offset: Offset within the 'struct' region of the device tree
454        Returns:
455            Position of @offset within the device tree binary
456        """
457        return self._fdt_obj.off_dt_struct() + offset
458
459    @classmethod
460    def Node(self, fdt, parent, offset, name, path):
461        """Create a new node
462
463        This is used by Fdt.Scan() to create a new node using the correct
464        class.
465
466        Args:
467            fdt: Fdt object
468            parent: Parent node, or None if this is the root node
469            offset: Offset of node
470            name: Node name
471            path: Full path to node
472        """
473        node = Node(fdt, parent, offset, name, path)
474        return node
475
476def FdtScan(fname):
477    """Returns a new Fdt object"""
478    dtb = Fdt(fname)
479    dtb.Scan()
480    return dtb
481