Home | History | Annotate | Download | only in src
      1 #! /usr/bin/env python
      2 # Copyright 2017, The Android Open Source Project
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #     http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 
     16 from __future__ import print_function
     17 
     18 """Tool for packing multiple DTB/DTBO files into a single image"""
     19 
     20 import argparse
     21 import os
     22 from array import array
     23 from collections import namedtuple
     24 import struct
     25 from sys import stdout
     26 
     27 
     28 class DtEntry(object):
     29     """Provides individual DT image file arguments to be added to a DTBO.
     30 
     31     Attributes:
     32         _REQUIRED_KEYS: 'keys' needed to be present in the dictionary passed to instantiate
     33             an object of this class.
     34     """
     35 
     36     REQUIRED_KEYS = ('dt_file', 'dt_size', 'dt_offset', 'id', 'rev', 'custom0',
     37                      'custom1', 'custom2', 'custom3')
     38     @staticmethod
     39     def __get_number_or_prop(arg):
     40         """Converts string to integer or reads the property from DT image.
     41 
     42         Args:
     43             arg: String containing the argument provided on the command line.
     44 
     45         Returns:
     46             An integer property read from DT file or argument string
     47             converted to integer
     48         """
     49 
     50         if not arg or arg[0] == '+' or arg[0] == '-':
     51             raise ValueError('Invalid argument passed to DTImage')
     52         if arg[0] == '/':
     53             # TODO(b/XXX): Use pylibfdt to get property value from DT
     54             raise ValueError('Invalid argument passed to DTImage')
     55         else:
     56             base = 10
     57             if arg.startswith('0x') or arg.startswith('0X'):
     58                 base = 16
     59             elif arg.startswith('0'):
     60                 base = 8
     61             return int(arg, base)
     62 
     63     def __init__(self, **kwargs):
     64         """Constructor for DtEntry object.
     65 
     66         Initializes attributes from dictionary object that contains
     67         values keyed with names equivalent to the class's attributes.
     68 
     69         Args:
     70             kwargs: Dictionary object containing values to instantiate
     71                 class members with. Expected keys in dictionary are from
     72                 the tuple (_REQUIRED_KEYS)
     73         """
     74 
     75         missing_keys = set(self.REQUIRED_KEYS) - set(kwargs)
     76         if missing_keys:
     77             raise ValueError('Missing keys in DtEntry constructor: %r' %
     78                              sorted(missing_keys))
     79 
     80         self.__dt_file = kwargs['dt_file']
     81         self.__dt_offset = kwargs['dt_offset']
     82         self.__dt_size = kwargs['dt_size']
     83         self.__id = self.__get_number_or_prop(kwargs['id'])
     84         self.__rev = self.__get_number_or_prop(kwargs['rev'])
     85         self.__custom0 = self.__get_number_or_prop(kwargs['custom0'])
     86         self.__custom1 = self.__get_number_or_prop(kwargs['custom1'])
     87         self.__custom2 = self.__get_number_or_prop(kwargs['custom2'])
     88         self.__custom3 = self.__get_number_or_prop(kwargs['custom3'])
     89 
     90     def __str__(self):
     91         sb = []
     92         sb.append('{key:>20} = {value:d}'.format(key='dt_size',
     93                                                  value=self.__dt_size))
     94         sb.append('{key:>20} = {value:d}'.format(key='dt_offset',
     95                                                  value=self.__dt_offset))
     96         sb.append('{key:>20} = {value:08x}'.format(key='id',
     97                                                    value=self.__id))
     98         sb.append('{key:>20} = {value:08x}'.format(key='rev',
     99                                                    value=self.__rev))
    100         sb.append('{key:>20} = {value:08x}'.format(key='custom[0]',
    101                                                    value=self.__custom0))
    102         sb.append('{key:>20} = {value:08x}'.format(key='custom[1]',
    103                                                    value=self.__custom1))
    104         sb.append('{key:>20} = {value:08x}'.format(key='custom[2]',
    105                                                    value=self.__custom2))
    106         sb.append('{key:>20} = {value:08x}'.format(key='custom[3]',
    107                                                    value=self.__custom3))
    108         return '\n'.join(sb)
    109 
    110     @property
    111     def dt_file(self):
    112         """file: File handle to the DT image file."""
    113         return self.__dt_file
    114 
    115     @property
    116     def size(self):
    117         """int: size in bytes of the DT image file."""
    118         return self.__dt_size
    119 
    120     @property
    121     def dt_offset(self):
    122         """int: offset in DTBO file for this DT image."""
    123         return self.__dt_offset
    124 
    125     @dt_offset.setter
    126     def dt_offset(self, value):
    127         self.__dt_offset = value
    128 
    129     @property
    130     def image_id(self):
    131         """int: DT entry _id for this DT image."""
    132         return self.__id
    133 
    134     @property
    135     def rev(self):
    136         """int: DT entry _rev for this DT image."""
    137         return self.__rev
    138 
    139     @property
    140     def custom0(self):
    141         """int: DT entry _custom0 for this DT image."""
    142         return self.__custom0
    143 
    144     @property
    145     def custom1(self):
    146         """int: DT entry _custom1 for this DT image."""
    147         return self.__custom1
    148 
    149     @property
    150     def custom2(self):
    151         """int: DT entry _custom2 for this DT image."""
    152         return self.__custom2
    153 
    154     @property
    155     def custom3(self):
    156         """int: DT entry custom3 for this DT image."""
    157         return self.__custom3
    158 
    159 
    160 class Dtbo(object):
    161     """
    162     Provides parser, reader, writer for dumping and creating Device Tree Blob
    163     Overlay (DTBO) images.
    164 
    165     Attributes:
    166         _DTBO_MAGIC: Device tree table header magic.
    167         _DT_TABLE_HEADER_SIZE: Size of Device tree table header.
    168         _DT_TABLE_HEADER_INTS: Number of integers in DT table header.
    169         _DT_ENTRY_HEADER_SIZE: Size of Device tree entry header within a DTBO.
    170         _DT_ENTRY_HEADER_INTS: Number of integers in DT entry header.
    171     """
    172 
    173     _DTBO_MAGIC = 0xd7b7ab1e
    174     _DT_TABLE_HEADER_SIZE = struct.calcsize('>8I')
    175     _DT_TABLE_HEADER_INTS = 8
    176     _DT_ENTRY_HEADER_SIZE = struct.calcsize('>8I')
    177     _DT_ENTRY_HEADER_INTS = 8
    178 
    179     def _update_dt_table_header(self):
    180         """Converts header entries into binary data for DTBO header.
    181 
    182         Packs the current Device tree table header attribute values in
    183         metadata buffer.
    184         """
    185         struct.pack_into('>8I', self.__metadata, 0, self.magic,
    186                          self.total_size, self.header_size,
    187                          self.dt_entry_size, self.dt_entry_count,
    188                          self.dt_entries_offset, self.page_size,
    189                          self.version)
    190 
    191     def _update_dt_entry_header(self, dt_entry, metadata_offset):
    192         """Converts each DT entry header entry into binary data for DTBO file.
    193 
    194         Packs the current device tree table entry attribute into
    195         metadata buffer as device tree entry header.
    196 
    197         Args:
    198             dt_entry: DtEntry object for the header to be packed.
    199             metadata_offset: Offset into metadata buffer to begin writing.
    200             dtbo_offset: Offset where the DT image file for this dt_entry can
    201                 be found in the resulting DTBO image.
    202         """
    203         struct.pack_into('>8I', self.__metadata, metadata_offset, dt_entry.size,
    204                          dt_entry.dt_offset, dt_entry.image_id, dt_entry.rev,
    205                          dt_entry.custom0, dt_entry.custom1, dt_entry.custom2,
    206                          dt_entry.custom3)
    207 
    208     def _update_metadata(self):
    209         """Updates the DTBO metadata.
    210 
    211         Initialize the internal metadata buffer and fill it with all Device
    212         Tree table entries and update the DTBO header.
    213         """
    214 
    215         self.__metadata = array('c', ' ' * self.__metadata_size)
    216         metadata_offset = self.header_size
    217         for dt_entry in self.__dt_entries:
    218             self._update_dt_entry_header(dt_entry, metadata_offset)
    219             metadata_offset += self.dt_entry_size
    220         self._update_dt_table_header()
    221 
    222     def _read_dtbo_header(self, buf):
    223         """Reads DTBO file header into metadata buffer.
    224 
    225         Unpack and read the DTBO table header from given buffer. The
    226         buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE.
    227 
    228         Args:
    229             buf: Bytebuffer read directly from the file of size
    230                 _DT_TABLE_HEADER_SIZE.
    231         """
    232         (self.magic, self.total_size, self.header_size,
    233          self.dt_entry_size, self.dt_entry_count, self.dt_entries_offset,
    234          self.page_size, self.version) = struct.unpack_from('>8I', buf, 0)
    235 
    236         # verify the header
    237         if self.magic != self._DTBO_MAGIC:
    238             raise ValueError('Invalid magic number 0x%x in DTBO file' %
    239                              (self.magic))
    240 
    241         if self.header_size != self._DT_TABLE_HEADER_SIZE:
    242             raise ValueError('Invalid header size (%d) in DTBO file' %
    243                              (self.header_size))
    244 
    245         if self.dt_entry_size != self._DT_ENTRY_HEADER_SIZE:
    246             raise ValueError('Invalid DT entry header size (%d) in DTBO file' %
    247                              (self.dt_entry_size))
    248 
    249     def _read_dt_entries_from_metadata(self):
    250         """Reads individual DT entry headers from metadata buffer.
    251 
    252         Unpack and read the DTBO DT entry headers from the internal buffer.
    253         The buffer size must exactly be equal to _DT_TABLE_HEADER_SIZE +
    254         (_DT_ENTRY_HEADER_SIZE * dt_entry_count). The method raises exception
    255         if DT entries have already been set for this object.
    256         """
    257 
    258         if self.__dt_entries:
    259             raise ValueError('DTBO DT entries can be added only once')
    260 
    261         offset = self.dt_entries_offset / 4
    262         params = {}
    263         params['dt_file'] = None
    264         for i in range(0, self.dt_entry_count):
    265             dt_table_entry = self.__metadata[offset:offset + self._DT_ENTRY_HEADER_INTS]
    266             params['dt_size'] = dt_table_entry[0]
    267             params['dt_offset'] = dt_table_entry[1]
    268             for j in range(2, self._DT_ENTRY_HEADER_INTS):
    269                 params[DtEntry.REQUIRED_KEYS[j + 1]] = str(dt_table_entry[j])
    270             dt_entry = DtEntry(**params)
    271             self.__dt_entries.append(dt_entry)
    272             offset += self._DT_ENTRY_HEADER_INTS
    273 
    274     def _read_dtbo_image(self):
    275         """Parse the input file and instantiate this object."""
    276 
    277         # First check if we have enough to read the header
    278         file_size = os.fstat(self.__file.fileno()).st_size
    279         if file_size < self._DT_TABLE_HEADER_SIZE:
    280             raise ValueError('Invalid DTBO file')
    281 
    282         self.__file.seek(0)
    283         buf = self.__file.read(self._DT_TABLE_HEADER_SIZE)
    284         self._read_dtbo_header(buf)
    285 
    286         self.__metadata_size = (self.header_size +
    287                                 self.dt_entry_count * self.dt_entry_size)
    288         if file_size < self.__metadata_size:
    289             raise ValueError('Invalid or truncated DTBO file of size %d expected %d' %
    290                              file_size, self.__metadata_size)
    291 
    292         num_ints = (self._DT_TABLE_HEADER_INTS +
    293                     self.dt_entry_count * self._DT_ENTRY_HEADER_INTS)
    294         if self.dt_entries_offset > self._DT_TABLE_HEADER_SIZE:
    295             num_ints += (self.dt_entries_offset - self._DT_TABLE_HEADER_SIZE) / 4
    296         format_str = '>' + str(num_ints) + 'I'
    297         self.__file.seek(0)
    298         self.__metadata = struct.unpack(format_str,
    299                                         self.__file.read(self.__metadata_size))
    300         self._read_dt_entries_from_metadata()
    301 
    302     def _find_dt_entry_with_same_file(self, dt_entry):
    303         """Finds DT Entry that has identical backing DT file.
    304 
    305         Args:
    306             dt_entry: DtEntry object whose 'dtfile' we find for existence in the
    307                 current 'dt_entries'.
    308         Returns:
    309             If a match by file path is found, the corresponding DtEntry object
    310             from internal list is returned. If not, 'None' is returned.
    311         """
    312 
    313         dt_entry_path = os.path.realpath(dt_entry.dt_file.name)
    314         for entry in self.__dt_entries:
    315             entry_path = os.path.realpath(entry.dt_file.name)
    316             if entry_path == dt_entry_path:
    317                 return entry
    318         return None
    319 
    320     def __init__(self, file_handle, page_size=None, version=0):
    321         """Constructor for Dtbo Object
    322 
    323         Args:
    324             file_handle: The Dtbo File handle corresponding to this object.
    325                 The file handle can be used to write to (in case of 'create')
    326                 or read from (in case of 'dump')
    327         """
    328 
    329         self.__file = file_handle
    330         self.__dt_entries = []
    331         self.__metadata = None
    332         self.__metadata_size = 0
    333 
    334         # if page_size is given, assume the object is being instantiated to
    335         # create a DTBO file
    336         if page_size:
    337             self.magic = self._DTBO_MAGIC
    338             self.total_size = self._DT_TABLE_HEADER_SIZE
    339             self.header_size = self._DT_TABLE_HEADER_SIZE
    340             self.dt_entry_size = self._DT_ENTRY_HEADER_SIZE
    341             self.dt_entry_count = 0
    342             self.dt_entries_offset = self._DT_TABLE_HEADER_SIZE
    343             self.page_size = page_size
    344             self.version = version
    345             self.__metadata_size = self._DT_TABLE_HEADER_SIZE
    346         else:
    347             self._read_dtbo_image()
    348 
    349     def __str__(self):
    350         sb = []
    351         sb.append('dt_table_header:')
    352         _keys = ('magic', 'total_size', 'header_size', 'dt_entry_size',
    353                  'dt_entry_count', 'dt_entries_offset', 'page_size', 'version')
    354         for key in _keys:
    355             if key == 'magic':
    356                 sb.append('{key:>20} = {value:08x}'.format(key=key,
    357                                                            value=self.__dict__[key]))
    358             else:
    359                 sb.append('{key:>20} = {value:d}'.format(key=key,
    360                                                          value=self.__dict__[key]))
    361         count = 0
    362         for dt_entry in self.__dt_entries:
    363             sb.append('dt_table_entry[{0:d}]:'.format(count))
    364             sb.append(str(dt_entry))
    365             count = count + 1
    366         return '\n'.join(sb)
    367 
    368     @property
    369     def dt_entries(self):
    370         """Returns a list of DtEntry objects found in DTBO file."""
    371         return self.__dt_entries
    372 
    373     def add_dt_entries(self, dt_entries):
    374         """Adds DT image files to the DTBO object.
    375 
    376         Adds a list of Dtentry Objects to the DTBO image. The changes are not
    377         committed to the output file until commit() is called.
    378 
    379         Args:
    380             dt_entries: List of DtEntry object to be added.
    381 
    382         """
    383         if not dt_entries:
    384             raise ValueError('Attempted to add empty list of DT entries')
    385 
    386         if self.__dt_entries:
    387             raise ValueError('DTBO DT entries can be added only once')
    388 
    389         dt_entry_count = len(dt_entries)
    390         dt_offset = (self.header_size +
    391                      dt_entry_count * self.dt_entry_size)
    392 
    393         for dt_entry in dt_entries:
    394             if not isinstance(dt_entry, DtEntry):
    395                 raise ValueError('Adding invalid DT entry object to DTBO')
    396             entry = self._find_dt_entry_with_same_file(dt_entry)
    397             if entry:
    398                 dt_entry.dt_offset = entry.dt_offset
    399             else:
    400                 dt_entry.dt_offset = dt_offset
    401                 dt_offset += dt_entry.size
    402                 self.total_size += dt_entry.size
    403             self.__dt_entries.append(dt_entry)
    404             self.dt_entry_count += 1
    405             self.__metadata_size += self.dt_entry_size
    406             self.total_size += self.dt_entry_size
    407 
    408     def extract_dt_file(self, idx, fout):
    409         """Extract DT Image files embedded in the DTBO file.
    410 
    411         Extracts Device Tree blob image file at given index into a file handle.
    412 
    413         Args:
    414             idx: Index of the DT entry in the DTBO file.
    415             fout: File handle where the DTB at index idx to be extracted into.
    416         """
    417         if idx > self.dt_entry_count:
    418             raise ValueError('Invalid index %d of DtEntry' % idx)
    419 
    420         size = self.dt_entries[idx].size
    421         offset = self.dt_entries[idx].dt_offset
    422         self.__file.seek(offset, 0)
    423         fout.seek(0)
    424         fout.write(self.__file.read(size))
    425 
    426     def commit(self):
    427         """Write out staged changes to the DTBO object to create a DTBO file.
    428 
    429         Writes a fully instantiated Dtbo Object into the output file using the
    430         file handle present in '_file'. No checks are performed on the object
    431         except for existence of output file handle on the object before writing
    432         out the file.
    433         """
    434         if not self.__file:
    435             raise ValueError('No file given to write to.')
    436 
    437         if not self.__dt_entries:
    438             raise ValueError('No DT image files to embed into DTBO image given.')
    439 
    440         self._update_metadata()
    441         self.__file.seek(0)
    442         self.__file.write(self.__metadata)
    443         for dt_entry in self.__dt_entries:
    444             self.__file.write(dt_entry.dt_file.read())
    445         self.__file.flush()
    446 
    447 
    448 def parse_dt_entry(global_args, arglist):
    449     """Parse arguments for single DT entry file.
    450 
    451     Parses command line arguments for single DT image file while
    452     creating a Device tree blob overlay (DTBO).
    453 
    454     Args:
    455         global_args: Dtbo object containing global default values
    456             for DtEntry attributes.
    457         arglist: Command line argument list for this DtEntry.
    458 
    459     Returns:
    460         A Namespace object containing all values to instantiate DtEntry object.
    461     """
    462 
    463     parser = argparse.ArgumentParser(add_help=False)
    464     parser.add_argument('dt_file', nargs='?',
    465                         type=argparse.FileType('rb'),
    466                         default=None)
    467     parser.add_argument('--id', type=str, dest='id', action='store',
    468                         default=global_args.global_id)
    469     parser.add_argument('--rev', type=str, dest='rev',
    470                         action='store', default=global_args.global_rev)
    471     parser.add_argument('--custom0', type=str, dest='custom0',
    472                         action='store',
    473                         default=global_args.global_custom0)
    474     parser.add_argument('--custom1', type=str, dest='custom1',
    475                         action='store',
    476                         default=global_args.global_custom1)
    477     parser.add_argument('--custom2', type=str, dest='custom2',
    478                         action='store',
    479                         default=global_args.global_custom2)
    480     parser.add_argument('--custom3', type=str, dest='custom3',
    481                         action='store',
    482                         default=global_args.global_custom3)
    483     return parser.parse_args(arglist)
    484 
    485 
    486 def parse_dt_entries(global_args, arg_list):
    487     """Parse all DT entries from command line.
    488 
    489     Parse all DT image files and their corresponding attribute from
    490     command line
    491 
    492     Args:
    493         global_args: Argument containing default global values for _id,
    494             _rev and customX.
    495         arg_list: The remainder of the command line after global options
    496             DTBO creation have been parsed.
    497 
    498     Returns:
    499         A List of DtEntry objects created after parsing the command line
    500         given in argument.
    501     """
    502     dt_entries = []
    503     img_file_idx = []
    504     idx = 0
    505     # find all positional arguments (i.e. DT image file paths)
    506     for arg in arg_list:
    507         if not arg.startswith("--"):
    508             img_file_idx.append(idx)
    509         idx = idx + 1
    510 
    511     if not img_file_idx:
    512         raise ValueError('Input DT images must be provided')
    513 
    514     total_images = len(img_file_idx)
    515     for idx in xrange(total_images):
    516         start_idx = img_file_idx[idx]
    517         if idx == total_images - 1:
    518             argv = arg_list[start_idx:]
    519         else:
    520             end_idx = img_file_idx[idx + 1]
    521             argv = arg_list[start_idx:end_idx]
    522         args = parse_dt_entry(global_args, argv)
    523         params = vars(args)
    524         params['dt_offset'] = 0
    525         params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size
    526         dt_entries.append(DtEntry(**params))
    527 
    528     return dt_entries
    529 
    530 def parse_config_option(line, is_global, dt_keys, global_keys):
    531     """Parses a single line from the configuration file.
    532 
    533     Args:
    534         line: String containing the key=value line from the file.
    535         is_global: Boolean indicating if we should parse global or DT entry
    536             specific option.
    537         dt_keys: Tuple containing all valid DT entry and global option strings
    538             in configuration file.
    539         global_keys: Tuple containing all exclusive valid global option strings
    540             in configuration file that are not repeated in dt entry options.
    541 
    542     Returns:
    543         Returns a tuple for parsed key and value for the option. Also, checks
    544         the key to make sure its valid.
    545     """
    546 
    547     if line.find('=') == -1:
    548         raise ValueError('Invalid line (%s) in configuration file' % line)
    549 
    550     key, value = (x.strip() for x in line.split('='))
    551     if key not in dt_keys:
    552         if is_global and key in global_keys:
    553             value = int(value)
    554         else:
    555             raise ValueError('Invalid option (%s) in configuration file' % key)
    556 
    557     return key, value
    558 
    559 def parse_config_file(fin, dt_keys, global_keys):
    560     """Parses the configuration file for creating DTBO image.
    561 
    562     Args:
    563         fin: File handle for configuration file
    564         is_global: Boolean indicating if we should parse global or DT entry
    565             specific option.
    566         dt_keys: Tuple containing all valid DT entry and global option strings
    567             in configuration file.
    568         global_keys: Tuple containing all exclusive valid global option strings
    569             in configuration file that are not repeated in dt entry options.
    570 
    571     Returns:
    572         global_args, dt_args: Tuple of a dictionary with global arguments
    573         and a list of dictionaries for all DT entry specific arguments the
    574         following format.
    575             global_args:
    576                 {'id' : <value>, 'rev' : <value> ...}
    577             dt_args:
    578                 [{'filename' : 'dt_file_name', 'id' : <value>,
    579                  'rev' : <value> ...},
    580                  {'filename' : 'dt_file_name2', 'id' : <value2>,
    581                   'rev' : <value2> ...}, ...
    582                 ]
    583     """
    584 
    585     # set all global defaults
    586     global_args = dict((k, '0') for k in dt_keys)
    587     global_args['page_size'] = 2048
    588     global_args['version'] = 0
    589 
    590     dt_args = []
    591     found_dt_entry = False
    592     count = -1
    593     for line in fin:
    594         line = line.rstrip()
    595         if line.lstrip().startswith('#'):
    596             continue
    597         comment_idx = line.find('#')
    598         line = line if comment_idx == -1 else line[0:comment_idx]
    599         if not line or line.isspace():
    600             continue
    601         if line.startswith(' ') and not found_dt_entry:
    602             # This is a global argument
    603             key, value = parse_config_option(line, True, dt_keys, global_keys)
    604             global_args[key] = value
    605         elif line.find('=') != -1:
    606             key, value = parse_config_option(line, False, dt_keys, global_keys)
    607             dt_args[-1][key] = value
    608         else:
    609             found_dt_entry = True
    610             count += 1
    611             dt_args.append({})
    612             dt_args[-1]['filename'] = line.strip()
    613     return global_args, dt_args
    614 
    615 def parse_create_args(arg_list):
    616     """Parse command line arguments for 'create' sub-command.
    617 
    618     Args:
    619         arg_list: All command line arguments except the outfile file name.
    620 
    621     Returns:
    622         The list of remainder of the command line arguments after parsing
    623         for 'create'.
    624     """
    625 
    626     image_arg_index = 0
    627     for arg in arg_list:
    628         if not arg.startswith("--"):
    629             break
    630         image_arg_index = image_arg_index + 1
    631 
    632     argv = arg_list[0:image_arg_index]
    633     remainder = arg_list[image_arg_index:]
    634     parser = argparse.ArgumentParser(prog='create', add_help=False)
    635     parser.add_argument('--page_size', type=int, dest='page_size',
    636                         action='store', default=2048)
    637     parser.add_argument('--version', type=int, dest='version',
    638                         action='store', default=0)
    639     parser.add_argument('--id', type=str, dest='global_id',
    640                         action='store', default='0')
    641     parser.add_argument('--rev', type=str, dest='global_rev',
    642                         action='store', default='0')
    643     parser.add_argument('--custom0', type=str, dest='global_custom0',
    644                         action='store', default='0')
    645     parser.add_argument('--custom1', type=str, dest='global_custom1',
    646                         action='store', default='0')
    647     parser.add_argument('--custom2', type=str, dest='global_custom2',
    648                         action='store', default='0')
    649     parser.add_argument('--custom3', type=str, dest='global_custom3',
    650                         action='store', default='0')
    651     args = parser.parse_args(argv)
    652     return args, remainder
    653 
    654 def parse_dump_cmd_args(arglist):
    655     """Parse command line arguments for 'dump' sub-command.
    656 
    657     Args:
    658         arglist: List of all command line arguments including the outfile
    659             file name if exists.
    660 
    661     Returns:
    662         A namespace object of parsed arguments.
    663     """
    664 
    665     parser = argparse.ArgumentParser(prog='dump')
    666     parser.add_argument('--output', '-o', nargs='?',
    667                         type=argparse.FileType('wb'),
    668                         dest='outfile',
    669                         default=stdout)
    670     parser.add_argument('--dtb', '-b', nargs='?', type=str,
    671                         dest='dtfilename')
    672     return parser.parse_args(arglist)
    673 
    674 def parse_config_create_cmd_args(arglist):
    675     """Parse command line arguments for 'cfg_create subcommand.
    676 
    677     Args:
    678         arglist: A list of all command line arguments including the
    679             mandatory input configuration file name.
    680 
    681     Returns:
    682         A Namespace object of parsed arguments.
    683     """
    684     parser = argparse.ArgumentParser(prog='cfg_create')
    685     parser.add_argument('conf_file', nargs='?',
    686                         type=argparse.FileType('rb'),
    687                         default=None)
    688     cwd = os.getcwd()
    689     parser.add_argument('--dtb-dir', '-d', nargs='?', type=str,
    690                         dest='dtbdir', default=cwd)
    691     return parser.parse_args(arglist)
    692 
    693 def create_dtbo_image(fout, argv):
    694     """Create Device Tree Blob Overlay image using provided arguments.
    695 
    696     Args:
    697         fout: Output file handle to write to.
    698         argv: list of command line arguments.
    699     """
    700 
    701     global_args, remainder = parse_create_args(argv)
    702     if not remainder:
    703         raise ValueError('List of dtimages to add to DTBO not provided')
    704     dt_entries = parse_dt_entries(global_args, remainder)
    705     dtbo = Dtbo(fout, global_args.page_size, global_args.version)
    706     dtbo.add_dt_entries(dt_entries)
    707     dtbo.commit()
    708     fout.close()
    709 
    710 def dump_dtbo_image(fin, argv):
    711     """Dump DTBO file.
    712 
    713     Dump Device Tree Blob Overlay metadata as output and the device
    714     tree image files embedded in the DTBO image into file(s) provided
    715     as arguments
    716 
    717     Args:
    718         fin: Input DTBO image files.
    719         argv: list of command line arguments.
    720     """
    721     dtbo = Dtbo(fin)
    722     args = parse_dump_cmd_args(argv)
    723     if args.dtfilename:
    724         num_entries = len(dtbo.dt_entries)
    725         for idx in range(0, num_entries):
    726             with open(args.dtfilename + '.{:d}'.format(idx), 'wb') as fout:
    727                 dtbo.extract_dt_file(idx, fout)
    728     args.outfile.write(str(dtbo) + '\n')
    729     args.outfile.close()
    730 
    731 def create_dtbo_image_from_config(fout, argv):
    732     """Create DTBO file from a configuration file.
    733 
    734     Args:
    735         fout: Output file handle to write to.
    736         argv: list of command line arguments.
    737     """
    738     args = parse_config_create_cmd_args(argv)
    739     if not args.conf_file:
    740         raise ValueError('Configuration file must be provided')
    741 
    742     _DT_KEYS = ('id', 'rev', 'custom0', 'custom1', 'custom2', 'custom3')
    743     _GLOBAL_KEYS = ('page_size', 'version')
    744 
    745     global_args, dt_args = parse_config_file(args.conf_file,
    746                                              _DT_KEYS, _GLOBAL_KEYS)
    747     params = {}
    748     dt_entries = []
    749     for dt_arg in dt_args:
    750         filepath = args.dtbdir + os.sep + dt_arg['filename']
    751         params['dt_file'] = open(filepath, 'rb')
    752         params['dt_offset'] = 0
    753         params['dt_size'] = os.fstat(params['dt_file'].fileno()).st_size
    754         for key in _DT_KEYS:
    755             if key not in dt_arg:
    756                 params[key] = global_args[key]
    757             else:
    758                 params[key] = dt_arg[key]
    759         dt_entries.append(DtEntry(**params))
    760 
    761     # Create and write DTBO file
    762     dtbo = Dtbo(fout, global_args['page_size'], global_args['version'])
    763     dtbo.add_dt_entries(dt_entries)
    764     dtbo.commit()
    765     fout.close()
    766 
    767 def print_default_usage(progname):
    768     """Prints program's default help string.
    769 
    770     Args:
    771         progname: This program's name.
    772     """
    773     sb = []
    774     sb.append('  ' + progname + ' help all')
    775     sb.append('  ' + progname + ' help <command>\n')
    776     sb.append('    commands:')
    777     sb.append('      help, dump, create, cfg_create')
    778     print('\n'.join(sb))
    779 
    780 def print_dump_usage(progname):
    781     """Prints usage for 'dump' sub-command.
    782 
    783     Args:
    784         progname: This program's name.
    785     """
    786     sb = []
    787     sb.append('  ' + progname + ' dump <image_file> (<option>...)\n')
    788     sb.append('    options:')
    789     sb.append('      -o, --output <filename>  Output file name.')
    790     sb.append('                               Default is output to stdout.')
    791     sb.append('      -b, --dtb <filename>     Dump dtb/dtbo files from image.')
    792     sb.append('                               Will output to <filename>.0, <filename>.1, etc.')
    793     print('\n'.join(sb))
    794 
    795 def print_create_usage(progname):
    796     """Prints usage for 'create' subcommand.
    797 
    798     Args:
    799         progname: This program's name.
    800     """
    801     sb = []
    802     sb.append('  ' + progname + ' create <image_file> (<global_option>...) (<dtb_file> (<entry_option>...) ...)\n')
    803     sb.append('    global_options:')
    804     sb.append('      --page_size <number>     Page size. Default: 2048')
    805     sb.append('      --version <number>       DTBO version. Default: 0')
    806     sb.append('      --id <number>       The default value to set property id in dt_table_entry. Default: 0')
    807     sb.append('      --rev <number>')
    808     sb.append('      --custom0=<number>')
    809     sb.append('      --custom2=<number>')
    810     sb.append('      --custom3=<number>')
    811     sb.append('      --custom4=<number>\n')
    812 
    813     sb.append('      The value could be a number or a DT node path.')
    814     sb.append('      <number> could be a 32-bits digit or hex value, ex. 68000, 0x6800.')
    815     sb.append('      <path> format is <full_node_path>:<property_name>, ex. /board/:id,')
    816     sb.append('      will read the value in given FTB file with the path.')
    817     print('\n'.join(sb))
    818 
    819 def print_cfg_create_usage(progname):
    820     """Prints usage for 'cfg_create' sub-command.
    821 
    822     Args:
    823         progname: This program's name.
    824     """
    825     sb = []
    826     sb.append('  ' + progname + ' cfg_create <image_file> <config_file> (<option>...)\n')
    827     sb.append('    options:')
    828     sb.append('      -d, --dtb-dir <dir>      The path to load dtb files.')
    829     sb.append('                               Default is load from the current path.')
    830     print('\n'.join(sb))
    831 
    832 def print_usage(cmd, _):
    833     """Prints usage for this program.
    834 
    835     Args:
    836         cmd: The string sub-command for which help (usage) is requested.
    837     """
    838     prog_name = os.path.basename(__file__)
    839     if not cmd:
    840         print_default_usage(prog_name)
    841         return
    842 
    843     HelpCommand = namedtuple('HelpCommand', 'help_cmd, help_func')
    844     help_commands = (HelpCommand('dump', print_dump_usage),
    845                      HelpCommand('create', print_create_usage),
    846                      HelpCommand('cfg_create', print_cfg_create_usage),
    847                      )
    848 
    849     if cmd == 'all':
    850         print_default_usage(prog_name)
    851 
    852     for help_cmd, help_func in help_commands:
    853         if cmd == 'all' or cmd == help_cmd:
    854             help_func(prog_name)
    855             if cmd != 'all':
    856                 return
    857 
    858     print('Unsupported help command: %s' % cmd, end='\n\n')
    859     print_default_usage(prog_name)
    860     return
    861 
    862 def main():
    863     """Main entry point for mkdtboimg."""
    864 
    865     parser = argparse.ArgumentParser(prog='mkdtboimg.py')
    866 
    867     subparser = parser.add_subparsers(title='subcommand',
    868                                       description='Valid subcommands')
    869 
    870     create_parser = subparser.add_parser('create', add_help=False)
    871     create_parser.add_argument('argfile', nargs='?',
    872                                action='store', help='Output File',
    873                                type=argparse.FileType('wb'))
    874     create_parser.set_defaults(func=create_dtbo_image)
    875 
    876     config_parser = subparser.add_parser('cfg_create', add_help=False)
    877     config_parser.add_argument('argfile', nargs='?',
    878                                action='store',
    879                                type=argparse.FileType('wb'))
    880     config_parser.set_defaults(func=create_dtbo_image_from_config)
    881 
    882     dump_parser = subparser.add_parser('dump', add_help=False)
    883     dump_parser.add_argument('argfile', nargs='?',
    884                              action='store',
    885                              type=argparse.FileType('rb'))
    886     dump_parser.set_defaults(func=dump_dtbo_image)
    887 
    888     help_parser = subparser.add_parser('help', add_help=False)
    889     help_parser.add_argument('argfile', nargs='?', action='store')
    890     help_parser.set_defaults(func=print_usage)
    891 
    892     (subcmd, subcmd_args) = parser.parse_known_args()
    893     subcmd.func(subcmd.argfile, subcmd_args)
    894 
    895 if __name__ == '__main__':
    896     main()
    897