uboot/tools/dtoc/fdt.py
<<
>>
Prefs
   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
   8from enum import IntEnum
   9import struct
  10import sys
  11
  12from dtoc import fdt_util
  13import libfdt
  14from libfdt import QUIET_NOTFOUND
  15from patman import tools
  16
  17# This deals with a device tree, presenting it as an assortment of Node and
  18# Prop objects, representing nodes and properties, respectively. This file
  19# contains the base classes and defines the high-level API. You can use
  20# FdtScan() as a convenience function to create and scan an Fdt.
  21
  22# This implementation uses a libfdt Python library to access the device tree,
  23# so it is fairly efficient.
  24
  25# A list of types we support
  26class Type(IntEnum):
  27    # Types in order from widest to narrowest
  28    (BYTE, INT, STRING, BOOL, INT64) = range(5)
  29
  30    def needs_widening(self, other):
  31        """Check if this type needs widening to hold a value from another type
  32
  33        A wider type is one that can hold a wider array of information than
  34        another one, or is less restrictive, so it can hold the information of
  35        another type as well as its own. This is similar to the concept of
  36        type-widening in C.
  37
  38        This uses a simple arithmetic comparison, since type values are in order
  39        from widest (BYTE) to narrowest (INT64).
  40
  41        Args:
  42            other: Other type to compare against
  43
  44        Return:
  45            True if the other type is wider
  46        """
  47        return self.value > other.value
  48
  49def CheckErr(errnum, msg):
  50    if errnum:
  51        raise ValueError('Error %d: %s: %s' %
  52            (errnum, libfdt.fdt_strerror(errnum), msg))
  53
  54
  55def BytesToValue(data):
  56    """Converts a string of bytes into a type and value
  57
  58    Args:
  59        A bytes value (which on Python 2 is an alias for str)
  60
  61    Return:
  62        A tuple:
  63            Type of data
  64            Data, either a single element or a list of elements. Each element
  65            is one of:
  66                Type.STRING: str/bytes value from the property
  67                Type.INT: a byte-swapped integer stored as a 4-byte str/bytes
  68                Type.BYTE: a byte stored as a single-byte str/bytes
  69    """
  70    data = bytes(data)
  71    size = len(data)
  72    strings = data.split(b'\0')
  73    is_string = True
  74    count = len(strings) - 1
  75    if count > 0 and not len(strings[-1]):
  76        for string in strings[:-1]:
  77            if not string:
  78                is_string = False
  79                break
  80            for ch in string:
  81                if ch < 32 or ch > 127:
  82                    is_string = False
  83                    break
  84    else:
  85        is_string = False
  86    if is_string:
  87        if count == 1: 
  88            return Type.STRING, strings[0].decode()
  89        else:
  90            return Type.STRING, [s.decode() for s in strings[:-1]]
  91    if size % 4:
  92        if size == 1:
  93            return Type.BYTE, chr(data[0])
  94        else:
  95            return Type.BYTE, [chr(ch) for ch in list(data)]
  96    val = []
  97    for i in range(0, size, 4):
  98        val.append(data[i:i + 4])
  99    if size == 4:
 100        return Type.INT, val[0]
 101    else:
 102        return Type.INT, val
 103
 104
 105class Prop:
 106    """A device tree property
 107
 108    Properties:
 109        node: Node containing this property
 110        offset: Offset of the property (None if still to be synced)
 111        name: Property name (as per the device tree)
 112        value: Property value as a string of bytes, or a list of strings of
 113            bytes
 114        type: Value type
 115    """
 116    def __init__(self, node, offset, name, data):
 117        self._node = node
 118        self._offset = offset
 119        self.name = name
 120        self.value = None
 121        self.bytes = bytes(data)
 122        self.dirty = offset is None
 123        if not data:
 124            self.type = Type.BOOL
 125            self.value = True
 126            return
 127        self.type, self.value = BytesToValue(bytes(data))
 128
 129    def RefreshOffset(self, poffset):
 130        self._offset = poffset
 131
 132    def Widen(self, newprop):
 133        """Figure out which property type is more general
 134
 135        Given a current property and a new property, this function returns the
 136        one that is less specific as to type. The less specific property will
 137        be ble to represent the data in the more specific property. This is
 138        used for things like:
 139
 140            node1 {
 141                compatible = "fred";
 142                value = <1>;
 143            };
 144            node1 {
 145                compatible = "fred";
 146                value = <1 2>;
 147            };
 148
 149        He we want to use an int array for 'value'. The first property
 150        suggests that a single int is enough, but the second one shows that
 151        it is not. Calling this function with these two propertes would
 152        update the current property to be like the second, since it is less
 153        specific.
 154        """
 155        if self.type.needs_widening(newprop.type):
 156
 157            # A boolean has an empty value: if it exists it is True and if not
 158            # it is False. So when widening we always start with an empty list
 159            # since the only valid integer property would be an empty list of
 160            # integers.
 161            # e.g. this is a boolean:
 162            #    some-prop;
 163            # and it would be widened to int list by:
 164            #    some-prop = <1 2>;
 165            if self.type == Type.BOOL:
 166                self.type = Type.INT
 167                self.value = [self.GetEmpty(self.type)]
 168            if self.type == Type.INT and newprop.type == Type.BYTE:
 169                if type(self.value) == list:
 170                    new_value = []
 171                    for val in self.value:
 172                        new_value += [chr(by) for by in val]
 173                else:
 174                    new_value = [chr(by) for by in self.value]
 175                self.value = new_value
 176            self.type = newprop.type
 177
 178        if type(newprop.value) == list:
 179            if type(self.value) != list:
 180                self.value = [self.value]
 181
 182            if len(newprop.value) > len(self.value):
 183                val = self.GetEmpty(self.type)
 184                while len(self.value) < len(newprop.value):
 185                    self.value.append(val)
 186
 187    @classmethod
 188    def GetEmpty(self, type):
 189        """Get an empty / zero value of the given type
 190
 191        Returns:
 192            A single value of the given type
 193        """
 194        if type == Type.BYTE:
 195            return chr(0)
 196        elif type == Type.INT:
 197            return struct.pack('>I', 0);
 198        elif type == Type.STRING:
 199            return ''
 200        else:
 201            return True
 202
 203    def GetOffset(self):
 204        """Get the offset of a property
 205
 206        Returns:
 207            The offset of the property (struct fdt_property) within the file
 208        """
 209        self._node._fdt.CheckCache()
 210        return self._node._fdt.GetStructOffset(self._offset)
 211
 212    def SetInt(self, val):
 213        """Set the integer value of the property
 214
 215        The device tree is marked dirty so that the value will be written to
 216        the block on the next sync.
 217
 218        Args:
 219            val: Integer value (32-bit, single cell)
 220        """
 221        self.bytes = struct.pack('>I', val);
 222        self.value = self.bytes
 223        self.type = Type.INT
 224        self.dirty = True
 225
 226    def SetData(self, bytes):
 227        """Set the value of a property as bytes
 228
 229        Args:
 230            bytes: New property value to set
 231        """
 232        self.bytes = bytes
 233        self.type, self.value = BytesToValue(bytes)
 234        self.dirty = True
 235
 236    def Sync(self, auto_resize=False):
 237        """Sync property changes back to the device tree
 238
 239        This updates the device tree blob with any changes to this property
 240        since the last sync.
 241
 242        Args:
 243            auto_resize: Resize the device tree automatically if it does not
 244                have enough space for the update
 245
 246        Raises:
 247            FdtException if auto_resize is False and there is not enough space
 248        """
 249        if self.dirty:
 250            node = self._node
 251            fdt_obj = node._fdt._fdt_obj
 252            node_name = fdt_obj.get_name(node._offset)
 253            if node_name and node_name != node.name:
 254                raise ValueError("Internal error, node '%s' name mismatch '%s'" %
 255                                 (node.path, node_name))
 256
 257            if auto_resize:
 258                while fdt_obj.setprop(node.Offset(), self.name, self.bytes,
 259                                    (libfdt.NOSPACE,)) == -libfdt.NOSPACE:
 260                    fdt_obj.resize(fdt_obj.totalsize() + 1024 +
 261                                   len(self.bytes))
 262                    fdt_obj.setprop(node.Offset(), self.name, self.bytes)
 263            else:
 264                fdt_obj.setprop(node.Offset(), self.name, self.bytes)
 265            self.dirty = False
 266
 267
 268class Node:
 269    """A device tree node
 270
 271    Properties:
 272        parent: Parent Node
 273        offset: Integer offset in the device tree (None if to be synced)
 274        name: Device tree node tname
 275        path: Full path to node, along with the node name itself
 276        _fdt: Device tree object
 277        subnodes: A list of subnodes for this node, each a Node object
 278        props: A dict of properties for this node, each a Prop object.
 279            Keyed by property name
 280    """
 281    def __init__(self, fdt, parent, offset, name, path):
 282        self._fdt = fdt
 283        self.parent = parent
 284        self._offset = offset
 285        self.name = name
 286        self.path = path
 287        self.subnodes = []
 288        self.props = {}
 289
 290    def GetFdt(self):
 291        """Get the Fdt object for this node
 292
 293        Returns:
 294            Fdt object
 295        """
 296        return self._fdt
 297
 298    def FindNode(self, name):
 299        """Find a node given its name
 300
 301        Args:
 302            name: Node name to look for
 303        Returns:
 304            Node object if found, else None
 305        """
 306        for subnode in self.subnodes:
 307            if subnode.name == name:
 308                return subnode
 309        return None
 310
 311    def Offset(self):
 312        """Returns the offset of a node, after checking the cache
 313
 314        This should be used instead of self._offset directly, to ensure that
 315        the cache does not contain invalid offsets.
 316        """
 317        self._fdt.CheckCache()
 318        return self._offset
 319
 320    def Scan(self):
 321        """Scan a node's properties and subnodes
 322
 323        This fills in the props and subnodes properties, recursively
 324        searching into subnodes so that the entire tree is built.
 325        """
 326        fdt_obj = self._fdt._fdt_obj
 327        self.props = self._fdt.GetProps(self)
 328        phandle = fdt_obj.get_phandle(self.Offset())
 329        if phandle:
 330            self._fdt.phandle_to_node[phandle] = self
 331
 332        offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND)
 333        while offset >= 0:
 334            sep = '' if self.path[-1] == '/' else '/'
 335            name = fdt_obj.get_name(offset)
 336            path = self.path + sep + name
 337            node = Node(self._fdt, self, offset, name, path)
 338            self.subnodes.append(node)
 339
 340            node.Scan()
 341            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
 342
 343    def Refresh(self, my_offset):
 344        """Fix up the _offset for each node, recursively
 345
 346        Note: This does not take account of property offsets - these will not
 347        be updated.
 348        """
 349        fdt_obj = self._fdt._fdt_obj
 350        if self._offset != my_offset:
 351            self._offset = my_offset
 352        name = fdt_obj.get_name(self._offset)
 353        if name and self.name != name:
 354            raise ValueError("Internal error, node '%s' name mismatch '%s'" %
 355                             (self.path, name))
 356
 357        offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND)
 358        for subnode in self.subnodes:
 359            if subnode.name != fdt_obj.get_name(offset):
 360                raise ValueError('Internal error, node name mismatch %s != %s' %
 361                                 (subnode.name, fdt_obj.get_name(offset)))
 362            subnode.Refresh(offset)
 363            offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND)
 364        if offset != -libfdt.FDT_ERR_NOTFOUND:
 365            raise ValueError('Internal error, offset == %d' % offset)
 366
 367        poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND)
 368        while poffset >= 0:
 369            p = fdt_obj.get_property_by_offset(poffset)
 370            prop = self.props.get(p.name)
 371            if not prop:
 372                raise ValueError("Internal error, node '%s' property '%s' missing, "
 373                                 'offset %d' % (self.path, p.name, poffset))
 374            prop.RefreshOffset(poffset)
 375            poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND)
 376
 377    def DeleteProp(self, prop_name):
 378        """Delete a property of a node
 379
 380        The property is deleted and the offset cache is invalidated.
 381
 382        Args:
 383            prop_name: Name of the property to delete
 384        Raises:
 385            ValueError if the property does not exist
 386        """
 387        CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name),
 388                 "Node '%s': delete property: '%s'" % (self.path, prop_name))
 389        del self.props[prop_name]
 390        self._fdt.Invalidate()
 391
 392    def AddZeroProp(self, prop_name):
 393        """Add a new property to the device tree with an integer value of 0.
 394
 395        Args:
 396            prop_name: Name of property
 397        """
 398        self.props[prop_name] = Prop(self, None, prop_name,
 399                                     tools.GetBytes(0, 4))
 400
 401    def AddEmptyProp(self, prop_name, len):
 402        """Add a property with a fixed data size, for filling in later
 403
 404        The device tree is marked dirty so that the value will be written to
 405        the blob on the next sync.
 406
 407        Args:
 408            prop_name: Name of property
 409            len: Length of data in property
 410        """
 411        value = tools.GetBytes(0, len)
 412        self.props[prop_name] = Prop(self, None, prop_name, value)
 413
 414    def _CheckProp(self, prop_name):
 415        """Check if a property is present
 416
 417        Args:
 418            prop_name: Name of property
 419
 420        Returns:
 421            self
 422
 423        Raises:
 424            ValueError if the property is missing
 425        """
 426        if prop_name not in self.props:
 427            raise ValueError("Fdt '%s', node '%s': Missing property '%s'" %
 428                             (self._fdt._fname, self.path, prop_name))
 429        return self
 430
 431    def SetInt(self, prop_name, val):
 432        """Update an integer property int the device tree.
 433
 434        This is not allowed to change the size of the FDT.
 435
 436        The device tree is marked dirty so that the value will be written to
 437        the blob on the next sync.
 438
 439        Args:
 440            prop_name: Name of property
 441            val: Value to set
 442        """
 443        self._CheckProp(prop_name).props[prop_name].SetInt(val)
 444
 445    def SetData(self, prop_name, val):
 446        """Set the data value of a property
 447
 448        The device tree is marked dirty so that the value will be written to
 449        the blob on the next sync.
 450
 451        Args:
 452            prop_name: Name of property to set
 453            val: Data value to set
 454        """
 455        self._CheckProp(prop_name).props[prop_name].SetData(val)
 456
 457    def SetString(self, prop_name, val):
 458        """Set the string value of a property
 459
 460        The device tree is marked dirty so that the value will be written to
 461        the blob on the next sync.
 462
 463        Args:
 464            prop_name: Name of property to set
 465            val: String value to set (will be \0-terminated in DT)
 466        """
 467        if type(val) == str:
 468            val = val.encode('utf-8')
 469        self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0')
 470
 471    def AddData(self, prop_name, val):
 472        """Add a new property to a node
 473
 474        The device tree is marked dirty so that the value will be written to
 475        the blob on the next sync.
 476
 477        Args:
 478            prop_name: Name of property to add
 479            val: Bytes value of property
 480
 481        Returns:
 482            Prop added
 483        """
 484        prop = Prop(self, None, prop_name, val)
 485        self.props[prop_name] = prop
 486        return prop
 487
 488    def AddString(self, prop_name, val):
 489        """Add a new string property to a node
 490
 491        The device tree is marked dirty so that the value will be written to
 492        the blob on the next sync.
 493
 494        Args:
 495            prop_name: Name of property to add
 496            val: String value of property
 497
 498        Returns:
 499            Prop added
 500        """
 501        val = bytes(val, 'utf-8')
 502        return self.AddData(prop_name, val + b'\0')
 503
 504    def AddInt(self, prop_name, val):
 505        """Add a new integer property to a node
 506
 507        The device tree is marked dirty so that the value will be written to
 508        the blob on the next sync.
 509
 510        Args:
 511            prop_name: Name of property to add
 512            val: Integer value of property
 513
 514        Returns:
 515            Prop added
 516        """
 517        return self.AddData(prop_name, struct.pack('>I', val))
 518
 519    def AddSubnode(self, name):
 520        """Add a new subnode to the node
 521
 522        Args:
 523            name: name of node to add
 524
 525        Returns:
 526            New subnode that was created
 527        """
 528        path = self.path + '/' + name
 529        subnode = Node(self._fdt, self, None, name, path)
 530        self.subnodes.append(subnode)
 531        return subnode
 532
 533    def Sync(self, auto_resize=False):
 534        """Sync node changes back to the device tree
 535
 536        This updates the device tree blob with any changes to this node and its
 537        subnodes since the last sync.
 538
 539        Args:
 540            auto_resize: Resize the device tree automatically if it does not
 541                have enough space for the update
 542
 543        Returns:
 544            True if the node had to be added, False if it already existed
 545
 546        Raises:
 547            FdtException if auto_resize is False and there is not enough space
 548        """
 549        added = False
 550        if self._offset is None:
 551            # The subnode doesn't exist yet, so add it
 552            fdt_obj = self._fdt._fdt_obj
 553            if auto_resize:
 554                while True:
 555                    offset = fdt_obj.add_subnode(self.parent._offset, self.name,
 556                                                (libfdt.NOSPACE,))
 557                    if offset != -libfdt.NOSPACE:
 558                        break
 559                    fdt_obj.resize(fdt_obj.totalsize() + 1024)
 560            else:
 561                offset = fdt_obj.add_subnode(self.parent._offset, self.name)
 562            self._offset = offset
 563            added = True
 564
 565        # Sync the existing subnodes first, so that we can rely on the offsets
 566        # being correct. As soon as we add new subnodes, it pushes all the
 567        # existing subnodes up.
 568        for node in reversed(self.subnodes):
 569            if node._offset is not None:
 570                node.Sync(auto_resize)
 571
 572        # Sync subnodes in reverse so that we get the expected order. Each
 573        # new node goes at the start of the subnode list. This avoids an O(n^2)
 574        # rescan of node offsets.
 575        num_added = 0
 576        for node in reversed(self.subnodes):
 577            if node.Sync(auto_resize):
 578                num_added += 1
 579        if num_added:
 580            # Reorder our list of nodes to put the new ones first, since that's
 581            # what libfdt does
 582            old_count = len(self.subnodes) - num_added
 583            subnodes = self.subnodes[old_count:] + self.subnodes[:old_count]
 584            self.subnodes = subnodes
 585
 586        # Sync properties now, whose offsets should not have been disturbed,
 587        # since properties come before subnodes. This is done after all the
 588        # subnode processing above, since updating properties can disturb the
 589        # offsets of those subnodes.
 590        # Properties are synced in reverse order, with new properties added
 591        # before existing properties are synced. This ensures that the offsets
 592        # of earlier properties are not disturbed.
 593        # Note that new properties will have an offset of None here, which
 594        # Python cannot sort against int. So use a large value instead so that
 595        # new properties are added first.
 596        prop_list = sorted(self.props.values(),
 597                           key=lambda prop: prop._offset or 1 << 31,
 598                           reverse=True)
 599        for prop in prop_list:
 600            prop.Sync(auto_resize)
 601        return added
 602
 603
 604class Fdt:
 605    """Provides simple access to a flat device tree blob using libfdts.
 606
 607    Properties:
 608      fname: Filename of fdt
 609      _root: Root of device tree (a Node object)
 610      name: Helpful name for this Fdt for the user (useful when creating the
 611        DT from data rather than a file)
 612    """
 613    def __init__(self, fname):
 614        self._fname = fname
 615        self._cached_offsets = False
 616        self.phandle_to_node = {}
 617        self.name = ''
 618        if self._fname:
 619            self.name = self._fname
 620            self._fname = fdt_util.EnsureCompiled(self._fname)
 621
 622            with open(self._fname, 'rb') as fd:
 623                self._fdt_obj = libfdt.Fdt(fd.read())
 624
 625    @staticmethod
 626    def FromData(data, name=''):
 627        """Create a new Fdt object from the given data
 628
 629        Args:
 630            data: Device-tree data blob
 631            name: Helpful name for this Fdt for the user
 632
 633        Returns:
 634            Fdt object containing the data
 635        """
 636        fdt = Fdt(None)
 637        fdt._fdt_obj = libfdt.Fdt(bytes(data))
 638        fdt.name = name
 639        return fdt
 640
 641    def LookupPhandle(self, phandle):
 642        """Look up a phandle
 643
 644        Args:
 645            phandle: Phandle to look up (int)
 646
 647        Returns:
 648            Node object the phandle points to
 649        """
 650        return self.phandle_to_node.get(phandle)
 651
 652    def Scan(self, root='/'):
 653        """Scan a device tree, building up a tree of Node objects
 654
 655        This fills in the self._root property
 656
 657        Args:
 658            root: Ignored
 659
 660        TODO(sjg@chromium.org): Implement the 'root' parameter
 661        """
 662        self._cached_offsets = True
 663        self._root = self.Node(self, None, 0, '/', '/')
 664        self._root.Scan()
 665
 666    def GetRoot(self):
 667        """Get the root Node of the device tree
 668
 669        Returns:
 670            The root Node object
 671        """
 672        return self._root
 673
 674    def GetNode(self, path):
 675        """Look up a node from its path
 676
 677        Args:
 678            path: Path to look up, e.g. '/microcode/update@0'
 679        Returns:
 680            Node object, or None if not found
 681        """
 682        node = self._root
 683        parts = path.split('/')
 684        if len(parts) < 2:
 685            return None
 686        if len(parts) == 2 and parts[1] == '':
 687            return node
 688        for part in parts[1:]:
 689            node = node.FindNode(part)
 690            if not node:
 691                return None
 692        return node
 693
 694    def Flush(self):
 695        """Flush device tree changes back to the file
 696
 697        If the device tree has changed in memory, write it back to the file.
 698        """
 699        with open(self._fname, 'wb') as fd:
 700            fd.write(self._fdt_obj.as_bytearray())
 701
 702    def Sync(self, auto_resize=False):
 703        """Make sure any DT changes are written to the blob
 704
 705        Args:
 706            auto_resize: Resize the device tree automatically if it does not
 707                have enough space for the update
 708
 709        Raises:
 710            FdtException if auto_resize is False and there is not enough space
 711        """
 712        self.CheckCache()
 713        self._root.Sync(auto_resize)
 714        self.Refresh()
 715
 716    def Pack(self):
 717        """Pack the device tree down to its minimum size
 718
 719        When nodes and properties shrink or are deleted, wasted space can
 720        build up in the device tree binary.
 721        """
 722        CheckErr(self._fdt_obj.pack(), 'pack')
 723        self.Refresh()
 724
 725    def GetContents(self):
 726        """Get the contents of the FDT
 727
 728        Returns:
 729            The FDT contents as a string of bytes
 730        """
 731        return bytes(self._fdt_obj.as_bytearray())
 732
 733    def GetFdtObj(self):
 734        """Get the contents of the FDT
 735
 736        Returns:
 737            The FDT contents as a libfdt.Fdt object
 738        """
 739        return self._fdt_obj
 740
 741    def GetProps(self, node):
 742        """Get all properties from a node.
 743
 744        Args:
 745            node: Full path to node name to look in.
 746
 747        Returns:
 748            A dictionary containing all the properties, indexed by node name.
 749            The entries are Prop objects.
 750
 751        Raises:
 752            ValueError: if the node does not exist.
 753        """
 754        props_dict = {}
 755        poffset = self._fdt_obj.first_property_offset(node._offset,
 756                                                      QUIET_NOTFOUND)
 757        while poffset >= 0:
 758            p = self._fdt_obj.get_property_by_offset(poffset)
 759            prop = Prop(node, poffset, p.name, p)
 760            props_dict[prop.name] = prop
 761
 762            poffset = self._fdt_obj.next_property_offset(poffset,
 763                                                         QUIET_NOTFOUND)
 764        return props_dict
 765
 766    def Invalidate(self):
 767        """Mark our offset cache as invalid"""
 768        self._cached_offsets = False
 769
 770    def CheckCache(self):
 771        """Refresh the offset cache if needed"""
 772        if self._cached_offsets:
 773            return
 774        self.Refresh()
 775
 776    def Refresh(self):
 777        """Refresh the offset cache"""
 778        self._root.Refresh(0)
 779        self._cached_offsets = True
 780
 781    def GetStructOffset(self, offset):
 782        """Get the file offset of a given struct offset
 783
 784        Args:
 785            offset: Offset within the 'struct' region of the device tree
 786        Returns:
 787            Position of @offset within the device tree binary
 788        """
 789        return self._fdt_obj.off_dt_struct() + offset
 790
 791    @classmethod
 792    def Node(self, fdt, parent, offset, name, path):
 793        """Create a new node
 794
 795        This is used by Fdt.Scan() to create a new node using the correct
 796        class.
 797
 798        Args:
 799            fdt: Fdt object
 800            parent: Parent node, or None if this is the root node
 801            offset: Offset of node
 802            name: Node name
 803            path: Full path to node
 804        """
 805        node = Node(fdt, parent, offset, name, path)
 806        return node
 807
 808    def GetFilename(self):
 809        """Get the filename of the device tree
 810
 811        Returns:
 812            String filename
 813        """
 814        return self._fname
 815
 816def FdtScan(fname):
 817    """Returns a new Fdt object"""
 818    dtb = Fdt(fname)
 819    dtb.Scan()
 820    return dtb
 821