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 13*7b75b448SSimon 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 17*7b75b448SSimon Glass# contains the base classes and defines the high-level API. See fdt_select.py 18*7b75b448SSimon Glass# for how to create an Fdt object. 19*7b75b448SSimon Glass 20*7b75b448SSimon Glass# This implementation uses a libfdt Python library to access the device tree, 21*7b75b448SSimon Glass# so it is fairly efficient. 22a06a34b2SSimon Glass 23bc1dea36SSimon Glass# A list of types we support 24bc1dea36SSimon Glass(TYPE_BYTE, TYPE_INT, TYPE_STRING, TYPE_BOOL) = range(4) 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 31*7b75b448SSimon 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 """ 40*7b75b448SSimon 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 45*7b75b448SSimon Glass self.bytes = str(bytes) 46*7b75b448SSimon Glass if not bytes: 47*7b75b448SSimon Glass self.type = TYPE_BOOL 48*7b75b448SSimon Glass self.value = True 49*7b75b448SSimon Glass return 50*7b75b448SSimon 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: 161*7b75b448SSimon Glass The offset of the property (struct fdt_property) within the file 162babdbde6SSimon Glass """ 163*7b75b448SSimon Glass return self._node._fdt.GetStructOffset(self._offset) 164babdbde6SSimon Glass 165*7b75b448SSimon 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 """ 177a06a34b2SSimon Glass def __init__(self, fdt, offset, name, path): 178a06a34b2SSimon Glass self._fdt = fdt 179a06a34b2SSimon Glass self._offset = offset 180a06a34b2SSimon Glass self.name = name 181a06a34b2SSimon Glass self.path = path 182a06a34b2SSimon Glass self.subnodes = [] 183a06a34b2SSimon Glass self.props = {} 184a06a34b2SSimon Glass 185f7a2aeeeSSimon Glass def _FindNode(self, name): 186f7a2aeeeSSimon Glass """Find a node given its name 187f7a2aeeeSSimon Glass 188f7a2aeeeSSimon Glass Args: 189f7a2aeeeSSimon Glass name: Node name to look for 190f7a2aeeeSSimon Glass Returns: 191f7a2aeeeSSimon Glass Node object if found, else None 192f7a2aeeeSSimon Glass """ 193f7a2aeeeSSimon Glass for subnode in self.subnodes: 194f7a2aeeeSSimon Glass if subnode.name == name: 195f7a2aeeeSSimon Glass return subnode 196f7a2aeeeSSimon Glass return None 197f7a2aeeeSSimon Glass 198*7b75b448SSimon Glass def Offset(self): 199*7b75b448SSimon Glass """Returns the offset of a node, after checking the cache 200f7a2aeeeSSimon Glass 201*7b75b448SSimon Glass This should be used instead of self._offset directly, to ensure that 202*7b75b448SSimon Glass the cache does not contain invalid offsets. 203f7a2aeeeSSimon Glass """ 204*7b75b448SSimon Glass self._fdt.CheckCache() 205*7b75b448SSimon Glass return self._offset 206*7b75b448SSimon Glass 207*7b75b448SSimon Glass def Scan(self): 208*7b75b448SSimon Glass """Scan a node's properties and subnodes 209*7b75b448SSimon Glass 210*7b75b448SSimon Glass This fills in the props and subnodes properties, recursively 211*7b75b448SSimon Glass searching into subnodes so that the entire tree is built. 212*7b75b448SSimon Glass """ 213*7b75b448SSimon Glass self.props = self._fdt.GetProps(self) 214*7b75b448SSimon Glass 215*7b75b448SSimon Glass offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self.Offset()) 216*7b75b448SSimon Glass while offset >= 0: 217*7b75b448SSimon Glass sep = '' if self.path[-1] == '/' else '/' 218*7b75b448SSimon Glass name = self._fdt._fdt_obj.get_name(offset) 219*7b75b448SSimon Glass path = self.path + sep + name 220*7b75b448SSimon Glass node = Node(self._fdt, offset, name, path) 221*7b75b448SSimon Glass self.subnodes.append(node) 222*7b75b448SSimon Glass 223*7b75b448SSimon Glass node.Scan() 224*7b75b448SSimon Glass offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) 225*7b75b448SSimon Glass 226*7b75b448SSimon Glass def Refresh(self, my_offset): 227*7b75b448SSimon Glass """Fix up the _offset for each node, recursively 228*7b75b448SSimon Glass 229*7b75b448SSimon Glass Note: This does not take account of property offsets - these will not 230*7b75b448SSimon Glass be updated. 231*7b75b448SSimon Glass """ 232*7b75b448SSimon Glass if self._offset != my_offset: 233*7b75b448SSimon Glass #print '%s: %d -> %d\n' % (self.path, self._offset, my_offset) 234*7b75b448SSimon Glass self._offset = my_offset 235*7b75b448SSimon Glass offset = libfdt.fdt_first_subnode(self._fdt.GetFdt(), self._offset) 236*7b75b448SSimon Glass for subnode in self.subnodes: 237*7b75b448SSimon Glass subnode.Refresh(offset) 238*7b75b448SSimon Glass offset = libfdt.fdt_next_subnode(self._fdt.GetFdt(), offset) 239f7a2aeeeSSimon Glass 2402a70d897SSimon Glass def DeleteProp(self, prop_name): 2412a70d897SSimon Glass """Delete a property of a node 2422a70d897SSimon Glass 243*7b75b448SSimon Glass The property is deleted and the offset cache is invalidated. 2442a70d897SSimon Glass 2452a70d897SSimon Glass Args: 2462a70d897SSimon Glass prop_name: Name of the property to delete 247*7b75b448SSimon Glass Raises: 248*7b75b448SSimon Glass ValueError if the property does not exist 2492a70d897SSimon Glass """ 250*7b75b448SSimon Glass CheckErr(libfdt.fdt_delprop(self._fdt.GetFdt(), self.Offset(), prop_name), 251*7b75b448SSimon Glass "Node '%s': delete property: '%s'" % (self.path, prop_name)) 252*7b75b448SSimon Glass del self.props[prop_name] 253*7b75b448SSimon Glass self._fdt.Invalidate() 2542a70d897SSimon Glass 255a06a34b2SSimon Glassclass Fdt: 256*7b75b448SSimon Glass """Provides simple access to a flat device tree blob using libfdts. 257a06a34b2SSimon Glass 258a06a34b2SSimon Glass Properties: 259a06a34b2SSimon Glass fname: Filename of fdt 260a06a34b2SSimon Glass _root: Root of device tree (a Node object) 261a06a34b2SSimon Glass """ 262a06a34b2SSimon Glass def __init__(self, fname): 263a06a34b2SSimon Glass self._fname = fname 264*7b75b448SSimon Glass self._cached_offsets = False 265*7b75b448SSimon Glass if self._fname: 266*7b75b448SSimon Glass self._fname = fdt_util.EnsureCompiled(self._fname) 267*7b75b448SSimon Glass 268*7b75b448SSimon Glass with open(self._fname) as fd: 269*7b75b448SSimon Glass self._fdt = bytearray(fd.read()) 270*7b75b448SSimon Glass self._fdt_obj = libfdt.Fdt(self._fdt) 271f7a2aeeeSSimon Glass 272f7a2aeeeSSimon Glass def Scan(self, root='/'): 273f7a2aeeeSSimon Glass """Scan a device tree, building up a tree of Node objects 274f7a2aeeeSSimon Glass 275f7a2aeeeSSimon Glass This fills in the self._root property 276f7a2aeeeSSimon Glass 277f7a2aeeeSSimon Glass Args: 278f7a2aeeeSSimon Glass root: Ignored 279f7a2aeeeSSimon Glass 280f7a2aeeeSSimon Glass TODO(sjg@chromium.org): Implement the 'root' parameter 281f7a2aeeeSSimon Glass """ 282f7a2aeeeSSimon Glass self._root = self.Node(self, 0, '/', '/') 283f7a2aeeeSSimon Glass self._root.Scan() 284f7a2aeeeSSimon Glass 285f7a2aeeeSSimon Glass def GetRoot(self): 286f7a2aeeeSSimon Glass """Get the root Node of the device tree 287f7a2aeeeSSimon Glass 288f7a2aeeeSSimon Glass Returns: 289f7a2aeeeSSimon Glass The root Node object 290f7a2aeeeSSimon Glass """ 291f7a2aeeeSSimon Glass return self._root 292f7a2aeeeSSimon Glass 293f7a2aeeeSSimon Glass def GetNode(self, path): 294f7a2aeeeSSimon Glass """Look up a node from its path 295f7a2aeeeSSimon Glass 296f7a2aeeeSSimon Glass Args: 297f7a2aeeeSSimon Glass path: Path to look up, e.g. '/microcode/update@0' 298f7a2aeeeSSimon Glass Returns: 299f7a2aeeeSSimon Glass Node object, or None if not found 300f7a2aeeeSSimon Glass """ 301f7a2aeeeSSimon Glass node = self._root 302f7a2aeeeSSimon Glass for part in path.split('/')[1:]: 303f7a2aeeeSSimon Glass node = node._FindNode(part) 304f7a2aeeeSSimon Glass if not node: 305f7a2aeeeSSimon Glass return None 306f7a2aeeeSSimon Glass return node 307f7a2aeeeSSimon Glass 308da5f7499SSimon Glass def Flush(self): 309da5f7499SSimon Glass """Flush device tree changes back to the file 310da5f7499SSimon Glass 311da5f7499SSimon Glass If the device tree has changed in memory, write it back to the file. 312da5f7499SSimon Glass """ 313*7b75b448SSimon Glass with open(self._fname, 'wb') as fd: 314*7b75b448SSimon Glass fd.write(self._fdt) 315da5f7499SSimon Glass 316da5f7499SSimon Glass def Pack(self): 317da5f7499SSimon Glass """Pack the device tree down to its minimum size 318da5f7499SSimon Glass 319da5f7499SSimon Glass When nodes and properties shrink or are deleted, wasted space can 320*7b75b448SSimon Glass build up in the device tree binary. 321da5f7499SSimon Glass """ 322*7b75b448SSimon Glass CheckErr(libfdt.fdt_pack(self._fdt), 'pack') 323*7b75b448SSimon Glass fdt_len = libfdt.fdt_totalsize(self._fdt) 324*7b75b448SSimon Glass del self._fdt[fdt_len:] 325*7b75b448SSimon Glass 326*7b75b448SSimon Glass def GetFdt(self): 327*7b75b448SSimon Glass """Get the contents of the FDT 328*7b75b448SSimon Glass 329*7b75b448SSimon Glass Returns: 330*7b75b448SSimon Glass The FDT contents as a string of bytes 331*7b75b448SSimon Glass """ 332*7b75b448SSimon Glass return self._fdt 333*7b75b448SSimon Glass 334*7b75b448SSimon Glass def CheckErr(errnum, msg): 335*7b75b448SSimon Glass if errnum: 336*7b75b448SSimon Glass raise ValueError('Error %d: %s: %s' % 337*7b75b448SSimon Glass (errnum, libfdt.fdt_strerror(errnum), msg)) 338*7b75b448SSimon Glass 339*7b75b448SSimon Glass 340*7b75b448SSimon Glass def GetProps(self, node): 341*7b75b448SSimon Glass """Get all properties from a node. 342*7b75b448SSimon Glass 343*7b75b448SSimon Glass Args: 344*7b75b448SSimon Glass node: Full path to node name to look in. 345*7b75b448SSimon Glass 346*7b75b448SSimon Glass Returns: 347*7b75b448SSimon Glass A dictionary containing all the properties, indexed by node name. 348*7b75b448SSimon Glass The entries are Prop objects. 349*7b75b448SSimon Glass 350*7b75b448SSimon Glass Raises: 351*7b75b448SSimon Glass ValueError: if the node does not exist. 352*7b75b448SSimon Glass """ 353*7b75b448SSimon Glass props_dict = {} 354*7b75b448SSimon Glass poffset = libfdt.fdt_first_property_offset(self._fdt, node._offset) 355*7b75b448SSimon Glass while poffset >= 0: 356*7b75b448SSimon Glass p = self._fdt_obj.get_property_by_offset(poffset) 357*7b75b448SSimon Glass prop = Prop(node, poffset, p.name, p.value) 358*7b75b448SSimon Glass props_dict[prop.name] = prop 359*7b75b448SSimon Glass 360*7b75b448SSimon Glass poffset = libfdt.fdt_next_property_offset(self._fdt, poffset) 361*7b75b448SSimon Glass return props_dict 362*7b75b448SSimon Glass 363*7b75b448SSimon Glass def Invalidate(self): 364*7b75b448SSimon Glass """Mark our offset cache as invalid""" 365*7b75b448SSimon Glass self._cached_offsets = False 366*7b75b448SSimon Glass 367*7b75b448SSimon Glass def CheckCache(self): 368*7b75b448SSimon Glass """Refresh the offset cache if needed""" 369*7b75b448SSimon Glass if self._cached_offsets: 370*7b75b448SSimon Glass return 371*7b75b448SSimon Glass self.Refresh() 372*7b75b448SSimon Glass self._cached_offsets = True 373*7b75b448SSimon Glass 374*7b75b448SSimon Glass def Refresh(self): 375*7b75b448SSimon Glass """Refresh the offset cache""" 376*7b75b448SSimon Glass self._root.Refresh(0) 377*7b75b448SSimon Glass 378*7b75b448SSimon Glass def GetStructOffset(self, offset): 379*7b75b448SSimon Glass """Get the file offset of a given struct offset 380*7b75b448SSimon Glass 381*7b75b448SSimon Glass Args: 382*7b75b448SSimon Glass offset: Offset within the 'struct' region of the device tree 383*7b75b448SSimon Glass Returns: 384*7b75b448SSimon Glass Position of @offset within the device tree binary 385*7b75b448SSimon Glass """ 386*7b75b448SSimon Glass return libfdt.fdt_off_dt_struct(self._fdt) + offset 387*7b75b448SSimon Glass 388*7b75b448SSimon Glass @classmethod 389*7b75b448SSimon Glass def Node(self, fdt, offset, name, path): 390*7b75b448SSimon Glass """Create a new node 391*7b75b448SSimon Glass 392*7b75b448SSimon Glass This is used by Fdt.Scan() to create a new node using the correct 393*7b75b448SSimon Glass class. 394*7b75b448SSimon Glass 395*7b75b448SSimon Glass Args: 396*7b75b448SSimon Glass fdt: Fdt object 397*7b75b448SSimon Glass offset: Offset of node 398*7b75b448SSimon Glass name: Node name 399*7b75b448SSimon Glass path: Full path to node 400*7b75b448SSimon Glass """ 401*7b75b448SSimon Glass node = Node(fdt, offset, name, path) 402*7b75b448SSimon Glass return node 403