• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
16from __future__ import print_function
17
18"""Tool for packing multiple DTB/DTBO files into a single image"""
19
20import argparse
21import os
22from array import array
23from collections import namedtuple
24import struct
25from sys import stdout
26
27
28class 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
160class 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
448def 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
486def 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
530def 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
559def 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
615def 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
654def 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
674def 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
693def 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
710def 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
731def 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
767def 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
780def 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
795def 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
819def 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
832def 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
862def 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
895if __name__ == '__main__':
896    main()
897