From 3da05785813662f647b07400734337630a9f6f78 Mon Sep 17 00:00:00 2001 From: Glenn Bergmans Date: Wed, 16 Dec 2015 15:43:42 +0000 Subject: [PATCH] ext: DT autogeneration - Add PyFtd to m5 space This patch adds pyfdt.py to the m5.ext module. This is used in succeeding patches for generating and editing dtb files and flat device trees for DT autogeneration. The file is in the m5_root/src/python/m5/ext directory, as opposed to the m5_root/ext, because this library is part of the m5 object space and linking to the m5_root/ext directory from the SConscript file in src/python can not be done reliably. Linking from the root level SConscript is also not an option, because it doesn't have the PySource method defined. Cloned from: https://github.com/superna9999/pyfdt Commit: accbcd254584c9295a18878d32999d0c7c156f8e Version: 0.3 Change-Id: I928bdc912a9507d1f8a3290acf445c7cae496552 Reviewed-by: Andreas Sandberg Reviewed-by: Curtis Dunham Reviewed-on: https://gem5-review.googlesource.com/5961 Reviewed-by: Jason Lowe-Power Maintainer: Andreas Sandberg --- src/python/SConscript | 3 + src/python/m5/ext/__init__.py | 38 + src/python/m5/ext/pyfdt/LICENCE | 70 ++ src/python/m5/ext/pyfdt/README.md | 55 ++ src/python/m5/ext/pyfdt/__init__.py | 0 src/python/m5/ext/pyfdt/pyfdt.py | 1083 +++++++++++++++++++++++++++ 6 files changed, 1249 insertions(+) create mode 100644 src/python/m5/ext/__init__.py create mode 100644 src/python/m5/ext/pyfdt/LICENCE create mode 100644 src/python/m5/ext/pyfdt/README.md create mode 100644 src/python/m5/ext/pyfdt/__init__.py create mode 100644 src/python/m5/ext/pyfdt/pyfdt.py diff --git a/src/python/SConscript b/src/python/SConscript index cfd2afeff..c974238cd 100644 --- a/src/python/SConscript +++ b/src/python/SConscript @@ -63,6 +63,9 @@ PySource('m5.util', 'm5/util/pybind.py') PySource('m5.internal', 'm5/internal/__init__.py') PySource('m5.internal', 'm5/internal/params.py') +PySource('m5.ext', 'm5/ext/__init__.py') +PySource('m5.ext.pyfdt', 'm5/ext/pyfdt/pyfdt.py') +PySource('m5.ext.pyfdt', 'm5/ext/pyfdt/__init__.py') Source('pybind11/core.cc', add_tags='python') Source('pybind11/debug.cc', add_tags='python') diff --git a/src/python/m5/ext/__init__.py b/src/python/m5/ext/__init__.py new file mode 100644 index 000000000..a5bc8ef26 --- /dev/null +++ b/src/python/m5/ext/__init__.py @@ -0,0 +1,38 @@ +# -*- mode:python -*- + +# Copyright (c) 2015 ARM Limited +# All rights reserved. +# +# The license below extends only to copyright in the software and shall +# not be construed as granting a license to any other intellectual +# property including but not limited to intellectual property relating +# to a hardware implementation of the functionality of the software +# licensed hereunder. You may use the software subject to the license +# terms below provided that you ensure that this notice is replicated +# unmodified and in its entirety in all distributions of the software, +# modified or unmodified, in source code or in binary form. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer; +# redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution; +# neither the name of the copyright holders nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# Authors: Glenn Bergmans diff --git a/src/python/m5/ext/pyfdt/LICENCE b/src/python/m5/ext/pyfdt/LICENCE new file mode 100644 index 000000000..2c2554d09 --- /dev/null +++ b/src/python/m5/ext/pyfdt/LICENCE @@ -0,0 +1,70 @@ +Apache License, Version 2.0 + +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. + +"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: + + You must give any other recipients of the Work or Derivative Works a copy of this License; and + You must cause any modified files to carry prominent notices stating that You changed the files; and + You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and + If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. + +You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/src/python/m5/ext/pyfdt/README.md b/src/python/m5/ext/pyfdt/README.md new file mode 100644 index 000000000..75769a9a3 --- /dev/null +++ b/src/python/m5/ext/pyfdt/README.md @@ -0,0 +1,55 @@ +## pyfdt : Python Flattened Device Tree Manipulation ## +---------- +The pyfdt library is aimed to facilitate manipulation of the flattened device tree in order to parse it and generate output in various formats. + +It is highly based on fdtdump for the dtc compiler package. + +Support Inputs : + - Device Tree Blob (.dtb) + - Filesystem + - JSON (See JSONDeviceTree.md) + +Supported Outputs : + - Device Tree Blob (DTB) + - Device Tree Structure (text DTS) + - JSON (See JSONDeviceTree.md) + +Device Tree filesystem 'output' is available via the fusemount.py FUSE sample using [fusepy](https://github.com/terencehonles/fusepy) library. + +The object data permits : + - add/delete/pop nodes and attributes + - create attributes dynamically with native python types + - walk throught the tree + - resolve and generate "paths" + - parse from DTB or filesystem + - output DTB or DTS + - output JSON + - compare two tree + - merge two trees + +Any API, code, syntax, tests or whatever enhancement is welcomed, but consider this an alpha version project not yet used in production. + +No DTS parser/compiler is event considered since "dtc" is the official compiler, but i'm open to any compiler implementation over pyfdt... + +Typical usage is : +``` +from pyfdt import FdtBlobParse +with open("myfdt.dtb") as infile: + dtb = FdtBlobParse(infile) + print dtb.to_fdt().to_dts() +``` + +Will open a binary DTB and output an human readable DTS structure. + +The samples directory shows how to : + - checkpath.py : resolve a FDT path to get a node object + - dtbtodts.py : how to convert from DTB to DTS + - fusemount.py : how to mount the DTB into a Device Tree filesystem you can recompile using dtc + - python-generate.py : generate a FDT in 100% python and generate a DTS from it + - walktree.py : List all paths of the device tree + - fstodtb.py : Device Tree blob creation from Filesystem device tree like DTC + +[Device Tree Wiki](http://www.devicetree.org) +[Device Tree Compiler](http://www.devicetree.org/Device_Tree_Compiler) + +[![Build Status](https://travis-ci.org/superna9999/pyfdt.svg?branch=master)](https://travis-ci.org/superna9999/pyfdt) diff --git a/src/python/m5/ext/pyfdt/__init__.py b/src/python/m5/ext/pyfdt/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/src/python/m5/ext/pyfdt/pyfdt.py b/src/python/m5/ext/pyfdt/pyfdt.py new file mode 100644 index 000000000..3b3238e25 --- /dev/null +++ b/src/python/m5/ext/pyfdt/pyfdt.py @@ -0,0 +1,1083 @@ +# -*- coding: utf-8 -*- +""" +Device Tree Blob Parser + + Copyright 2014 Neil 'superna' Armstrong + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +@author: Neil 'superna' Armstrong +""" + +import string +import os +import json +from copy import deepcopy, copy +from struct import Struct, unpack, pack + +FDT_MAGIC = 0xd00dfeed +FDT_BEGIN_NODE = 0x1 +FDT_END_NODE = 0x2 +FDT_PROP = 0x3 +FDT_NOP = 0x4 +FDT_END = 0x9 + +INDENT = ' ' * 4 + +FDT_MAX_VERSION = 17 + + +class FdtProperty(object): + """ Represents an empty property""" + + @staticmethod + def __validate_dt_name(name): + """Checks the name validity""" + return not any([True for char in name + if char not in string.printable]) + + def __init__(self, name): + """Init with name""" + self.name = name + if not FdtProperty.__validate_dt_name(self.name): + raise Exception("Invalid name '%s'" % self.name) + + def get_name(self): + """Get property name""" + return self.name + + def __str__(self): + """String representation""" + return "Property(%s)" % self.name + + def dts_represent(self, depth=0): + """Get dts string representation""" + return INDENT*depth + self.name + ';' + + def dtb_represent(self, string_store, pos=0, version=17): + """Get blob representation""" + # print "%x:%s" % (pos, self) + strpos = string_store.find(self.name+'\0') + if strpos < 0: + strpos = len(string_store) + string_store += self.name+'\0' + pos += 12 + return (pack('>III', FDT_PROP, 0, strpos), + string_store, pos) + + def json_represent(self, depth=0): + """Ouput JSON""" + return '%s: null' % json.dumps(self.name) + + def to_raw(self): + """Return RAW value representation""" + return '' + + def __getitem__(self, value): + """Returns No Items""" + return None + + def __ne__(self, node): + """Check property inequality + """ + return not self.__eq__(node) + + def __eq__(self, node): + """Check node equality + check properties are the same (same values) + """ + if not isinstance(node, FdtProperty): + raise Exception("Invalid object type") + if self.name != node.get_name(): + return False + return True + + @staticmethod + def __check_prop_strings(value): + """Check property string validity + Python version of util_is_printable_string from dtc + """ + pos = 0 + posi = 0 + end = len(value) + + if not len(value): + return None + + #Needed for python 3 support: If a bytes object is passed, + #decode it with the ascii codec. If the decoding fails, assume + #it was not a string object. + try: + value = value.decode('ascii') + except ValueError: + return None + + #Test both against string 0 and int 0 because of + # python2/3 compatibility + if value[-1] != '\0': + return None + + while pos < end: + posi = pos + while pos < end and value[pos] != '\0' \ + and value[pos] in string.printable \ + and value[pos] not in ('\r', '\n'): + pos += 1 + + if value[pos] != '\0' or pos == posi: + return None + pos += 1 + + return True + + @staticmethod + def new_raw_property(name, raw_value): + """Instantiate property with raw value type""" + if FdtProperty.__check_prop_strings(raw_value): + return FdtPropertyStrings.init_raw(name, raw_value) + elif len(raw_value) and len(raw_value) % 4 == 0: + return FdtPropertyWords.init_raw(name, raw_value) + elif len(raw_value) and len(raw_value): + return FdtPropertyBytes.init_raw(name, raw_value) + else: + return FdtProperty(name) + + +class FdtPropertyStrings(FdtProperty): + """Property with strings as value""" + + @classmethod + def __extract_prop_strings(cls, value): + """Extract strings from raw_value""" + return [st for st in \ + value.decode('ascii').split('\0') if len(st)] + + def __init__(self, name, strings): + """Init with strings""" + FdtProperty.__init__(self, name) + if not strings: + raise Exception("Invalid strings") + for stri in strings: + if len(stri) == 0: + raise Exception("Invalid strings") + if any([True for char in stri + if char not in string.printable + or char in ('\r', '\n')]): + raise Exception("Invalid chars in strings") + self.strings = strings + + @classmethod + def init_raw(cls, name, raw_value): + """Init from raw""" + return cls(name, cls.__extract_prop_strings(raw_value)) + + def dts_represent(self, depth=0): + """Get dts string representation""" + return INDENT*depth + self.name + ' = "' + \ + '", "'.join(self.strings) + '";' + + def dtb_represent(self, string_store, pos=0, version=17): + """Get blob representation""" + # print "%x:%s" % (pos, self) + blob = pack('') + for chars in self.strings: + blob += chars.encode('ascii') + pack('b', 0) + blob_len = len(blob) + if version < 16 and (pos+12) % 8 != 0: + blob = pack('b', 0) * (8-((pos+12) % 8)) + blob + if blob_len % 4: + blob += pack('b', 0) * (4-(blob_len % 4)) + strpos = string_store.find(self.name+'\0') + if strpos < 0: + strpos = len(string_store) + string_store += self.name+'\0' + blob = pack('>III', FDT_PROP, blob_len, strpos) + blob + pos += len(blob) + return (blob, string_store, pos) + + def json_represent(self, depth=0): + """Ouput JSON""" + result = '%s: ["strings", ' % json.dumps(self.name) + result += ', '.join([json.dumps(stri) for stri in self.strings]) + result += ']' + return result + + def to_raw(self): + """Return RAW value representation""" + return ''.join([chars+'\0' for chars in self.strings]) + + def __str__(self): + """String representation""" + return "Property(%s,Strings:%s)" % (self.name, self.strings) + + def __getitem__(self, index): + """Get strings, returns a string""" + return self.strings[index] + + def __len__(self): + """Get strings count""" + return len(self.strings) + + def __eq__(self, node): + """Check node equality + check properties are the same (same values) + """ + if not FdtProperty.__eq__(self, node): + return False + if self.__len__() != len(node): + return False + for index in range(self.__len__()): + if self.strings[index] != node[index]: + return False + return True + +class FdtPropertyWords(FdtProperty): + """Property with words as value""" + + def __init__(self, name, words): + """Init with words""" + FdtProperty.__init__(self, name) + for word in words: + if not 0 <= word <= 4294967295: + raise Exception(("Invalid word value %d, requires " + + "0 <= number <= 4294967295") % word) + if not len(words): + raise Exception("Invalid Words") + self.words = words + + @classmethod + def init_raw(cls, name, raw_value): + """Init from raw""" + if len(raw_value) % 4 == 0: + words = [unpack(">I", raw_value[i:i+4])[0] + for i in range(0, len(raw_value), 4)] + return cls(name, words) + else: + raise Exception("Invalid raw Words") + + def dts_represent(self, depth=0): + """Get dts string representation""" + return INDENT*depth + self.name + ' = <' + \ + ' '.join(["0x%08x" % word for word in self.words]) + ">;" + + def dtb_represent(self, string_store, pos=0, version=17): + """Get blob representation""" + # # print "%x:%s" % (pos, self) + strpos = string_store.find(self.name+'\0') + if strpos < 0: + strpos = len(string_store) + string_store += self.name+'\0' + blob = pack('>III', FDT_PROP, len(self.words)*4, strpos) + \ + pack('').join([pack('>I', word) for word in self.words]) + pos += len(blob) + return (blob, string_store, pos) + + def json_represent(self, depth=0): + """Ouput JSON""" + result = '%s: ["words", "' % json.dumps(self.name) + result += '", "'.join(["0x%08x" % word for word in self.words]) + result += '"]' + return result + + def to_raw(self): + """Return RAW value representation""" + return ''.join([pack('>I', word) for word in self.words]) + + def __str__(self): + """String representation""" + return "Property(%s,Words:%s)" % (self.name, self.words) + + def __getitem__(self, index): + """Get words, returns a word integer""" + return self.words[index] + + def __len__(self): + """Get words count""" + return len(self.words) + + def __eq__(self, node): + """Check node equality + check properties are the same (same values) + """ + if not FdtProperty.__eq__(self, node): + return False + if self.__len__() != len(node): + return False + for index in range(self.__len__()): + if self.words[index] != node[index]: + return False + return True + + +class FdtPropertyBytes(FdtProperty): + """Property with signed bytes as value""" + + def __init__(self, name, bytez): + """Init with bytes""" + FdtProperty.__init__(self, name) + for byte in bytez: + if not -128 <= byte <= 127: + raise Exception(("Invalid value for byte %d, " + + "requires -128 <= number <= 127") % byte) + if not bytez: + raise Exception("Invalid Bytes") + self.bytes = bytez + + @classmethod + def init_raw(cls, name, raw_value): + """Init from raw""" + return cls(name, unpack('b' * len(raw_value), raw_value)) + + def dts_represent(self, depth=0): + """Get dts string representation""" + return INDENT*depth + self.name + ' = [' + \ + ' '.join(["%02x" % (byte & int('ffffffff',16)) + for byte in self.bytes]) + "];" + + def dtb_represent(self, string_store, pos=0, version=17): + """Get blob representation""" + # print "%x:%s" % (pos, self) + strpos = string_store.find(self.name+'\0') + if strpos < 0: + strpos = len(string_store) + string_store += self.name+'\0' + blob = pack('>III', FDT_PROP, len(self.bytes), strpos) + blob += pack('').join([pack('>b', byte) for byte in self.bytes]) + if len(blob) % 4: + blob += pack('b', 0) * (4-(len(blob) % 4)) + pos += len(blob) + return (blob, string_store, pos) + + def json_represent(self, depth=0): + """Ouput JSON""" + result = '%s: ["bytes", "' % json.dumps(self.name) + result += '", "'.join(["%02x" % byte + for byte in self.bytes]) + result += '"]' + return result + + def to_raw(self): + """Return RAW value representation""" + return ''.join([pack('>b', byte) for byte in self.bytes]) + + def __str__(self): + """String representation""" + return "Property(%s,Bytes:%s)" % (self.name, self.bytes) + + def __getitem__(self, index): + """Get bytes, returns a byte""" + return self.bytes[index] + + def __len__(self): + """Get strings count""" + return len(self.bytes) + + def __eq__(self, node): + """Check node equality + check properties are the same (same values) + """ + if not FdtProperty.__eq__(self, node): + return False + if self.__len__() != len(node): + return False + for index in range(self.__len__()): + if self.bytes[index] != node[index]: + return False + return True + + +class FdtNop(object): # pylint: disable-msg=R0903 + """Nop child representation""" + + def __init__(self): + """Init with nothing""" + + def get_name(self): # pylint: disable-msg=R0201 + """Return name""" + return None + + def __str__(self): + """String representation""" + return '' + + def dts_represent(self, depth=0): # pylint: disable-msg=R0201 + """Get dts string representation""" + return INDENT*depth+'// [NOP]' + + def dtb_represent(self, string_store, pos=0, version=17): + """Get blob representation""" + # print "%x:%s" % (pos, self) + pos += 4 + return (pack('>I', FDT_NOP), string_store, pos) + + +class FdtNode(object): + """Node representation""" + + @staticmethod + def __validate_dt_name(name): + """Checks the name validity""" + return not any([True for char in name + if char not in string.printable]) + + def __init__(self, name): + """Init node with name""" + self.name = name + self.subdata = [] + self.parent = None + if not FdtNode.__validate_dt_name(self.name): + raise Exception("Invalid name '%s'" % self.name) + + def get_name(self): + """Get property name""" + return self.name + + def __check_name_duplicate(self, name): + """Checks if name is not in a subnode""" + for data in self.subdata: + if not isinstance(data, FdtNop) \ + and data.get_name() == name: + return True + return False + + def add_subnode(self, node): + """Add child, deprecated use append()""" + self.append(node) + + def add_raw_attribute(self, name, raw_value): + """Construct a raw attribute and add to child""" + self.append(FdtProperty.new_raw_property(name, raw_value)) + + def set_parent_node(self, node): + """Set parent node, None and FdtNode accepted""" + if node is not None and \ + not isinstance(node, FdtNode): + raise Exception("Invalid object type") + self.parent = node + + def get_parent_node(self): + """Get parent node""" + return self.parent + + def __str__(self): + """String representation""" + return "Node(%s)" % self.name + + def dts_represent(self, depth=0): + """Get dts string representation""" + result = ('\n').join([sub.dts_represent(depth+1) + for sub in self.subdata]) + if len(result) > 0: + result += '\n' + return INDENT*depth + self.name + ' {\n' + \ + result + INDENT*depth + "};" + + def dtb_represent(self, strings_store, pos=0, version=17): + """Get blob representation + Pass string storage as strings_store, pos for current node start + and version as current dtb version + """ + # print "%x:%s" % (pos, self) + strings = strings_store + if self.get_name() == '/': + blob = pack('>II', FDT_BEGIN_NODE, 0) + else: + blob = pack('>I', FDT_BEGIN_NODE) + blob += self.get_name().encode('ascii') + pack('b', 0) + if len(blob) % 4: + blob += pack('b', 0) * (4-(len(blob) % 4)) + pos += len(blob) + for sub in self.subdata: + (data, strings, pos) = sub.dtb_represent(strings, pos, version) + blob += data + pos += 4 + blob += pack('>I', FDT_END_NODE) + return (blob, strings, pos) + + def json_represent(self, depth=0): + """Get dts string representation""" + result = (',\n'+ \ + INDENT*(depth+1)).join([sub.json_represent(depth+1) + for sub in self.subdata + if not isinstance(sub, FdtNop)]) + if len(result) > 0: + result = INDENT + result + '\n'+INDENT*depth + if self.get_name() == '/': + return "{\n" + INDENT*(depth) + result + "}" + else: + return json.dumps(self.name) + ': {\n' + \ + INDENT*(depth) + result + "}" + + def __getitem__(self, index): + """Get subnodes, returns either a Node, a Property or a Nop""" + return self.subdata[index] + + def __setitem__(self, index, subnode): + """Set node at index, replacing previous subnode, + must not be a duplicate name + """ + if self.subdata[index].get_name() != subnode.get_name() and \ + self.__check_name_duplicate(subnode.get_name()): + raise Exception("%s : %s subnode already exists" % \ + (self, subnode)) + if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): + raise Exception("Invalid object type") + self.subdata[index] = subnode + + def __len__(self): + """Get strings count""" + return len(self.subdata) + + def __ne__(self, node): + """Check node inequality + i.e. is subnodes are the same, in either order + and properties are the same (same values) + The FdtNop is excluded from the check + """ + return not self.__eq__(node) + + def __eq__(self, node): + """Check node equality + i.e. is subnodes are the same, in either order + and properties are the same (same values) + The FdtNop is excluded from the check + """ + if not isinstance(node, FdtNode): + raise Exception("Invalid object type") + if self.name != node.get_name(): + return False + curnames = set([subnode.get_name() for subnode in self.subdata + if not isinstance(subnode, FdtNop)]) + cmpnames = set([subnode.get_name() for subnode in node + if not isinstance(subnode, FdtNop)]) + if curnames != cmpnames: + return False + for subnode in [subnode for subnode in self.subdata + if not isinstance(subnode, FdtNop)]: + index = node.index(subnode.get_name()) + if subnode != node[index]: + return False + return True + + def append(self, subnode): + """Append subnode, same as add_subnode""" + if self.__check_name_duplicate(subnode.get_name()): + raise Exception("%s : %s subnode already exists" % \ + (self, subnode)) + if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): + raise Exception("Invalid object type") + self.subdata.append(subnode) + + def pop(self, index=-1): + """Remove and returns subnode at index, default the last""" + return self.subdata.pop(index) + + def insert(self, index, subnode): + """Insert subnode before index, must not be a duplicate name""" + if self.__check_name_duplicate(subnode.get_name()): + raise Exception("%s : %s subnode already exists" % \ + (self, subnode)) + if not isinstance(subnode, (FdtNode, FdtProperty, FdtNop)): + raise Exception("Invalid object type") + self.subdata.insert(index, subnode) + + def _find(self, name): + """Find name in subnodes""" + for i in range(0, len(self.subdata)): + if not isinstance(self.subdata[i], FdtNop) and \ + name == self.subdata[i].get_name(): + return i + return None + + def remove(self, name): + """Remove subnode with the name + Raises ValueError is not present + """ + index = self._find(name) + if index is None: + raise ValueError("Not present") + return self.subdata.pop(index) + + def index(self, name): + """Returns position of subnode with the name + Raises ValueError is not present + """ + index = self._find(name) + if index is None: + raise ValueError("Not present") + return index + + def merge(self, node): + """Merge two nodes and subnodes + Replace current properties with the given properties + """ + if not isinstance(node, FdtNode): + raise Exception("Can only merge with a FdtNode") + for subnode in [obj for obj in node + if isinstance(obj, (FdtNode, FdtProperty))]: + index = self._find(subnode.get_name()) + if index is None: + dup = deepcopy(subnode) + if isinstance(subnode, FdtNode): + dup.set_parent_node(self) + self.append(dup) + elif isinstance(subnode, FdtNode): + self.subdata[index].merge(subnode) + else: + self.subdata[index] = copy(subnode) + + def walk(self): + """Walk into subnodes and yield paths and objects + Returns set with (path string, node object) + """ + node = self + start = 0 + hist = [] + curpath = [] + + while True: + for index in range(start, len(node)): + if isinstance(node[index], (FdtNode, FdtProperty)): + yield ('/' + '/'.join(curpath+[node[index].get_name()]), + node[index]) + if isinstance(node[index], FdtNode): + if len(node[index]): + hist.append((node, index+1)) + curpath.append(node[index].get_name()) + node = node[index] + start = 0 + index = -1 + break + if index >= 0: + if len(hist): + (node, start) = hist.pop() + curpath.pop() + else: + break + + +class Fdt(object): + """Flattened Device Tree representation""" + + def __init__(self, version=17, last_comp_version=16, boot_cpuid_phys=0): + """Init FDT object with version and boot values""" + self.header = {'magic': FDT_MAGIC, + 'totalsize': 0, + 'off_dt_struct': 0, + 'off_dt_strings': 0, + 'off_mem_rsvmap': 0, + 'version': version, + 'last_comp_version': last_comp_version, + 'boot_cpuid_phys': boot_cpuid_phys, + 'size_dt_strings': 0, + 'size_dt_struct': 0} + self.rootnode = None + self.prenops = None + self.postnops = None + self.reserve_entries = None + + def add_rootnode(self, rootnode, prenops=None, postnops=None): + """Add root node""" + self.rootnode = rootnode + self.prenops = prenops + self.postnops = postnops + + def get_rootnode(self): + """Get root node""" + return self.rootnode + + def add_reserve_entries(self, reserve_entries): + """Add reserved entries as list of dict with + 'address' and 'size' keys""" + self.reserve_entries = reserve_entries + + def to_dts(self): + """Export to DTS representation in string format""" + result = "/dts-v1/;\n" + result += "// version:\t\t%d\n" % self.header['version'] + result += "// last_comp_version:\t%d\n" % \ + self.header['last_comp_version'] + if self.header['version'] >= 2: + result += "// boot_cpuid_phys:\t0x%x\n" % \ + self.header['boot_cpuid_phys'] + result += '\n' + if self.reserve_entries is not None: + for entry in self.reserve_entries: + result += "/memreserve/ " + if entry['address']: + result += "%#x " % entry['address'] + else: + result += "0 " + if entry['size']: + result += "%#x" % entry['size'] + else: + result += "0" + result += ";\n" + if self.prenops: + result += '\n'.join([nop.dts_represent() for nop in self.prenops]) + result += '\n' + if self.rootnode is not None: + result += self.rootnode.dts_represent() + if self.postnops: + result += '\n' + result += '\n'.join([nop.dts_represent() for nop in self.postnops]) + return result + + def to_dtb(self): + """Export to Blob format""" + if self.rootnode is None: + return None + blob_reserve_entries = pack('') + if self.reserve_entries is not None: + for entry in self.reserve_entries: + blob_reserve_entries += pack('>QQ', + entry['address'], + entry['size']) + blob_reserve_entries += pack('>QQ', 0, 0) + header_size = 7 * 4 + if self.header['version'] >= 2: + header_size += 4 + if self.header['version'] >= 3: + header_size += 4 + if self.header['version'] >= 17: + header_size += 4 + header_adjust = pack('') + if header_size % 8 != 0: + header_adjust = pack('b', 0) * (8 - (header_size % 8)) + header_size += len(header_adjust) + dt_start = header_size + len(blob_reserve_entries) + # print "dt_start %d" % dt_start + (blob_dt, blob_strings, dt_pos) = \ + self.rootnode.dtb_represent('', dt_start, self.header['version']) + if self.prenops is not None: + blob_dt = pack('').join([nop.dtb_represent('')[0] + for nop in self.prenops])\ + + blob_dt + if self.postnops is not None: + blob_dt += pack('').join([nop.dtb_represent('')[0] + for nop in self.postnops]) + blob_dt += pack('>I', FDT_END) + self.header['size_dt_strings'] = len(blob_strings) + self.header['size_dt_struct'] = len(blob_dt) + self.header['off_mem_rsvmap'] = header_size + self.header['off_dt_struct'] = dt_start + self.header['off_dt_strings'] = dt_start + len(blob_dt) + self.header['totalsize'] = dt_start + len(blob_dt) + len(blob_strings) + blob_header = pack('>IIIIIII', self.header['magic'], + self.header['totalsize'], + self.header['off_dt_struct'], + self.header['off_dt_strings'], + self.header['off_mem_rsvmap'], + self.header['version'], + self.header['last_comp_version']) + if self.header['version'] >= 2: + blob_header += pack('>I', self.header['boot_cpuid_phys']) + if self.header['version'] >= 3: + blob_header += pack('>I', self.header['size_dt_strings']) + if self.header['version'] >= 17: + blob_header += pack('>I', self.header['size_dt_struct']) + return blob_header + header_adjust + blob_reserve_entries + \ + blob_dt + blob_strings.encode('ascii') + + def to_json(self): + """Ouput JSON""" + if self.rootnode is None: + return None + return self.rootnode.json_represent() + + def resolve_path(self, path): + """Resolve path like /memory/reg and return either a FdtNode, + a FdtProperty or None""" + if self.rootnode is None: + return None + if not path.startswith('/'): + return None + if len(path) > 1 and path.endswith('/'): + path = path[:-1] + if path == '/': + return self.rootnode + curnode = self.rootnode + for subpath in path[1:].split('/'): + found = None + if not isinstance(curnode, FdtNode): + return None + for node in curnode: + if subpath == node.get_name(): + found = node + break + if found is None: + return None + curnode = found + return curnode + +def _add_json_to_fdtnode(node, subjson): + """Populate FdtNode with JSON dict items""" + for (key, value) in subjson.items(): + if isinstance(value, dict): + subnode = FdtNode(key) + subnode.set_parent_node(node) + node.append(subnode) + _add_json_to_fdtnode(subnode, value) + elif isinstance(value, list): + if len(value) < 2: + raise Exception("Invalid list for %s" % key) + if value[0] == "words": + words = [int(word, 16) for word in value[1:]] + node.append(FdtPropertyWords(key, words)) + elif value[0] == "bytes": + bytez = [int(byte, 16) for byte in value[1:]] + node.append(FdtPropertyBytes(key, bytez)) + elif value[0] == "strings": + node.append(FdtPropertyStrings(key, \ + [s for s in value[1:]])) + else: + raise Exception("Invalid list for %s" % key) + elif value is None: + node.append(FdtProperty(key)) + else: + raise Exception("Invalid value for %s" % key) + +def FdtJsonParse(buf): + """Import FDT from JSON representation, see JSONDeviceTree.md for + structure and encoding + Returns an Fdt object + """ + tree = json.loads(buf) + + root = FdtNode('/') + + _add_json_to_fdtnode(root, tree) + + fdt = Fdt() + fdt.add_rootnode(root) + return fdt + +def FdtFsParse(path): + """Parse device tree filesystem and return a Fdt instance + Should be /proc/device-tree on a device, or the fusemount.py + mount point. + """ + root = FdtNode("/") + + if path.endswith('/'): + path = path[:-1] + + nodes = {path: root} + + for subpath, subdirs, files in os.walk(path): + if subpath not in nodes.keys(): + raise Exception("os.walk error") + cur = nodes[subpath] + for f in files: + with open(subpath+'/'+f, 'rb') as content_file: + content = content_file.read() + prop = FdtProperty.new_raw_property(f, content) + cur.add_subnode(prop) + for subdir in subdirs: + subnode = FdtNode(subdir) + cur.add_subnode(subnode) + subnode.set_parent_node(cur) + nodes[subpath+'/'+subdir] = subnode + + fdt = Fdt() + fdt.add_rootnode(root) + return fdt + +class FdtBlobParse(object): # pylint: disable-msg=R0903 + """Parse from file input""" + + __fdt_header_format = ">IIIIIII" + __fdt_header_names = ('magic', 'totalsize', 'off_dt_struct', + 'off_dt_strings', 'off_mem_rsvmap', 'version', + 'last_comp_version') + + __fdt_reserve_entry_format = ">QQ" + __fdt_reserve_entry_names = ('address', 'size') + + __fdt_dt_cell_format = ">I" + __fdt_dt_prop_format = ">II" + __fdt_dt_tag_name = {FDT_BEGIN_NODE: 'node_begin', + FDT_END_NODE: 'node_end', + FDT_PROP: 'prop', + FDT_NOP: 'nop', + FDT_END: 'end'} + + def __extract_fdt_header(self): + """Extract DTB header""" + header = Struct(self.__fdt_header_format) + header_entry = Struct(">I") + data = self.infile.read(header.size) + result = dict(zip(self.__fdt_header_names, header.unpack_from(data))) + if result['version'] >= 2: + data = self.infile.read(header_entry.size) + result['boot_cpuid_phys'] = header_entry.unpack_from(data)[0] + if result['version'] >= 3: + data = self.infile.read(header_entry.size) + result['size_dt_strings'] = header_entry.unpack_from(data)[0] + if result['version'] >= 17: + data = self.infile.read(header_entry.size) + result['size_dt_struct'] = header_entry.unpack_from(data)[0] + return result + + def __extract_fdt_reserve_entries(self): + """Extract reserved memory entries""" + header = Struct(self.__fdt_reserve_entry_format) + entries = [] + self.infile.seek(self.fdt_header['off_mem_rsvmap']) + while True: + data = self.infile.read(header.size) + result = dict(zip(self.__fdt_reserve_entry_names, + header.unpack_from(data))) + if result['address'] == 0 and result['size'] == 0: + return entries + entries.append(result) + + def __extract_fdt_nodename(self): + """Extract node name""" + data = '' + pos = self.infile.tell() + while True: + byte = self.infile.read(1) + if ord(byte) == 0: + break + data += byte.decode('ascii') + align_pos = pos + len(data) + 1 + align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1)) + self.infile.seek(align_pos) + return data + + def __extract_fdt_string(self, prop_string_pos): + """Extract string from string pool""" + data = '' + pos = self.infile.tell() + self.infile.seek(self.fdt_header['off_dt_strings']+prop_string_pos) + while True: + byte = self.infile.read(1) + if ord(byte) == 0: + break + data += byte.decode('ascii') + self.infile.seek(pos) + return data + + def __extract_fdt_prop(self): + """Extract property""" + prop = Struct(self.__fdt_dt_prop_format) + pos = self.infile.tell() + data = self.infile.read(prop.size) + (prop_size, prop_string_pos,) = prop.unpack_from(data) + + prop_start = pos + prop.size + if self.fdt_header['version'] < 16 and prop_size >= 8: + prop_start = (((prop_start) + ((8) - 1)) & ~((8) - 1)) + + self.infile.seek(prop_start) + value = self.infile.read(prop_size) + + align_pos = self.infile.tell() + align_pos = (((align_pos) + ((4) - 1)) & ~((4) - 1)) + self.infile.seek(align_pos) + + return (self.__extract_fdt_string(prop_string_pos), value) + + def __extract_fdt_dt(self): + """Extract tags""" + cell = Struct(self.__fdt_dt_cell_format) + tags = [] + self.infile.seek(self.fdt_header['off_dt_struct']) + while True: + data = self.infile.read(cell.size) + if len(data) < cell.size: + break + tag, = cell.unpack_from(data) + # print "*** %s" % self.__fdt_dt_tag_name.get(tag, '') + if self.__fdt_dt_tag_name.get(tag, '') in 'node_begin': + name = self.__extract_fdt_nodename() + if len(name) == 0: + name = '/' + tags.append((tag, name)) + elif self.__fdt_dt_tag_name.get(tag, '') in ('node_end', 'nop'): + tags.append((tag, '')) + elif self.__fdt_dt_tag_name.get(tag, '') in 'end': + tags.append((tag, '')) + break + elif self.__fdt_dt_tag_name.get(tag, '') in 'prop': + propdata = self.__extract_fdt_prop() + tags.append((tag, propdata)) + else: + print("Unknown Tag %d" % tag) + return tags + + def __init__(self, infile): + """Init with file input""" + self.infile = infile + self.fdt_header = self.__extract_fdt_header() + if self.fdt_header['magic'] != FDT_MAGIC: + raise Exception('Invalid Magic') + if self.fdt_header['version'] > FDT_MAX_VERSION: + raise Exception('Invalid Version %d' % self.fdt_header['version']) + if self.fdt_header['last_comp_version'] > FDT_MAX_VERSION-1: + raise Exception('Invalid last compatible Version %d' % + self.fdt_header['last_comp_version']) + self.fdt_reserve_entries = self.__extract_fdt_reserve_entries() + self.fdt_dt_tags = self.__extract_fdt_dt() + + def __to_nodes(self): + """Represent fdt as Node and properties structure + Returns a set with the pre-node Nops, the Root Node, + and the post-node Nops. + """ + prenops = [] + postnops = [] + rootnode = None + curnode = None + for tag in self.fdt_dt_tags: + if self.__fdt_dt_tag_name.get(tag[0], '') in 'node_begin': + newnode = FdtNode(tag[1]) + if rootnode is None: + rootnode = newnode + if curnode is not None: + curnode.add_subnode(newnode) + newnode.set_parent_node(curnode) + curnode = newnode + elif self.__fdt_dt_tag_name.get(tag[0], '') in 'node_end': + if curnode is not None: + curnode = curnode.get_parent_node() + elif self.__fdt_dt_tag_name.get(tag[0], '') in 'nop': + if curnode is not None: + curnode.add_subnode(FdtNop()) + elif rootnode is not None: + postnops.append(FdtNop()) + else: + prenops.append(FdtNop()) + elif self.__fdt_dt_tag_name.get(tag[0], '') in 'prop': + if curnode is not None: + curnode.add_raw_attribute(tag[1][0], tag[1][1]) + elif self.__fdt_dt_tag_name.get(tag[0], '') in 'end': + continue + return (prenops, rootnode, postnops) + + def to_fdt(self): + """Create a fdt object + Returns a Fdt object + """ + if self.fdt_header['version'] >= 2: + boot_cpuid_phys = self.fdt_header['boot_cpuid_phys'] + else: + boot_cpuid_phys = 0 + fdt = Fdt(version=self.fdt_header['version'], + last_comp_version=self.fdt_header['last_comp_version'], + boot_cpuid_phys=boot_cpuid_phys) + (prenops, rootnode, postnops) = self.__to_nodes() + fdt.add_rootnode(rootnode, prenops=prenops, postnops=postnops) + fdt.add_reserve_entries(self.fdt_reserve_entries) + return fdt -- 2.30.2