1#!/usr/bin/env python3 2# 3# Copyright (c) 2024 Nuvoton Technology Corporation 4# 5# SPDX-License-Identifier: Apache-2.0 6 7# This script will append/paste specific header to tell ROM code (Booter) of 8# NPCM eSIO series how to load the firmware from flash to code ram 9# Usage python3 ${ZEPHYR_BASE}/scripts/esiost.py 10# -i in_file.bin -o out_file.bin 11# [-chip <name>] [-v] 12 13import sys 14import hashlib 15from colorama import init, Fore 16from esiost_args import EsiostArgs, exit_with_failure 17from pathlib import Path 18 19# ESIOST 20ESIOST_VER = "1.0.0" 21 22# Offsets inside the header 23HDR_ANCHOR_OFFSET = 0x0 24HDR_FW_ENTRY_POINT_OFFSET = 0x21C 25HDR_FW_FLASH_ADDR_START_LOAD_OFFSET = 0x220 26HDR_FW_FLASH_ADDR_END_LOAD_OFFSET = 0x224 27HDR_FW_LOAD_START_ADDR_OFFSET = 0x228 28HDR_FW_LENGTH_OFFSET = 0x22C 29HDR_FW_LOAD_HASH_OFFSET = 0x480 30HDR_FW_SEG1_START_OFFSET = 0x4C0 31HDR_FW_SEG1_SIZE_OFFSET = 0x4C4 32HDR_FW_SEG2_START_OFFSET = 0x4C8 33HDR_FW_SEG2_SIZE_OFFSET = 0x4CC 34HDR_FW_SEG3_START_OFFSET = 0x4D0 35HDR_FW_SEG3_SIZE_OFFSET = 0x4D4 36HDR_FW_SEG4_START_OFFSET = 0x4D8 37HDR_FW_SEG4_SIZE_OFFSET = 0x4DC 38HDR_FW_SEG1_HASH_OFFSET = 0x500 39HDR_FW_SEG2_HASH_OFFSET = 0x540 40HDR_FW_SEG3_HASH_OFFSET = 0x580 41HDR_FW_SEG4_HASH_OFFSET = 0x5C0 42FW_IMAGE_OFFSET = 0x600 43 44ARM_FW_ENTRY_POINT_OFFSET = 0x004 45 46# Header field known values 47FW_HDR_ANCHOR = '%FiMg94@' 48FW_HDR_SEG1_START = 0x210 49FW_HDR_SEG1_SIZE = 0x2F0 50FW_HDR_SEG2_START = 0x0 51FW_HDR_SEG2_SIZE = 0x0 52FW_HDR_SEG3_START = 0x600 53FW_HDR_SEG4_START = 0x0 54FW_HDR_SEG4_SIZE = 0x0 55 56# Header fields default values. 57ADDR_16_BYTES_ALIGNED_MASK = 0x0000000f 58ADDR_4_BYTES_ALIGNED_MASK = 0x00000003 59ADDR_4K_BYTES_ALIGNED_MASK = 0x00000fff 60 61INVALID_INPUT = -1 62HEADER_SIZE = FW_IMAGE_OFFSET 63 64# Verbose related values 65NO_VERBOSE = 0 66REG_VERBOSE = 0 67 68# Success/failure codes 69EXIT_SUCCESS_STATUS = 0 70EXIT_FAILURE_STATUS = 1 71 72def _bt_mode_handler(esiost_args): 73 """creates the bootloader table using the provided arguments. 74 75 :param esiost_args: the object representing the command line arguments. 76 """ 77 78 output_file = _set_input_and_output(esiost_args) 79 _check_chip(output_file, esiost_args) 80 81 _copy_image(output_file, esiost_args) 82 _set_anchor(output_file, esiost_args) 83 _set_firmware_load_start_address(output_file, esiost_args) 84 _set_firmware_entry_point(output_file, esiost_args) 85 _set_firmware_length(output_file, esiost_args) 86 _set_firmware_load_hash(output_file, esiost_args) 87 _set_firmware_segment(output_file, esiost_args) 88 _set_firmware_segment_hash(output_file, esiost_args) 89 90 _exit_with_success() 91 92def _set_input_and_output(esiost_args): 93 """checks the input file and output and sets the output file. 94 95 checks input file existence, creates an output file according 96 to the 'output' argument. 97 98 Note: input file size has to be greater than 0, and named differently 99 from output file 100 101 :param esiost_args: the object representing the command line arguments. 102 103 :returns: output file path object, or -1 if fails 104 """ 105 input_file = esiost_args.input 106 output = esiost_args.output 107 input_file_size = 0 108 109 if not input_file: 110 exit_with_failure("Define input file, using -i flag") 111 112 input_file_path = Path(input_file) 113 114 if not input_file_path.exists(): 115 exit_with_failure(f'Cannot open {input_file}') 116 elif input_file_path.stat().st_size == 0: 117 exit_with_failure(f'BIN Input file ({input_file}) is empty') 118 else: 119 input_file_size = input_file_path.stat().st_size 120 121 if not output: 122 output_file = Path("out_" + input_file_path.name) 123 else: 124 output_file = Path(output) 125 126 if output_file.exists(): 127 if output_file.samefile(input_file_path): 128 exit_with_failure(f'Input file name {input_file} ' 129 f'should be differed from' 130 f' Output file name {output}') 131 output_file.unlink() 132 133 output_file.touch() 134 135 if esiost_args.verbose == REG_VERBOSE: 136 print(Fore.LIGHTCYAN_EX + f'\nBIN file: {input_file}, size:' 137 f' {input_file_size} bytes') 138 print(f'Output file name: {output_file.name} \n') 139 140 return output_file 141 142def _check_chip(output, esiost_args): 143 """checks if the chip entered is a legal chip, generates an error 144 and closes the application, deletes the output file if the chip name 145 is illegal. 146 147 :param output: the output file object, 148 :param esiost_args: the object representing the command line arguments. 149 """ 150 151 if esiost_args.chip_name == INVALID_INPUT: 152 message = f'Invalid chip name, ' 153 message += "should be npcm400." 154 _exit_with_failure_delete_file(output, message) 155 156def _set_anchor(output, esiost_args): 157 """writes the anchor value to the output file 158 159 :param output: the output file object. 160 :param esiost_args: the object representing the command line arguments. 161 """ 162 163 if len(FW_HDR_ANCHOR) > 8: 164 message = f'ANCHOR max support 8 bytes' 165 _exit_with_failure_delete_file(output, message) 166 167 with output.open("r+b") as output_file: 168 output_file.seek(HDR_ANCHOR_OFFSET) 169 anchor_hex = FW_HDR_ANCHOR.encode('ascii') 170 output_file.write(anchor_hex) 171 if esiost_args.verbose == REG_VERBOSE: 172 print(f'- HDR - FW Header ANCHOR - Offset ' 173 f'{HDR_ANCHOR_OFFSET} - %s' % FW_HDR_ANCHOR) 174 175 output_file.close() 176 177def _set_firmware_load_start_address(output, esiost_args): 178 """writes the fw load address to the output file 179 180 :param output: the output file object, 181 :param esiost_args: the object representing the command line arguments. 182 """ 183 input_file_path = Path(esiost_args.input) 184 185 start_ram = esiost_args.chip_ram_address 186 end_ram = start_ram + esiost_args.chip_ram_size 187 fw_load_addr = esiost_args.firmware_load_address 188 fw_length = esiost_args.firmware_length 189 fw_end_addr = fw_load_addr + fw_length 190 start_flash_addr = esiost_args.chip_flash_address + HEADER_SIZE 191 end_flash_addr = start_flash_addr + fw_length 192 193 start_ram_to_print = _hex_print_format(start_ram) 194 end_ram_to_print = _hex_print_format(end_ram) 195 fw_load_addr_to_print = _hex_print_format(fw_load_addr) 196 fw_end_addr_to_print = _hex_print_format(fw_end_addr) 197 198 if fw_length == INVALID_INPUT: 199 message = f'Cannot read firmware length' 200 _exit_with_failure_delete_file(output, message) 201 202 if fw_load_addr is INVALID_INPUT: 203 message = f'Cannot read FW Load start address' 204 _exit_with_failure_delete_file(output, message) 205 206 if fw_load_addr & ADDR_16_BYTES_ALIGNED_MASK != 0: 207 message = f'Firmware load address ({fw_load_addr_to_print}) ' \ 208 f'is not 16 bytes aligned' 209 _exit_with_failure_delete_file(output, message) 210 211 if (fw_load_addr > end_ram) or (fw_load_addr < start_ram): 212 message = f'Firmware load address ({fw_load_addr_to_print}) ' \ 213 f'should be between start ({start_ram_to_print}) '\ 214 f'and end ({end_ram_to_print}) of RAM' 215 _exit_with_failure_delete_file(output, message) 216 217 with output.open("r+b") as output_file: 218 # check fw_entry pt location in flash or not 219 with input_file_path.open("r+b") as input_file: 220 input_file.seek(ARM_FW_ENTRY_POINT_OFFSET) 221 fw_arm_entry_byte = input_file.read(4) 222 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 223 224 if fw_arm_entry_pt == 0: 225 input_file.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) 226 fw_arm_entry_byte = input_file.read(4) 227 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 228 else: 229 if fw_end_addr > end_ram: 230 message = f'Firmware end address ({fw_end_addr_to_print}) should be ' 231 message += f'less than end of RAM address ({end_ram_to_print})' 232 _exit_with_failure_delete_file(output, message) 233 234 if start_flash_addr < fw_arm_entry_pt < end_flash_addr: 235 fw_load_addr = 0x0 236 start_flash_addr = 0x0 237 end_flash_addr = 0x0 238 239 input_file.close() 240 241 # set start load flash address 242 output_file.seek(HDR_FW_FLASH_ADDR_START_LOAD_OFFSET) 243 output_file.write(start_flash_addr.to_bytes(4, "little")) 244 245 # set end load flash address 246 output_file.seek(HDR_FW_FLASH_ADDR_END_LOAD_OFFSET) 247 output_file.write(end_flash_addr.to_bytes(4, "little")) 248 249 # set load start address (RAM) 250 output_file.seek(HDR_FW_LOAD_START_ADDR_OFFSET) 251 output_file.write(fw_load_addr.to_bytes(4, "little")) 252 253 if esiost_args.verbose == REG_VERBOSE: 254 print(f'- HDR - FW load start address - Offset ' 255 f'{HDR_FW_LOAD_START_ADDR_OFFSET} - ' 256 f'{_hex_print_format(fw_load_addr)}') 257 print(f'- HDR - flash load start address - Offset ' 258 f'{HDR_FW_FLASH_ADDR_START_LOAD_OFFSET} - ' 259 f'{_hex_print_format(start_flash_addr)}') 260 print(f'- HDR - flash load end address - Offset ' 261 f'{HDR_FW_FLASH_ADDR_END_LOAD_OFFSET} - ' 262 f'{_hex_print_format(end_flash_addr)}') 263 264 output_file.close() 265 266def _set_firmware_entry_point(output, esiost_args): 267 """writes the fw entry point to the output file. 268 proportions: 269 270 :param output: the output file object, 271 :param esiost_args: the object representing the command line arguments. 272 """ 273 input_file_path = Path(esiost_args.input) 274 fw_entry_pt = esiost_args.firmware_entry_point 275 start_flash_addr = esiost_args.chip_flash_address + HEADER_SIZE 276 end_flash_addr = start_flash_addr + esiost_args.chip_flash_size 277 278 # check if fwep flag wasn't set and set it to fw load address if needed 279 if fw_entry_pt is None: 280 fw_entry_pt = esiost_args.firmware_load_address 281 282 # check fw_entry pt location in flash or not 283 with input_file_path.open("r+b") as input_file: 284 input_file.seek(ARM_FW_ENTRY_POINT_OFFSET) 285 fw_arm_entry_byte = input_file.read(4) 286 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 287 288 if fw_arm_entry_pt == 0: 289 input_file.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) 290 fw_arm_entry_byte = input_file.read(4) 291 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 292 293 if start_flash_addr < fw_arm_entry_pt < end_flash_addr: 294 fw_entry_pt = start_flash_addr 295 296 input_file.close() 297 298 with output.open("r+b") as output_file: 299 output_file.seek(HDR_FW_ENTRY_POINT_OFFSET) 300 output_file.write(fw_entry_pt.to_bytes(4, "little")) 301 output_file.close() 302 303 if esiost_args.verbose == REG_VERBOSE: 304 print(f'- HDR - FW Entry point - Offset ' 305 f'{HDR_FW_ENTRY_POINT_OFFSET} - ' 306 f'{_hex_print_format(fw_entry_pt)}') 307 308def _openssl_digest(filepath): 309 """Computes the SHA-256 digest of a file using hashlib. 310 311 :param filepath: Path to the file to digest. 312 :return: The SHA-256 digest of the file as a bytearray. 313 """ 314 sha256_hash = hashlib.sha256() 315 with open(filepath, "rb") as f: 316 # Read and update hash string value in blocks of 4K 317 for byte_block in iter(lambda: f.read(4096), b""): 318 sha256_hash.update(byte_block) 319 return bytearray(sha256_hash.digest()) 320 321def _set_firmware_length(output, esiost_args): 322 """writes the flash size value to the output file 323 Note: the firmware length value has already been checked before 324 this method 325 326 :param output: the output file object, 327 :param esiost_args: the object representing the command line arguments. 328 """ 329 330 fw_length = esiost_args.firmware_length 331 fw_length_to_print = _hex_print_format(fw_length) 332 333 with output.open("r+b") as output_file: 334 output_file.seek(HDR_FW_LENGTH_OFFSET) 335 output_file.write(fw_length.to_bytes(4, "big")) 336 if esiost_args.verbose == REG_VERBOSE: 337 print(f'- HDR - FW Length - Offset ' 338 f'{HDR_FW_LENGTH_OFFSET} - ' 339 f'{fw_length_to_print}') 340 output_file.close() 341 342def _set_firmware_load_hash(output, esiost_args): 343 """writes the load hash value to the output file 344 Note: the firmware length value has already been checked before 345 this method 346 347 :param output: the output file object, 348 :param esiost_args: the object representing the command line arguments. 349 """ 350 sha256_hash = hashlib.sha256() 351 with output.open("r+b") as f: 352 f.seek(HEADER_SIZE) 353 # Read and update hash string value in blocks of 4K 354 for byte_block in iter(lambda: f.read(4096), b""): 355 sha256_hash.update(byte_block) 356 357 hash_data = bytearray(sha256_hash.digest()) 358 359 with output.open("r+b") as output_file: 360 output_file.seek(HDR_FW_LOAD_HASH_OFFSET) 361 output_file.write(hash_data) 362 output_file.close() 363 364def _set_firmware_segment(output, esiost_args): 365 """writes the segment start and size value to the output file 366 Note: the firmware length value has already been checked before 367 this method 368 369 :param output: the output file object, 370 :param esiost_args: the object representing the command line arguments. 371 """ 372 373 fw_length = esiost_args.firmware_length 374 375 with output.open("r+b") as output_file: 376 # set segment_1 start and size 377 output_file.seek(HDR_FW_SEG1_START_OFFSET) 378 output_file.write(FW_HDR_SEG1_START.to_bytes(4, "little")) 379 output_file.seek(HDR_FW_SEG1_SIZE_OFFSET) 380 output_file.write(FW_HDR_SEG1_SIZE.to_bytes(4, "little")) 381 382 # set segment_2 start and size 383 output_file.seek(HDR_FW_SEG2_START_OFFSET) 384 output_file.write(FW_HDR_SEG2_START.to_bytes(4, "little")) 385 output_file.seek(HDR_FW_SEG2_SIZE_OFFSET) 386 output_file.write(FW_HDR_SEG2_SIZE.to_bytes(4, "little")) 387 388 # set segment_3 start and size 389 output_file.seek(HDR_FW_SEG3_START_OFFSET) 390 output_file.write(FW_HDR_SEG3_START.to_bytes(4, "little")) 391 output_file.seek(HDR_FW_SEG3_SIZE_OFFSET) 392 output_file.write(fw_length.to_bytes(4, "little")) 393 394 # set segment_4 start and size 395 output_file.seek(HDR_FW_SEG4_START_OFFSET) 396 output_file.write(FW_HDR_SEG4_START.to_bytes(4, "little")) 397 output_file.seek(HDR_FW_SEG4_SIZE_OFFSET) 398 output_file.write(FW_HDR_SEG4_SIZE.to_bytes(4, "little")) 399 400 segment1_start_to_print = _hex_print_format(FW_HDR_SEG1_START) 401 segment1_size_to_print = _hex_print_format(FW_HDR_SEG1_SIZE) 402 segment2_start_to_print = _hex_print_format(FW_HDR_SEG2_START) 403 segment2_size_to_print = _hex_print_format(FW_HDR_SEG2_SIZE) 404 segment3_start_to_print = _hex_print_format(FW_HDR_SEG3_START) 405 segment3_size_to_print = _hex_print_format(fw_length) 406 segment4_start_to_print = _hex_print_format(FW_HDR_SEG4_START) 407 segment4_size_to_print = _hex_print_format(FW_HDR_SEG4_SIZE) 408 409 if esiost_args.verbose == REG_VERBOSE: 410 print(f'- HDR - Segment1 start address - Offset ' 411 f'{HDR_FW_SEG1_START_OFFSET} - ' 412 f'{segment1_start_to_print}') 413 print(f'- HDR - Segment1 size - Offset ' 414 f'{HDR_FW_SEG1_SIZE_OFFSET} - ' 415 f'{segment1_size_to_print}') 416 print(f'- HDR - Segment2 start address - Offset ' 417 f'{HDR_FW_SEG2_START_OFFSET} - ' 418 f'{segment2_start_to_print}') 419 print(f'- HDR - Segment2 size - Offset ' 420 f'{HDR_FW_SEG2_SIZE_OFFSET} - ' 421 f'{segment2_size_to_print}') 422 print(f'- HDR - Segment3 start address - Offset ' 423 f'{HDR_FW_SEG3_START_OFFSET} - ' 424 f'{segment3_start_to_print}') 425 print(f'- HDR - Segment3 size - Offset ' 426 f'{HDR_FW_SEG3_SIZE_OFFSET} - ' 427 f'{segment3_size_to_print}') 428 print(f'- HDR - Segment4 start address - Offset ' 429 f'{HDR_FW_SEG4_START_OFFSET} - ' 430 f'{segment4_start_to_print}') 431 print(f'- HDR - Segment4 size - Offset ' 432 f'{HDR_FW_SEG4_SIZE_OFFSET} - ' 433 f'{segment4_size_to_print}') 434 435 output_file.close() 436 437def _clearup_tempfiles(output, esiost_args): 438 """clearup the tempfiles 439 440 :param output: the output file object, 441 :param esiost_args: the object representing the command line arguments. 442 """ 443 444 output_file = Path(output) 445 446 seg1_file = Path("seg1_" + output_file.name) 447 if seg1_file.exists(): 448 seg1_file.unlink() 449 450def _set_firmware_segment_hash(output, esiost_args): 451 """Writes the segment hash value to the output file. 452 Note: the firmware length value has already been checked before this method. 453 454 :param output: the output file object, 455 :param esiost_args: the object representing the command line arguments. 456 """ 457 458 # Generate segment files 459 with output.open("r+b") as output_file: 460 # seg1 461 output_file.seek(HDR_FW_SEG1_START_OFFSET) 462 seg1_start = int.from_bytes(output_file.read(4), "little") 463 output_file.seek(HDR_FW_SEG1_SIZE_OFFSET) 464 seg1_size = int.from_bytes(output_file.read(4), "little") 465 output_file.seek(seg1_start) 466 467 seg1_data = output_file.read(seg1_size) 468 469 seg1_file_path = Path("seg1_" + output_file.name) 470 with seg1_file_path.open("wb") as seg1_file: 471 seg1_file.write(seg1_data) 472 473 # set hash 474 475 # seg1 hash 476 hash_data = _openssl_digest(seg1_file_path) 477 output_file.seek(HDR_FW_SEG1_HASH_OFFSET) 478 output_file.write(hash_data) 479 480 # seg3 hash 481 sha256_hash = hashlib.sha256() 482 output_file.seek(HEADER_SIZE) 483 # Read and update hash string value in blocks of 4K 484 for byte_block in iter(lambda: output_file.read(4096), b""): 485 sha256_hash.update(byte_block) 486 487 hash_data = bytearray(sha256_hash.digest()) 488 489 output_file.seek(HDR_FW_SEG3_HASH_OFFSET) 490 output_file.write(hash_data) 491 492 _clearup_tempfiles(output, esiost_args) 493 494def _copy_image(output, esiost_args): 495 """copies the fw image from the input file to the output file 496 if firmware header offset is defined, just copies the input file to the 497 output file 498 499 :param output: the output file object, 500 :param esiost_args: the object representing the command line arguments. 501 """ 502 503 # check input file offset 504 with open(esiost_args.input, "rb") as firmware_image: 505 firmware_image.seek(ARM_FW_ENTRY_POINT_OFFSET) 506 fw_arm_entry_byte = firmware_image.read(4) 507 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 508 if fw_arm_entry_pt != 0: 509 image_offset = 0 510 input_file_size = Path(esiost_args.input).stat().st_size 511 else: 512 firmware_image.seek(ARM_FW_ENTRY_POINT_OFFSET + HEADER_SIZE) 513 fw_arm_entry_byte = firmware_image.read(4) 514 fw_arm_entry_pt = int.from_bytes(fw_arm_entry_byte, "little") 515 if fw_arm_entry_pt == 0: 516 sys.exit(EXIT_FAILURE_STATUS) 517 else: 518 image_offset = 1 519 input_file_size = Path(esiost_args.input).stat().st_size - HEADER_SIZE 520 521 firmware_image.close() 522 523 with open(esiost_args.input, "rb") as firmware_image: 524 with open(output, "r+b") as output_file: 525 if image_offset == 0: 526 output_file.seek(HEADER_SIZE) 527 for line in firmware_image: 528 output_file.write(line) 529 output_file.close() 530 firmware_image.close() 531 532 # update firmware length if needed 533 fw_length = esiost_args.firmware_length 534 if fw_length is None: 535 esiost_args.firmware_length = input_file_size 536 537def _hex_print_format(value): 538 """hex representation of an integer 539 540 :param value: an integer to be represented in hex 541 """ 542 return "0x{:08x}".format(value) 543 544def _exit_with_failure_delete_file(output, message): 545 """formatted failure message printer, prints the 546 relevant error message, deletes the output file, 547 and exits the application. 548 549 :param message: the error message to be printed 550 """ 551 output_file = Path(output) 552 if output_file.exists(): 553 output_file.unlink() 554 555 message = '\n' + message 556 message += '\n' 557 message += '******************************\n' 558 message += '*** FAILED ***\n' 559 message += '******************************\n' 560 print(Fore.RED + message) 561 562 sys.exit(EXIT_FAILURE_STATUS) 563 564def _exit_with_success(): 565 """formatted success message printer, prints the 566 success message and exits the application. 567 """ 568 message = '\n' 569 message += '******************************\n' 570 message += '*** SUCCESS ***\n' 571 message += '******************************\n' 572 print(Fore.GREEN + message) 573 574 sys.exit(EXIT_SUCCESS_STATUS) 575 576def main(): 577 """main of the application 578 """ 579 init() # colored print initialization for windows 580 581 if len(sys.argv) < 2: 582 sys.exit(EXIT_FAILURE_STATUS) 583 584 esiost_obj = EsiostArgs() 585 586 if esiost_obj.error_args: 587 for err_arg in esiost_obj.error_args: 588 message = f'unKnown flag: {err_arg}' 589 exit_with_failure(message) 590 sys.exit(EXIT_SUCCESS_STATUS) 591 592 # Start to handle booter header table 593 _bt_mode_handler(esiost_obj) 594 595if __name__ == '__main__': 596 main() 597