#!/usr/bin/env python3 # # Copyright (c) 2020 Nuvoton Technology Corporation # # SPDX-License-Identifier: Apache-2.0 # This script will append/paste specific header to tell ROM code (Booter) of # NPCX EC series how to load the firmware from flash to code ram # Usage python3 ${ZEPHYR_BASE}/scripts/ecst.py # -i in_file.bin -o out_file.bin # [-chip ] [-v|-vv] # [-nohcrc] [-nofcrc] [-ph ] # [-flashsize <1|2|4|8|16>] # [-spimaxclk <20|25|33|40|50>] # [-spireadmode ] import sys from colorama import init, Fore from ecst_args import EcstArgs, exit_with_failure from pathlib import Path # ECST Version ECST_VER = "2.0.1" # Offsets inside the header HDR_ANCHOR_OFFSET = 0 HDR_EXTENDED_ANCHOR_OFFSET = 4 HDR_SPI_MAX_CLK_OFFSET = 6 HDR_SPI_READ_MODE_OFFSET = 7 HDR_ERR_DETECTION_CONF_OFFSET = 8 HDR_FW_LOAD_START_ADDR_OFFSET = 9 HDR_FW_ENTRY_POINT_OFFSET = 13 HDR_FW_ERR_DETECT_START_ADDR_OFFSET = 17 HDR_FW_ERR_DETECT_END_ADDR_OFFSET = 21 HDR_FW_LENGTH_OFFSET = 25 HDR_FLASH_SIZE_OFFSET = 29 OTP_WRITE_PROTECT_OFFSET = 30 KEY_VALID_OFFSET = 31 FIRMWARE_VALID_OFFSET = 32 RESERVED_BYTES_OFFSET = 33 HDR_FW_HEADER_SIG_OFFSET = 56 HDR_FW_IMAGE_SIG_OFFSET = 60 FW_IMAGE_OFFSET = 64 SIGNATURE_OFFSET = 0 POINTER_OFFSET = 4 ARM_FW_ENTRY_POINT_OFFSET = 4 # Header field known values FW_HDR_ANCHOR = 0x2A3B4D5E FW_HDR_EXT_ANCHOR_ENABLE = 0xAB1E FW_HDR_EXT_ANCHOR_DISABLE = 0x54E1 FW_HDR_CRC_DISABLE = 0x00 FW_HDR_CRC_ENABLE = 0x02 FW_CRC_DISABLE = 0x00 FW_CRC_ENABLE = 0x02 HDR_PTR_SIGNATURE = 0x55AA650E BOOTLOADER_TABLE_MODE = "bt" # SPI related values SPI_MAX_CLOCK_20_MHZ_VAL = "20" SPI_MAX_CLOCK_25_MHZ_VAL = "25" SPI_MAX_CLOCK_33_MHZ_VAL = "33" SPI_MAX_CLOCK_40_MHZ_VAL = "40" SPI_MAX_CLOCK_50_MHZ_VAL = "50" SPI_MAX_CLOCK_20_MHZ = 0x00 SPI_MAX_CLOCK_25_MHZ = 0x01 SPI_MAX_CLOCK_33_MHZ = 0x02 SPI_MAX_CLOCK_40_MHZ = 0x03 SPI_MAX_CLOCK_50_MHZ = 0x04 SPI_CLOCK_RATIO_1_VAL = 1 SPI_CLOCK_RATIO_2_VAL = 2 SPI_CLOCK_RATIO_1 = 0x00 SPI_CLOCK_RATIO_2 = 0x08 SPI_NORMAL_MODE_VAL = 'normal' SPI_SINGLE_MODE_VAL = 'fast' SPI_DUAL_MODE_VAL = 'dual' SPI_QUAD_MODE_VAL = 'quad' SPI_NORMAL_MODE = 0x00 SPI_SINGLE_MODE = 0x01 SPI_DUAL_MODE = 0x03 SPI_QUAD_MODE = 0x04 # Flash related values FLASH_SIZE_1_MBYTES_VAL = "1" FLASH_SIZE_2_MBYTES_VAL = "2" FLASH_SIZE_4_MBYTES_VAL = "4" FLASH_SIZE_8_MBYTES_VAL = "8" FLASH_SIZE_16_MBYTES_VAL = "16" FLASH_SIZE_1_MBYTES = 0x01 FLASH_SIZE_2_MBYTES = 0x03 FLASH_SIZE_4_MBYTES = 0x07 FLASH_SIZE_8_MBYTES = 0x0f FLASH_SIZE_8_MBYTES = 0x0f FLASH_SIZE_16_MBYTES = 0x1f MAX_FLASH_SIZE = 0x03ffffff # Header fields default values. ADDR_16_BYTES_ALIGNED_MASK = 0x0000000f ADDR_4_BYTES_ALIGNED_MASK = 0x00000003 ADDR_4K_BYTES_ALIGNED_MASK = 0x00000fff NUM_OF_BYTES = 32 INVALID_INPUT = -1 HEADER_SIZE = 64 PAD_BYTE = b'\x00' BYTES_TO_PAD = HDR_FW_HEADER_SIG_OFFSET - RESERVED_BYTES_OFFSET # Verbose related values NO_VERBOSE = 0 REG_VERBOSE = 1 SUPER_VERBOSE = 1 # Success/failure codes EXIT_SUCCESS_STATUS = 0 EXIT_FAILURE_STATUS = 1 def _bt_mode_handler(ecst_args): """creates the bootloader table using the provided arguments. :param ecst_args: the object representing the command line arguments. """ output_file = _set_input_and_output(ecst_args) _check_chip(output_file, ecst_args) if ecst_args.paste_firmware_header != 0: _check_firmware_header_offset(output_file, ecst_args) _copy_image(output_file, ecst_args) _set_anchor(output_file, ecst_args) _set_extended_anchor(output_file, ecst_args) _set_spi_flash_maximum_clock(output_file, ecst_args) _set_spi_flash_mode(output_file, ecst_args) _set_error_detection_configuration(output_file, ecst_args) _set_firmware_load_start_address(output_file, ecst_args) _set_firmware_entry_point(output_file, ecst_args) _set_firmware_crc_start_and_size(output_file, ecst_args) _set_firmware_length(output_file, ecst_args) _set_flash_size(output_file, ecst_args) _set_reserved_bytes(output_file, ecst_args) _set_firmware_header_crc_signature(output_file, ecst_args) _set_firmware_image_crc_signature(output_file, ecst_args) _exit_with_success() def _set_input_and_output(ecst_args): """checks the input file and output and sets the output file. checks input file existence, creates an output file according to the 'output' argument. Note: input file size has to be greater than 0, and named differently from output file :param ecst_args: the object representing the command line arguments. :returns: output file path object, or -1 if fails """ input_file = ecst_args.input output = ecst_args.output input_file_size = 0 if not input_file: exit_with_failure("Define input file, using -i flag") input_file_path = Path(input_file) if not input_file_path.exists(): exit_with_failure(f'Cannot open {input_file}') elif input_file_path.stat().st_size == 0: exit_with_failure(f'BIN Input file ({input_file}) is empty') else: input_file_size = input_file_path.stat().st_size if not output: output_file = Path("out_" + input_file_path.name) else: output_file = Path(output) if output_file.exists(): if output_file.samefile(input_file_path): exit_with_failure(f'Input file name {input_file} ' f'should be differed from' f' Output file name {output}') output_file.unlink() output_file.touch() if ecst_args.verbose == REG_VERBOSE: print(Fore.LIGHTCYAN_EX + f'\nBIN file: {input_file}, size:' f' {input_file_size} bytes') print(f'Output file name: {output_file.name} \n') return output_file def _check_chip(output, ecst_args): """checks if the chip entered is a legal chip, generates an error and closes the application, deletes the output file if the chip name is illegal. :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ if ecst_args.chip_name == INVALID_INPUT: message = f'Invalid chip name, ' message += "should be npcx4m3, npcx4m8, npcx9m8, npcx9m7, npcx9m6, " \ "npcx7m7, npcx7m6, npcx7m5." _exit_with_failure_delete_file(output, message) def _set_anchor(output, ecst_args): """writes the anchor value to the output file :param output: the output file object. :param ecst_args: the object representing the command line arguments. """ with output.open("r+b") as output_file: output_file.seek(HDR_ANCHOR_OFFSET + ecst_args.paste_firmware_header) output_file.write(FW_HDR_ANCHOR.to_bytes(4, "little")) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW Header ANCHOR - Offset ' f'{HDR_ANCHOR_OFFSET} - {_hex_print_format(FW_HDR_ANCHOR)}') output_file.close() def _set_extended_anchor(output, ecst_args): """writes the extended anchor value to the output file :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ with output.open("r+b") as output_file: output_file.seek(HDR_EXTENDED_ANCHOR_OFFSET + \ ecst_args.paste_firmware_header) if ecst_args.firmware_header_crc is FW_HDR_CRC_ENABLE: output_file.write(FW_HDR_EXT_ANCHOR_ENABLE.to_bytes(2, "little")) anchor_to_print = _hex_print_format(FW_HDR_EXT_ANCHOR_ENABLE) else: output_file.write(FW_HDR_EXT_ANCHOR_DISABLE.to_bytes(2, "little")) anchor_to_print = _hex_print_format(FW_HDR_EXT_ANCHOR_DISABLE) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - Header EXTENDED ANCHOR - Offset' f' {HDR_EXTENDED_ANCHOR_OFFSET} - {anchor_to_print}') output_file.close() def _check_firmware_header_offset(output, ecst_args): """checks if the firmware header offset entered is valid. proportions: firmware header offset is a non-negative integer. firmware header offset is 16 bytes aligned firmware header offset equals/smaller than input file minus FW HEADER SIZE (64 KB) input file size is bigger than FW HEADER SIZE (64 KB) :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ input_file = Path(ecst_args.input) paste_fw_offset = ecst_args.paste_firmware_header input_file_size = input_file.stat().st_size if paste_fw_offset == INVALID_INPUT: _exit_with_failure_delete_file(output, "Cannot read paste" " firmware offset") paste_fw_offset_to_print = _hex_print_format(paste_fw_offset) if paste_fw_offset & ADDR_16_BYTES_ALIGNED_MASK != 0: message = f'Paste firmware address ({paste_fw_offset_to_print}) ' \ f'is not 16 bytes aligned' _exit_with_failure_delete_file(output, message) if input_file_size <= HEADER_SIZE: message = f' input file size ({input_file_size} bytes) ' \ f'should be bigger than fw header size ({HEADER_SIZE} bytes)' _exit_with_failure_delete_file(output, message) if input_file_size - HEADER_SIZE < paste_fw_offset: message = f'FW offset ({paste_fw_offset_to_print})should be less ' \ f'than input file size ({input_file_size}) minus fw header size' \ f' {HEADER_SIZE}' _exit_with_failure_delete_file(output, message) def _set_spi_flash_maximum_clock(output, ecst_args): """Sets the maximum allowable clock frequency (for firmware loading); also sets the ratio between the Core clock and the SPI clock for the specified mode. writes the data into the output file. the application is closed, and an error is generated if the fields are not valid. Bits 2-0 - SPI MAX Clock Bit 3: SPI Clock Ratio :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ spi_max_clock_to_write = 0 with output.open("r+b") as output_file: output_file.seek(HDR_SPI_MAX_CLK_OFFSET + ecst_args.paste_firmware_header) spi_max_clock = ecst_args.spi_flash_maximum_clock spi_clock_ratio = ecst_args.spi_flash_clock_ratio if spi_clock_ratio == SPI_CLOCK_RATIO_1_VAL: spi_clock_ratio = SPI_CLOCK_RATIO_1 elif spi_clock_ratio == SPI_CLOCK_RATIO_2_VAL: spi_clock_ratio = SPI_CLOCK_RATIO_2 elif spi_clock_ratio == INVALID_INPUT: message = f'Cannot read SPI Clock Ratio' output_file.close() _exit_with_failure_delete_file(output, message) else: message = f'Invalid SPI Core Clock Ratio (3) - it should be 1 or 2' output_file.close() _exit_with_failure_delete_file(output, message) if spi_max_clock == SPI_MAX_CLOCK_20_MHZ_VAL: spi_max_clock_to_write = SPI_MAX_CLOCK_20_MHZ | spi_clock_ratio output_file.write(spi_max_clock_to_write.to_bytes(1, "little")) elif spi_max_clock == SPI_MAX_CLOCK_25_MHZ_VAL: spi_max_clock_to_write = SPI_MAX_CLOCK_25_MHZ | spi_clock_ratio output_file.write(spi_max_clock_to_write.to_bytes(1, "little")) elif spi_max_clock == SPI_MAX_CLOCK_33_MHZ_VAL: spi_max_clock_to_write = SPI_MAX_CLOCK_33_MHZ | spi_clock_ratio output_file.write(spi_max_clock_to_write.to_bytes(1, "little")) elif spi_max_clock == SPI_MAX_CLOCK_40_MHZ_VAL: spi_max_clock_to_write = SPI_MAX_CLOCK_40_MHZ | spi_clock_ratio output_file.write(spi_max_clock_to_write.to_bytes(1, "little")) elif spi_max_clock == SPI_MAX_CLOCK_50_MHZ_VAL: spi_max_clock_to_write = SPI_MAX_CLOCK_50_MHZ | spi_clock_ratio output_file.write(spi_max_clock_to_write.to_bytes(1, "little")) elif not str(spi_max_clock).isdigit(): output_file.close() _exit_with_failure_delete_file(output, "Cannot read SPI Flash Max Clock") else: message = f'Invalid SPI Flash MAX Clock size ({spi_max_clock}) ' message += '- it should be 20, 25, 33, 40 or 50 MHz' output_file.close() _exit_with_failure_delete_file(output, message) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - SPI flash MAX Clock - Offset ' f'{HDR_SPI_MAX_CLK_OFFSET} - ' f'{_hex_print_format(spi_max_clock_to_write)}') output_file.close() def _set_spi_flash_mode(output, ecst_args): """Sets the read mode used for firmware loading and enables the Unlimited Burst functionality. writes the data into the output file. the application is closed, and an error is generated if the fields are not valid. Bits 2-0 - SPI Flash Read Mode Bit 3: Unlimited Burst Mode Note: unlimburst is not relevant for npcx5mn chips family. :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ spi_flash_read_mode = ecst_args.spi_flash_read_mode spi_unlimited_burst_mode = ecst_args.unlimited_burst_mode spi_read_mode_to_write = 0 with output.open("r+b") as output_file: output_file.seek(HDR_SPI_READ_MODE_OFFSET + ecst_args.paste_firmware_header) if spi_flash_read_mode == SPI_NORMAL_MODE_VAL: spi_read_mode_to_write = SPI_NORMAL_MODE | spi_unlimited_burst_mode output_file.write(spi_read_mode_to_write.to_bytes(1, "little")) elif spi_flash_read_mode == SPI_SINGLE_MODE_VAL: spi_read_mode_to_write = SPI_SINGLE_MODE | spi_unlimited_burst_mode output_file.write(spi_read_mode_to_write.to_bytes(1, "little")) elif spi_flash_read_mode == SPI_DUAL_MODE_VAL: spi_read_mode_to_write = SPI_DUAL_MODE | spi_unlimited_burst_mode output_file.write(spi_read_mode_to_write.to_bytes(1, "little")) elif spi_flash_read_mode == SPI_QUAD_MODE_VAL: spi_read_mode_to_write = SPI_QUAD_MODE | spi_unlimited_burst_mode output_file.write(spi_read_mode_to_write.to_bytes(1, "little")) else: message = f'Invalid SPI Flash Read Mode ({spi_flash_read_mode}),' message += 'it should be normal, fast, dual, quad' output_file.close() _exit_with_failure_delete_file(output, message) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - SPI flash Read Mode - Offset ' f'{HDR_SPI_READ_MODE_OFFSET} - ' f'{_hex_print_format(spi_read_mode_to_write)}') output_file.close() def _set_error_detection_configuration(output, ecst_args): """writes the error detection configuration value (enabled/disabled) to the output file :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ with output.open("r+b") as output_file: output_file.seek(HDR_ERR_DETECTION_CONF_OFFSET + ecst_args.paste_firmware_header) if ecst_args.firmware_crc == FW_CRC_ENABLE: err_detect_config_to_print = _hex_print_format(FW_CRC_ENABLE) output_file.write(FW_CRC_ENABLE.to_bytes(1, "little")) else: err_detect_config_to_print = _hex_print_format(FW_CRC_DISABLE) output_file.write(FW_CRC_DISABLE.to_bytes(1, "little")) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW CRC Enabled - Offset ' f'{HDR_ERR_DETECTION_CONF_OFFSET} - ' f'{err_detect_config_to_print}') output_file.close() def _set_firmware_load_start_address(output, ecst_args): """writes the fw load address to the output file :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ start_ram = ecst_args.chip_ram_address end_ram = start_ram + ecst_args.chip_ram_size fw_load_addr = ecst_args.firmware_load_address fw_length = ecst_args.firmware_length fw_end_addr = fw_load_addr + fw_length start_ram_to_print = _hex_print_format(start_ram) end_ram_to_print = _hex_print_format(end_ram) fw_load_addr_to_print = _hex_print_format(fw_load_addr) fw_length_to_print = _hex_print_format(fw_length) fw_end_addr_to_print = _hex_print_format(fw_end_addr) if fw_length == INVALID_INPUT: message = f'Cannot read firmware length' _exit_with_failure_delete_file(output, message) if fw_length & ADDR_16_BYTES_ALIGNED_MASK != 0: message = f'Firmware length ({fw_length_to_print}) ' \ f'is not 16 bytes aligned' _exit_with_failure_delete_file(output, message) if fw_load_addr is INVALID_INPUT: message = f'Cannot read FW Load start address' _exit_with_failure_delete_file(output, message) if fw_load_addr & ADDR_16_BYTES_ALIGNED_MASK != 0: message = f'Firmware load address ({fw_load_addr_to_print}) ' \ f'is not 16 bytes aligned' _exit_with_failure_delete_file(output, message) if (fw_load_addr > end_ram) or (fw_load_addr < start_ram): message = f'Firmware load address ({fw_load_addr_to_print}) ' \ f'should be between start ({start_ram_to_print}) '\ f'and end ({end_ram_to_print}) of RAM' _exit_with_failure_delete_file(output, message) if fw_end_addr > end_ram: message = f'Firmware end address ({fw_end_addr_to_print}) should be ' message += f'less than end of RAM address ({end_ram_to_print})' _exit_with_failure_delete_file(output, message) with output.open("r+b") as output_file: output_file.seek(HDR_FW_LOAD_START_ADDR_OFFSET + ecst_args.paste_firmware_header) output_file.write(ecst_args.firmware_load_address. to_bytes(4, "little")) output_file.seek(HDR_FW_LENGTH_OFFSET + ecst_args.paste_firmware_header) output_file.write(fw_length.to_bytes(4, "little")) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW load start address - Offset ' f'{HDR_FW_LOAD_START_ADDR_OFFSET} - ' f'{fw_load_addr_to_print}') output_file.close() def _set_firmware_entry_point(output, ecst_args): """writes the fw entry point to the output file. proportions: :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ entry_pt_none = False input_file_path = Path(ecst_args.input) fw_entry_pt = ecst_args.firmware_entry_point fw_use_arm_reset = ecst_args.use_arm_reset fw_length = ecst_args.firmware_length fw_load_addr = ecst_args.firmware_load_address fw_end_addr = fw_load_addr + fw_length # check if fwep flag wasn't set and set it to fw load address if needed if fw_entry_pt is None: entry_pt_none = True fw_entry_pt = ecst_args.firmware_load_address if not entry_pt_none and fw_use_arm_reset: message = f'-usearmrst not allowed, FW entry point already set using '\ f'-fwep !' _exit_with_failure_delete_file(output, message) with input_file_path.open("r+b") as input_file: with output.open("r+b") as output_file: if fw_use_arm_reset: input_file.seek(ARM_FW_ENTRY_POINT_OFFSET + ecst_args.paste_firmware_header) fw_entry_byte = input_file.read(4) fw_entry_pt = int.from_bytes(fw_entry_byte, "little") if fw_entry_pt < fw_load_addr or fw_entry_pt > fw_end_addr: output_file.close() input_file.close() message = f'Firmware entry point ' \ f'({_hex_print_format(fw_entry_pt)}) ' \ f'should be between the FW load address ' \ f'({_hex_print_format(fw_load_addr)})' \ f' and FW end address ({_hex_print_format(fw_end_addr)})' _exit_with_failure_delete_file(output, message) output_file.seek(HDR_FW_ENTRY_POINT_OFFSET + ecst_args.paste_firmware_header) output_file.write(fw_entry_pt.to_bytes(4, "little")) output_file.close() input_file.close() if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW Entry point - Offset ' f'{HDR_FW_ENTRY_POINT_OFFSET} - ' f'{_hex_print_format(fw_entry_pt)}') def _set_firmware_crc_start_and_size(output, ecst_args): """writes the fw crc start address and the crc size to the output file. proportions: --crc start address should be 4 byte aligned, bigger than crc end address --crc size should be 4 byte aligned, and be set to firmware length minus crc start offset by default --crc end address is crc start address + crc size bytes the application is closed, and an error is generated if the mentioned fields not comply with the proportions. :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ fw_crc_size = ecst_args.firmware_crc_size fw_crc_start = ecst_args.firmware_crc_start if fw_crc_size is None: fw_crc_end = ecst_args.firmware_length - 1 # default value for crc size fw_crc_size = ecst_args.firmware_length - fw_crc_start ecst_args.firmware_crc_size = fw_crc_size else: fw_crc_end = fw_crc_start + fw_crc_size - 1 fw_crc_start_to_print = _hex_print_format(fw_crc_start) fw_crc_end_to_print = _hex_print_format(fw_crc_end) fw_length_to_print = _hex_print_format(ecst_args.firmware_length) fw_crc_size_to_print = _hex_print_format(fw_crc_size) if fw_crc_start & ADDR_4_BYTES_ALIGNED_MASK != 0: message = f'Firmware crc offset address ' \ f'({fw_crc_start_to_print}) is not 4 bytes aligned' _exit_with_failure_delete_file(output, message) if fw_crc_start > fw_crc_end: message = f'CRC start address ({fw_crc_start_to_print}) should' \ f' be less or equal to CRC end address ({fw_crc_end_to_print}) \n' _exit_with_failure_delete_file(output, message) if fw_crc_size & ADDR_4_BYTES_ALIGNED_MASK != 0: message = f'Firmware crc size ({fw_crc_size_to_print}) ' \ f'is not 4 bytes aligned' _exit_with_failure_delete_file(output, message) if fw_crc_end > ecst_args.firmware_length - 1: message = f'CRC end address ({fw_crc_end_to_print}) should be less' \ f' than the FW length ({fw_length_to_print}) \n' _exit_with_failure_delete_file(output, message) with output.open("r+b") as output_file: output_file.seek(HDR_FW_ERR_DETECT_START_ADDR_OFFSET + ecst_args.paste_firmware_header) output_file.write(fw_crc_start.to_bytes(4, "little")) output_file.seek(HDR_FW_ERR_DETECT_END_ADDR_OFFSET + ecst_args.paste_firmware_header) output_file.write(fw_crc_end.to_bytes(4, "little")) output_file.close() if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW CRC Start - Offset ' f'{HDR_FW_ERR_DETECT_START_ADDR_OFFSET} - ' f'{fw_crc_start_to_print}') print(f'- HDR - FW CRC End - Offset ' f'{HDR_FW_ERR_DETECT_END_ADDR_OFFSET} - ' f'{fw_crc_end_to_print}') def _set_firmware_length(output, ecst_args): """writes the flash size value to the output file Note: the firmware length value has already been checked before this method :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ fw_length = ecst_args.firmware_length fw_length_to_print = _hex_print_format(fw_length) with output.open("r+b") as output_file: output_file.seek(HDR_FW_LENGTH_OFFSET + ecst_args.paste_firmware_header) output_file.write(fw_length.to_bytes(4, "little")) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - FW Length - Offset ' f'{HDR_FW_LENGTH_OFFSET} - ' f'{fw_length_to_print}') output_file.close() def _set_flash_size(output, ecst_args): """writes the flash size value to the output file valid values are 1,2,4,8 or 16 (default is 16). the application is closed and the output file is deleted if the flash size is invalid :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ flash_size_to_print = "" with output.open("r+b") as output_file: output_file.seek(HDR_FLASH_SIZE_OFFSET + ecst_args.paste_firmware_header) flash_size = ecst_args.flash_size if flash_size == FLASH_SIZE_1_MBYTES_VAL: output_file.write(FLASH_SIZE_1_MBYTES.to_bytes(4, "little")) flash_size_to_print = _hex_print_format(FLASH_SIZE_1_MBYTES) elif flash_size == FLASH_SIZE_2_MBYTES_VAL: output_file.write(FLASH_SIZE_2_MBYTES.to_bytes(4, "little")) flash_size_to_print = _hex_print_format(FLASH_SIZE_2_MBYTES) elif flash_size == FLASH_SIZE_4_MBYTES_VAL: output_file.write(FLASH_SIZE_4_MBYTES.to_bytes(4, "little")) flash_size_to_print = _hex_print_format(FLASH_SIZE_4_MBYTES) elif flash_size == FLASH_SIZE_8_MBYTES_VAL: output_file.write(FLASH_SIZE_8_MBYTES.to_bytes(4, "little")) flash_size_to_print = _hex_print_format(FLASH_SIZE_8_MBYTES) elif flash_size == FLASH_SIZE_16_MBYTES_VAL: output_file.write(FLASH_SIZE_16_MBYTES.to_bytes(4, "little")) flash_size_to_print = _hex_print_format(FLASH_SIZE_16_MBYTES) elif not flash_size.isdigit(): output_file.close() _exit_with_failure_delete_file(output, "Cannot read Flash size") else: message = f'Invalid flash size ({flash_size} MBytes), ' \ f' it should be 0.5, 1, 2, 4, 8, 16 MBytes \n' \ f' please note - for 0.5 MBytes flash, enter \'1\' ' output_file.close() _exit_with_failure_delete_file(output, message) if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - Flash size - Offset ' f'{HDR_FLASH_SIZE_OFFSET} - ' f' {flash_size_to_print}') output_file.close() def _set_reserved_bytes(output, ecst_args): """fills the reserved part of the image at the relevant offset with the PAD_BYTE :param output: the output file object :param ecst_args: the object representing the command line arguments. """ with output.open("r+b") as output_file: for i in range(BYTES_TO_PAD): output_file.seek(RESERVED_BYTES_OFFSET + ecst_args.paste_firmware_header + i) output_file.write(PAD_BYTE) output_file.close() def _set_firmware_header_crc_signature(output, ecst_args): """writes the firmware header crc signature (4 bytes) to the output file :param output: the output file object :param ecst_args: the object representing the command line arguments. """ crc_to_print = _hex_print_format(0) # calculating crc only if the header crc check is enabled if ecst_args.firmware_header_crc != FW_HDR_CRC_DISABLE: with output.open("r+b") as output_file: crc_calc = 0xffffffff table = _create_table() for i in range(HDR_FW_HEADER_SIG_OFFSET): output_file.seek(ecst_args.paste_firmware_header + i) current = output_file.read(1) crc_calc = _crc_update( int.from_bytes(current, "little"), crc_calc, table) crc = _finalize_crc(crc_calc) crc_to_write = crc.to_bytes(4, "little") crc_to_print = _hex_print_format(crc) output_file.seek(ecst_args.paste_firmware_header + HDR_FW_HEADER_SIG_OFFSET) output_file.write(crc_to_write) output_file.close() if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - Header CRC - Offset ' f'{HDR_FW_HEADER_SIG_OFFSET} - ' f' {crc_to_print}') def _set_firmware_image_crc_signature(output, ecst_args): """writes the firmware image crc signature (4 bytes) to the output file. :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ # calculating crc only if the image crc check is enabled crc_to_print = _hex_print_format(0) if ecst_args.firmware_crc != FW_CRC_DISABLE: with output.open("r+b") as output_file: crc_calc = 0xffffffff table = _create_table() output_file.seek(FW_IMAGE_OFFSET + ecst_args.paste_firmware_header + ecst_args.firmware_crc_start) for _ in range(ecst_args.firmware_crc_size): current = output_file.read(1) crc_calc = _crc_update(int.from_bytes(current, "little"), \ crc_calc, table) crc = _finalize_crc(crc_calc) crc_to_write = crc.to_bytes(4, "little") crc_to_print = _hex_print_format(crc) output_file.seek(HDR_FW_IMAGE_SIG_OFFSET + ecst_args.paste_firmware_header) output_file.write(crc_to_write) output_file.close() if ecst_args.verbose == REG_VERBOSE: print(f'- HDR - Header CRC - Offset ' f'{HDR_FW_IMAGE_SIG_OFFSET} - ' f' {crc_to_print}') def _copy_image(output, ecst_args): """copies the fw image from the input file to the output file if firmware header offset is defined, just copies the input file to the output file :param output: the output file object, :param ecst_args: the object representing the command line arguments. """ with open(ecst_args.input, "rb") as firmware_image: with open(output, "r+b") as output_file: if ecst_args.paste_firmware_header == 0: output_file.seek(FW_IMAGE_OFFSET) else: output_file.seek(0) for line in firmware_image: output_file.write(line) output_file.close() firmware_image.close() # pad fw image to be 16 byte aligned if needed input_file_size = Path(ecst_args.input).stat().st_size bytes_to_pad_num = abs((16 - input_file_size) % 16) with open(output, "r+b") as output_file: i = bytes_to_pad_num while i != 0: output_file.seek(0, 2) # seek end of file output_file.write(PAD_BYTE) i -= 1 output_file.close() # update firmware length if needed fw_length = ecst_args.firmware_length if fw_length is None: if ecst_args.paste_firmware_header == 0: ecst_args.firmware_length = input_file_size + bytes_to_pad_num else: ecst_args.firmware_length = input_file_size - HEADER_SIZE - \ ecst_args.paste_firmware_header def _merge_file_with_bt_header(ecst_args): """Merge the BT with the BH according to the bhoffset and pointer flags :param ecst_args: the object representing the command line arguments. """ bh_index = ecst_args.bh_offset file_name_to_print = "" if not ecst_args.input: exit_with_failure('No input BIN file selected for' 'Bootloader header file.') else: input_file = Path(ecst_args.input) if not input_file.exists(): exit_with_failure(f'Cannot open {ecst_args.input}') if input_file.stat().st_size < bh_index: message = f'Bootloader header offset ({bh_index} bytes) should be ' message += f'less than file size ' \ f'({input_file.stat().st_size } bytes)' exit_with_failure(message) # create a file if the output parameter is not None, # otherwise write to the input file if ecst_args.output is not None: output_file = Path(ecst_args.output) output_file.touch() with input_file.open("r+b") as firmware_image: with output_file.open("r+b") as output_file: file_name_to_print = output_file.name for line in firmware_image: output_file.write(line) output_file.seek(bh_index) output_file.write(HDR_PTR_SIGNATURE.to_bytes(4, "little")) output_file.seek(bh_index + 4) output_file.write(ecst_args.pointer.to_bytes(4, "little")) output_file.close() firmware_image.close() else: with input_file.open("r+b") as file_to_merge: file_name_to_print = file_to_merge.name file_to_merge.seek(bh_index + SIGNATURE_OFFSET) file_to_merge.write(HDR_PTR_SIGNATURE.to_bytes(4, "little")) file_to_merge.seek(bh_index + POINTER_OFFSET) file_to_merge.write(ecst_args.pointer.to_bytes(4, "little")) file_to_merge.close() if ecst_args.verbose == REG_VERBOSE: print(Fore.LIGHTCYAN_EX + f'BootLoader Header file:' f' {file_name_to_print}') print(f'Offset: {_hex_print_format(ecst_args.bh_offset)},' f' Signature: {_hex_print_format(HDR_PTR_SIGNATURE)},' f' Pointer: {_hex_print_format(ecst_args.pointer)}') def _create_bt_header(ecst_args): """create bootloader table header, consist of 4 bytes signature and 4 bytes pointer :param ecst_args: the object representing the command line arguments. """ if not ecst_args.output: exit_with_failure("No output file selected for " "Bootloader header file.") else: output_file = Path(ecst_args.output) if not output_file.exists(): output_file.touch() with open(output_file, "r+b") as boot_loader_header_file: boot_loader_header_file.seek(SIGNATURE_OFFSET) boot_loader_header_file.write(HDR_PTR_SIGNATURE.to_bytes(4, \ "little")) boot_loader_header_file.seek(POINTER_OFFSET) boot_loader_header_file.write(ecst_args.pointer.to_bytes(4, \ "little")) boot_loader_header_file.close() if ecst_args.verbose == REG_VERBOSE: print(Fore.LIGHTCYAN_EX + f'BootLoader Header file:' f' {output_file.name}') print(f'Signature: {_hex_print_format(HDR_PTR_SIGNATURE)}, ' f'Pointer: {_hex_print_format(ecst_args.pointer)}') def _create_table(): """helper for crc calculation""" table = [] for i in range(256): k = i for _ in range(8): if k & 1: k = (k >> 1) ^ 0xEDB88320 else: k >>= 1 table.append(k) return table def _crc_update(cur, crc, table): """helper for crc calculation :param cur :param crc :param table """ l_crc = 0x000000ff & cur tmp = crc ^ l_crc crc = (crc >> 8) ^ table[(tmp & 0xff)] return crc def _finalize_crc(crc): """helper for crc calculation :param crc """ final_crc = 0 for j in range(NUM_OF_BYTES): current_bit = crc & (1 << j) current_bit = current_bit >> j final_crc |= current_bit << (NUM_OF_BYTES - 1) - j return final_crc def _hex_print_format(value): """hex representation of an integer :param value: an integer to be represented in hex """ return "0x{:08x}".format(value) def _exit_with_failure_delete_file(output, message): """formatted failure message printer, prints the relevant error message, deletes the output file, and exits the application. :param message: the error message to be printed """ output_file = Path(output) if output_file.exists(): output_file.unlink() message = '\n' + message message += '\n' message += '******************************\n' message += '*** FAILED ***\n' message += '******************************\n' print(Fore.RED + message) sys.exit(EXIT_FAILURE_STATUS) def _exit_with_success(): """formatted success message printer, prints the success message and exits the application. """ message = '\n' message += '******************************\n' message += '*** SUCCESS ***\n' message += '******************************\n' print(Fore.GREEN + message) sys.exit(EXIT_SUCCESS_STATUS) def main(): """main of the application """ init() # colored print initialization for windows if len(sys.argv) < 2: sys.exit(EXIT_FAILURE_STATUS) ecst_obj = EcstArgs() if ecst_obj.error_args: for err_arg in ecst_obj.error_args: message = f'unKnown flag: {err_arg}' exit_with_failure(message) sys.exit(EXIT_SUCCESS_STATUS) # Start to handle booter header table _bt_mode_handler(ecst_obj) if __name__ == '__main__': main()