Home | History | Annotate | Download | only in dtoc
      1 #!/usr/bin/python
      2 # SPDX-License-Identifier: GPL-2.0+
      3 #
      4 # Copyright (C) 2016 Google, Inc
      5 # Written by Simon Glass <sjg (at] chromium.org>
      6 #
      7 
      8 import struct
      9 import sys
     10 
     11 import fdt_util
     12 import libfdt
     13 
     14 # This deals with a device tree, presenting it as an assortment of Node and
     15 # Prop objects, representing nodes and properties, respectively. This file
     16 # contains the base classes and defines the high-level API. You can use
     17 # FdtScan() as a convenience function to create and scan an Fdt.
     18 
     19 # This implementation uses a libfdt Python library to access the device tree,
     20 # so it is fairly efficient.
     21 
     22 # A list of types we support
     23 (TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5)
     24 
     25 def CheckErr(errnum, msg):
     26     if errnum:
     27         raise ValueError('Error %d: %s: %s' %
     28             (errnum, libfdt.fdt_strerror(errnum), msg))
     29 
     30 class Prop:
     31     """A device tree property
     32 
     33     Properties:
     34         name: Property name (as per the device tree)
     35         value: Property value as a string of bytes, or a list of strings of
     36             bytes
     37         type: Value type
     38     """
     39     def __init__(self, node, offset, name, bytes):
     40         self._node = node
     41         self._offset = offset
     42         self.name = name
     43         self.value = None
     44         self.bytes = str(bytes)
     45         if not bytes:
     46             self.type = TYPE_BOOL
     47             self.value = True
     48             return
     49         self.type, self.value = self.BytesToValue(bytes)
     50 
     51     def GetPhandle(self):
     52         """Get a (single) phandle value from a property
     53 
     54         Gets the phandle valuie from a property and returns it as an integer
     55         """
     56         return fdt_util.fdt32_to_cpu(self.value[:4])
     57 
     58     def Widen(self, newprop):
     59         """Figure out which property type is more general
     60 
     61         Given a current property and a new property, this function returns the
     62         one that is less specific as to type. The less specific property will
     63         be ble to represent the data in the more specific property. This is
     64         used for things like:
     65 
     66             node1 {
     67                 compatible = "fred";
     68                 value = <1>;
     69             };
     70             node1 {
     71                 compatible = "fred";
     72                 value = <1 2>;
     73             };
     74 
     75         He we want to use an int array for 'value'. The first property
     76         suggests that a single int is enough, but the second one shows that
     77         it is not. Calling this function with these two propertes would
     78         update the current property to be like the second, since it is less
     79         specific.
     80         """
     81         if newprop.type < self.type:
     82             self.type = newprop.type
     83 
     84         if type(newprop.value) == list and type(self.value) != list:
     85             self.value = [self.value]
     86 
     87         if type(self.value) == list and len(newprop.value) > len(self.value):
     88             val = self.GetEmpty(self.type)
     89             while len(self.value) < len(newprop.value):
     90                 self.value.append(val)
     91 
     92     def BytesToValue(self, bytes):
     93         """Converts a string of bytes into a type and value
     94 
     95         Args:
     96             A string containing bytes
     97 
     98         Return:
     99             A tuple:
    100                 Type of data
    101                 Data, either a single element or a list of elements. Each element
    102                 is one of:
    103                     TYPE_STRING: string value from the property
    104                     TYPE_INT: a byte-swapped integer stored as a 4-byte string
    105                     TYPE_BYTE: a byte stored as a single-byte string
    106         """
    107         bytes = str(bytes)
    108         size = len(bytes)
    109         strings = bytes.split('\0')
    110         is_string = True
    111         count = len(strings) - 1
    112         if count > 0 and not strings[-1]:
    113             for string in strings[:-1]:
    114                 if not string:
    115                     is_string = False
    116                     break
    117                 for ch in string:
    118                     if ch < ' ' or ch > '~':
    119                         is_string = False
    120                         break
    121         else:
    122             is_string = False
    123         if is_string:
    124             if count == 1:
    125                 return TYPE_STRING, strings[0]
    126             else:
    127                 return TYPE_STRING, strings[:-1]
    128         if size % 4:
    129             if size == 1:
    130                 return TYPE_BYTE, bytes[0]
    131             else:
    132                 return TYPE_BYTE, list(bytes)
    133         val = []
    134         for i in range(0, size, 4):
    135             val.append(bytes[i:i + 4])
    136         if size == 4:
    137             return TYPE_INT, val[0]
    138         else:
    139             return TYPE_INT, val
    140 
    141     def GetEmpty(self, type):
    142         """Get an empty / zero value of the given type
    143 
    144         Returns:
    145             A single value of the given type
    146         """
    147         if type == TYPE_BYTE:
    148             return chr(0)
    149         elif type == TYPE_INT:
    150             return struct.pack('<I', 0);
    151         elif type == TYPE_STRING:
    152             return ''
    153         else:
    154             return True
    155 
    156     def GetOffset(self):
    157         """Get the offset of a property
    158 
    159         Returns:
    160             The offset of the property (struct fdt_property) within the file
    161         """
    162         return self._node._fdt.GetStructOffset(self._offset)
    163 
    164 class Node:
    165     """A device tree node
    166 
    167     Properties:
    168         offset: Integer offset in the device tree
    169         name: Device tree node tname
    170         path: Full path to node, along with the node name itself
    171         _fdt: Device tree object
    172         subnodes: A list of subnodes for this node, each a Node object
    173         props: A dict of properties for this node, each a Prop object.
    174             Keyed by property name
    175     """
    176     def __init__(self, fdt, parent, offset, name, path):
    177         self._fdt = fdt
    178         self.parent = parent
    179         self._offset = offset
    180         self.name = name
    181         self.path = path
    182         self.subnodes = []
    183         self.props = {}
    184 
    185     def _FindNode(self, name):
    186         """Find a node given its name
    187 
    188         Args:
    189             name: Node name to look for
    190         Returns:
    191             Node object if found, else None
    192         """
    193         for subnode in self.subnodes:
    194             if subnode.name == name:
    195                 return subnode
    196         return None
    197 
    198     def Offset(self):
    199         """Returns the offset of a node, after checking the cache
    200 
    201         This should be used instead of self._offset directly, to ensure that
    202         the cache does not contain invalid offsets.
    203         """
    204         self._fdt.CheckCache()
    205         return self._offset
    206 
    207     def Scan(self):
    208         """Scan a node's properties and subnodes
    209 
    210         This fills in the props and subnodes properties, recursively
    211         searching into subnodes so that the entire tree is built.
    212         """
    213         self.props = self._fdt.GetProps(self)
    214         phandle = self.props.get('phandle')
    215         if phandle:
    216             val = fdt_util.fdt32_to_cpu(phandle.value)
    217             self._fdt.phandle_to_node[val] = self
    218 
    219         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset())
    220         while offset >= 0:
    221             sep = '' if self.path[-1] == '/' else '/'
    222             name = self._fdt._fdt_obj.get_name(offset)
    223             path = self.path + sep + name
    224             node = Node(self._fdt, self, offset, name, path)
    225             self.subnodes.append(node)
    226 
    227             node.Scan()
    228             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
    229 
    230     def Refresh(self, my_offset):
    231         """Fix up the _offset for each node, recursively
    232 
    233         Note: This does not take account of property offsets - these will not
    234         be updated.
    235         """
    236         if self._offset != my_offset:
    237             #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset)
    238             self._offset = my_offset
    239         offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset)
    240         for subnode in self.subnodes:
    241             subnode.Refresh(offset)
    242             offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset)
    243 
    244     def DeleteProp(self, prop_name):
    245         """Delete a property of a node
    246 
    247         The property is deleted and the offset cache is invalidated.
    248 
    249         Args:
    250             prop_name: Name of the property to delete
    251         Raises:
    252             ValueError if the property does not exist
    253         """
    254         CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name),
    255                  "Node '%s': delete property: '%s'" % (self.path, prop_name))
    256         del self.props[prop_name]
    257         self._fdt.Invalidate()
    258 
    259 class Fdt:
    260     """Provides simple access to a flat device tree blob using libfdts.
    261 
    262     Properties:
    263       fname: Filename of fdt
    264       _root: Root of device tree (a Node object)
    265     """
    266     def __init__(self, fname):
    267         self._fname = fname
    268         self._cached_offsets = False
    269         self.phandle_to_node = {}
    270         if self._fname:
    271             self._fname = fdt_util.EnsureCompiled(self._fname)
    272 
    273             with open(self._fname) as fd:
    274                 self._fdt = bytearray(fd.read())
    275                 self._fdt_obj = libfdt.Fdt(self._fdt)
    276 
    277     def Scan(self, root='/'):
    278         """Scan a device tree, building up a tree of Node objects
    279 
    280         This fills in the self._root property
    281 
    282         Args:
    283             root: Ignored
    284 
    285         TODO(sjg (at] chromium.org): Implement the 'root' parameter
    286         """
    287         self._root = self.Node(self, None, 0, '/', '/')
    288         self._root.Scan()
    289 
    290     def GetRoot(self):
    291         """Get the root Node of the device tree
    292 
    293         Returns:
    294             The root Node object
    295         """
    296         return self._root
    297 
    298     def GetNode(self, path):
    299         """Look up a node from its path
    300 
    301         Args:
    302             path: Path to look up, e.g. '/microcode/update@0'
    303         Returns:
    304             Node object, or None if not found
    305         """
    306         node = self._root
    307         for part in path.split('/')[1:]:
    308             node = node._FindNode(part)
    309             if not node:
    310                 return None
    311         return node
    312 
    313     def Flush(self):
    314         """Flush device tree changes back to the file
    315 
    316         If the device tree has changed in memory, write it back to the file.
    317         """
    318         with open(self._fname, 'wb') as fd:
    319             fd.write(self._fdt)
    320 
    321     def Pack(self):
    322         """Pack the device tree down to its minimum size
    323 
    324         When nodes and properties shrink or are deleted, wasted space can
    325         build up in the device tree binary.
    326         """
    327         CheckErr(libfdt.fdt_pack(self._fdt), 'pack')
    328         fdt_len = libfdt.fdt_totalsize(self._fdt)
    329         del self._fdt[fdt_len:]
    330 
    331     def GetFdt(self):
    332         """Get the contents of the FDT
    333 
    334         Returns:
    335             The FDT contents as a string of bytes
    336         """
    337         return self._fdt
    338 
    339     def CheckErr(errnum, msg):
    340         if errnum:
    341             raise ValueError('Error %d: %s: %s' %
    342                 (errnum, libfdt.fdt_strerror(errnum), msg))
    343 
    344 
    345     def GetProps(self, node):
    346         """Get all properties from a node.
    347 
    348         Args:
    349             node: Full path to node name to look in.
    350 
    351         Returns:
    352             A dictionary containing all the properties, indexed by node name.
    353             The entries are Prop objects.
    354 
    355         Raises:
    356             ValueError: if the node does not exist.
    357         """
    358         props_dict = {}
    359         poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset)
    360         while poffset >= 0:
    361             p = self._fdt_obj.get_property_by_offset(poffset)
    362             prop = Prop(node, poffset, p.name, p.value)
    363             props_dict[prop.name] = prop
    364 
    365             poffset = libfdt.fdt_next_property_offset(self._fdt, poffset)
    366         return props_dict
    367 
    368     def Invalidate(self):
    369         """Mark our offset cache as invalid"""
    370         self._cached_offsets = False
    371 
    372     def CheckCache(self):
    373         """Refresh the offset cache if needed"""
    374         if self._cached_offsets:
    375             return
    376         self.Refresh()
    377         self._cached_offsets = True
    378 
    379     def Refresh(self):
    380         """Refresh the offset cache"""
    381         self._root.Refresh(0)
    382 
    383     def GetStructOffset(self, offset):
    384         """Get the file offset of a given struct offset
    385 
    386         Args:
    387             offset: Offset within the 'struct' region of the device tree
    388         Returns:
    389             Position of @offset within the device tree binary
    390         """
    391         return libfdt.fdt_off_dt_struct(self._fdt) + offset
    392 
    393     @classmethod
    394     def Node(self, fdt, parent, offset, name, path):
    395         """Create a new node
    396 
    397         This is used by Fdt.Scan() to create a new node using the correct
    398         class.
    399 
    400         Args:
    401             fdt: Fdt object
    402             parent: Parent node, or None if this is the root node
    403             offset: Offset of node
    404             name: Node name
    405             path: Full path to node
    406         """
    407         node = Node(fdt, parent, offset, name, path)
    408         return node
    409 
    410 def FdtScan(fname):
    411     """Returns a new Fdt object from the implementation we are using"""
    412     dtb = Fdt(fname)
    413     dtb.Scan()
    414     return dtb
    415