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