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