You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

238 lines
8.7 KiB
Python

#!/usr/bin/env python
# Copyright 2017 Martin Olejar
#
# 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.
import os
import sys
import fdt
import argparse
########################################################################################################################
# Helper Functions
########################################################################################################################
def parse_fdt(file_path: str, file_type: str):
"""
Parse *.dtb ot *.dts input file and return FDT object
:param file_path: The path to input file
:param file_type: File type 'dtb', 'dts' or 'auto'
"""
if not os.path.exists(file_path):
raise Exception('File doesnt exist: {}'.format(file_path))
if file_type == 'auto':
if file_path.endswith(".dtb"):
file_type = 'dtb'
elif file_path.endswith(".dts"):
file_type = 'dts'
else:
raise Exception('Not supported file extension: {}'.format(file_path))
if file_type == 'dtb':
with open(file_path, 'rb') as f:
obj = fdt.parse_dtb(f.read())
else:
with open(file_path, 'r') as f:
obj = fdt.parse_dts(f.read(), os.path.dirname(file_path))
return obj
########################################################################################################################
# Commands Functions
########################################################################################################################
def pack(in_file: str, out_file: str, version: int, lc_version: int, cpu_id: int, update_phandles: bool):
"""
The implementation of pack command.
:param in_file: Input File Path
:param out_file: Output File Path
:param version: DTB version
:param lc_version: DTB Last Compatible Version
:param cpu_id: Boot CPU ID
:param update_phandles: If True phandles will be updated
"""
if version is not None and version > fdt.Header.MAX_VERSION:
raise Exception("DTB Version must be lover or equal {} !".format(fdt.Header.MAX_VERSION))
fdt_obj = parse_fdt(in_file, 'dts')
if update_phandles:
fdt_obj.update_phandles()
raw_data = fdt_obj.to_dtb(version, lc_version, cpu_id)
with open(out_file, 'wb') as f:
f.write(raw_data)
print(" DTB saved as: {}".format(out_file))
def unpack(in_file: str, out_file: str, tab_size):
"""
The implementation of unpack command.
:param in_file: Input File Path
:param out_file: Output File Path
:param tab_size: Tabulator size in count of spaces
"""
fdt_obj = parse_fdt(in_file, 'dtb')
with open(out_file, 'w') as f:
f.write(fdt_obj.to_dts(tab_size))
print(" DTS saved as: {}".format(out_file))
def merge(out_file: str, in_files: list, file_type: str, tab_size: int):
"""
The implementation of merge command.
:param out_file: Output File Path
:param in_files: Input Files Path
:param file_type: The type of input files
:param tab_size: Tabulator size in count of spaces
"""
fdt_obj = None
for file in in_files:
obj = parse_fdt(file, file_type)
if fdt_obj is None:
fdt_obj = obj
else:
fdt_obj.merge(obj)
with open(out_file, 'w') as f:
f.write(fdt_obj.to_dts(tab_size))
print(" Output saved as: {}".format(out_file))
def diff(in_file1: str, in_file2: str, file_type: str, out_dir: str):
"""
The implementation of diff command.
:param in_file1: Input File1 Path
:param in_file2: Input File2 Path
:param file_type: The type of input files
:param out_dir: Path to output directory
"""
# load input files
fdt1 = parse_fdt(in_file1, file_type)
fdt2 = parse_fdt(in_file2, file_type)
# compare it
diff = fdt.diff(fdt1, fdt2)
if diff[0].empty:
print(" Input files are completely different !")
sys.exit()
# create output directory
os.makedirs(out_dir, exist_ok=True)
# get names for output files
file_name = (
"same.dts",
os.path.splitext(os.path.basename(in_file1))[0] + ".dts",
os.path.splitext(os.path.basename(in_file2))[0] + ".dts")
# save output files
for index, obj in enumerate(diff):
if not obj.empty:
with open(os.path.join(out_dir, file_name[index]), 'w') as f:
f.write(obj.to_dts())
print(" Diff output saved into: {}".format(out_dir))
########################################################################################################################
# Main
########################################################################################################################
def main():
# cli interface
parser = argparse.ArgumentParser(
prog="pydtc",
description="Flat Device Tree (FDT) tool for manipulation with *.dtb and *.dts files")
parser.add_argument('-v', '--version', action='version', version=fdt.__version__)
subparsers = parser.add_subparsers(dest='command')
# pack command
pack_parser = subparsers.add_parser('pack', help='Pack *.dts into binary blob (*.dtb)')
pack_parser.add_argument('dts_file', nargs=1, help='Path to *.dts file')
pack_parser.add_argument('-v', dest='version', type=int, help='DTB Version')
pack_parser.add_argument('-l', dest='lc_version', type=int, help='DTB Last Compatible Version')
pack_parser.add_argument('-c', dest='cpu_id', type=int, help='Boot CPU ID')
pack_parser.add_argument('-p', dest='phandles', action='store_true', help='Update phandles')
pack_parser.add_argument('-o', dest='dtb_file', type=str, help='Output path with file name (*.dtb)')
# unpack command
unpack_parser = subparsers.add_parser('unpack', help='Unpack *.dtb into readable format (*.dts)')
unpack_parser.add_argument('dtb_file', nargs=1, help='Path to *.dtb file')
unpack_parser.add_argument('-s', dest='tab_size', type=int, default=4, help='Tabulator Size')
unpack_parser.add_argument('-o', dest='dts_file', type=str, help='Output path with file name (*.dts)')
# merge command
merge_parser = subparsers.add_parser('merge', help='Merge more files in *.dtb or *.dts format')
merge_parser.add_argument('out_file', nargs=1, help='Output path with file name (*.dts or *.dtb)')
merge_parser.add_argument('in_files', nargs='+', help='Path to input files')
merge_parser.add_argument('-t', dest='type', type=str, choices=['auto', 'dts', 'dtb'], help='Input file type')
merge_parser.add_argument('-s', dest='tab_size', type=int, default=4, help='Tabulator Size for dts')
# diff command
diff_parser = subparsers.add_parser('diff', help='Compare two files in *.dtb or *.dts format')
diff_parser.add_argument('in_file1', nargs=1, help='Path to dts or dtb file')
diff_parser.add_argument('in_file2', nargs=1, help='Path to dts or dtb file')
diff_parser.add_argument('-t', dest='type', type=str, choices=['auto', 'dts', 'dtb'], help='Input file type')
diff_parser.add_argument('-o', dest='out_dir', type=str, help='Output directory')
args = parser.parse_args()
try:
if args.command == 'pack':
in_file = args.dts_file[0]
if args.dtb_file is None:
out_file = os.path.splitext(os.path.basename(in_file))[0] + ".dtb"
else:
out_file = args.dtb_file.lstrip()
pack(in_file, out_file, args.version, args.lc_version, args.cpu_id, args.phandles)
elif args.command == 'unpack':
in_file = args.dtb_file[0]
if args.dts_file is None:
out_file = os.path.splitext(os.path.basename(in_file))[0] + ".dts"
else:
out_file = args.dts_file.lstrip()
unpack(in_file, out_file, args.tab_size)
elif args.command == 'merge':
merge(args.out_file[0], args.in_files, args.type, args.tab_size)
elif args.command == 'diff':
out_dir = args.out_dir if args.out_dir else os.path.join(os.getcwd(), 'diff_out')
diff(args.in_file1[0], args.in_file2[0], args.type, out_dir.lstrip())
else:
parser.print_help()
except Exception as e:
print("[pydtc] Execution Error !")
print(str(e) if str(e) else "Unknown Error", file=sys.stderr)
sys.exit(1)
if __name__ == '__main__':
main()