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 8import struct 9import sys 10 11import fdt_util 12import libfdt 13from libfdt import QUIET_NOTFOUND 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, TYPE_INT64) = range(5) 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 self.dirty = False 47 if not bytes: 48 self.type = TYPE_BOOL 49 self.value = True 50 return 51 self.type, self.value = self.BytesToValue(bytes) 52 53 def RefreshOffset(self, poffset): 54 self._offset = poffset 55 56 def Widen(self, newprop): 57 """Figure out which property type is more general 58 59 Given a current property and a new property, this function returns the 60 one that is less specific as to type. The less specific property will 61 be ble to represent the data in the more specific property. This is 62 used for things like: 63 64 node1 { 65 compatible = "fred"; 66 value = <1>; 67 }; 68 node1 { 69 compatible = "fred"; 70 value = <1 2>; 71 }; 72 73 He we want to use an int array for 'value'. The first property 74 suggests that a single int is enough, but the second one shows that 75 it is not. Calling this function with these two propertes would 76 update the current property to be like the second, since it is less 77 specific. 78 """ 79 if newprop.type < self.type: 80 self.type = newprop.type 81 82 if type(newprop.value) == list and type(self.value) != list: 83 self.value = [self.value] 84 85 if type(self.value) == list and len(newprop.value) > len(self.value): 86 val = self.GetEmpty(self.type) 87 while len(self.value) < len(newprop.value): 88 self.value.append(val) 89 90 def BytesToValue(self, bytes): 91 """Converts a string of bytes into a type and value 92 93 Args: 94 A string containing bytes 95 96 Return: 97 A tuple: 98 Type of data 99 Data, either a single element or a list of elements. Each element 100 is one of: 101 TYPE_STRING: string value from the property 102 TYPE_INT: a byte-swapped integer stored as a 4-byte string 103 TYPE_BYTE: a byte stored as a single-byte string 104 """ 105 bytes = str(bytes) 106 size = len(bytes) 107 strings = bytes.split('\0') 108 is_string = True 109 count = len(strings) - 1 110 if count > 0 and not strings[-1]: 111 for string in strings[:-1]: 112 if not string: 113 is_string = False 114 break 115 for ch in string: 116 if ch < ' ' or ch > '~': 117 is_string = False 118 break 119 else: 120 is_string = False 121 if is_string: 122 if count == 1: 123 return TYPE_STRING, strings[0] 124 else: 125 return TYPE_STRING, strings[:-1] 126 if size % 4: 127 if size == 1: 128 return TYPE_BYTE, bytes[0] 129 else: 130 return TYPE_BYTE, list(bytes) 131 val = [] 132 for i in range(0, size, 4): 133 val.append(bytes[i:i + 4]) 134 if size == 4: 135 return TYPE_INT, val[0] 136 else: 137 return TYPE_INT, val 138 139 @classmethod 140 def GetEmpty(self, type): 141 """Get an empty / zero value of the given type 142 143 Returns: 144 A single value of the given type 145 """ 146 if type == TYPE_BYTE: 147 return chr(0) 148 elif type == TYPE_INT: 149 return struct.pack('>I', 0); 150 elif type == TYPE_STRING: 151 return '' 152 else: 153 return True 154 155 def GetOffset(self): 156 """Get the offset of a property 157 158 Returns: 159 The offset of the property (struct fdt_property) within the file 160 """ 161 self._node._fdt.CheckCache() 162 return self._node._fdt.GetStructOffset(self._offset) 163 164 def SetInt(self, val): 165 """Set the integer value of the property 166 167 The device tree is marked dirty so that the value will be written to 168 the block on the next sync. 169 170 Args: 171 val: Integer value (32-bit, single cell) 172 """ 173 self.bytes = struct.pack('>I', val); 174 self.value = val 175 self.type = TYPE_INT 176 self.dirty = True 177 178 def Sync(self, auto_resize=False): 179 """Sync property changes back to the device tree 180 181 This updates the device tree blob with any changes to this property 182 since the last sync. 183 184 Args: 185 auto_resize: Resize the device tree automatically if it does not 186 have enough space for the update 187 188 Raises: 189 FdtException if auto_resize is False and there is not enough space 190 """ 191 if self._offset is None or self.dirty: 192 node = self._node 193 fdt_obj = node._fdt._fdt_obj 194 if auto_resize: 195 while fdt_obj.setprop(node.Offset(), self.name, self.bytes, 196 (libfdt.NOSPACE,)) == -libfdt.NOSPACE: 197 fdt_obj.resize(fdt_obj.totalsize() + 1024) 198 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 199 else: 200 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 201 202 203class Node: 204 """A device tree node 205 206 Properties: 207 offset: Integer offset in the device tree 208 name: Device tree node tname 209 path: Full path to node, along with the node name itself 210 _fdt: Device tree object 211 subnodes: A list of subnodes for this node, each a Node object 212 props: A dict of properties for this node, each a Prop object. 213 Keyed by property name 214 """ 215 def __init__(self, fdt, parent, offset, name, path): 216 self._fdt = fdt 217 self.parent = parent 218 self._offset = offset 219 self.name = name 220 self.path = path 221 self.subnodes = [] 222 self.props = {} 223 224 def GetFdt(self): 225 """Get the Fdt object for this node 226 227 Returns: 228 Fdt object 229 """ 230 return self._fdt 231 232 def FindNode(self, name): 233 """Find a node given its name 234 235 Args: 236 name: Node name to look for 237 Returns: 238 Node object if found, else None 239 """ 240 for subnode in self.subnodes: 241 if subnode.name == name: 242 return subnode 243 return None 244 245 def Offset(self): 246 """Returns the offset of a node, after checking the cache 247 248 This should be used instead of self._offset directly, to ensure that 249 the cache does not contain invalid offsets. 250 """ 251 self._fdt.CheckCache() 252 return self._offset 253 254 def Scan(self): 255 """Scan a node's properties and subnodes 256 257 This fills in the props and subnodes properties, recursively 258 searching into subnodes so that the entire tree is built. 259 """ 260 fdt_obj = self._fdt._fdt_obj 261 self.props = self._fdt.GetProps(self) 262 phandle = fdt_obj.get_phandle(self.Offset()) 263 if phandle: 264 self._fdt.phandle_to_node[phandle] = self 265 266 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) 267 while offset >= 0: 268 sep = '' if self.path[-1] == '/' else '/' 269 name = fdt_obj.get_name(offset) 270 path = self.path + sep + name 271 node = Node(self._fdt, self, offset, name, path) 272 self.subnodes.append(node) 273 274 node.Scan() 275 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 276 277 def Refresh(self, my_offset): 278 """Fix up the _offset for each node, recursively 279 280 Note: This does not take account of property offsets - these will not 281 be updated. 282 """ 283 fdt_obj = self._fdt._fdt_obj 284 if self._offset != my_offset: 285 self._offset = my_offset 286 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) 287 for subnode in self.subnodes: 288 if subnode.name != fdt_obj.get_name(offset): 289 raise ValueError('Internal error, node name mismatch %s != %s' % 290 (subnode.name, fdt_obj.get_name(offset))) 291 subnode.Refresh(offset) 292 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 293 if offset != -libfdt.FDT_ERR_NOTFOUND: 294 raise ValueError('Internal error, offset == %d' % offset) 295 296 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 297 while poffset >= 0: 298 p = fdt_obj.get_property_by_offset(poffset) 299 prop = self.props.get(p.name) 300 if not prop: 301 raise ValueError("Internal error, property '%s' missing, " 302 'offset %d' % (p.name, poffset)) 303 prop.RefreshOffset(poffset) 304 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 305 306 def DeleteProp(self, prop_name): 307 """Delete a property of a node 308 309 The property is deleted and the offset cache is invalidated. 310 311 Args: 312 prop_name: Name of the property to delete 313 Raises: 314 ValueError if the property does not exist 315 """ 316 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 317 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 318 del self.props[prop_name] 319 self._fdt.Invalidate() 320 321 def AddZeroProp(self, prop_name): 322 """Add a new property to the device tree with an integer value of 0. 323 324 Args: 325 prop_name: Name of property 326 """ 327 self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4) 328 329 def SetInt(self, prop_name, val): 330 """Update an integer property int the device tree. 331 332 This is not allowed to change the size of the FDT. 333 334 Args: 335 prop_name: Name of property 336 val: Value to set 337 """ 338 self.props[prop_name].SetInt(val) 339 340 def AddSubnode(self, name): 341 path = self.path + '/' + name 342 subnode = Node(self._fdt, self, None, name, path) 343 self.subnodes.append(subnode) 344 return subnode 345 346 def Sync(self, auto_resize=False): 347 """Sync node changes back to the device tree 348 349 This updates the device tree blob with any changes to this node and its 350 subnodes since the last sync. 351 352 Args: 353 auto_resize: Resize the device tree automatically if it does not 354 have enough space for the update 355 356 Raises: 357 FdtException if auto_resize is False and there is not enough space 358 """ 359 if self._offset is None: 360 # The subnode doesn't exist yet, so add it 361 fdt_obj = self._fdt._fdt_obj 362 if auto_resize: 363 while True: 364 offset = fdt_obj.add_subnode(self.parent._offset, self.name, 365 (libfdt.NOSPACE,)) 366 if offset != -libfdt.NOSPACE: 367 break 368 fdt_obj.resize(fdt_obj.totalsize() + 1024) 369 else: 370 offset = fdt_obj.add_subnode(self.parent._offset, self.name) 371 self._offset = offset 372 373 # Sync subnodes in reverse so that we don't disturb node offsets for 374 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of 375 # node offsets. 376 for node in reversed(self.subnodes): 377 node.Sync(auto_resize) 378 379 # Sync properties now, whose offsets should not have been disturbed. 380 # We do this after subnodes, since this disturbs the offsets of these 381 # properties. 382 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset, 383 reverse=True) 384 for prop in prop_list: 385 prop.Sync(auto_resize) 386 387 388class Fdt: 389 """Provides simple access to a flat device tree blob using libfdts. 390 391 Properties: 392 fname: Filename of fdt 393 _root: Root of device tree (a Node object) 394 """ 395 def __init__(self, fname): 396 self._fname = fname 397 self._cached_offsets = False 398 self.phandle_to_node = {} 399 if self._fname: 400 self._fname = fdt_util.EnsureCompiled(self._fname) 401 402 with open(self._fname) as fd: 403 self._fdt_obj = libfdt.Fdt(fd.read()) 404 405 def LookupPhandle(self, phandle): 406 """Look up a phandle 407 408 Args: 409 phandle: Phandle to look up (int) 410 411 Returns: 412 Node object the phandle points to 413 """ 414 return self.phandle_to_node.get(phandle) 415 416 def Scan(self, root='/'): 417 """Scan a device tree, building up a tree of Node objects 418 419 This fills in the self._root property 420 421 Args: 422 root: Ignored 423 424 TODO(sjg@chromium.org): Implement the 'root' parameter 425 """ 426 self._cached_offsets = True 427 self._root = self.Node(self, None, 0, '/', '/') 428 self._root.Scan() 429 430 def GetRoot(self): 431 """Get the root Node of the device tree 432 433 Returns: 434 The root Node object 435 """ 436 return self._root 437 438 def GetNode(self, path): 439 """Look up a node from its path 440 441 Args: 442 path: Path to look up, e.g. '/microcode/update@0' 443 Returns: 444 Node object, or None if not found 445 """ 446 node = self._root 447 parts = path.split('/') 448 if len(parts) < 2: 449 return None 450 for part in parts[1:]: 451 node = node.FindNode(part) 452 if not node: 453 return None 454 return node 455 456 def Flush(self): 457 """Flush device tree changes back to the file 458 459 If the device tree has changed in memory, write it back to the file. 460 """ 461 with open(self._fname, 'wb') as fd: 462 fd.write(self._fdt_obj.as_bytearray()) 463 464 def Sync(self, auto_resize=False): 465 """Make sure any DT changes are written to the blob 466 467 Args: 468 auto_resize: Resize the device tree automatically if it does not 469 have enough space for the update 470 471 Raises: 472 FdtException if auto_resize is False and there is not enough space 473 """ 474 self._root.Sync(auto_resize) 475 self.Invalidate() 476 477 def Pack(self): 478 """Pack the device tree down to its minimum size 479 480 When nodes and properties shrink or are deleted, wasted space can 481 build up in the device tree binary. 482 """ 483 CheckErr(self._fdt_obj.pack(), 'pack') 484 self.Invalidate() 485 486 def GetContents(self): 487 """Get the contents of the FDT 488 489 Returns: 490 The FDT contents as a string of bytes 491 """ 492 return self._fdt_obj.as_bytearray() 493 494 def GetFdtObj(self): 495 """Get the contents of the FDT 496 497 Returns: 498 The FDT contents as a libfdt.Fdt object 499 """ 500 return self._fdt_obj 501 502 def GetProps(self, node): 503 """Get all properties from a node. 504 505 Args: 506 node: Full path to node name to look in. 507 508 Returns: 509 A dictionary containing all the properties, indexed by node name. 510 The entries are Prop objects. 511 512 Raises: 513 ValueError: if the node does not exist. 514 """ 515 props_dict = {} 516 poffset = self._fdt_obj.first_property_offset(node._offset, 517 QUIET_NOTFOUND) 518 while poffset >= 0: 519 p = self._fdt_obj.get_property_by_offset(poffset) 520 prop = Prop(node, poffset, p.name, p) 521 props_dict[prop.name] = prop 522 523 poffset = self._fdt_obj.next_property_offset(poffset, 524 QUIET_NOTFOUND) 525 return props_dict 526 527 def Invalidate(self): 528 """Mark our offset cache as invalid""" 529 self._cached_offsets = False 530 531 def CheckCache(self): 532 """Refresh the offset cache if needed""" 533 if self._cached_offsets: 534 return 535 self.Refresh() 536 self._cached_offsets = True 537 538 def Refresh(self): 539 """Refresh the offset cache""" 540 self._root.Refresh(0) 541 542 def GetStructOffset(self, offset): 543 """Get the file offset of a given struct offset 544 545 Args: 546 offset: Offset within the 'struct' region of the device tree 547 Returns: 548 Position of @offset within the device tree binary 549 """ 550 return self._fdt_obj.off_dt_struct() + offset 551 552 @classmethod 553 def Node(self, fdt, parent, offset, name, path): 554 """Create a new node 555 556 This is used by Fdt.Scan() to create a new node using the correct 557 class. 558 559 Args: 560 fdt: Fdt object 561 parent: Parent node, or None if this is the root node 562 offset: Offset of node 563 name: Node name 564 path: Full path to node 565 """ 566 node = Node(fdt, parent, offset, name, path) 567 return node 568 569def FdtScan(fname): 570 """Returns a new Fdt object""" 571 dtb = Fdt(fname) 572 dtb.Scan() 573 return dtb 574