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 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._offset is None: 360 continue 361 if subnode.name != fdt_obj.get_name(offset): 362 raise ValueError('Internal error, node name mismatch %s != %s' % 363 (subnode.name, fdt_obj.get_name(offset))) 364 subnode.Refresh(offset) 365 offset = fdt_obj.next_subnode(offset, QUIET_NOTFOUND) 366 if offset != -libfdt.FDT_ERR_NOTFOUND: 367 raise ValueError('Internal error, offset == %d' % offset) 368 369 poffset = fdt_obj.first_property_offset(self._offset, QUIET_NOTFOUND) 370 while poffset >= 0: 371 p = fdt_obj.get_property_by_offset(poffset) 372 prop = self.props.get(p.name) 373 if not prop: 374 raise ValueError("Internal error, node '%s' property '%s' missing, " 375 'offset %d' % (self.path, p.name, poffset)) 376 prop.RefreshOffset(poffset) 377 poffset = fdt_obj.next_property_offset(poffset, QUIET_NOTFOUND) 378 379 def DeleteProp(self, prop_name): 380 """Delete a property of a node 381 382 The property is deleted and the offset cache is invalidated. 383 384 Args: 385 prop_name: Name of the property to delete 386 Raises: 387 ValueError if the property does not exist 388 """ 389 CheckErr(self._fdt._fdt_obj.delprop(self.Offset(), prop_name), 390 "Node '%s': delete property: '%s'" % (self.path, prop_name)) 391 del self.props[prop_name] 392 self._fdt.Invalidate() 393 394 def AddZeroProp(self, prop_name): 395 """Add a new property to the device tree with an integer value of 0. 396 397 Args: 398 prop_name: Name of property 399 """ 400 self.props[prop_name] = Prop(self, None, prop_name, 401 tools.get_bytes(0, 4)) 402 403 def AddEmptyProp(self, prop_name, len): 404 """Add a property with a fixed data size, for filling in later 405 406 The device tree is marked dirty so that the value will be written to 407 the blob on the next sync. 408 409 Args: 410 prop_name: Name of property 411 len: Length of data in property 412 """ 413 value = tools.get_bytes(0, len) 414 self.props[prop_name] = Prop(self, None, prop_name, value) 415 416 def _CheckProp(self, prop_name): 417 """Check if a property is present 418 419 Args: 420 prop_name: Name of property 421 422 Returns: 423 self 424 425 Raises: 426 ValueError if the property is missing 427 """ 428 if prop_name not in self.props: 429 raise ValueError("Fdt '%s', node '%s': Missing property '%s'" % 430 (self._fdt._fname, self.path, prop_name)) 431 return self 432 433 def SetInt(self, prop_name, val): 434 """Update an integer property int the device tree. 435 436 This is not allowed to change the size of the FDT. 437 438 The device tree is marked dirty so that the value will be written to 439 the blob on the next sync. 440 441 Args: 442 prop_name: Name of property 443 val: Value to set 444 """ 445 self._CheckProp(prop_name).props[prop_name].SetInt(val) 446 447 def SetData(self, prop_name, val): 448 """Set the data value of a property 449 450 The device tree is marked dirty so that the value will be written to 451 the blob on the next sync. 452 453 Args: 454 prop_name: Name of property to set 455 val: Data value to set 456 """ 457 self._CheckProp(prop_name).props[prop_name].SetData(val) 458 459 def SetString(self, prop_name, val): 460 """Set the string value of a property 461 462 The device tree is marked dirty so that the value will be written to 463 the blob on the next sync. 464 465 Args: 466 prop_name: Name of property to set 467 val: String value to set (will be \0-terminated in DT) 468 """ 469 if type(val) == str: 470 val = val.encode('utf-8') 471 self._CheckProp(prop_name).props[prop_name].SetData(val + b'\0') 472 473 def AddData(self, prop_name, val): 474 """Add a new property to a node 475 476 The device tree is marked dirty so that the value will be written to 477 the blob on the next sync. 478 479 Args: 480 prop_name: Name of property to add 481 val: Bytes value of property 482 483 Returns: 484 Prop added 485 """ 486 prop = Prop(self, None, prop_name, val) 487 self.props[prop_name] = prop 488 return prop 489 490 def AddString(self, prop_name, val): 491 """Add a new string property to a node 492 493 The device tree is marked dirty so that the value will be written to 494 the blob on the next sync. 495 496 Args: 497 prop_name: Name of property to add 498 val: String value of property 499 500 Returns: 501 Prop added 502 """ 503 val = bytes(val, 'utf-8') 504 return self.AddData(prop_name, val + b'\0') 505 506 def AddStringList(self, prop_name, val): 507 """Add a new string-list property to a node 508 509 The device tree is marked dirty so that the value will be written to 510 the blob on the next sync. 511 512 Args: 513 prop_name: Name of property to add 514 val (list of str): List of strings to add 515 516 Returns: 517 Prop added 518 """ 519 out = b'\0'.join(bytes(s, 'utf-8') for s in val) + b'\0' if val else b'' 520 return self.AddData(prop_name, out) 521 522 def AddInt(self, prop_name, val): 523 """Add a new integer property to a node 524 525 The device tree is marked dirty so that the value will be written to 526 the blob on the next sync. 527 528 Args: 529 prop_name: Name of property to add 530 val: Integer value of property 531 532 Returns: 533 Prop added 534 """ 535 return self.AddData(prop_name, struct.pack('>I', val)) 536 537 def AddSubnode(self, name): 538 """Add a new subnode to the node 539 540 Args: 541 name: name of node to add 542 543 Returns: 544 New subnode that was created 545 """ 546 path = self.path + '/' + name 547 subnode = Node(self._fdt, self, None, name, path) 548 self.subnodes.append(subnode) 549 return subnode 550 551 def Delete(self): 552 """Delete a node 553 554 The node is deleted and the offset cache is invalidated. 555 556 Args: 557 node (Node): Node to delete 558 559 Raises: 560 ValueError if the node does not exist 561 """ 562 CheckErr(self._fdt._fdt_obj.del_node(self.Offset()), 563 "Node '%s': delete" % self.path) 564 parent = self.parent 565 self._fdt.Invalidate() 566 parent.subnodes.remove(self) 567 568 def Sync(self, auto_resize=False): 569 """Sync node changes back to the device tree 570 571 This updates the device tree blob with any changes to this node and its 572 subnodes since the last sync. 573 574 Args: 575 auto_resize: Resize the device tree automatically if it does not 576 have enough space for the update 577 578 Returns: 579 True if the node had to be added, False if it already existed 580 581 Raises: 582 FdtException if auto_resize is False and there is not enough space 583 """ 584 added = False 585 if self._offset is None: 586 # The subnode doesn't exist yet, so add it 587 fdt_obj = self._fdt._fdt_obj 588 if auto_resize: 589 while True: 590 offset = fdt_obj.add_subnode(self.parent._offset, self.name, 591 (libfdt.NOSPACE,)) 592 if offset != -libfdt.NOSPACE: 593 break 594 fdt_obj.resize(fdt_obj.totalsize() + 1024) 595 else: 596 offset = fdt_obj.add_subnode(self.parent._offset, self.name) 597 self._offset = offset 598 added = True 599 600 # Sync the existing subnodes first, so that we can rely on the offsets 601 # being correct. As soon as we add new subnodes, it pushes all the 602 # existing subnodes up. 603 for node in reversed(self.subnodes): 604 if node._offset is not None: 605 node.Sync(auto_resize) 606 607 # Sync subnodes in reverse so that we get the expected order. Each 608 # new node goes at the start of the subnode list. This avoids an O(n^2) 609 # rescan of node offsets. 610 num_added = 0 611 for node in reversed(self.subnodes): 612 if node.Sync(auto_resize): 613 num_added += 1 614 if num_added: 615 # Reorder our list of nodes to put the new ones first, since that's 616 # what libfdt does 617 old_count = len(self.subnodes) - num_added 618 subnodes = self.subnodes[old_count:] + self.subnodes[:old_count] 619 self.subnodes = subnodes 620 621 # Sync properties now, whose offsets should not have been disturbed, 622 # since properties come before subnodes. This is done after all the 623 # subnode processing above, since updating properties can disturb the 624 # offsets of those subnodes. 625 # Properties are synced in reverse order, with new properties added 626 # before existing properties are synced. This ensures that the offsets 627 # of earlier properties are not disturbed. 628 # Note that new properties will have an offset of None here, which 629 # Python cannot sort against int. So use a large value instead so that 630 # new properties are added first. 631 prop_list = sorted(self.props.values(), 632 key=lambda prop: prop._offset or 1 << 31, 633 reverse=True) 634 for prop in prop_list: 635 prop.Sync(auto_resize) 636 return added 637 638 639class Fdt: 640 """Provides simple access to a flat device tree blob using libfdts. 641 642 Properties: 643 fname: Filename of fdt 644 _root: Root of device tree (a Node object) 645 name: Helpful name for this Fdt for the user (useful when creating the 646 DT from data rather than a file) 647 """ 648 def __init__(self, fname): 649 self._fname = fname 650 self._cached_offsets = False 651 self.phandle_to_node = {} 652 self.name = '' 653 if self._fname: 654 self.name = self._fname 655 self._fname = fdt_util.EnsureCompiled(self._fname) 656 657 with open(self._fname, 'rb') as fd: 658 self._fdt_obj = libfdt.Fdt(fd.read()) 659 660 @staticmethod 661 def FromData(data, name=''): 662 """Create a new Fdt object from the given data 663 664 Args: 665 data: Device-tree data blob 666 name: Helpful name for this Fdt for the user 667 668 Returns: 669 Fdt object containing the data 670 """ 671 fdt = Fdt(None) 672 fdt._fdt_obj = libfdt.Fdt(bytes(data)) 673 fdt.name = name 674 return fdt 675 676 def LookupPhandle(self, phandle): 677 """Look up a phandle 678 679 Args: 680 phandle: Phandle to look up (int) 681 682 Returns: 683 Node object the phandle points to 684 """ 685 return self.phandle_to_node.get(phandle) 686 687 def Scan(self, root='/'): 688 """Scan a device tree, building up a tree of Node objects 689 690 This fills in the self._root property 691 692 Args: 693 root: Ignored 694 695 TODO(sjg@chromium.org): Implement the 'root' parameter 696 """ 697 self._cached_offsets = True 698 self._root = self.Node(self, None, 0, '/', '/') 699 self._root.Scan() 700 701 def GetRoot(self): 702 """Get the root Node of the device tree 703 704 Returns: 705 The root Node object 706 """ 707 return self._root 708 709 def GetNode(self, path): 710 """Look up a node from its path 711 712 Args: 713 path: Path to look up, e.g. '/microcode/update@0' 714 Returns: 715 Node object, or None if not found 716 """ 717 node = self._root 718 parts = path.split('/') 719 if len(parts) < 2: 720 return None 721 if len(parts) == 2 and parts[1] == '': 722 return node 723 for part in parts[1:]: 724 node = node.FindNode(part) 725 if not node: 726 return None 727 return node 728 729 def Flush(self): 730 """Flush device tree changes back to the file 731 732 If the device tree has changed in memory, write it back to the file. 733 """ 734 with open(self._fname, 'wb') as fd: 735 fd.write(self._fdt_obj.as_bytearray()) 736 737 def Sync(self, auto_resize=False): 738 """Make sure any DT changes are written to the blob 739 740 Args: 741 auto_resize: Resize the device tree automatically if it does not 742 have enough space for the update 743 744 Raises: 745 FdtException if auto_resize is False and there is not enough space 746 """ 747 self.CheckCache() 748 self._root.Sync(auto_resize) 749 self.Refresh() 750 751 def Pack(self): 752 """Pack the device tree down to its minimum size 753 754 When nodes and properties shrink or are deleted, wasted space can 755 build up in the device tree binary. 756 """ 757 CheckErr(self._fdt_obj.pack(), 'pack') 758 self.Refresh() 759 760 def GetContents(self): 761 """Get the contents of the FDT 762 763 Returns: 764 The FDT contents as a string of bytes 765 """ 766 return bytes(self._fdt_obj.as_bytearray()) 767 768 def GetFdtObj(self): 769 """Get the contents of the FDT 770 771 Returns: 772 The FDT contents as a libfdt.Fdt object 773 """ 774 return self._fdt_obj 775 776 def GetProps(self, node): 777 """Get all properties from a node. 778 779 Args: 780 node: Full path to node name to look in. 781 782 Returns: 783 A dictionary containing all the properties, indexed by node name. 784 The entries are Prop objects. 785 786 Raises: 787 ValueError: if the node does not exist. 788 """ 789 props_dict = {} 790 poffset = self._fdt_obj.first_property_offset(node._offset, 791 QUIET_NOTFOUND) 792 while poffset >= 0: 793 p = self._fdt_obj.get_property_by_offset(poffset) 794 prop = Prop(node, poffset, p.name, p) 795 props_dict[prop.name] = prop 796 797 poffset = self._fdt_obj.next_property_offset(poffset, 798 QUIET_NOTFOUND) 799 return props_dict 800 801 def Invalidate(self): 802 """Mark our offset cache as invalid""" 803 self._cached_offsets = False 804 805 def CheckCache(self): 806 """Refresh the offset cache if needed""" 807 if self._cached_offsets: 808 return 809 self.Refresh() 810 811 def Refresh(self): 812 """Refresh the offset cache""" 813 self._root.Refresh(0) 814 self._cached_offsets = True 815 816 def GetStructOffset(self, offset): 817 """Get the file offset of a given struct offset 818 819 Args: 820 offset: Offset within the 'struct' region of the device tree 821 Returns: 822 Position of @offset within the device tree binary 823 """ 824 return self._fdt_obj.off_dt_struct() + offset 825 826 @classmethod 827 def Node(self, fdt, parent, offset, name, path): 828 """Create a new node 829 830 This is used by Fdt.Scan() to create a new node using the correct 831 class. 832 833 Args: 834 fdt: Fdt object 835 parent: Parent node, or None if this is the root node 836 offset: Offset of node 837 name: Node name 838 path: Full path to node 839 """ 840 node = Node(fdt, parent, offset, name, path) 841 return node 842 843 def GetFilename(self): 844 """Get the filename of the device tree 845 846 Returns: 847 String filename 848 """ 849 return self._fname 850 851def FdtScan(fname): 852 """Returns a new Fdt object""" 853 dtb = Fdt(fname) 854 dtb.Scan() 855 return dtb 856