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