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