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 = self.bytes 175 self.type = TYPE_INT 176 self.dirty = True 177 178 def SetData(self, bytes): 179 """Set the value of a property as bytes 180 181 Args: 182 bytes: New property value to set 183 """ 184 self.bytes = str(bytes) 185 self.type, self.value = self.BytesToValue(bytes) 186 self.dirty = True 187 188 def Sync(self, auto_resize=False): 189 """Sync property changes back to the device tree 190 191 This updates the device tree blob with any changes to this property 192 since the last sync. 193 194 Args: 195 auto_resize: Resize the device tree automatically if it does not 196 have enough space for the update 197 198 Raises: 199 FdtException if auto_resize is False and there is not enough space 200 """ 201 if self._offset is None or self.dirty: 202 node = self._node 203 fdt_obj = node._fdt._fdt_obj 204 if auto_resize: 205 while fdt_obj.setprop(node.Offset(), self.name, self.bytes, 206 (libfdt.NOSPACE,)) == -libfdt.NOSPACE: 207 fdt_obj.resize(fdt_obj.totalsize() + 1024) 208 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 209 else: 210 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 211 212 213class Node: 214 """A device tree node 215 216 Properties: 217 offset: Integer offset in the device tree 218 name: Device tree node tname 219 path: Full path to node, along with the node name itself 220 _fdt: Device tree object 221 subnodes: A list of subnodes for this node, each a Node object 222 props: A dict of properties for this node, each a Prop object. 223 Keyed by property name 224 """ 225 def __init__(self, fdt, parent, offset, name, path): 226 self._fdt = fdt 227 self.parent = parent 228 self._offset = offset 229 self.name = name 230 self.path = path 231 self.subnodes = [] 232 self.props = {} 233 234 def GetFdt(self): 235 """Get the Fdt object for this node 236 237 Returns: 238 Fdt object 239 """ 240 return self._fdt 241 242 def FindNode(self, name): 243 """Find a node given its name 244 245 Args: 246 name: Node name to look for 247 Returns: 248 Node object if found, else None 249 """ 250 for subnode in self.subnodes: 251 if subnode.name == name: 252 return subnode 253 return None 254 255 def Offset(self): 256 """Returns the offset of a node, after checking the cache 257 258 This should be used instead of self._offset directly, to ensure that 259 the cache does not contain invalid offsets. 260 """ 261 self._fdt.CheckCache() 262 return self._offset 263 264 def Scan(self): 265 """Scan a node's properties and subnodes 266 267 This fills in the props and subnodes properties, recursively 268 searching into subnodes so that the entire tree is built. 269 """ 270 fdt_obj = self._fdt._fdt_obj 271 self.props = self._fdt.GetProps(self) 272 phandle = fdt_obj.get_phandle(self.Offset()) 273 if phandle: 274 self._fdt.phandle_to_node[phandle] = self 275 276 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) 277 while offset >= 0: 278 sep = '' if self.path[-1] == '/' else '/' 279 name = fdt_obj.get_name(offset) 280 path = self.path + sep + name 281 node = Node(self._fdt, self, offset, name, path) 282 self.subnodes.append(node) 283 284 node.Scan() 285 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 286 287 def Refresh(self, my_offset): 288 """Fix up the _offset for each node, recursively 289 290 Note: This does not take account of property offsets - these will not 291 be updated. 292 """ 293 fdt_obj = self._fdt._fdt_obj 294 if self._offset != my_offset: 295 self._offset = my_offset 296 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) 297 for subnode in self.subnodes: 298 if subnode.name != fdt_obj.get_name(offset): 299 raise ValueError('Internal error, node name mismatch %s != %s' % 300 (subnode.name, fdt_obj.get_name(offset))) 301 subnode.Refresh(offset) 302 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 303 if offset != -libfdt.FDT_ERR_NOTFOUND: 304 raise ValueError('Internal error, offset == %d' % offset) 305 306 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 307 while poffset >= 0: 308 p = fdt_obj.get_property_by_offset(poffset) 309 prop = self.props.get(p.name) 310 if not prop: 311 raise ValueError("Internal error, property '%s' missing, " 312 'offset %d' % (p.name, poffset)) 313 prop.RefreshOffset(poffset) 314 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 315 316 def DeleteProp(self, prop_name): 317 """Delete a property of a node 318 319 The property is deleted and the offset cache is invalidated. 320 321 Args: 322 prop_name: Name of the property to delete 323 Raises: 324 ValueError if the property does not exist 325 """ 326 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 327 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 328 del self.props[prop_name] 329 self._fdt.Invalidate() 330 331 def AddZeroProp(self, prop_name): 332 """Add a new property to the device tree with an integer value of 0. 333 334 Args: 335 prop_name: Name of property 336 """ 337 self.props[prop_name] = Prop(self, None, prop_name, '\0' * 4) 338 339 def AddEmptyProp(self, prop_name, len): 340 """Add a property with a fixed data size, for filling in later 341 342 The device tree is marked dirty so that the value will be written to 343 the blob on the next sync. 344 345 Args: 346 prop_name: Name of property 347 len: Length of data in property 348 """ 349 value = chr(0) * len 350 self.props[prop_name] = Prop(self, None, prop_name, value) 351 352 def SetInt(self, prop_name, val): 353 """Update an integer property int the device tree. 354 355 This is not allowed to change the size of the FDT. 356 357 The device tree is marked dirty so that the value will be written to 358 the blob on the next sync. 359 360 Args: 361 prop_name: Name of property 362 val: Value to set 363 """ 364 self.props[prop_name].SetInt(val) 365 366 def SetData(self, prop_name, val): 367 """Set the data value of a property 368 369 The device tree is marked dirty so that the value will be written to 370 the blob on the next sync. 371 372 Args: 373 prop_name: Name of property to set 374 val: Data value to set 375 """ 376 self.props[prop_name].SetData(val) 377 378 def SetString(self, prop_name, val): 379 """Set the string value of a property 380 381 The device tree is marked dirty so that the value will be written to 382 the blob on the next sync. 383 384 Args: 385 prop_name: Name of property to set 386 val: String value to set (will be \0-terminated in DT) 387 """ 388 self.props[prop_name].SetData(val + chr(0)) 389 390 def AddString(self, prop_name, val): 391 """Add a new string property to a node 392 393 The device tree is marked dirty so that the value will be written to 394 the blob on the next sync. 395 396 Args: 397 prop_name: Name of property to add 398 val: String value of property 399 """ 400 self.props[prop_name] = Prop(self, None, prop_name, val + chr(0)) 401 402 def AddSubnode(self, name): 403 """Add a new subnode to the node 404 405 Args: 406 name: name of node to add 407 408 Returns: 409 New subnode that was created 410 """ 411 path = self.path + '/' + name 412 subnode = Node(self._fdt, self, None, name, path) 413 self.subnodes.append(subnode) 414 return subnode 415 416 def Sync(self, auto_resize=False): 417 """Sync node changes back to the device tree 418 419 This updates the device tree blob with any changes to this node and its 420 subnodes since the last sync. 421 422 Args: 423 auto_resize: Resize the device tree automatically if it does not 424 have enough space for the update 425 426 Raises: 427 FdtException if auto_resize is False and there is not enough space 428 """ 429 if self._offset is None: 430 # The subnode doesn't exist yet, so add it 431 fdt_obj = self._fdt._fdt_obj 432 if auto_resize: 433 while True: 434 offset = fdt_obj.add_subnode(self.parent._offset, self.name, 435 (libfdt.NOSPACE,)) 436 if offset != -libfdt.NOSPACE: 437 break 438 fdt_obj.resize(fdt_obj.totalsize() + 1024) 439 else: 440 offset = fdt_obj.add_subnode(self.parent._offset, self.name) 441 self._offset = offset 442 443 # Sync subnodes in reverse so that we don't disturb node offsets for 444 # nodes that are earlier in the DT. This avoids an O(n^2) rescan of 445 # node offsets. 446 for node in reversed(self.subnodes): 447 node.Sync(auto_resize) 448 449 # Sync properties now, whose offsets should not have been disturbed. 450 # We do this after subnodes, since this disturbs the offsets of these 451 # properties. 452 prop_list = sorted(self.props.values(), key=lambda prop: prop._offset, 453 reverse=True) 454 for prop in prop_list: 455 prop.Sync(auto_resize) 456 457 458class Fdt: 459 """Provides simple access to a flat device tree blob using libfdts. 460 461 Properties: 462 fname: Filename of fdt 463 _root: Root of device tree (a Node object) 464 """ 465 def __init__(self, fname): 466 self._fname = fname 467 self._cached_offsets = False 468 self.phandle_to_node = {} 469 if self._fname: 470 self._fname = fdt_util.EnsureCompiled(self._fname) 471 472 with open(self._fname) as fd: 473 self._fdt_obj = libfdt.Fdt(fd.read()) 474 475 @staticmethod 476 def FromData(data): 477 """Create a new Fdt object from the given data 478 479 Args: 480 data: Device-tree data blob 481 482 Returns: 483 Fdt object containing the data 484 """ 485 fdt = Fdt(None) 486 fdt._fdt_obj = libfdt.Fdt(bytearray(data)) 487 return fdt 488 489 def LookupPhandle(self, phandle): 490 """Look up a phandle 491 492 Args: 493 phandle: Phandle to look up (int) 494 495 Returns: 496 Node object the phandle points to 497 """ 498 return self.phandle_to_node.get(phandle) 499 500 def Scan(self, root='/'): 501 """Scan a device tree, building up a tree of Node objects 502 503 This fills in the self._root property 504 505 Args: 506 root: Ignored 507 508 TODO(sjg@chromium.org): Implement the 'root' parameter 509 """ 510 self._cached_offsets = True 511 self._root = self.Node(self, None, 0, '/', '/') 512 self._root.Scan() 513 514 def GetRoot(self): 515 """Get the root Node of the device tree 516 517 Returns: 518 The root Node object 519 """ 520 return self._root 521 522 def GetNode(self, path): 523 """Look up a node from its path 524 525 Args: 526 path: Path to look up, e.g. '/microcode/update@0' 527 Returns: 528 Node object, or None if not found 529 """ 530 node = self._root 531 parts = path.split('/') 532 if len(parts) < 2: 533 return None 534 for part in parts[1:]: 535 node = node.FindNode(part) 536 if not node: 537 return None 538 return node 539 540 def Flush(self): 541 """Flush device tree changes back to the file 542 543 If the device tree has changed in memory, write it back to the file. 544 """ 545 with open(self._fname, 'wb') as fd: 546 fd.write(self._fdt_obj.as_bytearray()) 547 548 def Sync(self, auto_resize=False): 549 """Make sure any DT changes are written to the blob 550 551 Args: 552 auto_resize: Resize the device tree automatically if it does not 553 have enough space for the update 554 555 Raises: 556 FdtException if auto_resize is False and there is not enough space 557 """ 558 self._root.Sync(auto_resize) 559 self.Invalidate() 560 561 def Pack(self): 562 """Pack the device tree down to its minimum size 563 564 When nodes and properties shrink or are deleted, wasted space can 565 build up in the device tree binary. 566 """ 567 CheckErr(self._fdt_obj.pack(), 'pack') 568 self.Invalidate() 569 570 def GetContents(self): 571 """Get the contents of the FDT 572 573 Returns: 574 The FDT contents as a string of bytes 575 """ 576 return self._fdt_obj.as_bytearray() 577 578 def GetFdtObj(self): 579 """Get the contents of the FDT 580 581 Returns: 582 The FDT contents as a libfdt.Fdt object 583 """ 584 return self._fdt_obj 585 586 def GetProps(self, node): 587 """Get all properties from a node. 588 589 Args: 590 node: Full path to node name to look in. 591 592 Returns: 593 A dictionary containing all the properties, indexed by node name. 594 The entries are Prop objects. 595 596 Raises: 597 ValueError: if the node does not exist. 598 """ 599 props_dict = {} 600 poffset = self._fdt_obj.first_property_offset(node._offset, 601 QUIET_NOTFOUND) 602 while poffset >= 0: 603 p = self._fdt_obj.get_property_by_offset(poffset) 604 prop = Prop(node, poffset, p.name, p) 605 props_dict[prop.name] = prop 606 607 poffset = self._fdt_obj.next_property_offset(poffset, 608 QUIET_NOTFOUND) 609 return props_dict 610 611 def Invalidate(self): 612 """Mark our offset cache as invalid""" 613 self._cached_offsets = False 614 615 def CheckCache(self): 616 """Refresh the offset cache if needed""" 617 if self._cached_offsets: 618 return 619 self.Refresh() 620 self._cached_offsets = True 621 622 def Refresh(self): 623 """Refresh the offset cache""" 624 self._root.Refresh(0) 625 626 def GetStructOffset(self, offset): 627 """Get the file offset of a given struct offset 628 629 Args: 630 offset: Offset within the 'struct' region of the device tree 631 Returns: 632 Position of @offset within the device tree binary 633 """ 634 return self._fdt_obj.off_dt_struct() + offset 635 636 @classmethod 637 def Node(self, fdt, parent, offset, name, path): 638 """Create a new node 639 640 This is used by Fdt.Scan() to create a new node using the correct 641 class. 642 643 Args: 644 fdt: Fdt object 645 parent: Parent node, or None if this is the root node 646 offset: Offset of node 647 name: Node name 648 path: Full path to node 649 """ 650 node = Node(fdt, parent, offset, name, path) 651 return node 652 653def FdtScan(fname): 654 """Returns a new Fdt object""" 655 dtb = Fdt(fname) 656 dtb.Scan() 657 return dtb 658