summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSandeep Patil <sspatil@google.com>2018-02-06 23:50:20 +0000
committerandroid-build-merger <android-build-merger@google.com>2018-02-06 23:50:20 +0000
commit8491f7086164218f394e4c5ac9802b598013e5f1 (patch)
tree7e5f52e5228657c09c9ac84c48c60ec5b51f4870
parent4bfa6ad9853428b21520b3422c9462974e770259 (diff)
parent6fb1bf011605d51ae5741a457f024ceba0b7d6c6 (diff)
downloadlibufdt-8491f7086164218f394e4c5ac9802b598013e5f1.tar.gz
Add mkdtboimg am: 9bde0c43d9
am: 6fb1bf0116 Change-Id: I06dbac53c9a988ae490200f4c496acb483268b6f
-rw-r--r--utils/src/Android.mk11
-rwxr-xr-xutils/src/mkdtboimg.py898
-rw-r--r--utils/tests/data/mkdtboimg.cfg18
-rwxr-xr-xutils/tests/mkdtboimg_testdata.sh108
4 files changed, 1034 insertions, 1 deletions
diff --git a/utils/src/Android.mk b/utils/src/Android.mk
index 21d4279..2345ac9 100644
--- a/utils/src/Android.mk
+++ b/utils/src/Android.mk
@@ -38,6 +38,15 @@ LOCAL_CXX_STL := none
include $(BUILD_HOST_EXECUTABLE)
+$(call dist-for-goals, dist_files, $(ALL_MODULES.mkdtimg.BUILT):libufdt/mkdtimg)
###################################################
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := mkdtboimg.py
+LOCAL_SRC_FILES := mkdtboimg.py
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_PREBUILT)
-$(call dist-for-goals, dist_files, $(ALL_MODULES.mkdtimg.BUILT):libufdt/mkdtimg)
diff --git a/utils/src/mkdtboimg.py b/utils/src/mkdtboimg.py
new file mode 100755
index 0000000..5f7e40f
--- /dev/null
+++ b/utils/src/mkdtboimg.py
@@ -0,0 +1,898 @@
+#! /usr/bin/env python
+# Copyright 2017, The Android Open Source Project
+#
+# 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.
+
+from __future__ import print_function
+
+"""Tool for packing multiple DTB/DTBO files into a single image"""
+
+import argparse
+import os
+from array import array
+from collections import namedtuple
+import struct
+from sys import stdout
+
+
+class DtEntry(object):
+ """Provides individual DT image file arguments to be added to a DTBO.
+
+ Attributes:
+ _REQUIRED_KEYS: 'keys' needed to be present in the dictionary passed to instantiate
+ an object of this class.
+ """
+
+ REQUIRED_KEYS = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', 'custom0',
+ 'custom1', 'custom2', 'custom3')
+ @staticmethod
+ def __get_number_or_prop(arg):
+ """Converts string to integer or reads the property from DT image.
+
+ Args:
+ arg: String containing the argument provided on the command line.
+
+ Returns:
+ An integer property read from DT file or argument string
+ converted to integer
+ """
+
+ if not arg or arg[0] == '+' or arg[0] == '-':
+ raise ValueError('Invalid argument passed to DTImage')
+ if arg[0] == '/':
+ # TODO(b/XXX): Use pylibfdt to get property value from DT
+ raise ValueError('Invalid argument passed to DTImage')
+ else:
+ base = 10
+ if arg.startswith('0x') or arg.startswith('0X'):
+ base = 16
+ elif arg.startswith('0'):
+ base = 8
+ return int(arg, base)
+
+ def __init__(self, **kwargs):
+ """Constructor for DtEntry object.
+
+ Initializes attributes from dictionary object that contains
+ values keyed with names equivalent to the class's attributes.
+
+ Args:
+ kwargs: Dictionary object containing values to instantiate
+ class members with. Expected keys in dictionary are from
+ the tuple (_REQUIRED_KEYS)
+ """
+
+ missing_keys = set(self.REQUIRED_KEYS) - set(kwargs)
+ if missing_keys:
+ raise ValueError('Missing keys in DtEntry constructor: %r' %
+ sorted(missing_keys))
+
+ self.__dt_file = kwargs['dt_file']
+ self.__dt_offset = kwargs['dt_offset']
+ self.__dt_size = kwargs['dt_size']
+ self.__id = self.__get_number_or_prop(kwargs['id'])
+ self.__rev = self.__get_number_or_prop(kwargs['rev'])
+ self.__custom0 = self.__get_number_or_prop(kwargs['custom0'])
+ self.__custom1 = self.__get_number_or_prop(kwargs['custom1'])
+ self.__custom2 = self.__get_number_or_prop(kwargs['custom2'])
+ self.__custom3 = self.__get_number_or_prop(kwargs['custom3'])
+
+ def __str__(self):
+ sb = []
+ sb.append('{key:>20} = {value:d}'.format(key='dt_size',
+ value=self.__dt_size))
+ sb.append('{key:>20} = {value:d}'.format(key='dt_offset',
+ value=self.__dt_offset))
+ sb.append('{key:>20} = {value:08x}'.format(key='id',
+ value=self.__id))
+ sb.append('{key:>20} = {value:08x}'.format(key='rev',
+ value=self.__rev))
+ sb.append('{key:>20} = {value:08x}'.format(key='custom[0]',
+ value=self.__custom0))
+ sb.append('{key:>20} = {value:08x}'.format(key='custom[1]',
+ value=self.__custom1))
+ sb.append('{key:>20} = {value:08x}'.format(key='custom[2]',
+ value=self.__custom2))
+ sb.append('{key:>20} = {value:08x}'.format(key='custom[3]',
+ value=self.__custom3))
+ return '\n'.join(sb)
+
+ @property
+ def dt_file(self):
+ """file: File handle to the DT image file."""
+ return self.__dt_file
+
+ @property
+ def size(self):
+ """int: size in bytes of the DT image file."""
+ return self.__dt_size
+
+ @property
+ def dt_offset(self):
+ """int: offset in DTBO file for this DT image."""
+ return self.__dt_offset
+
+ @dt_offset.setter
+ def dt_offset(self, value):
+ self.__dt_offset = value
+
+ @property
+ def image_id(self):
+ """int: DT entry _id for this DT image."""
+ return self.__id
+
+ @property
+ def rev(self):
+ """int: DT entry _rev for this DT image."""
+ return self.__rev
+
+ @property
+ def custom0(self):
+ """int: DT entry _custom0 for this DT image."""
+ return self.__custom0
+
+ @property
+ def custom1(self):
+ """int: DT entry _custom1 for this DT image."""
+ return self.__custom1
+
+ @property
+ def custom2(self):
+ """int: DT entry _custom2 for this DT image."""
+ return self.__custom2
+
+ @property
+ def custom3(self):
+ """int: DT entry custom3 for this DT image."""
+ return self.__custom3
+
+
+class Dtbo(object):
+ """
+ Provides parser, reader, writer for dumping and creating Device Tree Blob
+ Overlay (DTBO) images.
+
+ Attributes:
+ _DTBO_MAGIC: Device tree table header magic.
+ _DT_TABLE_HEADER_SIZE: Size of Device tree table header.
+ _DT_TABLE_HEADER_INTS: Number of integers in DT table header.
+ _DT_ENTRY_HEADER_SIZE: Size of Device tree entry header within a DTBO.
+ _DT_ENTRY_HEADER_INTS: Number of integers in DT entry header.
+ """
+
+ _DTBO_MAGIC = 0xd7b7ab1e
+ _DT_TABLE_HEADER_SIZE = struct.calcsize('>8I')
+ _DT_TABLE_HEADER_INTS = 8
+ _DT_ENTRY_HEADER_SIZE = struct.calcsize('>8I')
+ _DT_ENTRY_HEADER_INTS = 8
+
+ def _update_dt_table_header(self):
+ """Converts header entries into binary data for DTBO header.
+
+ Packs the current Device tree table header attribute values in
+ metadata buffer.
+ """
+ struct.pack_into('>8I', self.__metadata, 0, self.magic,
+ self.total_size, self.header_size,
+ self.dt_entry_size, self.dt_entry_count,
+ self.dt_entries_offset, self.page_size,
+ self.reserved)
+
+ def _update_dt_entry_header(self, dt_entry, metadata_offset):
+ """Converts each DT entry header entry into binary data for DTBO file.
+
+ Packs the current device tree table entry attribute into
+ metadata buffer as device tree entry header.
+
+ Args:
+ dt_entry: DtEntry object for the header to be packed.
+ metadata_offset: Offset into metadata buffer to begin writing.
+ dtbo_offset: Offset where the DT image file for this dt_entry can
+ be found in the resulting DTBO image.
+ """
+ struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size,
+ dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev,
+ dt_entry.custom0, dt_entry.custom1, dt_entry.custom2,
+ dt_entry.custom3)
+
+ def _update_metadata(self):
+ """Updates the DTBO metadata.
+
+ Initialize the internal metadata buffer and fill it with all Device
+ Tree table entries and update the DTBO header.
+ """
+
+ self.__metadata = array('c', ' ' * self.__metadata_size)
+ metadata_offset = self.header_size
+ for dt_entry in self.__dt_entries:
+ self._update_dt_entry_header(dt_entry, metadata_offset)
+ metadata_offset += self.dt_entry_size
+ self._update_dt_table_header()
+
+ def _read_dtbo_header(self, buf):
+ """Reads DTBO file header into metadata buffer.
+
+ Unpack and read the DTBO table header from given buffer. The
+ buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE.
+
+ Args:
+ buf: Bytebuffer read directly from the file of size
+ _DT_TABLE_HEADER_SIZE.
+ """
+ (self.magic, self.total_size, self.header_size,
+ self.dt_entry_size, self.dt_entry_count, self.dt_entries_offset,
+ self.page_size, self.reserved) = struct.unpack_from('>8I', buf, 0)
+
+ # verify the header
+ if self.magic != self._DTBO_MAGIC:
+ raise ValueError('Invalid magic number 0x%x in DTBO file' %
+ (self.magic))
+
+ if self.header_size != self._DT_TABLE_HEADER_SIZE:
+ raise ValueError('Invalid header size (%d) in DTBO file' %
+ (self.header_size))
+
+ if self.dt_entry_size != self._DT_ENTRY_HEADER_SIZE:
+ raise ValueError('Invalid DT entry header size (%d) in DTBO file' %
+ (self.dt_entry_size))
+
+ if self.reserved:
+ raise ValueError('Invalid reserved field (%d) in DTBO header' %
+ (self.reserved))
+
+ def _read_dt_entries_from_metadata(self):
+ """Reads individual DT entry headers from metadata buffer.
+
+ Unpack and read the DTBO DT entry headers from the internal buffer.
+ The buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE +
+ (_DT_ENTRY_HEADER_SIZE * dt_entry_count). The method raises exception
+ if DT entries have already been set for this object.
+ """
+
+ if self.__dt_entries:
+ raise ValueError('DTBO DT entries can be added only once')
+
+ offset = self.dt_entries_offset / 4
+ params = {}
+ params['dt_file'] = None
+ for i in range(0, self.dt_entry_count):
+ dt_table_entry = self.__metadata[offset:offset + self._DT_ENTRY_HEADER_INTS]
+ params['dt_size'] = dt_table_entry[0]
+ params['dt_offset'] = dt_table_entry[1]
+ for j in range(2, self._DT_ENTRY_HEADER_INTS):
+ params[DtEntry.REQUIRED_KEYS[j + 1]] = str(dt_table_entry[j])
+ dt_entry = DtEntry(**params)
+ self.__dt_entries.append(dt_entry)
+ offset += self._DT_ENTRY_HEADER_INTS
+
+ def _read_dtbo_image(self):
+ """Parse the input file and instantiate this object."""
+
+ # First check if we have enough to read the header
+ file_size = os.fstat(self.__file.fileno()).st_size
+ if file_size < self._DT_TABLE_HEADER_SIZE:
+ raise ValueError('Invalid DTBO file')
+
+ self.__file.seek(0)
+ buf = self.__file.read(self._DT_TABLE_HEADER_SIZE)
+ self._read_dtbo_header(buf)
+
+ self.__metadata_size = (self.header_size +
+ self.dt_entry_count * self.dt_entry_size)
+ if file_size < self.__metadata_size:
+ raise ValueError('Invalid or truncated DTBO file of size %d expected %d' %
+ file_size, self.__metadata_size)
+
+ num_ints = (self._DT_TABLE_HEADER_INTS +
+ self.dt_entry_count * self._DT_ENTRY_HEADER_INTS)
+ if self.dt_entries_offset > self._DT_TABLE_HEADER_SIZE:
+ num_ints += (self.dt_entries_offset - self._DT_TABLE_HEADER_SIZE) / 4
+ format_str = '>' + str(num_ints) + 'I'
+ self.__file.seek(0)
+ self.__metadata = struct.unpack(format_str,
+ self.__file.read(self.__metadata_size))
+ self._read_dt_entries_from_metadata()
+
+ def _find_dt_entry_with_same_file(self, dt_entry):
+ """Finds DT Entry that has identical backing DT file.
+
+ Args:
+ dt_entry: DtEntry object whose 'dtfile' we find for existence in the
+ current 'dt_entries'.
+ Returns:
+ If a match by file path is found, the corresponding DtEntry object
+ from internal list is returned. If not, 'None' is returned.
+ """
+
+ dt_entry_path = os.path.realpath(dt_entry.dt_file.name)
+ for entry in self.__dt_entries:
+ entry_path = os.path.realpath(entry.dt_file.name)
+ if entry_path == dt_entry_path:
+ return entry
+ return None
+
+ def __init__(self, file_handle, page_size=None):
+ """Constructor for Dtbo Object
+
+ Args:
+ file_handle: The Dtbo File handle corresponding to this object.
+ The file handle can be used to write to (in case of 'create')
+ or read from (in case of 'dump')
+ """
+
+ self.__file = file_handle
+ self.__dt_entries = []
+ self.__metadata = None
+ self.__metadata_size = 0
+
+ # if page_size is given, assume the object is being instantiated to
+ # create a DTBO file
+ if page_size:
+ self.magic = self._DTBO_MAGIC
+ self.total_size = self._DT_TABLE_HEADER_SIZE
+ self.header_size = self._DT_TABLE_HEADER_SIZE
+ self.dt_entry_size = self._DT_ENTRY_HEADER_SIZE
+ self.dt_entry_count = 0
+ self.dt_entries_offset = self._DT_TABLE_HEADER_SIZE
+ self.page_size = page_size
+ self.reserved = 0
+ self.__metadata_size = self._DT_TABLE_HEADER_SIZE
+ else:
+ self._read_dtbo_image()
+
+ def __str__(self):
+ sb = []
+ sb.append('dt_table_header:')
+ _keys = ('magic', 'total_size', 'header_size', 'dt_entry_size',
+ 'dt_entry_count', 'dt_entries_offset', 'page_size')
+ for key in _keys:
+ if key == 'magic':
+ sb.append('{key:>20} = {value:08x}'.format(key=key,
+ value=self.__dict__[key]))
+ else:
+ sb.append('{key:>20} = {value:d}'.format(key=key,
+ value=self.__dict__[key]))
+ sb.append('{key:>20} = {value:08d}'.format(key='reserved[0]',
+ value=self.reserved))
+ count = 0
+ for dt_entry in self.__dt_entries:
+ sb.append('dt_table_entry[{0:d}]:'.format(count))
+ sb.append(str(dt_entry))
+ count = count + 1
+ return '\n'.join(sb)
+
+ @property
+ def dt_entries(self):
+ """Returns a list of DtEntry objects found in DTBO file."""
+ return self.__dt_entries
+
+ def add_dt_entries(self, dt_entries):
+ """Adds DT image files to the DTBO object.
+
+ Adds a list of Dtentry Objects to the DTBO image. The changes are not
+ committed to the output file until commit() is called.
+
+ Args:
+ dt_entries: List of DtEntry object to be added.
+
+ """
+ if not dt_entries:
+ raise ValueError('Attempted to add empty list of DT entries')
+
+ if self.__dt_entries:
+ raise ValueError('DTBO DT entries can be added only once')
+
+ dt_entry_count = len(dt_entries)
+ dt_offset = (self.header_size +
+ dt_entry_count * self.dt_entry_size)
+
+ for dt_entry in dt_entries:
+ if not isinstance(dt_entry, DtEntry):
+ raise ValueError('Adding invalid DT entry object to DTBO')
+ entry = self._find_dt_entry_with_same_file(dt_entry)
+ if entry:
+ dt_entry.dt_offset = entry.dt_offset
+ else:
+ dt_entry.dt_offset = dt_offset
+ dt_offset += dt_entry.size
+ self.total_size += dt_entry.size
+ self.__dt_entries.append(dt_entry)
+ self.dt_entry_count += 1
+ self.__metadata_size += self.dt_entry_size
+ self.total_size += self.dt_entry_size
+
+ def extract_dt_file(self, idx, fout):
+ """Extract DT Image files embedded in the DTBO file.
+
+ Extracts Device Tree blob image file at given index into a file handle.
+
+ Args:
+ idx: Index of the DT entry in the DTBO file.
+ fout: File handle where the DTB at index idx to be extracted into.
+ """
+ if idx > self.dt_entry_count:
+ raise ValueError('Invalid index %d of DtEntry' % idx)
+
+ size = self.dt_entries[idx].size
+ offset = self.dt_entries[idx].dt_offset
+ self.__file.seek(offset, 0)
+ fout.seek(0)
+ fout.write(self.__file.read(size))
+
+ def commit(self):
+ """Write out staged changes to the DTBO object to create a DTBO file.
+
+ Writes a fully instantiated Dtbo Object into the output file using the
+ file handle present in '_file'. No checks are performed on the object
+ except for existence of output file handle on the object before writing
+ out the file.
+ """
+ if not self.__file:
+ raise ValueError('No file given to write to.')
+
+ if not self.__dt_entries:
+ raise ValueError('No DT image files to embed into DTBO image given.')
+
+ self._update_metadata()
+ self.__file.seek(0)
+ self.__file.write(self.__metadata)
+ for dt_entry in self.__dt_entries:
+ self.__file.write(dt_entry.dt_file.read())
+ self.__file.flush()
+
+
+def parse_dt_entry(global_args, arglist):
+ """Parse arguments for single DT entry file.
+
+ Parses command line arguments for single DT image file while
+ creating a Device tree blob overlay (DTBO).
+
+ Args:
+ global_args: Dtbo object containing global default values
+ for DtEntry attributes.
+ arglist: Command line argument list for this DtEntry.
+
+ Returns:
+ A Namespace object containing all values to instantiate DtEntry object.
+ """
+
+ parser = argparse.ArgumentParser(add_help=False)
+ parser.add_argument('dt_file', nargs='?',
+ type=argparse.FileType('rb'),
+ default=None)
+ parser.add_argument('--id', type=str, dest='id', action='store',
+ default=global_args.global_id)
+ parser.add_argument('--rev', type=str, dest='rev',
+ action='store', default=global_args.global_rev)
+ parser.add_argument('--custom0', type=str, dest='custom0',
+ action='store',
+ default=global_args.global_custom0)
+ parser.add_argument('--custom1', type=str, dest='custom1',
+ action='store',
+ default=global_args.global_custom1)
+ parser.add_argument('--custom2', type=str, dest='custom2',
+ action='store',
+ default=global_args.global_custom2)
+ parser.add_argument('--custom3', type=str, dest='custom3',
+ action='store',
+ default=global_args.global_custom3)
+ return parser.parse_args(arglist)
+
+
+def parse_dt_entries(global_args, arg_list):
+ """Parse all DT entries from command line.
+
+ Parse all DT image files and their corresponding attribute from
+ command line
+
+ Args:
+ global_args: Argument containing default global values for _id,
+ _rev and customX.
+ arg_list: The remainder of the command line after global options
+ DTBO creation have been parsed.
+
+ Returns:
+ A List of DtEntry objects created after parsing the command line
+ given in argument.
+ """
+ dt_entries = []
+ img_file_idx = []
+ idx = 0
+ # find all positional arguments (i.e. DT image file paths)
+ for arg in arg_list:
+ if not arg.startswith("--"):
+ img_file_idx.append(idx)
+ idx = idx + 1
+
+ if not img_file_idx:
+ raise ValueError('Input DT images must be provided')
+
+ total_images = len(img_file_idx)
+ for idx in xrange(total_images):
+ start_idx = img_file_idx[idx]
+ if idx == total_images - 1:
+ argv = arg_list[start_idx:]
+ else:
+ end_idx = img_file_idx[idx + 1]
+ argv = arg_list[start_idx:end_idx]
+ args = parse_dt_entry(global_args, argv)
+ params = vars(args)
+ params['dt_offset'] = 0
+ params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size
+ dt_entries.append(DtEntry(**params))
+
+ return dt_entries
+
+def parse_config_option(line, is_global, dt_keys, global_keys):
+ """Parses a single line from the configuration file.
+
+ Args:
+ line: String containing the key=value line from the file.
+ is_global: Boolean indicating if we should parse global or DT entry
+ specific option.
+ dt_keys: Tuple containing all valid DT entry and global option strings
+ in configuration file.
+ global_keys: Tuple containing all exclusive valid global option strings
+ in configuration file that are not repeated in dt entry options.
+
+ Returns:
+ Returns a tuple for parsed key and value for the option. Also, checks
+ the key to make sure its valid.
+ """
+
+ if line.find('=') == -1:
+ raise ValueError('Invalid line (%s) in configuration file' % line)
+
+ key, value = (x.strip() for x in line.split('='))
+ if key not in dt_keys:
+ if is_global and key in global_keys:
+ value = int(value)
+ else:
+ raise ValueError('Invalid option (%s) in configuration file' % key)
+
+ return key, value
+
+def parse_config_file(fin, dt_keys, global_keys):
+ """Parses the configuration file for creating DTBO image.
+
+ Args:
+ fin: File handle for configuration file
+ is_global: Boolean indicating if we should parse global or DT entry
+ specific option.
+ dt_keys: Tuple containing all valid DT entry and global option strings
+ in configuration file.
+ global_keys: Tuple containing all exclusive valid global option strings
+ in configuration file that are not repeated in dt entry options.
+
+ Returns:
+ global_args, dt_args: Tuple of a dictionary with global arguments
+ and a list of dictionaries for all DT entry specific arguments the
+ following format.
+ global_args:
+ {'id' : <value>, 'rev' : <value> ...}
+ dt_args:
+ [{'filename' : 'dt_file_name', 'id' : <value>,
+ 'rev' : <value> ...},
+ {'filename' : 'dt_file_name2', 'id' : <value2>,
+ 'rev' : <value2> ...}, ...
+ ]
+ """
+
+ # set all global defaults
+ global_args = dict((k, '0') for k in dt_keys)
+ global_args['page_size'] = 2048
+
+ dt_args = []
+ found_dt_entry = False
+ count = -1
+ for line in fin:
+ line = line.rstrip()
+ if line.lstrip().startswith('#'):
+ continue
+ comment_idx = line.find('#')
+ line = line if comment_idx == -1 else line[0:comment_idx]
+ if not line or line.isspace():
+ continue
+ if line.startswith(' ') and not found_dt_entry:
+ # This is a global argument
+ key, value = parse_config_option(line, True, dt_keys, global_keys)
+ global_args[key] = value
+ elif line.find('=') != -1:
+ key, value = parse_config_option(line, False, dt_keys, global_keys)
+ dt_args[-1][key] = value
+ else:
+ found_dt_entry = True
+ count += 1
+ dt_args.append({})
+ dt_args[-1]['filename'] = line.strip()
+ return global_args, dt_args
+
+def parse_create_args(arg_list):
+ """Parse command line arguments for 'create' sub-command.
+
+ Args:
+ arg_list: All command line arguments except the outfile file name.
+
+ Returns:
+ The list of remainder of the command line arguments after parsing
+ for 'create'.
+ """
+
+ image_arg_index = 0
+ for arg in arg_list:
+ if not arg.startswith("--"):
+ break
+ image_arg_index = image_arg_index + 1
+
+ argv = arg_list[0:image_arg_index]
+ remainder = arg_list[image_arg_index:]
+ parser = argparse.ArgumentParser(prog='create', add_help=False)
+ parser.add_argument('--page_size', type=int, dest='page_size',
+ action='store', default=2048)
+ parser.add_argument('--id', type=str, dest='global_id',
+ action='store', default='0')
+ parser.add_argument('--rev', type=str, dest='global_rev',
+ action='store', default='0')
+ parser.add_argument('--custom0', type=str, dest='global_custom0',
+ action='store', default='0')
+ parser.add_argument('--custom1', type=str, dest='global_custom1',
+ action='store', default='0')
+ parser.add_argument('--custom2', type=str, dest='global_custom2',
+ action='store', default='0')
+ parser.add_argument('--custom3', type=str, dest='global_custom3',
+ action='store', default='0')
+ args = parser.parse_args(argv)
+ return args, remainder
+
+def parse_dump_cmd_args(arglist):
+ """Parse command line arguments for 'dump' sub-command.
+
+ Args:
+ arglist: List of all command line arguments including the outfile
+ file name if exists.
+
+ Returns:
+ A namespace object of parsed arguments.
+ """
+
+ parser = argparse.ArgumentParser(prog='dump')
+ parser.add_argument('--output', '-o', nargs='?',
+ type=argparse.FileType('wb'),
+ dest='outfile',
+ default=stdout)
+ parser.add_argument('--dtb', '-b', nargs='?', type=str,
+ dest='dtfilename')
+ return parser.parse_args(arglist)
+
+def parse_config_create_cmd_args(arglist):
+ """Parse command line arguments for 'cfg_create subcommand.
+
+ Args:
+ arglist: A list of all command line arguments including the
+ mandatory input configuration file name.
+
+ Returns:
+ A Namespace object of parsed arguments.
+ """
+ parser = argparse.ArgumentParser(prog='cfg_create')
+ parser.add_argument('conf_file', nargs='?',
+ type=argparse.FileType('rb'),
+ default=None)
+ cwd = os.getcwd()
+ parser.add_argument('--dtb-dir', '-d', nargs='?', type=str,
+ dest='dtbdir', default=cwd)
+ return parser.parse_args(arglist)
+
+def create_dtbo_image(fout, argv):
+ """Create Device Tree Blob Overlay image using provided arguments.
+
+ Args:
+ fout: Output file handle to write to.
+ argv: list of command line arguments.
+ """
+
+ global_args, remainder = parse_create_args(argv)
+ if not remainder:
+ raise ValueError('List of dtimages to add to DTBO not provided')
+ dt_entries = parse_dt_entries(global_args, remainder)
+ dtbo = Dtbo(fout, global_args.page_size)
+ dtbo.add_dt_entries(dt_entries)
+ dtbo.commit()
+ fout.close()
+
+def dump_dtbo_image(fin, argv):
+ """Dump DTBO file.
+
+ Dump Device Tree Blob Overlay metadata as output and the device
+ tree image files embedded in the DTBO image into file(s) provided
+ as arguments
+
+ Args:
+ fin: Input DTBO image files.
+ argv: list of command line arguments.
+ """
+ dtbo = Dtbo(fin)
+ args = parse_dump_cmd_args(argv)
+ if args.dtfilename:
+ num_entries = len(dtbo.dt_entries)
+ for idx in range(0, num_entries):
+ with open(args.dtfilename + '.{:d}'.format(idx), 'wb') as fout:
+ dtbo.extract_dt_file(idx, fout)
+ args.outfile.write(str(dtbo) + '\n')
+ args.outfile.close()
+
+def create_dtbo_image_from_config(fout, argv):
+ """Create DTBO file from a configuration file.
+
+ Args:
+ fout: Output file handle to write to.
+ argv: list of command line arguments.
+ """
+ args = parse_config_create_cmd_args(argv)
+ if not args.conf_file:
+ raise ValueError('Configuration file must be provided')
+
+ _DT_KEYS = ('id', 'rev', 'custom0', 'custom1', 'custom2', 'custom3')
+ _GLOBAL_KEYS = ('page_size')
+
+ global_args, dt_args = parse_config_file(args.conf_file,
+ _DT_KEYS, _GLOBAL_KEYS)
+ params = {}
+ dt_entries = []
+ for dt_arg in dt_args:
+ filepath = args.dtbdir + os.sep + dt_arg['filename']
+ params['dt_file'] = open(filepath, 'rb')
+ params['dt_offset'] = 0
+ params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size
+ for key in _DT_KEYS:
+ if key not in dt_arg:
+ params[key] = global_args[key]
+ else:
+ params[key] = dt_arg[key]
+ dt_entries.append(DtEntry(**params))
+
+ # Create and write DTBO file
+ dtbo = Dtbo(fout, global_args['page_size'])
+ dtbo.add_dt_entries(dt_entries)
+ dtbo.commit()
+ fout.close()
+
+def print_default_usage(progname):
+ """Prints program's default help string.
+
+ Args:
+ progname: This program's name.
+ """
+ sb = []
+ sb.append(' ' + progname + ' help all')
+ sb.append(' ' + progname + ' help <command>\n')
+ sb.append(' commands:')
+ sb.append(' help, dump, create, cfg_create')
+ print('\n'.join(sb))
+
+def print_dump_usage(progname):
+ """Prints usage for 'dump' sub-command.
+
+ Args:
+ progname: This program's name.
+ """
+ sb = []
+ sb.append(' ' + progname + ' dump <image_file> (<option>...)\n')
+ sb.append(' options:')
+ sb.append(' -o, --output <filename> Output file name.')
+ sb.append(' Default is output to stdout.')
+ sb.append(' -b, --dtb <filename> Dump dtb/dtbo files from image.')
+ sb.append(' Will output to <filename>.0, <filename>.1, etc.')
+ print('\n'.join(sb))
+
+def print_create_usage(progname):
+ """Prints usage for 'create' subcommand.
+
+ Args:
+ progname: This program's name.
+ """
+ sb = []
+ sb.append(' ' + progname + ' create <image_file> (<global_option>...) (<dtb_file> (<entry_option>...) ...)\n')
+ sb.append(' global_options:')
+ sb.append(' --page_size <number> Output file name. Default: 2048')
+ sb.append(' --id <number> The default value to set property id in dt_table_entry. Default: 0')
+ sb.append(' --rev <number>')
+ sb.append(' --custom0=<number>')
+ sb.append(' --custom2=<number>')
+ sb.append(' --custom3=<number>')
+ sb.append(' --custom4=<number>\n')
+
+ sb.append(' The value could be a number or a DT node path.')
+ sb.append(' <number> could be a 32-bits digit or hex value, ex. 68000, 0x6800.')
+ sb.append(' <path> format is <full_node_path>:<property_name>, ex. /board/:id,')
+ sb.append(' will read the value in given FTB file with the path.')
+ print('\n'.join(sb))
+
+def print_cfg_create_usage(progname):
+ """Prints usage for 'cfg_create' sub-command.
+
+ Args:
+ progname: This program's name.
+ """
+ sb = []
+ sb.append(' ' + progname + ' cfg_create <image_file> <config_file> (<option>...)\n')
+ sb.append(' options:')
+ sb.append(' -d, --dtb-dir <dir> The path to load dtb files.')
+ sb.append(' Default is load from the current path.')
+ print('\n'.join(sb))
+
+def print_usage(cmd, _):
+ """Prints usage for this program.
+
+ Args:
+ cmd: The string sub-command for which help (usage) is requested.
+ """
+ prog_name = os.path.basename(__file__)
+ if not cmd:
+ print_default_usage(prog_name)
+ return
+
+ HelpCommand = namedtuple('HelpCommand', 'help_cmd, help_func')
+ help_commands = (HelpCommand('dump', print_dump_usage),
+ HelpCommand('create', print_create_usage),
+ HelpCommand('cfg_create', print_cfg_create_usage),
+ )
+
+ if cmd == 'all':
+ print_default_usage(prog_name)
+
+ for help_cmd, help_func in help_commands:
+ if cmd == 'all' or cmd == help_cmd:
+ help_func(prog_name)
+ if cmd != 'all':
+ return
+
+ print('Unsupported help command: %s' % cmd, end='\n\n')
+ print_default_usage(prog_name)
+ return
+
+def main():
+ """Main entry point for mkdtboimg."""
+
+ parser = argparse.ArgumentParser(prog='mkdtboimg.py')
+
+ subparser = parser.add_subparsers(title='subcommand',
+ description='Valid subcommands')
+
+ create_parser = subparser.add_parser('create', add_help=False)
+ create_parser.add_argument('argfile', nargs='?',
+ action='store', help='Output File',
+ type=argparse.FileType('wb'))
+ create_parser.set_defaults(func=create_dtbo_image)
+
+ config_parser = subparser.add_parser('cfg_create', add_help=False)
+ config_parser.add_argument('argfile', nargs='?',
+ action='store',
+ type=argparse.FileType('wb'))
+ config_parser.set_defaults(func=create_dtbo_image_from_config)
+
+ dump_parser = subparser.add_parser('dump', add_help=False)
+ dump_parser.add_argument('argfile', nargs='?',
+ action='store',
+ type=argparse.FileType('rb'))
+ dump_parser.set_defaults(func=dump_dtbo_image)
+
+ help_parser = subparser.add_parser('help', add_help=False)
+ help_parser.add_argument('argfile', nargs='?', action='store')
+ help_parser.set_defaults(func=print_usage)
+
+ (subcmd, subcmd_args) = parser.parse_known_args()
+ subcmd.func(subcmd.argfile, subcmd_args)
+
+if __name__ == '__main__':
+ main()
diff --git a/utils/tests/data/mkdtboimg.cfg b/utils/tests/data/mkdtboimg.cfg
new file mode 100644
index 0000000..a5e8c2d
--- /dev/null
+++ b/utils/tests/data/mkdtboimg.cfg
@@ -0,0 +1,18 @@
+# Global options
+ page_size=4096
+ id=0x100 # Read the value form the path in dtb
+ rev=0x100 # Read the value form the path in dtb
+ custom0=0xabc # default value
+
+board1v1.dts.dtb
+
+board1v1_1.dts.dtb
+ id=0xddccbbaa # override with another path
+ rev=0x01000100 # override default rev
+
+board2v1.dts.dtb
+ id=0x200
+ rev=0x201 # override with another value
+
+board1v1.dts.dtb
+ custom0=0xdef # override with another value
diff --git a/utils/tests/mkdtboimg_testdata.sh b/utils/tests/mkdtboimg_testdata.sh
new file mode 100755
index 0000000..50cafe2
--- /dev/null
+++ b/utils/tests/mkdtboimg_testdata.sh
@@ -0,0 +1,108 @@
+#! /bin/bash
+# Copyright (C) 2017 The Android Open Source Project
+#
+# 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.
+
+SRCDIR="data"
+DTS_LIST="
+ board1v1.dts
+ board1v1_1.dts
+ board2v1.dts
+"
+DTB_LIST=(
+ "board1v1.dts.dtb"
+ "board1v1_1.dts.dtb"
+ "board2v1.dts.dtb"
+ "board1v1.dts.dtb"
+)
+CONFIG="${SRCDIR}/mkdtimg.cfg"
+PYCONFIG="${SRCDIR}/mkdtboimg.cfg"
+
+ALIGN=4
+
+OUTDIR="out"
+MKDTIMG_OUT="${OUTDIR}/mkdtimg_out"
+MKDTIMG_DUMP="${MKDTIMG_OUT}"/dump.dtb
+
+MKDTBOIMG_OUT="${OUTDIR}/mkdtboimg_out"
+MKDTBOIMG_OUTCREATE="${MKDTBOIMG_OUT}/create"
+MKDTBOIMG_OUTCFG="${MKDTBOIMG_OUT}/cfg_create"
+MKDTBOIMG_CREATEDUMP="${MKDTBOIMG_OUTCREATE}"/dump.dtb
+MKDTBOIMG_CFGDUMP="${MKDTBOIMG_OUTCFG}"/dump.dtb
+
+mkdir -p "$MKDTIMG_OUT"
+mkdir -p "$MKDTBOIMG_OUTCREATE"
+mkdir -p "$MKDTBOIMG_OUTCFG"
+
+for dts in ${DTS_LIST}; do
+ echo "Building $dts..."
+ src_dts="${SRCDIR}/${dts}"
+ out_dtb="${OUTDIR}/${dts}.dtb"
+ dtc -O dtb -@ -qq -a "$ALIGN" -o "$out_dtb" "$src_dts"
+done
+
+echo "Creating dtbo image with mkdtbimg"
+mkdtimg create ${MKDTIMG_OUT}/create.img --page_size=4096 --id=0x100 \
+ --rev=0x100 --custom0=0xabc "${OUTDIR}/board1v1.dts.dtb" "${OUTDIR}/board1v1_1.dts.dtb" \
+ --id=0xddccbbaa --rev=0x01000100 "${OUTDIR}/board2v1.dts.dtb" --id=0x200 \
+ --rev=0x201 "${OUTDIR}/board1v1.dts.dtb" --custom0=0xdef > /dev/null
+
+echo "Creating dtbo image with mkdtboimg"
+../src/mkdtboimg.py create ${MKDTBOIMG_OUTCREATE}/create.img --page_size=4096 \
+ --id=0x100 --rev=0x100 --custom0=0xabc "${OUTDIR}/board1v1.dts.dtb" \
+ "${OUTDIR}/board1v1_1.dts.dtb" --id=0xddccbbaa --rev=0x01000100 \
+ "${OUTDIR}/board2v1.dts.dtb" --id=0x200 --rev=0x201 \
+ "${OUTDIR}/board1v1.dts.dtb" --custom0=0xdef > /dev/null
+
+echo "Creating dtbo image with ${PYCONFIG} config file"
+../src/mkdtboimg.py cfg_create ${MKDTBOIMG_OUTCFG}/create.img ${PYCONFIG} --dtb-dir "${OUTDIR}"
+
+echo "Dumping fragments from mkdtimg tool image"
+mkdtimg dump ${MKDTIMG_OUT}/create.img -b "${MKDTIMG_DUMP}"| grep -v 'FDT' > ${MKDTIMG_OUT}/create.dump
+
+echo "Dumping fragments from mkdtboimg.py tool for image generated with 'create'"
+../src/mkdtboimg.py dump ${MKDTBOIMG_OUTCREATE}/create.img --output ${MKDTBOIMG_OUTCREATE}/create.dump -b "${MKDTBOIMG_CREATEDUMP}"
+
+echo "Dumping fragments from mkdtboimg.py tool for image generated with 'cfg_create'"
+../src/mkdtboimg.py dump ${MKDTBOIMG_OUTCFG}/create.img --output ${MKDTBOIMG_OUTCFG}/create.dump -b "${MKDTBOIMG_CFGDUMP}"
+
+echo "======================================================================================"
+echo "Testing differences between image created by 'create' for 'mkdtimg' and 'mkdtboimg.py'"
+echo "======================================================================================"
+for x in `ls -1 ${MKDTIMG_DUMP}.*`
+do
+ file=`basename $x`
+ if [ ! -e ${MKDTBOIMG_OUTCREATE}/$file ]
+ then
+ continue
+ fi
+ echo "diff $x vs ${MKDTBOIMG_OUTCREATE}/$file"
+ diff $x ${MKDTBOIMG_OUTCREATE}/$file
+done
+echo "diff ${MKDTIMG_OUT}/create.dump vs ${MKDTBOIMG_OUTCREATE}/create.dump";
+diff ${MKDTIMG_OUT}/create.dump ${MKDTBOIMG_OUTCREATE}/create.dump
+echo "=========================================================================================="
+echo "Testing differences between image created by 'cfg_create' for 'mkdtimg' and 'mkdtboimg.py'"
+echo "=========================================================================================="
+for x in `ls -1 ${MKDTIMG_DUMP}.*`
+do
+ file=`basename $x`
+ if [ ! -e ${MKDTBOIMG_OUTCFG}/$file ]
+ then
+ continue
+ fi
+ echo "diff $x vs ${MKDTBOIMG_OUTCFG}/$file"
+ diff $x ${MKDTBOIMG_OUTCFG}/$file
+done
+echo "diff ${MKDTIMG_OUT}/create.dump vs ${MKDTBOIMG_OUTCFG}/create.dump";
+diff ${MKDTIMG_OUT}/create.dump ${MKDTBOIMG_OUTCFG}/create.dump