1a06a34b2SSimon Glass#!/usr/bin/python 2a06a34b2SSimon Glass# 3a06a34b2SSimon Glass# Copyright (C) 2016 Google, Inc 4a06a34b2SSimon Glass# Written by Simon Glass <sjg@chromium.org> 5a06a34b2SSimon Glass# 6a06a34b2SSimon Glass# SPDX-License-Identifier: GPL-2.0+ 7a06a34b2SSimon Glass# 8a06a34b2SSimon Glass 9a06a34b2SSimon Glassimport struct 10a06a34b2SSimon Glassimport sys 11a06a34b2SSimon Glass 12a06a34b2SSimon Glassimport fdt_util 137b75b448SSimon Glassimport libfdt 14a06a34b2SSimon Glass 15a06a34b2SSimon Glass# This deals with a device tree, presenting it as an assortment of Node and 16a06a34b2SSimon Glass# Prop objects, representing nodes and properties, respectively. This file 1799ed4a2eSSimon Glass# contains the base classes and defines the high-level API. You can use 1899ed4a2eSSimon Glass# FdtScan() as a convenience function to create and scan an Fdt. 197b75b448SSimon Glass 207b75b448SSimon Glass# This implementation uses a libfdt Python library to access the device tree, 217b75b448SSimon Glass# so it is fairly efficient. 22a06a34b2SSimon Glass 23bc1dea36SSimon Glass# A list of types we support 24ef2715f4SSimon Glass(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL, TYPE_INT64) = range(5) 25bc1dea36SSimon Glass 26a06a34b2SSimon Glassdef CheckErr(errnum, msg): 27a06a34b2SSimon Glass if errnum: 28a06a34b2SSimon Glass raise ValueError('Error %d: %s: %s' % 29a06a34b2SSimon Glass (errnum, libfdt.fdt_strerror(errnum), msg)) 30a06a34b2SSimon Glass 317b75b448SSimon Glassclass Prop: 32a06a34b2SSimon Glass """A device tree property 33a06a34b2SSimon Glass 34a06a34b2SSimon Glass Properties: 35a06a34b2SSimon Glass name: Property name (as per the device tree) 36a06a34b2SSimon Glass value: Property value as a string of bytes, or a list of strings of 37a06a34b2SSimon Glass bytes 38a06a34b2SSimon Glass type: Value type 39a06a34b2SSimon Glass """ 407b75b448SSimon Glass def __init__(self, node, offset, name, bytes): 41a06a34b2SSimon Glass self._node = node 42a06a34b2SSimon Glass self._offset = offset 43a06a34b2SSimon Glass self.name = name 44a06a34b2SSimon Glass self.value = None 457b75b448SSimon Glass self.bytes = str(bytes) 467b75b448SSimon Glass if not bytes: 477b75b448SSimon Glass self.type = TYPE_BOOL 487b75b448SSimon Glass self.value = True 497b75b448SSimon Glass return 507b75b448SSimon Glass self.type, self.value = self.BytesToValue(bytes) 51a06a34b2SSimon Glass 52c322a850SSimon Glass def GetPhandle(self): 53c322a850SSimon Glass """Get a (single) phandle value from a property 54c322a850SSimon Glass 55c322a850SSimon Glass Gets the phandle valuie from a property and returns it as an integer 56c322a850SSimon Glass """ 57c322a850SSimon Glass return fdt_util.fdt32_to_cpu(self.value[:4]) 58c322a850SSimon Glass 59c322a850SSimon Glass def Widen(self, newprop): 60c322a850SSimon Glass """Figure out which property type is more general 61c322a850SSimon Glass 62c322a850SSimon Glass Given a current property and a new property, this function returns the 63c322a850SSimon Glass one that is less specific as to type. The less specific property will 64c322a850SSimon Glass be ble to represent the data in the more specific property. This is 65c322a850SSimon Glass used for things like: 66c322a850SSimon Glass 67c322a850SSimon Glass node1 { 68c322a850SSimon Glass compatible = "fred"; 69c322a850SSimon Glass value = <1>; 70c322a850SSimon Glass }; 71c322a850SSimon Glass node1 { 72c322a850SSimon Glass compatible = "fred"; 73c322a850SSimon Glass value = <1 2>; 74c322a850SSimon Glass }; 75c322a850SSimon Glass 76c322a850SSimon Glass He we want to use an int array for 'value'. The first property 77c322a850SSimon Glass suggests that a single int is enough, but the second one shows that 78c322a850SSimon Glass it is not. Calling this function with these two propertes would 79c322a850SSimon Glass update the current property to be like the second, since it is less 80c322a850SSimon Glass specific. 81c322a850SSimon Glass """ 82c322a850SSimon Glass if newprop.type < self.type: 83c322a850SSimon Glass self.type = newprop.type 84c322a850SSimon Glass 85c322a850SSimon Glass if type(newprop.value) == list and type(self.value) != list: 86c322a850SSimon Glass self.value = [self.value] 87c322a850SSimon Glass 88c322a850SSimon Glass if type(self.value) == list and len(newprop.value) > len(self.value): 89c322a850SSimon Glass val = self.GetEmpty(self.type) 90c322a850SSimon Glass while len(self.value) < len(newprop.value): 91c322a850SSimon Glass self.value.append(val) 92c322a850SSimon Glass 93bc1dea36SSimon Glass def BytesToValue(self, bytes): 94bc1dea36SSimon Glass """Converts a string of bytes into a type and value 95bc1dea36SSimon Glass 96bc1dea36SSimon Glass Args: 97bc1dea36SSimon Glass A string containing bytes 98bc1dea36SSimon Glass 99bc1dea36SSimon Glass Return: 100bc1dea36SSimon Glass A tuple: 101bc1dea36SSimon Glass Type of data 102bc1dea36SSimon Glass Data, either a single element or a list of elements. Each element 103bc1dea36SSimon Glass is one of: 104bc1dea36SSimon Glass TYPE_STRING: string value from the property 105bc1dea36SSimon Glass TYPE_INT: a byte-swapped integer stored as a 4-byte string 106bc1dea36SSimon Glass TYPE_BYTE: a byte stored as a single-byte string 107bc1dea36SSimon Glass """ 108b4360206SSimon Glass bytes = str(bytes) 109bc1dea36SSimon Glass size = len(bytes) 110bc1dea36SSimon Glass strings = bytes.split('\0') 111bc1dea36SSimon Glass is_string = True 112bc1dea36SSimon Glass count = len(strings) - 1 113bc1dea36SSimon Glass if count > 0 and not strings[-1]: 114bc1dea36SSimon Glass for string in strings[:-1]: 115bc1dea36SSimon Glass if not string: 116bc1dea36SSimon Glass is_string = False 117bc1dea36SSimon Glass break 118bc1dea36SSimon Glass for ch in string: 119bc1dea36SSimon Glass if ch < ' ' or ch > '~': 120bc1dea36SSimon Glass is_string = False 121bc1dea36SSimon Glass break 122bc1dea36SSimon Glass else: 123bc1dea36SSimon Glass is_string = False 124bc1dea36SSimon Glass if is_string: 125bc1dea36SSimon Glass if count == 1: 126bc1dea36SSimon Glass return TYPE_STRING, strings[0] 127bc1dea36SSimon Glass else: 128bc1dea36SSimon Glass return TYPE_STRING, strings[:-1] 129bc1dea36SSimon Glass if size % 4: 130bc1dea36SSimon Glass if size == 1: 131bc1dea36SSimon Glass return TYPE_BYTE, bytes[0] 132bc1dea36SSimon Glass else: 133bc1dea36SSimon Glass return TYPE_BYTE, list(bytes) 134bc1dea36SSimon Glass val = [] 135bc1dea36SSimon Glass for i in range(0, size, 4): 136bc1dea36SSimon Glass val.append(bytes[i:i + 4]) 137bc1dea36SSimon Glass if size == 4: 138bc1dea36SSimon Glass return TYPE_INT, val[0] 139bc1dea36SSimon Glass else: 140bc1dea36SSimon Glass return TYPE_INT, val 141bc1dea36SSimon Glass 142bc1dea36SSimon Glass def GetEmpty(self, type): 143bc1dea36SSimon Glass """Get an empty / zero value of the given type 144bc1dea36SSimon Glass 145bc1dea36SSimon Glass Returns: 146bc1dea36SSimon Glass A single value of the given type 147bc1dea36SSimon Glass """ 148bc1dea36SSimon Glass if type == TYPE_BYTE: 149bc1dea36SSimon Glass return chr(0) 150bc1dea36SSimon Glass elif type == TYPE_INT: 151bc1dea36SSimon Glass return struct.pack('<I', 0); 152bc1dea36SSimon Glass elif type == TYPE_STRING: 153bc1dea36SSimon Glass return '' 154bc1dea36SSimon Glass else: 155bc1dea36SSimon Glass return True 156bc1dea36SSimon Glass 157babdbde6SSimon Glass def GetOffset(self): 158babdbde6SSimon Glass """Get the offset of a property 159babdbde6SSimon Glass 160babdbde6SSimon Glass Returns: 1617b75b448SSimon Glass The offset of the property (struct fdt_property) within the file 162babdbde6SSimon Glass """ 1637b75b448SSimon Glass return self._node._fdt.GetStructOffset(self._offset) 164babdbde6SSimon Glass 1657b75b448SSimon Glassclass Node: 166a06a34b2SSimon Glass """A device tree node 167a06a34b2SSimon Glass 168a06a34b2SSimon Glass Properties: 169a06a34b2SSimon Glass offset: Integer offset in the device tree 170a06a34b2SSimon Glass name: Device tree node tname 171a06a34b2SSimon Glass path: Full path to node, along with the node name itself 172a06a34b2SSimon Glass _fdt: Device tree object 173a06a34b2SSimon Glass subnodes: A list of subnodes for this node, each a Node object 174a06a34b2SSimon Glass props: A dict of properties for this node, each a Prop object. 175a06a34b2SSimon Glass Keyed by property name 176a06a34b2SSimon Glass """ 177f08a0424SSimon Glass def __init__(self, fdt, parent, offset, name, path): 178a06a34b2SSimon Glass self._fdt = fdt 179f08a0424SSimon Glass self.parent = parent 180a06a34b2SSimon Glass self._offset = offset 181a06a34b2SSimon Glass self.name = name 182a06a34b2SSimon Glass self.path = path 183a06a34b2SSimon Glass self.subnodes = [] 184a06a34b2SSimon Glass self.props = {} 185a06a34b2SSimon Glass 186f7a2aeeeSSimon Glass def _FindNode(self, name): 187f7a2aeeeSSimon Glass """Find a node given its name 188f7a2aeeeSSimon Glass 189f7a2aeeeSSimon Glass Args: 190f7a2aeeeSSimon Glass name: Node name to look for 191f7a2aeeeSSimon Glass Returns: 192f7a2aeeeSSimon Glass Node object if found, else None 193f7a2aeeeSSimon Glass """ 194f7a2aeeeSSimon Glass for subnode in self.subnodes: 195f7a2aeeeSSimon Glass if subnode.name == name: 196f7a2aeeeSSimon Glass return subnode 197f7a2aeeeSSimon Glass return None 198f7a2aeeeSSimon Glass 1997b75b448SSimon Glass def Offset(self): 2007b75b448SSimon Glass """Returns the offset of a node, after checking the cache 201f7a2aeeeSSimon Glass 2027b75b448SSimon Glass This should be used instead of self._offset directly, to ensure that 2037b75b448SSimon Glass the cache does not contain invalid offsets. 204f7a2aeeeSSimon Glass """ 2057b75b448SSimon Glass self._fdt.CheckCache() 2067b75b448SSimon Glass return self._offset 2077b75b448SSimon Glass 2087b75b448SSimon Glass def Scan(self): 2097b75b448SSimon Glass """Scan a node's properties and subnodes 2107b75b448SSimon Glass 2117b75b448SSimon Glass This fills in the props and subnodes properties, recursively 2127b75b448SSimon Glass searching into subnodes so that the entire tree is built. 2137b75b448SSimon Glass """ 2147b75b448SSimon Glass self.props = self._fdt.GetProps(self) 215*5a30597aSSimon Glass phandle = self.props.get('phandle') 216*5a30597aSSimon Glass if phandle: 217*5a30597aSSimon Glass val = fdt_util.fdt32_to_cpu(phandle.value) 218*5a30597aSSimon Glass self._fdt.phandle_to_node[val] = self 2197b75b448SSimon Glass 2207b75b448SSimon Glass offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset()) 2217b75b448SSimon Glass while offset >= 0: 2227b75b448SSimon Glass sep = '' if self.path[-1] == '/' else '/' 2237b75b448SSimon Glass name = self._fdt._fdt_obj.get_name(offset) 2247b75b448SSimon Glass path = self.path + sep + name 225f08a0424SSimon Glass node = Node(self._fdt, self, offset, name, path) 2267b75b448SSimon Glass self.subnodes.append(node) 2277b75b448SSimon Glass 2287b75b448SSimon Glass node.Scan() 2297b75b448SSimon Glass offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) 2307b75b448SSimon Glass 2317b75b448SSimon Glass def Refresh(self, my_offset): 2327b75b448SSimon Glass """Fix up the _offset for each node, recursively 2337b75b448SSimon Glass 2347b75b448SSimon Glass Note: This does not take account of property offsets - these will not 2357b75b448SSimon Glass be updated. 2367b75b448SSimon Glass """ 2377b75b448SSimon Glass if self._offset != my_offset: 2387b75b448SSimon Glass #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset) 2397b75b448SSimon Glass self._offset = my_offset 2407b75b448SSimon Glass offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset) 2417b75b448SSimon Glass for subnode in self.subnodes: 2427b75b448SSimon Glass subnode.Refresh(offset) 2437b75b448SSimon Glass offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) 244f7a2aeeeSSimon Glass 2452a70d897SSimon Glass def DeleteProp(self, prop_name): 2462a70d897SSimon Glass """Delete a property of a node 2472a70d897SSimon Glass 2487b75b448SSimon Glass The property is deleted and the offset cache is invalidated. 2492a70d897SSimon Glass 2502a70d897SSimon Glass Args: 2512a70d897SSimon Glass prop_name: Name of the property to delete 2527b75b448SSimon Glass Raises: 2537b75b448SSimon Glass ValueError if the property does not exist 2542a70d897SSimon Glass """ 2557b75b448SSimon Glass CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name), 2567b75b448SSimon Glass "Node '%s': delete property: '%s'" % (self.path, prop_name)) 2577b75b448SSimon Glass del self.props[prop_name] 2587b75b448SSimon Glass self._fdt.Invalidate() 2592a70d897SSimon Glass 260a06a34b2SSimon Glassclass Fdt: 2617b75b448SSimon Glass """Provides simple access to a flat device tree blob using libfdts. 262a06a34b2SSimon Glass 263a06a34b2SSimon Glass Properties: 264a06a34b2SSimon Glass fname: Filename of fdt 265a06a34b2SSimon Glass _root: Root of device tree (a Node object) 266a06a34b2SSimon Glass """ 267a06a34b2SSimon Glass def __init__(self, fname): 268a06a34b2SSimon Glass self._fname = fname 2697b75b448SSimon Glass self._cached_offsets = False 270*5a30597aSSimon Glass self.phandle_to_node = {} 2717b75b448SSimon Glass if self._fname: 2727b75b448SSimon Glass self._fname = fdt_util.EnsureCompiled(self._fname) 2737b75b448SSimon Glass 2747b75b448SSimon Glass with open(self._fname) as fd: 2757b75b448SSimon Glass self._fdt = bytearray(fd.read()) 2767b75b448SSimon Glass self._fdt_obj = libfdt.Fdt(self._fdt) 277f7a2aeeeSSimon Glass 278f7a2aeeeSSimon Glass def Scan(self, root='/'): 279f7a2aeeeSSimon Glass """Scan a device tree, building up a tree of Node objects 280f7a2aeeeSSimon Glass 281f7a2aeeeSSimon Glass This fills in the self._root property 282f7a2aeeeSSimon Glass 283f7a2aeeeSSimon Glass Args: 284f7a2aeeeSSimon Glass root: Ignored 285f7a2aeeeSSimon Glass 286f7a2aeeeSSimon Glass TODO(sjg@chromium.org): Implement the 'root' parameter 287f7a2aeeeSSimon Glass """ 288f08a0424SSimon Glass self._root = self.Node(self, None, 0, '/', '/') 289f7a2aeeeSSimon Glass self._root.Scan() 290f7a2aeeeSSimon Glass 291f7a2aeeeSSimon Glass def GetRoot(self): 292f7a2aeeeSSimon Glass """Get the root Node of the device tree 293f7a2aeeeSSimon Glass 294f7a2aeeeSSimon Glass Returns: 295f7a2aeeeSSimon Glass The root Node object 296f7a2aeeeSSimon Glass """ 297f7a2aeeeSSimon Glass return self._root 298f7a2aeeeSSimon Glass 299f7a2aeeeSSimon Glass def GetNode(self, path): 300f7a2aeeeSSimon Glass """Look up a node from its path 301f7a2aeeeSSimon Glass 302f7a2aeeeSSimon Glass Args: 303f7a2aeeeSSimon Glass path: Path to look up, e.g. '/microcode/update@0' 304f7a2aeeeSSimon Glass Returns: 305f7a2aeeeSSimon Glass Node object, or None if not found 306f7a2aeeeSSimon Glass """ 307f7a2aeeeSSimon Glass node = self._root 308f7a2aeeeSSimon Glass for part in path.split('/')[1:]: 309f7a2aeeeSSimon Glass node = node._FindNode(part) 310f7a2aeeeSSimon Glass if not node: 311f7a2aeeeSSimon Glass return None 312f7a2aeeeSSimon Glass return node 313f7a2aeeeSSimon Glass 314da5f7499SSimon Glass def Flush(self): 315da5f7499SSimon Glass """Flush device tree changes back to the file 316da5f7499SSimon Glass 317da5f7499SSimon Glass If the device tree has changed in memory, write it back to the file. 318da5f7499SSimon Glass """ 3197b75b448SSimon Glass with open(self._fname, 'wb') as fd: 3207b75b448SSimon Glass fd.write(self._fdt) 321da5f7499SSimon Glass 322da5f7499SSimon Glass def Pack(self): 323da5f7499SSimon Glass """Pack the device tree down to its minimum size 324da5f7499SSimon Glass 325da5f7499SSimon Glass When nodes and properties shrink or are deleted, wasted space can 3267b75b448SSimon Glass build up in the device tree binary. 327da5f7499SSimon Glass """ 3287b75b448SSimon Glass CheckErr(libfdt.fdt_pack(self._fdt), 'pack') 3297b75b448SSimon Glass fdt_len = libfdt.fdt_totalsize(self._fdt) 3307b75b448SSimon Glass del self._fdt[fdt_len:] 3317b75b448SSimon Glass 3327b75b448SSimon Glass def GetFdt(self): 3337b75b448SSimon Glass """Get the contents of the FDT 3347b75b448SSimon Glass 3357b75b448SSimon Glass Returns: 3367b75b448SSimon Glass The FDT contents as a string of bytes 3377b75b448SSimon Glass """ 3387b75b448SSimon Glass return self._fdt 3397b75b448SSimon Glass 3407b75b448SSimon Glass def CheckErr(errnum, msg): 3417b75b448SSimon Glass if errnum: 3427b75b448SSimon Glass raise ValueError('Error %d: %s: %s' % 3437b75b448SSimon Glass (errnum, libfdt.fdt_strerror(errnum), msg)) 3447b75b448SSimon Glass 3457b75b448SSimon Glass 3467b75b448SSimon Glass def GetProps(self, node): 3477b75b448SSimon Glass """Get all properties from a node. 3487b75b448SSimon Glass 3497b75b448SSimon Glass Args: 3507b75b448SSimon Glass node: Full path to node name to look in. 3517b75b448SSimon Glass 3527b75b448SSimon Glass Returns: 3537b75b448SSimon Glass A dictionary containing all the properties, indexed by node name. 3547b75b448SSimon Glass The entries are Prop objects. 3557b75b448SSimon Glass 3567b75b448SSimon Glass Raises: 3577b75b448SSimon Glass ValueError: if the node does not exist. 3587b75b448SSimon Glass """ 3597b75b448SSimon Glass props_dict = {} 3607b75b448SSimon Glass poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset) 3617b75b448SSimon Glass while poffset >= 0: 3627b75b448SSimon Glass p = self._fdt_obj.get_property_by_offset(poffset) 3637b75b448SSimon Glass prop = Prop(node, poffset, p.name, p.value) 3647b75b448SSimon Glass props_dict[prop.name] = prop 3657b75b448SSimon Glass 3667b75b448SSimon Glass poffset = libfdt.fdt_next_property_offset(self._fdt, poffset) 3677b75b448SSimon Glass return props_dict 3687b75b448SSimon Glass 3697b75b448SSimon Glass def Invalidate(self): 3707b75b448SSimon Glass """Mark our offset cache as invalid""" 3717b75b448SSimon Glass self._cached_offsets = False 3727b75b448SSimon Glass 3737b75b448SSimon Glass def CheckCache(self): 3747b75b448SSimon Glass """Refresh the offset cache if needed""" 3757b75b448SSimon Glass if self._cached_offsets: 3767b75b448SSimon Glass return 3777b75b448SSimon Glass self.Refresh() 3787b75b448SSimon Glass self._cached_offsets = True 3797b75b448SSimon Glass 3807b75b448SSimon Glass def Refresh(self): 3817b75b448SSimon Glass """Refresh the offset cache""" 3827b75b448SSimon Glass self._root.Refresh(0) 3837b75b448SSimon Glass 3847b75b448SSimon Glass def GetStructOffset(self, offset): 3857b75b448SSimon Glass """Get the file offset of a given struct offset 3867b75b448SSimon Glass 3877b75b448SSimon Glass Args: 3887b75b448SSimon Glass offset: Offset within the 'struct' region of the device tree 3897b75b448SSimon Glass Returns: 3907b75b448SSimon Glass Position of @offset within the device tree binary 3917b75b448SSimon Glass """ 3927b75b448SSimon Glass return libfdt.fdt_off_dt_struct(self._fdt) + offset 3937b75b448SSimon Glass 3947b75b448SSimon Glass @classmethod 395f08a0424SSimon Glass def Node(self, fdt, parent, offset, name, path): 3967b75b448SSimon Glass """Create a new node 3977b75b448SSimon Glass 3987b75b448SSimon Glass This is used by Fdt.Scan() to create a new node using the correct 3997b75b448SSimon Glass class. 4007b75b448SSimon Glass 4017b75b448SSimon Glass Args: 4027b75b448SSimon Glass fdt: Fdt object 403f08a0424SSimon Glass parent: Parent node, or None if this is the root node 4047b75b448SSimon Glass offset: Offset of node 4057b75b448SSimon Glass name: Node name 4067b75b448SSimon Glass path: Full path to node 4077b75b448SSimon Glass """ 408f08a0424SSimon Glass node = Node(fdt, parent, offset, name, path) 4097b75b448SSimon Glass return node 41099ed4a2eSSimon Glass 41199ed4a2eSSimon Glassdef FdtScan(fname): 41299ed4a2eSSimon Glass """Returns a new Fdt object from the implementation we are using""" 41399ed4a2eSSimon Glass dtb = Fdt(fname) 41499ed4a2eSSimon Glass dtb.Scan() 41599ed4a2eSSimon Glass return dtb 416