1# -*- coding: utf-8 -*- 2""" 3Device Tree Blob Parser 4 5 Copyright 2014 Neil 'superna' Armstrong <superna9999@gmail.com> 6 7 Licensed under the Apache License, Version 2.0 (the "License"); 8 you may not use this file except in compliance with the License. 9 You may obtain a copy of the License at 10 11 http://www.apache.org/licenses/LICENSE-2.0 12 13 Unless required by applicable law or agreed to in writing, software 14 distributed under the License is distributed on an "AS IS" BASIS, 15 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 See the License for the specific language governing permissions and 17 limitations under the License. 18 19@author: Neil 'superna' Armstrong <superna9999@gmail.com> 20""" 21 22from __future__ import print_function 23from __future__ import absolute_import 24 25import string 26import os 27import json 28from copy import deepcopy, copy 29from struct import Struct, unpack, pack 30 31FDT_MAGIC = 0xd00dfeed 32FDT_BEGIN_NODE = 0x1 33FDT_END_NODE = 0x2 34FDT_PROP = 0x3 35FDT_NOP = 0x4 36FDT_END = 0x9 37 38INDENT = ' ' * 4 39 40FDT_MAX_VERSION = 17 41 42 43class FdtProperty(object): 44 """ Represents an empty property""" 45 46 @staticmethod 47 def __validate_dt_name(name): 48 """Checks the name validity""" 49 return not any([True for char in name 50 if char not in string.printable]) 51 52 def __init__(self, name): 53 """Init with name""" 54 self.name = name 55 if not FdtProperty.__validate_dt_name(self.name): 56 raise Exception("Invalid name '%s'" % self.name) 57 58 def get_name(self): 59 """Get property name""" 60 return self.name 61 62 def __str__(self): 63 """String representation""" 64 return "Property(%s)" % self.name 65 66 def dts_represent(self, depth=0): 67 """Get dts string representation""" 68 return INDENT*depth + self.name + ';' 69 70 def dtb_represent(self, string_store, pos=0, version=17): 71 """Get blob representation""" 72 # print "%x:%s" % (pos, self) 73 strpos = string_store.find(self.name+'\0') 74 if strpos < 0: 75 strpos = len(string_store) 76 string_store += self.name+'\0' 77 pos += 12 78 return (pack('>III', FDT_PROP, 0, strpos), 79 string_store, pos) 80 81 def json_represent(self, depth=0): 82 """Ouput JSON""" 83 return '%s: null' % json.dumps(self.name) 84 85 def to_raw(self): 86 """Return RAW value representation""" 87 return '' 88 89 def __getitem__(self, value): 90 """Returns No Items""" 91 return None 92 93 def __ne__(self, node): 94 """Check property inequality 95 """ 96 return not self.__eq__(node) 97 98 def __eq__(self, node): 99 """Check node equality 100 check properties are the same (same values) 101 """ 102 if not isinstance(node, FdtProperty): 103 raise Exception("Invalid object type") 104 if self.name != node.get_name(): 105 return False 106 return True 107 108 @staticmethod 109 def __check_prop_strings(value): 110 """Check property string validity 111 Python version of util_is_printable_string from dtc 112 """ 113 pos = 0 114 posi = 0 115 end = len(value) 116 117 if not len(value): 118 return None 119 120 #Needed for python 3 support: If a bytes object is passed, 121 #decode it with the ascii codec. If the decoding fails, assume 122 #it was not a string object. 123 try: 124 value = value.decode('ascii') 125 except ValueError: 126 return None 127 128 #Test both against string 0 and int 0 because of 129 # python2/3 compatibility 130 if value[-1] != '\0': 131 return None 132 133 while pos < end: 134 posi = pos 135 while pos < end and value[pos] != '\0' \ 136 and value[pos] in string.printable \ 137 and value[pos] not in ('\r', '\n'): 138 pos += 1 139 140 if value[pos] != '\0' or pos == posi: 141 return None 142 pos += 1 143 144 return True 145 146 @staticmethod 147 def new_raw_property(name, raw_value): 148 """Instantiate property with raw value type""" 149 if FdtProperty.__check_prop_strings(raw_value): 150 return FdtPropertyStrings.init_raw(name, raw_value) 151 elif len(raw_value) and len(raw_value) % 4 == 0: 152 return FdtPropertyWords.init_raw(name, raw_value) 153 elif len(raw_value) and len(raw_value): 154 return FdtPropertyBytes.init_raw(name, raw_value) 155 else: 156 return FdtProperty(name) 157 158 159class FdtPropertyStrings(FdtProperty): 160 """Property with strings as value""" 161 162 @classmethod 163 def __extract_prop_strings(cls, value): 164 """Extract strings from raw_value""" 165 return [st for st in \ 166 value.decode('ascii').split('\0') if len(st)] 167 168 def __init__(self, name, strings): 169 """Init with strings""" 170 FdtProperty.__init__(self, name) 171 if not strings: 172 raise Exception("Invalid strings") 173 for stri in strings: 174 if len(stri) == 0: 175 raise Exception("Invalid strings") 176 if any([True for char in stri 177 if char not in string.printable 178 or char in ('\r', '\n')]): 179 raise Exception("Invalid chars in strings") 180 self.strings = strings 181 182 @classmethod 183 def init_raw(cls, name, raw_value): 184 """Init from raw""" 185 return cls(name, cls.__extract_prop_strings(raw_value)) 186 187 def dts_represent(self, depth=0): 188 """Get dts string representation""" 189 return INDENT*depth + self.name + ' = "' + \ 190 '", "'.join(self.strings) + '";' 191 192 def dtb_represent(self, string_store, pos=0, version=17): 193 """Get blob representation""" 194 # print "%x:%s" % (pos, self) 195 blob = pack('') 196 for chars in self.strings: 197 blob += chars.encode('ascii') + pack('b', 0) 198 blob_len = len(blob) 199 if version < 16 and (pos+12) % 8 != 0: 200 blob = pack('b', 0) * (8-((pos+12) % 8)) + blob 201 if blob_len % 4: 202 blob += pack('b', 0) * (4-(blob_len % 4)) 203 strpos = string_store.find(self.name+'\0') 204 if strpos < 0: 205 strpos = len(string_store) 206 string_store += self.name+'\0' 207 blob = pack('>III', FDT_PROP, blob_len, strpos) + blob 208 pos += len(blob) 209 return (blob, string_store, pos) 210 211 def json_represent(self, depth=0): 212 """Ouput JSON""" 213 result = '%s: ["strings", ' % json.dumps(self.name) 214 result += ', '.join([json.dumps(stri) for stri in self.strings]) 215 result += ']' 216 return result 217 218 def to_raw(self): 219 """Return RAW value representation""" 220 return ''.join([chars+'\0' for chars in self.strings]) 221 222 def __str__(self): 223 """String representation""" 224 return "Property(%s,Strings:%s)" % (self.name, self.strings) 225 226 def __getitem__(self, index): 227 """Get strings, returns a string""" 228 return self.strings[index] 229 230 def __len__(self): 231 """Get strings count""" 232 return len(self.strings) 233 234 def __eq__(self, node): 235 """Check node equality 236 check properties are the same (same values) 237 """ 238 if not FdtProperty.__eq__(self, node): 239 return False 240 if self.__len__() != len(node): 241 return False 242 for index in range(self.__len__()): 243 if self.strings[index] != node[index]: 244 return False 245 return True 246 247class FdtPropertyWords(FdtProperty): 248 """Property with words as value""" 249 250 def __init__(self, name, words): 251 """Init with words""" 252 FdtProperty.__init__(self, name) 253 for word in words: 254 if not 0 <= word <= 4294967295: 255 raise Exception(("Invalid word value %d, requires " + 256 "0 <= number <= 4294967295") % word) 257 if not len(words): 258 raise Exception("Invalid Words") 259 self.words = words 260 261 @classmethod 262 def init_raw(cls, name, raw_value): 263 """Init from raw""" 264 if len(raw_value) % 4 == 0: 265 words = [unpack(">I", raw_value[i:i+4])[0] 266 for i in range(0, len(raw_value), 4)] 267 return cls(name, words) 268 else: 269 raise Exception("Invalid raw Words") 270 271 def dts_represent(self, depth=0): 272 """Get dts string representation""" 273 return INDENT*depth + self.name + ' = <' + \ 274 ' '.join(["0x%08x" % word for word in self.words]) + ">;" 275 276 def dtb_represent(self, string_store, pos=0, version=17): 277 """Get blob representation""" 278 # # print "%x:%s" % (pos, self) 279 strpos = string_store.find(self.name+'\0') 280 if strpos < 0: 281 strpos = len(string_store) 282 string_store += self.name+'\0' 283 blob = pack('>III', FDT_PROP, len(self.words)*4, strpos) + \ 284 pack('').join([pack('>I', word) for word in self.words]) 285 pos += len(blob) 286 return (blob, string_store, pos) 287 288 def json_represent(self, depth=0): 289 """Ouput JSON""" 290 result = '%s: ["words", "' % json.dumps(self.name) 291 result += '", "'.join(["0x%08x" % word for word in self.words]) 292 result += '"]' 293 return result 294 295 def to_raw(self): 296 """Return RAW value representation""" 297 return ''.join([pack('>I', word) for word in self.words]) 298 299 def __str__(self): 300 """String representation""" 301 return "Property(%s,Words:%s)" % (self.name, self.words) 302 303 def __getitem__(self, index): 304 """Get words, returns a word integer""" 305 return self.words[index] 306 307 def __len__(self): 308 """Get words count""" 309 return len(self.words) 310 311 def __eq__(self, node): 312 """Check node equality 313 check properties are the same (same values) 314 """ 315 if not FdtProperty.__eq__(self, node): 316 return False 317 if self.__len__() != len(node): 318 return False 319 for index in range(self.__len__()): 320 if self.words[index] != node[index]: 321 return False 322 return True 323 324 325class FdtPropertyBytes(FdtProperty): 326 """Property with signed bytes as value""" 327 328 def __init__(self, name, bytez): 329 """Init with bytes""" 330 FdtProperty.__init__(self, name) 331 for byte in bytez: 332 if not -128 <= byte <= 127: 333 raise Exception(("Invalid value for byte %d, " + 334 "requires -128 <= number <= 127") % byte) 335 if not bytez: 336 raise Exception("Invalid Bytes") 337 self.bytes = bytez 338 339 @classmethod 340 def init_raw(cls, name, raw_value): 341 """Init from raw""" 342 return cls(name, unpack('b' * len(raw_value), raw_value)) 343 344 def dts_represent(self, depth=0): 345 """Get dts string representation""" 346 return INDENT*depth + self.name + ' = [' + \ 347 ' '.join(["%02x" % (byte & int('ffffffff',16)) 348 for byte in self.bytes]) + "];" 349 350 def dtb_represent(self, string_store, pos=0, version=17): 351 """Get blob representation""" 352 # print "%x:%s" % (pos, self) 353 strpos = string_store.find(self.name+'\0') 354 if strpos < 0: 355 strpos = len(string_store) 356 string_store += self.name+'\0' 357 blob = pack('>III', FDT_PROP, len(self.bytes), strpos) 358 blob += pack('').join([pack('>b', byte) for byte in self.bytes]) 359 if len(blob) % 4: 360 blob += pack('b', 0) * (4-(len(blob) % 4)) 361 pos += len(blob) 362 return (blob, string_store, pos) 363 364 def json_represent(self, depth=0): 365 """Ouput JSON""" 366 result = '%s: ["bytes", "' % json.dumps(self.name) 367 result += '", "'.join(["%02x" % byte 368 for byte in self.bytes]) 369 result += '"]' 370 return result 371 372 def to_raw(self): 373 """Return RAW value representation""" 374 return ''.join([pack('>b', byte) for byte in self.bytes]) 375 376 def __str__(self): 377 """String representation""" 378 return "Property(%s,Bytes:%s)" % (self.name, self.bytes) 379 380 def __getitem__(self, index): 381 """Get bytes, returns a byte""" 382 return self.bytes[index] 383 384 def __len__(self): 385 """Get strings count""" 386 return len(self.bytes) 387 388 def __eq__(self, node): 389 """Check node equality 390 check properties are the same (same values) 391 """ 392 if not FdtProperty.__eq__(self, node): 393 return False 394 if self.__len__() != len(node): 395 return False 396 for index in range(self.__len__()): 397 if self.bytes[index] != node[index]: 398 return False 399 return True 400 401 402class FdtNop(object): # pylint: disable-msg=R0903 403 """Nop child representation""" 404 405 def __init__(self): 406 """Init with nothing""" 407 408 def get_name(self): # pylint: disable-msg=R0201 409 """Return name""" 410 return None 411 412 def __str__(self): 413 """String representation""" 414 return '' 415 416 def dts_represent(self, depth=0): # pylint: disable-msg=R0201 417 """Get dts string representation""" 418 return INDENT*depth+'// [NOP]' 419 420 def dtb_represent(self, string_store, pos=0, version=17): 421 """Get blob representation""" 422 # print "%x:%s" % (pos, self) 423 pos += 4 424 return (pack('>I', FDT_NOP), string_store, pos) 425 426 427class FdtNode(object): 428 """Node representation""" 429 430 @staticmethod 431 def __validate_dt_name(name): 432 """Checks the name validity""" 433 return not any([True for char in name 434 if char not in string.printable]) 435 436 def __init__(self, name): 437 """Init node with name""" 438 self.name = name 439 self.subdata = [] 440 self.parent = None 441 if not FdtNode.__validate_dt_name(self.name): 442 raise Exception("Invalid name '%s'" % self.name) 443 444 def get_name(self): 445 """Get property name""" 446 return self.name 447 448 def __check_name_duplicate(self, name): 449 """Checks if name is not in a subnode""" 450 for data in self.subdata: 451 if not isinstance(data, FdtNop) \ 452 and data.get_name() == name: 453 return True 454 return False 455 456 def add_subnode(self, node): 457 """Add child, deprecated use append()""" 458 self.append(node) 459 460 def add_raw_attribute(self, name, raw_value): 461 """Construct a raw attribute and add to child""" 462 self.append(FdtProperty.new_raw_property(name, raw_value)) 463 464 def set_parent_node(self, node): 465 """Set parent node, None and FdtNode accepted""" 466 if node is not None and \ 467 not isinstance(node, FdtNode): 468 raise Exception("Invalid object type") 469 self.parent = node 470 471 def get_parent_node(self): 472 """Get parent node""" 473 return self.parent 474 475 def __str__(self): 476 """String representation""" 477 return "Node(%s)" % self.name 478 479 def dts_represent(self, depth=0): 480 """Get dts string representation""" 481 result = ('\n').join([sub.dts_represent(depth+1) 482 for sub in self.subdata]) 483 if len(result) > 0: 484 result += '\n' 485 return INDENT*depth + self.name + ' {\n' + \ 486 result + INDENT*depth + "};" 487 488 def dtb_represent(self, strings_store, pos=0, version=17): 489 """Get blob representation 490 Pass string storage as strings_store, pos for current node start 491 and version as current dtb version 492 """ 493 # print "%x:%s" % (pos, self) 494 strings = strings_store 495 if self.get_name() == '/': 496 blob = pack('>II', FDT_BEGIN_NODE, 0) 497 else: 498 blob = pack('>I', FDT_BEGIN_NODE) 499 blob += self.get_name().encode('ascii') + pack('b', 0) 500 if len(blob) % 4: 501 blob += pack('b', 0) * (4-(len(blob) % 4)) 502 pos += len(blob) 503 for sub in self.subdata: 504 (data, strings, pos) = sub.dtb_represent(strings, pos, version) 505 blob += data 506 pos += 4 507 blob += pack('>I', FDT_END_NODE) 508 return (blob, strings, pos) 509 510 def json_represent(self, depth=0): 511 """Get dts string representation""" 512 result = (',\n'+ \ 513 INDENT*(depth+1)).join([sub.json_represent(depth+1) 514 for sub in self.subdata 515 if not isinstance(sub, FdtNop)]) 516 if len(result) > 0: 517 result = INDENT + result + '\n'+INDENT*depth 518 if self.get_name() == '/': 519 return "{\n" + INDENT*(depth) + result + "}" 520 else: 521 return json.dumps(self.name) + ': {\n' + \ 522 INDENT*(depth) + result + "}" 523 524 def __getitem__(self, index): 525 """Get subnodes, returns either a Node, a Property or a Nop""" 526 return self.subdata[index] 527 528 def __setitem__(self, index, subnode): 529 """Set node at index, replacing previous subnode, 530 must not be a duplicate name 531 """ 532 if self.subdata[index].get_name() != subnode.get_name() and \ 533 self.__check_name_duplicate(subnode.get_name()): 534 raise Exception("%s : %s subnode already exists" % \ 535 (self, subnode)) 536 if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): 537 raise Exception("Invalid object type") 538 self.subdata[index] = subnode 539 540 def __len__(self): 541 """Get strings count""" 542 return len(self.subdata) 543 544 def __ne__(self, node): 545 """Check node inequality 546 i.e. is subnodes are the same, in either order 547 and properties are the same (same values) 548 The FdtNop is excluded from the check 549 """ 550 return not self.__eq__(node) 551 552 def __eq__(self, node): 553 """Check node equality 554 i.e. is subnodes are the same, in either order 555 and properties are the same (same values) 556 The FdtNop is excluded from the check 557 """ 558 if not isinstance(node, FdtNode): 559 raise Exception("Invalid object type") 560 if self.name != node.get_name(): 561 return False 562 curnames = set([subnode.get_name() for subnode in self.subdata 563 if not isinstance(subnode, FdtNop)]) 564 cmpnames = set([subnode.get_name() for subnode in node 565 if not isinstance(subnode, FdtNop)]) 566 if curnames != cmpnames: 567 return False 568 for subnode in [subnode for subnode in self.subdata 569 if not isinstance(subnode, FdtNop)]: 570 index = node.index(subnode.get_name()) 571 if subnode != node[index]: 572 return False 573 return True 574 575 def append(self, subnode): 576 """Append subnode, same as add_subnode""" 577 if self.__check_name_duplicate(subnode.get_name()): 578 raise Exception("%s : %s subnode already exists" % \ 579 (self, subnode)) 580 if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): 581 raise Exception("Invalid object type") 582 self.subdata.append(subnode) 583 584 def pop(self, index=-1): 585 """Remove and returns subnode at index, default the last""" 586 return self.subdata.pop(index) 587 588 def insert(self, index, subnode): 589 """Insert subnode before index, must not be a duplicate name""" 590 if self.__check_name_duplicate(subnode.get_name()): 591 raise Exception("%s : %s subnode already exists" % \ 592 (self, subnode)) 593 if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): 594 raise Exception("Invalid object type") 595 self.subdata.insert(index, subnode) 596 597 def _find(self, name): 598 """Find name in subnodes""" 599 for i in range(0, len(self.subdata)): 600 if not isinstance(self.subdata[i], FdtNop) and \ 601 name == self.subdata[i].get_name(): 602 return i 603 return None 604 605 def remove(self, name): 606 """Remove subnode with the name 607 Raises ValueError is not present 608 """ 609 index = self._find(name) 610 if index is None: 611 raise ValueError("Not present") 612 return self.subdata.pop(index) 613 614 def index(self, name): 615 """Returns position of subnode with the name 616 Raises ValueError is not present 617 """ 618 index = self._find(name) 619 if index is None: 620 raise ValueError("Not present") 621 return index 622 623 def merge(self, node): 624 """Merge two nodes and subnodes 625 Replace current properties with the given properties 626 """ 627 if not isinstance(node, FdtNode): 628 raise Exception("Can only merge with a FdtNode") 629 for subnode in [obj for obj in node 630 if isinstance(obj, (FdtNode, FdtProperty))]: 631 index = self._find(subnode.get_name()) 632 if index is None: 633 dup = deepcopy(subnode) 634 if isinstance(subnode, FdtNode): 635 dup.set_parent_node(self) 636 self.append(dup) 637 elif isinstance(subnode, FdtNode): 638 self.subdata[index].merge(subnode) 639 else: 640 self.subdata[index] = copy(subnode) 641 642 def walk(self): 643 """Walk into subnodes and yield paths and objects 644 Returns set with (path string, node object) 645 """ 646 node = self 647 start = 0 648 hist = [] 649 curpath = [] 650 651 while True: 652 for index in range(start, len(node)): 653 if isinstance(node[index], (FdtNode, FdtProperty)): 654 yield ('/' + '/'.join(curpath+[node[index].get_name()]), 655 node[index]) 656 if isinstance(node[index], FdtNode): 657 if len(node[index]): 658 hist.append((node, index+1)) 659 curpath.append(node[index].get_name()) 660 node = node[index] 661 start = 0 662 index = -1 663 break 664 if index >= 0: 665 if len(hist): 666 (node, start) = hist.pop() 667 curpath.pop() 668 else: 669 break 670 671 672class Fdt(object): 673 """Flattened Device Tree representation""" 674 675 def __init__(self, version=17, last_comp_version=16, boot_cpuid_phys=0): 676 """Init FDT object with version and boot values""" 677 self.header = {'magic': FDT_MAGIC, 678 'totalsize': 0, 679 'off_dt_struct': 0, 680 'off_dt_strings': 0, 681 'off_mem_rsvmap': 0, 682 'version': version, 683 'last_comp_version': last_comp_version, 684 'boot_cpuid_phys': boot_cpuid_phys, 685 'size_dt_strings': 0, 686 'size_dt_struct': 0} 687 self.rootnode = None 688 self.prenops = None 689 self.postnops = None 690 self.reserve_entries = None 691 692 def add_rootnode(self, rootnode, prenops=None, postnops=None): 693 """Add root node""" 694 self.rootnode = rootnode 695 self.prenops = prenops 696 self.postnops = postnops 697 698 def get_rootnode(self): 699 """Get root node""" 700 return self.rootnode 701 702 def add_reserve_entries(self, reserve_entries): 703 """Add reserved entries as list of dict with 704 'address' and 'size' keys""" 705 self.reserve_entries = reserve_entries 706 707 def to_dts(self): 708 """Export to DTS representation in string format""" 709 result = "/dts-v1/;\n" 710 result += "// version:\t\t%d\n" % self.header['version'] 711 result += "// last_comp_version:\t%d\n" % \ 712 self.header['last_comp_version'] 713 if self.header['version'] >= 2: 714 result += "// boot_cpuid_phys:\t0x%x\n" % \ 715 self.header['boot_cpuid_phys'] 716 result += '\n' 717 if self.reserve_entries is not None: 718 for entry in self.reserve_entries: 719 result += "/memreserve/ " 720 if entry['address']: 721 result += "%#x " % entry['address'] 722 else: 723 result += "0 " 724 if entry['size']: 725 result += "%#x" % entry['size'] 726 else: 727 result += "0" 728 result += ";\n" 729 if self.prenops: 730 result += '\n'.join([nop.dts_represent() for nop in self.prenops]) 731 result += '\n' 732 if self.rootnode is not None: 733 result += self.rootnode.dts_represent() 734 if self.postnops: 735 result += '\n' 736 result += '\n'.join([nop.dts_represent() for nop in self.postnops]) 737 return result 738 739 def to_dtb(self): 740 """Export to Blob format""" 741 if self.rootnode is None: 742 return None 743 blob_reserve_entries = pack('') 744 if self.reserve_entries is not None: 745 for entry in self.reserve_entries: 746 blob_reserve_entries += pack('>QQ', 747 entry['address'], 748 entry['size']) 749 blob_reserve_entries += pack('>QQ', 0, 0) 750 header_size = 7 * 4 751 if self.header['version'] >= 2: 752 header_size += 4 753 if self.header['version'] >= 3: 754 header_size += 4 755 if self.header['version'] >= 17: 756 header_size += 4 757 header_adjust = pack('') 758 if header_size % 8 != 0: 759 header_adjust = pack('b', 0) * (8 - (header_size % 8)) 760 header_size += len(header_adjust) 761 dt_start = header_size + len(blob_reserve_entries) 762 # print "dt_start %d" % dt_start 763 (blob_dt, blob_strings, dt_pos) = \ 764 self.rootnode.dtb_represent('', dt_start, self.header['version']) 765 if self.prenops is not None: 766 blob_dt = pack('').join([nop.dtb_represent('')[0] 767 for nop in self.prenops])\ 768 + blob_dt 769 if self.postnops is not None: 770 blob_dt += pack('').join([nop.dtb_represent('')[0] 771 for nop in self.postnops]) 772 blob_dt += pack('>I', FDT_END) 773 self.header['size_dt_strings'] = len(blob_strings) 774 self.header['size_dt_struct'] = len(blob_dt) 775 self.header['off_mem_rsvmap'] = header_size 776 self.header['off_dt_struct'] = dt_start 777 self.header['off_dt_strings'] = dt_start + len(blob_dt) 778 self.header['totalsize'] = dt_start + len(blob_dt) + len(blob_strings) 779 blob_header = pack('>IIIIIII', self.header['magic'], 780 self.header['totalsize'], 781 self.header['off_dt_struct'], 782 self.header['off_dt_strings'], 783 self.header['off_mem_rsvmap'], 784 self.header['version'], 785 self.header['last_comp_version']) 786 if self.header['version'] >= 2: 787 blob_header += pack('>I', self.header['boot_cpuid_phys']) 788 if self.header['version'] >= 3: 789 blob_header += pack('>I', self.header['size_dt_strings']) 790 if self.header['version'] >= 17: 791 blob_header += pack('>I', self.header['size_dt_struct']) 792 return blob_header + header_adjust + blob_reserve_entries + \ 793 blob_dt + blob_strings.encode('ascii') 794 795 def to_json(self): 796 """Ouput JSON""" 797 if self.rootnode is None: 798 return None 799 return self.rootnode.json_represent() 800 801 def resolve_path(self, path): 802 """Resolve path like /memory/reg and return either a FdtNode, 803 a FdtProperty or None""" 804 if self.rootnode is None: 805 return None 806 if not path.startswith('/'): 807 return None 808 if len(path) > 1 and path.endswith('/'): 809 path = path[:-1] 810 if path == '/': 811 return self.rootnode 812 curnode = self.rootnode 813 for subpath in path[1:].split('/'): 814 found = None 815 if not isinstance(curnode, FdtNode): 816 return None 817 for node in curnode: 818 if subpath == node.get_name(): 819 found = node 820 break 821 if found is None: 822 return None 823 curnode = found 824 return curnode 825 826def _add_json_to_fdtnode(node, subjson): 827 """Populate FdtNode with JSON dict items""" 828 for (key, value) in subjson.items(): 829 if isinstance(value, dict): 830 subnode = FdtNode(key) 831 subnode.set_parent_node(node) 832 node.append(subnode) 833 _add_json_to_fdtnode(subnode, value) 834 elif isinstance(value, list): 835 if len(value) < 2: 836 raise Exception("Invalid list for %s" % key) 837 if value[0] == "words": 838 words = [int(word, 16) for word in value[1:]] 839 node.append(FdtPropertyWords(key, words)) 840 elif value[0] == "bytes": 841 bytez = [int(byte, 16) for byte in value[1:]] 842 node.append(FdtPropertyBytes(key, bytez)) 843 elif value[0] == "strings": 844 node.append(FdtPropertyStrings(key, \ 845 [s for s in value[1:]])) 846 else: 847 raise Exception("Invalid list for %s" % key) 848 elif value is None: 849 node.append(FdtProperty(key)) 850 else: 851 raise Exception("Invalid value for %s" % key) 852 853def FdtJsonParse(buf): 854 """Import FDT from JSON representation, see JSONDeviceTree.md for 855 structure and encoding 856 Returns an Fdt object 857 """ 858 tree = json.loads(buf) 859 860 root = FdtNode('/') 861 862 _add_json_to_fdtnode(root, tree) 863 864 fdt = Fdt() 865 fdt.add_rootnode(root) 866 return fdt 867 868def FdtFsParse(path): 869 """Parse device tree filesystem and return a Fdt instance 870 Should be /proc/device-tree on a device, or the fusemount.py 871 mount point. 872 """ 873 root = FdtNode("/") 874 875 if path.endswith('/'): 876 path = path[:-1] 877 878 nodes = {path: root} 879 880 for subpath, subdirs, files in os.walk(path): 881 if subpath not in nodes.keys(): 882 raise Exception("os.walk error") 883 cur = nodes[subpath] 884 for f in files: 885 with open(subpath+'/'+f, 'rb') as content_file: 886 content = content_file.read() 887 prop = FdtProperty.new_raw_property(f, content) 888 cur.add_subnode(prop) 889 for subdir in subdirs: 890 subnode = FdtNode(subdir) 891 cur.add_subnode(subnode) 892 subnode.set_parent_node(cur) 893 nodes[subpath+'/'+subdir] = subnode 894 895 fdt = Fdt() 896 fdt.add_rootnode(root) 897 return fdt 898 899class FdtBlobParse(object): # pylint: disable-msg=R0903 900 """Parse from file input""" 901 902 __fdt_header_format = ">IIIIIII" 903 __fdt_header_names = ('magic', 'totalsize', 'off_dt_struct', 904 'off_dt_strings', 'off_mem_rsvmap', 'version', 905 'last_comp_version') 906 907 __fdt_reserve_entry_format = ">QQ" 908 __fdt_reserve_entry_names = ('address', 'size') 909 910 __fdt_dt_cell_format = ">I" 911 __fdt_dt_prop_format = ">II" 912 __fdt_dt_tag_name = {FDT_BEGIN_NODE: 'node_begin', 913 FDT_END_NODE: 'node_end', 914 FDT_PROP: 'prop', 915 FDT_NOP: 'nop', 916 FDT_END: 'end'} 917 918 def __extract_fdt_header(self): 919 """Extract DTB header""" 920 header = Struct(self.__fdt_header_format) 921 header_entry = Struct(">I") 922 data = self.infile.read(header.size) 923 result = dict(zip(self.__fdt_header_names, header.unpack_from(data))) 924 if result['version'] >= 2: 925 data = self.infile.read(header_entry.size) 926 result['boot_cpuid_phys'] = header_entry.unpack_from(data)[0] 927 if result['version'] >= 3: 928 data = self.infile.read(header_entry.size) 929 result['size_dt_strings'] = header_entry.unpack_from(data)[0] 930 if result['version'] >= 17: 931 data = self.infile.read(header_entry.size) 932 result['size_dt_struct'] = header_entry.unpack_from(data)[0] 933 return result 934 935 def __extract_fdt_reserve_entries(self): 936 """Extract reserved memory entries""" 937 header = Struct(self.__fdt_reserve_entry_format) 938 entries = [] 939 self.infile.seek(self.fdt_header['off_mem_rsvmap']) 940 while True: 941 data = self.infile.read(header.size) 942 result = dict(zip(self.__fdt_reserve_entry_names, 943 header.unpack_from(data))) 944 if result['address'] == 0 and result['size'] == 0: 945 return entries 946 entries.append(result) 947 948 def __extract_fdt_nodename(self): 949 """Extract node name""" 950 data = '' 951 pos = self.infile.tell() 952 while True: 953 byte = self.infile.read(1) 954 if ord(byte) == 0: 955 break 956 data += byte.decode('ascii') 957 align_pos = pos + len(data) + 1 958 align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1)) 959 self.infile.seek(align_pos) 960 return data 961 962 def __extract_fdt_string(self, prop_string_pos): 963 """Extract string from string pool""" 964 data = '' 965 pos = self.infile.tell() 966 self.infile.seek(self.fdt_header['off_dt_strings']+prop_string_pos) 967 while True: 968 byte = self.infile.read(1) 969 if ord(byte) == 0: 970 break 971 data += byte.decode('ascii') 972 self.infile.seek(pos) 973 return data 974 975 def __extract_fdt_prop(self): 976 """Extract property""" 977 prop = Struct(self.__fdt_dt_prop_format) 978 pos = self.infile.tell() 979 data = self.infile.read(prop.size) 980 (prop_size, prop_string_pos,) = prop.unpack_from(data) 981 982 prop_start = pos + prop.size 983 if self.fdt_header['version'] < 16 and prop_size >= 8: 984 prop_start = (((prop_start) + ((8) - 1)) & ~((8) - 1)) 985 986 self.infile.seek(prop_start) 987 value = self.infile.read(prop_size) 988 989 align_pos = self.infile.tell() 990 align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1)) 991 self.infile.seek(align_pos) 992 993 return (self.__extract_fdt_string(prop_string_pos), value) 994 995 def __extract_fdt_dt(self): 996 """Extract tags""" 997 cell = Struct(self.__fdt_dt_cell_format) 998 tags = [] 999 self.infile.seek(self.fdt_header['off_dt_struct']) 1000 while True: 1001 data = self.infile.read(cell.size) 1002 if len(data) < cell.size: 1003 break 1004 tag, = cell.unpack_from(data) 1005 # print "*** %s" % self.__fdt_dt_tag_name.get(tag, '') 1006 if self.__fdt_dt_tag_name.get(tag, '') in 'node_begin': 1007 name = self.__extract_fdt_nodename() 1008 if len(name) == 0: 1009 name = '/' 1010 tags.append((tag, name)) 1011 elif self.__fdt_dt_tag_name.get(tag, '') in ('node_end', 'nop'): 1012 tags.append((tag, '')) 1013 elif self.__fdt_dt_tag_name.get(tag, '') in 'end': 1014 tags.append((tag, '')) 1015 break 1016 elif self.__fdt_dt_tag_name.get(tag, '') in 'prop': 1017 propdata = self.__extract_fdt_prop() 1018 tags.append((tag, propdata)) 1019 else: 1020 print("Unknown Tag %d" % tag) 1021 return tags 1022 1023 def __init__(self, infile): 1024 """Init with file input""" 1025 self.infile = infile 1026 self.fdt_header = self.__extract_fdt_header() 1027 if self.fdt_header['magic'] != FDT_MAGIC: 1028 raise Exception('Invalid Magic') 1029 if self.fdt_header['version'] > FDT_MAX_VERSION: 1030 raise Exception('Invalid Version %d' % self.fdt_header['version']) 1031 if self.fdt_header['last_comp_version'] > FDT_MAX_VERSION-1: 1032 raise Exception('Invalid last compatible Version %d' % 1033 self.fdt_header['last_comp_version']) 1034 self.fdt_reserve_entries = self.__extract_fdt_reserve_entries() 1035 self.fdt_dt_tags = self.__extract_fdt_dt() 1036 1037 def __to_nodes(self): 1038 """Represent fdt as Node and properties structure 1039 Returns a set with the pre-node Nops, the Root Node, 1040 and the post-node Nops. 1041 """ 1042 prenops = [] 1043 postnops = [] 1044 rootnode = None 1045 curnode = None 1046 for tag in self.fdt_dt_tags: 1047 if self.__fdt_dt_tag_name.get(tag[0], '') in 'node_begin': 1048 newnode = FdtNode(tag[1]) 1049 if rootnode is None: 1050 rootnode = newnode 1051 if curnode is not None: 1052 curnode.add_subnode(newnode) 1053 newnode.set_parent_node(curnode) 1054 curnode = newnode 1055 elif self.__fdt_dt_tag_name.get(tag[0], '') in 'node_end': 1056 if curnode is not None: 1057 curnode = curnode.get_parent_node() 1058 elif self.__fdt_dt_tag_name.get(tag[0], '') in 'nop': 1059 if curnode is not None: 1060 curnode.add_subnode(FdtNop()) 1061 elif rootnode is not None: 1062 postnops.append(FdtNop()) 1063 else: 1064 prenops.append(FdtNop()) 1065 elif self.__fdt_dt_tag_name.get(tag[0], '') in 'prop': 1066 if curnode is not None: 1067 curnode.add_raw_attribute(tag[1][0], tag[1][1]) 1068 elif self.__fdt_dt_tag_name.get(tag[0], '') in 'end': 1069 continue 1070 return (prenops, rootnode, postnops) 1071 1072 def to_fdt(self): 1073 """Create a fdt object 1074 Returns a Fdt object 1075 """ 1076 if self.fdt_header['version'] >= 2: 1077 boot_cpuid_phys = self.fdt_header['boot_cpuid_phys'] 1078 else: 1079 boot_cpuid_phys = 0 1080 fdt = Fdt(version=self.fdt_header['version'], 1081 last_comp_version=self.fdt_header['last_comp_version'], 1082 boot_cpuid_phys=boot_cpuid_phys) 1083 (prenops, rootnode, postnops) = self.__to_nodes() 1084 fdt.add_rootnode(rootnode, prenops=prenops, postnops=postnops) 1085 fdt.add_reserve_entries(self.fdt_reserve_entries) 1086 return fdt 1087