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 8from enum import IntEnum 9import struct 10import sys 11 12from dtoc import fdt_util 13import libfdt 14from libfdt import QUIET_NOTFOUND 15from u_boot_pylib import tools 16from u_boot_pylib import tout 17 18# This deals with a device tree, presenting it as an assortment of Node and 19# Prop objects, representing nodes and properties, respectively. This file 20# contains the base classes and defines the high-level API. You can use 21# FdtScan() as a convenience function to create and scan an Fdt. 22 23# This implementation uses a libfdt Python library to access the device tree, 24# so it is fairly efficient. 25 26# A list of types we support 27class Type(IntEnum): 28 # Types in order from widest to narrowest 29 (BYTE, INT, STRING, BOOL, INT64) = range(5) 30 31 def needs_widening(self, other): 32 """Check if this type needs widening to hold a value from another type 33 34 A wider type is one that can hold a wider array of information than 35 another one, or is less restrictive, so it can hold the information of 36 another type as well as its own. This is similar to the concept of 37 type-widening in C. 38 39 This uses a simple arithmetic comparison, since type values are in order 40 from widest (BYTE) to narrowest (INT64). 41 42 Args: 43 other: Other type to compare against 44 45 Return: 46 True if the other type is wider 47 """ 48 return self.value > other.value 49 50def CheckErr(errnum, msg): 51 if errnum: 52 raise ValueError('Error %d: %s: %s' % 53 (errnum, libfdt.fdt_strerror(errnum), msg)) 54 55 56def BytesToValue(data): 57 """Converts a string of bytes into a type and value 58 59 Args: 60 A bytes value (which on Python 2 is an alias for str) 61 62 Return: 63 A tuple: 64 Type of data 65 Data, either a single element or a list of elements. Each element 66 is one of: 67 Type.STRING: str/bytes value from the property 68 Type.INT: a byte-swapped integer stored as a 4-byte str/bytes 69 Type.BYTE: a byte stored as a single-byte str/bytes 70 """ 71 data = bytes(data) 72 size = len(data) 73 strings = data.split(b'\0') 74 is_string = True 75 count = len(strings) - 1 76 if count > 0 and not len(strings[-1]): 77 for string in strings[:-1]: 78 if not string: 79 is_string = False 80 break 81 for ch in string: 82 if ch < 32 or ch > 127: 83 is_string = False 84 break 85 else: 86 is_string = False 87 if is_string: 88 if count == 1: 89 return Type.STRING, strings[0].decode() 90 else: 91 return Type.STRING, [s.decode() for s in strings[:-1]] 92 if size % 4: 93 if size == 1: 94 return Type.BYTE, chr(data[0]) 95 else: 96 return Type.BYTE, [chr(ch) for ch in list(data)] 97 val = [] 98 for i in range(0, size, 4): 99 val.append(data[i:i + 4]) 100 if size == 4: 101 return Type.INT, val[0] 102 else: 103 return Type.INT, val 104 105 106class Prop: 107 """A device tree property 108 109 Properties: 110 node: Node containing this property 111 offset: Offset of the property (None if still to be synced) 112 name: Property name (as per the device tree) 113 value: Property value as a string of bytes, or a list of strings of 114 bytes 115 type: Value type 116 """ 117 def __init__(self, node, offset, name, data): 118 self._node = node 119 self._offset = offset 120 self.name = name 121 self.value = None 122 self.bytes = bytes(data) 123 self.dirty = offset is None 124 if not data: 125 self.type = Type.BOOL 126 self.value = True 127 return 128 self.type, self.value = BytesToValue(bytes(data)) 129 130 def RefreshOffset(self, poffset): 131 self._offset = poffset 132 133 def Widen(self, newprop): 134 """Figure out which property type is more general 135 136 Given a current property and a new property, this function returns the 137 one that is less specific as to type. The less specific property will 138 be ble to represent the data in the more specific property. This is 139 used for things like: 140 141 node1 { 142 compatible = "fred"; 143 value = <1>; 144 }; 145 node1 { 146 compatible = "fred"; 147 value = <1 2>; 148 }; 149 150 He we want to use an int array for 'value'. The first property 151 suggests that a single int is enough, but the second one shows that 152 it is not. Calling this function with these two propertes would 153 update the current property to be like the second, since it is less 154 specific. 155 """ 156 if self.type.needs_widening(newprop.type): 157 158 # A boolean has an empty value: if it exists it is True and if not 159 # it is False. So when widening we always start with an empty list 160 # since the only valid integer property would be an empty list of 161 # integers. 162 # e.g. this is a boolean: 163 # some-prop; 164 # and it would be widened to int list by: 165 # some-prop = <1 2>; 166 if self.type == Type.BOOL: 167 self.type = Type.INT 168 self.value = [self.GetEmpty(self.type)] 169 if self.type == Type.INT and newprop.type == Type.BYTE: 170 if type(self.value) == list: 171 new_value = [] 172 for val in self.value: 173 new_value += [chr(by) for by in val] 174 else: 175 new_value = [chr(by) for by in self.value] 176 self.value = new_value 177 self.type = newprop.type 178 179 if type(newprop.value) == list: 180 if type(self.value) != list: 181 self.value = [self.value] 182 183 if len(newprop.value) > len(self.value): 184 val = self.GetEmpty(self.type) 185 while len(self.value) < len(newprop.value): 186 self.value.append(val) 187 188 @classmethod 189 def GetEmpty(self, type): 190 """Get an empty / zero value of the given type 191 192 Returns: 193 A single value of the given type 194 """ 195 if type == Type.BYTE: 196 return chr(0) 197 elif type == Type.INT: 198 return struct.pack('>I', 0); 199 elif type == Type.STRING: 200 return '' 201 else: 202 return True 203 204 def GetOffset(self): 205 """Get the offset of a property 206 207 Returns: 208 The offset of the property (struct fdt_property) within the file 209 """ 210 self._node._fdt.CheckCache() 211 return self._node._fdt.GetStructOffset(self._offset) 212 213 def SetInt(self, val): 214 """Set the integer value of the property 215 216 The device tree is marked dirty so that the value will be written to 217 the block on the next sync. 218 219 Args: 220 val: Integer value (32-bit, single cell) 221 """ 222 self.bytes = struct.pack('>I', val); 223 self.value = self.bytes 224 self.type = Type.INT 225 self.dirty = True 226 227 def SetData(self, bytes): 228 """Set the value of a property as bytes 229 230 Args: 231 bytes: New property value to set 232 """ 233 self.bytes = bytes 234 self.type, self.value = BytesToValue(bytes) 235 self.dirty = True 236 237 def Sync(self, auto_resize=False): 238 """Sync property changes back to the device tree 239 240 This updates the device tree blob with any changes to this property 241 since the last sync. 242 243 Args: 244 auto_resize: Resize the device tree automatically if it does not 245 have enough space for the update 246 247 Raises: 248 FdtException if auto_resize is False and there is not enough space 249 """ 250 if self.dirty: 251 node = self._node 252 tout.debug(f'sync {node.path}: {self.name}') 253 fdt_obj = node._fdt._fdt_obj 254 node_name = fdt_obj.get_name(node._offset) 255 if node_name and node_name != node.name: 256 raise ValueError("Internal error, node '%s' name mismatch '%s'" % 257 (node.path, node_name)) 258 259 if auto_resize: 260 while fdt_obj.setprop(node.Offset(), self.name, self.bytes, 261 (libfdt.NOSPACE,)) == -libfdt.NOSPACE: 262 fdt_obj.resize(fdt_obj.totalsize() + 1024 + 263 len(self.bytes)) 264 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 265 else: 266 fdt_obj.setprop(node.Offset(), self.name, self.bytes) 267 self.dirty = False 268 269 def purge(self): 270 """Set a property offset to None 271 272 The property remains in the tree structure and will be recreated when 273 the FDT is synced 274 """ 275 self._offset = None 276 self.dirty = True 277 278class Node: 279 """A device tree node 280 281 Properties: 282 parent: Parent Node 283 offset: Integer offset in the device tree (None if to be synced) 284 name: Device tree node tname 285 path: Full path to node, along with the node name itself 286 _fdt: Device tree object 287 subnodes: A list of subnodes for this node, each a Node object 288 props: A dict of properties for this node, each a Prop object. 289 Keyed by property name 290 """ 291 def __init__(self, fdt, parent, offset, name, path): 292 self._fdt = fdt 293 self.parent = parent 294 self._offset = offset 295 self.name = name 296 self.path = path 297 self.subnodes = [] 298 self.props = {} 299 300 def GetFdt(self): 301 """Get the Fdt object for this node 302 303 Returns: 304 Fdt object 305 """ 306 return self._fdt 307 308 def FindNode(self, name): 309 """Find a node given its name 310 311 Args: 312 name: Node name to look for 313 Returns: 314 Node object if found, else None 315 """ 316 for subnode in self.subnodes: 317 if subnode.name == name: 318 return subnode 319 return None 320 321 def Offset(self): 322 """Returns the offset of a node, after checking the cache 323 324 This should be used instead of self._offset directly, to ensure that 325 the cache does not contain invalid offsets. 326 """ 327 self._fdt.CheckCache() 328 return self._offset 329 330 def Scan(self): 331 """Scan a node's properties and subnodes 332 333 This fills in the props and subnodes properties, recursively 334 searching into subnodes so that the entire tree is built. 335 """ 336 fdt_obj = self._fdt._fdt_obj 337 self.props = self._fdt.GetProps(self) 338 phandle = fdt_obj.get_phandle(self.Offset()) 339 if phandle: 340 dup = self._fdt.phandle_to_node.get(phandle) 341 if dup: 342 raise ValueError( 343 f'Duplicate phandle {phandle} in nodes {dup.path} and {self.path}') 344 345 self._fdt.phandle_to_node[phandle] = self 346 347 offset = fdt_obj.first_subnode(self.Offset(), QUIET_NOTFOUND) 348 while offset >= 0: 349 sep = '' if self.path[-1] == '/' else '/' 350 name = fdt_obj.get_name(offset) 351 path = self.path + sep + name 352 node = Node(self._fdt, self, offset, name, path) 353 self.subnodes.append(node) 354 355 node.Scan() 356 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 357 358 def Refresh(self, my_offset): 359 """Fix up the _offset for each node, recursively 360 361 Note: This does not take account of property offsets - these will not 362 be updated. 363 """ 364 fdt_obj = self._fdt._fdt_obj 365 if self._offset != my_offset: 366 self._offset = my_offset 367 name = fdt_obj.get_name(self._offset) 368 if name and self.name != name: 369 raise ValueError("Internal error, node '%s' name mismatch '%s'" % 370 (self.path, name)) 371 372 offset = fdt_obj.first_subnode(self._offset, QUIET_NOTFOUND) 373 for subnode in self.subnodes: 374 if subnode._offset is None: 375 continue 376 if subnode.name != fdt_obj.get_name(offset): 377 raise ValueError('Internal error, node name mismatch %s != %s' % 378 (subnode.name, fdt_obj.get_name(offset))) 379 subnode.Refresh(offset) 380 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 381 if offset != -libfdt.FDT_ERR_NOTFOUND: 382 raise ValueError('Internal error, offset == %d' % offset) 383 384 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 385 while poffset >= 0: 386 p = fdt_obj.get_property_by_offset(poffset) 387 prop = self.props.get(p.name) 388 if not prop: 389 raise ValueError("Internal error, node '%s' property '%s' missing, " 390 'offset %d' % (self.path, p.name, poffset)) 391 prop.RefreshOffset(poffset) 392 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 393 394 def DeleteProp(self, prop_name): 395 """Delete a property of a node 396 397 The property is deleted and the offset cache is invalidated. 398 399 Args: 400 prop_name: Name of the property to delete 401 Raises: 402 ValueError if the property does not exist 403 """ 404 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 405 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 406 del self.props[prop_name] 407 self._fdt.Invalidate() 408 409 def AddZeroProp(self, prop_name): 410 """Add a new property to the device tree with an integer value of 0. 411 412 Args: 413 prop_name: Name of property 414 """ 415 self.props[prop_name] = Prop(self, None, prop_name, 416 tools.get_bytes(0, 4)) 417 418 def AddEmptyProp(self, prop_name, len): 419 """Add a property with a fixed data size, for filling in later 420 421 The device tree is marked dirty so that the value will be written to 422 the blob on the next sync. 423 424 Args: 425 prop_name: Name of property 426 len: Length of data in property 427 """ 428 value = tools.get_bytes(0, len) 429 self.props[prop_name] = Prop(self, None, prop_name, value) 430 431 def _CheckProp(self, prop_name): 432 """Check if a property is present 433 434 Args: 435 prop_name: Name of property 436 437 Returns: 438 self 439 440 Raises: 441 ValueError if the property is missing 442 """ 443 if prop_name not in self.props: 444 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" % 445 (self._fdt._fname, self.path, prop_name)) 446 return self 447 448 def SetInt(self, prop_name, val): 449 """Update an integer property int the device tree. 450 451 This is not allowed to change the size of the FDT. 452 453 The device tree is marked dirty so that the value will be written to 454 the blob on the next sync. 455 456 Args: 457 prop_name: Name of property 458 val: Value to set 459 """ 460 self._CheckProp(prop_name).props[prop_name].SetInt(val) 461 462 def SetData(self, prop_name, val): 463 """Set the data value of a property 464 465 The device tree is marked dirty so that the value will be written to 466 the blob on the next sync. 467 468 Args: 469 prop_name: Name of property to set 470 val: Data value to set 471 """ 472 self._CheckProp(prop_name).props[prop_name].SetData(val) 473 474 def SetString(self, prop_name, val): 475 """Set the string value of a property 476 477 The device tree is marked dirty so that the value will be written to 478 the blob on the next sync. 479 480 Args: 481 prop_name: Name of property to set 482 val: String value to set (will be \0-terminated in DT) 483 """ 484 if type(val) == str: 485 val = val.encode('utf-8') 486 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0') 487 488 def AddData(self, prop_name, val): 489 """Add a new property to a node 490 491 The device tree is marked dirty so that the value will be written to 492 the blob on the next sync. 493 494 Args: 495 prop_name: Name of property to add 496 val: Bytes value of property 497 498 Returns: 499 Prop added 500 """ 501 prop = Prop(self, None, prop_name, val) 502 self.props[prop_name] = prop 503 return prop 504 505 def AddString(self, prop_name, val): 506 """Add a new string property to a node 507 508 The device tree is marked dirty so that the value will be written to 509 the blob on the next sync. 510 511 Args: 512 prop_name: Name of property to add 513 val: String value of property 514 515 Returns: 516 Prop added 517 """ 518 val = bytes(val, 'utf-8') 519 return self.AddData(prop_name, val + b'\0') 520 521 def AddStringList(self, prop_name, val): 522 """Add a new string-list property to a node 523 524 The device tree is marked dirty so that the value will be written to 525 the blob on the next sync. 526 527 Args: 528 prop_name: Name of property to add 529 val (list of str): List of strings to add 530 531 Returns: 532 Prop added 533 """ 534 out = b'\0'.join(bytes(s, 'utf-8') for s in val) + b'\0' if val else b'' 535 return self.AddData(prop_name, out) 536 537 def AddInt(self, prop_name, val): 538 """Add a new integer property to a node 539 540 The device tree is marked dirty so that the value will be written to 541 the blob on the next sync. 542 543 Args: 544 prop_name: Name of property to add 545 val: Integer value of property 546 547 Returns: 548 Prop added 549 """ 550 return self.AddData(prop_name, struct.pack('>I', val)) 551 552 def Subnode(self, name): 553 """Create new subnode for the node 554 555 Args: 556 name: name of node to add 557 558 Returns: 559 New subnode that was created 560 """ 561 path = self.path + '/' + name 562 return Node(self._fdt, self, None, name, path) 563 564 def AddSubnode(self, name): 565 """Add a new subnode to the node, after all other subnodes 566 567 Args: 568 name: name of node to add 569 570 Returns: 571 New subnode that was created 572 """ 573 subnode = self.Subnode(name) 574 self.subnodes.append(subnode) 575 return subnode 576 577 def insert_subnode(self, name): 578 """Add a new subnode to the node, before all other subnodes 579 580 This deletes other subnodes and sets their offset to None, so that they 581 will be recreated after this one. 582 583 Args: 584 name: name of node to add 585 586 Returns: 587 New subnode that was created 588 """ 589 # Deleting a node invalidates the offsets of all following nodes, so 590 # process in reverse order so that the offset of each node remains valid 591 # until deletion. 592 for subnode in reversed(self.subnodes): 593 subnode.purge(True) 594 subnode = self.Subnode(name) 595 self.subnodes.insert(0, subnode) 596 return subnode 597 598 def purge(self, delete_it=False): 599 """Purge this node, setting offset to None and deleting from FDT""" 600 if self._offset is not None: 601 if delete_it: 602 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()), 603 "Node '%s': delete" % self.path) 604 self._offset = None 605 self._fdt.Invalidate() 606 607 for prop in self.props.values(): 608 prop.purge() 609 610 for subnode in self.subnodes: 611 subnode.purge(False) 612 613 def move_to_first(self): 614 """Move the current node to first in its parent's node list""" 615 parent = self.parent 616 if parent.subnodes and parent.subnodes[0] == self: 617 return 618 for subnode in reversed(parent.subnodes): 619 subnode.purge(True) 620 621 new_subnodes = [self] 622 for subnode in parent.subnodes: 623 #subnode.purge(False) 624 if subnode != self: 625 new_subnodes.append(subnode) 626 parent.subnodes = new_subnodes 627 628 def Delete(self): 629 """Delete a node 630 631 The node is deleted and the offset cache is invalidated. 632 633 Args: 634 node (Node): Node to delete 635 636 Raises: 637 ValueError if the node does not exist 638 """ 639 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()), 640 "Node '%s': delete" % self.path) 641 parent = self.parent 642 self._fdt.Invalidate() 643 parent.subnodes.remove(self) 644 645 def Sync(self, auto_resize=False): 646 """Sync node changes back to the device tree 647 648 This updates the device tree blob with any changes to this node and its 649 subnodes since the last sync. 650 651 Args: 652 auto_resize: Resize the device tree automatically if it does not 653 have enough space for the update 654 655 Returns: 656 True if the node had to be added, False if it already existed 657 658 Raises: 659 FdtException if auto_resize is False and there is not enough space 660 """ 661 added = False 662 if self._offset is None: 663 # The subnode doesn't exist yet, so add it 664 fdt_obj = self._fdt._fdt_obj 665 if auto_resize: 666 while True: 667 offset = fdt_obj.add_subnode(self.parent._offset, self.name, 668 (libfdt.NOSPACE,)) 669 if offset != -libfdt.NOSPACE: 670 break 671 fdt_obj.resize(fdt_obj.totalsize() + 1024) 672 else: 673 offset = fdt_obj.add_subnode(self.parent._offset, self.name) 674 self._offset = offset 675 added = True 676 677 # Sync the existing subnodes first, so that we can rely on the offsets 678 # being correct. As soon as we add new subnodes, it pushes all the 679 # existing subnodes up. 680 for node in reversed(self.subnodes): 681 if node._offset is not None: 682 node.Sync(auto_resize) 683 684 # Sync subnodes in reverse so that we get the expected order. Each 685 # new node goes at the start of the subnode list. This avoids an O(n^2) 686 # rescan of node offsets. 687 num_added = 0 688 for node in reversed(self.subnodes): 689 if node.Sync(auto_resize): 690 num_added += 1 691 if num_added: 692 # Reorder our list of nodes to put the new ones first, since that's 693 # what libfdt does 694 old_count = len(self.subnodes) - num_added 695 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count] 696 self.subnodes = subnodes 697 698 # Sync properties now, whose offsets should not have been disturbed, 699 # since properties come before subnodes. This is done after all the 700 # subnode processing above, since updating properties can disturb the 701 # offsets of those subnodes. 702 # Properties are synced in reverse order, with new properties added 703 # before existing properties are synced. This ensures that the offsets 704 # of earlier properties are not disturbed. 705 # Note that new properties will have an offset of None here, which 706 # Python cannot sort against int. So use a large value instead so that 707 # new properties are added first. 708 prop_list = sorted(self.props.values(), 709 key=lambda prop: prop._offset or 1 << 31, 710 reverse=True) 711 for prop in prop_list: 712 prop.Sync(auto_resize) 713 return added 714 715 def merge_props(self, src, copy_phandles): 716 """Copy missing properties (except 'phandle') from another node 717 718 Args: 719 src (Node): Node containing properties to copy 720 copy_phandles (bool): True to copy phandle properties in nodes 721 722 Adds properties which are present in src but not in this node. Any 723 'phandle' property is not copied since this might result in two nodes 724 with the same phandle, thus making phandle references ambiguous. 725 """ 726 tout.debug(f'copy to {self.path}: {src.path}') 727 for name, src_prop in src.props.items(): 728 done = False 729 if name not in self.props: 730 if copy_phandles or name != 'phandle': 731 self.props[name] = Prop(self, None, name, src_prop.bytes) 732 done = True 733 tout.debug(f" {name}{'' if done else ' - ignored'}") 734 735 def copy_node(self, src, copy_phandles=False): 736 """Copy a node and all its subnodes into this node 737 738 Args: 739 src (Node): Node to copy 740 copy_phandles (bool): True to copy phandle properties in nodes 741 742 Returns: 743 Node: Resulting destination node 744 745 This works recursively, with copy_phandles being set to True for the 746 recursive calls 747 748 The new node is put before all other nodes. If the node already 749 exists, just its subnodes and properties are copied, placing them before 750 any existing subnodes. Properties which exist in the destination node 751 already are not copied. 752 """ 753 dst = self.FindNode(src.name) 754 if dst: 755 dst.move_to_first() 756 else: 757 dst = self.insert_subnode(src.name) 758 dst.merge_props(src, copy_phandles) 759 760 # Process in reverse order so that they appear correctly in the result, 761 # since copy_node() puts the node first in the list 762 for node in reversed(src.subnodes): 763 dst.copy_node(node, True) 764 return dst 765 766 def copy_subnodes_from_phandles(self, phandle_list): 767 """Copy subnodes of a list of nodes into another node 768 769 Args: 770 phandle_list (list of int): List of phandles of nodes to copy 771 772 For each node in the phandle list, its subnodes and their properties are 773 copied recursively. Note that it does not copy the node itself, nor its 774 properties. 775 """ 776 # Process in reverse order, since new nodes are inserted at the start of 777 # the destination's node list. We want them to appear in order of the 778 # phandle list 779 for phandle in phandle_list.__reversed__(): 780 parent = self.GetFdt().LookupPhandle(phandle) 781 tout.debug(f'adding template {parent.path} to node {self.path}') 782 for node in parent.subnodes.__reversed__(): 783 dst = self.copy_node(node) 784 785 tout.debug(f'merge props from {parent.path} to {self.path}') 786 self.merge_props(parent, False) 787 788 789class Fdt: 790 """Provides simple access to a flat device tree blob using libfdts. 791 792 Properties: 793 fname: Filename of fdt 794 _root: Root of device tree (a Node object) 795 name: Helpful name for this Fdt for the user (useful when creating the 796 DT from data rather than a file) 797 """ 798 def __init__(self, fname): 799 self._fname = fname 800 self._cached_offsets = False 801 self.phandle_to_node = {} 802 self.name = '' 803 if self._fname: 804 self.name = self._fname 805 self._fname = fdt_util.EnsureCompiled(self._fname) 806 807 with open(self._fname, 'rb') as fd: 808 self._fdt_obj = libfdt.Fdt(fd.read()) 809 810 @staticmethod 811 def FromData(data, name=''): 812 """Create a new Fdt object from the given data 813 814 Args: 815 data: Device-tree data blob 816 name: Helpful name for this Fdt for the user 817 818 Returns: 819 Fdt object containing the data 820 """ 821 fdt = Fdt(None) 822 fdt._fdt_obj = libfdt.Fdt(bytes(data)) 823 fdt.name = name 824 return fdt 825 826 def LookupPhandle(self, phandle): 827 """Look up a phandle 828 829 Args: 830 phandle: Phandle to look up (int) 831 832 Returns: 833 Node object the phandle points to 834 """ 835 return self.phandle_to_node.get(phandle) 836 837 def Scan(self, root='/'): 838 """Scan a device tree, building up a tree of Node objects 839 840 This fills in the self._root property 841 842 Args: 843 root: Ignored 844 845 TODO(sjg@chromium.org): Implement the 'root' parameter 846 """ 847 self.phandle_to_node = {} 848 self._cached_offsets = True 849 self._root = self.Node(self, None, 0, '/', '/') 850 self._root.Scan() 851 852 def GetRoot(self): 853 """Get the root Node of the device tree 854 855 Returns: 856 The root Node object 857 """ 858 return self._root 859 860 def GetNode(self, path): 861 """Look up a node from its path 862 863 Args: 864 path: Path to look up, e.g. '/microcode/update@0' 865 Returns: 866 Node object, or None if not found 867 """ 868 node = self._root 869 parts = path.split('/') 870 if len(parts) < 2: 871 return None 872 if len(parts) == 2 and parts[1] == '': 873 return node 874 for part in parts[1:]: 875 node = node.FindNode(part) 876 if not node: 877 return None 878 return node 879 880 def Flush(self): 881 """Flush device tree changes back to the file 882 883 If the device tree has changed in memory, write it back to the file. 884 """ 885 with open(self._fname, 'wb') as fd: 886 fd.write(self._fdt_obj.as_bytearray()) 887 888 def Sync(self, auto_resize=False): 889 """Make sure any DT changes are written to the blob 890 891 Args: 892 auto_resize: Resize the device tree automatically if it does not 893 have enough space for the update 894 895 Raises: 896 FdtException if auto_resize is False and there is not enough space 897 """ 898 self.CheckCache() 899 self._root.Sync(auto_resize) 900 self.Refresh() 901 902 def Pack(self): 903 """Pack the device tree down to its minimum size 904 905 When nodes and properties shrink or are deleted, wasted space can 906 build up in the device tree binary. 907 """ 908 CheckErr(self._fdt_obj.pack(), 'pack') 909 self.Refresh() 910 911 def GetContents(self): 912 """Get the contents of the FDT 913 914 Returns: 915 The FDT contents as a string of bytes 916 """ 917 return bytes(self._fdt_obj.as_bytearray()) 918 919 def GetFdtObj(self): 920 """Get the contents of the FDT 921 922 Returns: 923 The FDT contents as a libfdt.Fdt object 924 """ 925 return self._fdt_obj 926 927 def GetProps(self, node): 928 """Get all properties from a node. 929 930 Args: 931 node: Full path to node name to look in. 932 933 Returns: 934 A dictionary containing all the properties, indexed by node name. 935 The entries are Prop objects. 936 937 Raises: 938 ValueError: if the node does not exist. 939 """ 940 props_dict = {} 941 poffset = self._fdt_obj.first_property_offset(node._offset, 942 QUIET_NOTFOUND) 943 while poffset >= 0: 944 p = self._fdt_obj.get_property_by_offset(poffset) 945 prop = Prop(node, poffset, p.name, p) 946 props_dict[prop.name] = prop 947 948 poffset = self._fdt_obj.next_property_offset(poffset, 949 QUIET_NOTFOUND) 950 return props_dict 951 952 def Invalidate(self): 953 """Mark our offset cache as invalid""" 954 self._cached_offsets = False 955 956 def CheckCache(self): 957 """Refresh the offset cache if needed""" 958 if self._cached_offsets: 959 return 960 self.Refresh() 961 962 def Refresh(self): 963 """Refresh the offset cache""" 964 self._root.Refresh(0) 965 self._cached_offsets = True 966 967 def GetStructOffset(self, offset): 968 """Get the file offset of a given struct offset 969 970 Args: 971 offset: Offset within the 'struct' region of the device tree 972 Returns: 973 Position of @offset within the device tree binary 974 """ 975 return self._fdt_obj.off_dt_struct() + offset 976 977 @classmethod 978 def Node(self, fdt, parent, offset, name, path): 979 """Create a new node 980 981 This is used by Fdt.Scan() to create a new node using the correct 982 class. 983 984 Args: 985 fdt: Fdt object 986 parent: Parent node, or None if this is the root node 987 offset: Offset of node 988 name: Node name 989 path: Full path to node 990 """ 991 node = Node(fdt, parent, offset, name, path) 992 return node 993 994 def GetFilename(self): 995 """Get the filename of the device tree 996 997 Returns: 998 String filename 999 """ 1000 return self._fname 1001 1002def FdtScan(fname): 1003 """Returns a new Fdt object""" 1004 dtb = Fdt(fname) 1005 dtb.Scan() 1006 return dtb 1007