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