1#!/usr/bin/env python3
2#
3# Copyright (c) 2020 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# NPCX EC series how to load the firmware from flash to code ram
9# Usage python3 ${ZEPHYR_BASE}/scripts/ecst.py
10#                    -i in_file.bin -o out_file.bin
11#                    [-chip <name>] [-v|-vv]
12#                    [-nohcrc] [-nofcrc] [-ph <offset>]
13#                    [-flashsize <1|2|4|8|16>]
14#                    [-spimaxclk <20|25|33|40|50>]
15#                    [-spireadmode <normal|fast|dual|quad>]
16
17import sys
18from colorama import init, Fore
19from ecst_args import EcstArgs, exit_with_failure
20from pathlib import Path
21
22# ECST Version
23ECST_VER = "2.0.1"
24
25# Offsets inside the header
26HDR_ANCHOR_OFFSET = 0
27HDR_EXTENDED_ANCHOR_OFFSET = 4
28HDR_SPI_MAX_CLK_OFFSET = 6
29HDR_SPI_READ_MODE_OFFSET = 7
30HDR_ERR_DETECTION_CONF_OFFSET = 8
31HDR_FW_LOAD_START_ADDR_OFFSET = 9
32HDR_FW_ENTRY_POINT_OFFSET = 13
33HDR_FW_ERR_DETECT_START_ADDR_OFFSET = 17
34HDR_FW_ERR_DETECT_END_ADDR_OFFSET = 21
35HDR_FW_LENGTH_OFFSET = 25
36HDR_FLASH_SIZE_OFFSET = 29
37OTP_WRITE_PROTECT_OFFSET = 30
38KEY_VALID_OFFSET = 31
39FIRMWARE_VALID_OFFSET = 32
40RESERVED_BYTES_OFFSET = 33
41HDR_FW_HEADER_SIG_OFFSET = 56
42HDR_FW_IMAGE_SIG_OFFSET = 60
43FW_IMAGE_OFFSET = 64
44
45SIGNATURE_OFFSET = 0
46POINTER_OFFSET = 4
47ARM_FW_ENTRY_POINT_OFFSET = 4
48
49# Header field known values
50FW_HDR_ANCHOR = 0x2A3B4D5E
51FW_HDR_EXT_ANCHOR_ENABLE = 0xAB1E
52FW_HDR_EXT_ANCHOR_DISABLE = 0x54E1
53FW_HDR_CRC_DISABLE = 0x00
54FW_HDR_CRC_ENABLE = 0x02
55FW_CRC_DISABLE = 0x00
56FW_CRC_ENABLE = 0x02
57HDR_PTR_SIGNATURE = 0x55AA650E
58
59BOOTLOADER_TABLE_MODE = "bt"
60
61# SPI related values
62SPI_MAX_CLOCK_20_MHZ_VAL = "20"
63SPI_MAX_CLOCK_25_MHZ_VAL = "25"
64SPI_MAX_CLOCK_33_MHZ_VAL = "33"
65SPI_MAX_CLOCK_40_MHZ_VAL = "40"
66SPI_MAX_CLOCK_50_MHZ_VAL = "50"
67
68SPI_MAX_CLOCK_20_MHZ = 0x00
69SPI_MAX_CLOCK_25_MHZ = 0x01
70SPI_MAX_CLOCK_33_MHZ = 0x02
71SPI_MAX_CLOCK_40_MHZ = 0x03
72SPI_MAX_CLOCK_50_MHZ = 0x04
73
74SPI_CLOCK_RATIO_1_VAL = 1
75SPI_CLOCK_RATIO_2_VAL = 2
76
77SPI_CLOCK_RATIO_1 = 0x00
78SPI_CLOCK_RATIO_2 = 0x08
79
80SPI_NORMAL_MODE_VAL = 'normal'
81SPI_SINGLE_MODE_VAL = 'fast'
82SPI_DUAL_MODE_VAL = 'dual'
83SPI_QUAD_MODE_VAL = 'quad'
84
85SPI_NORMAL_MODE = 0x00
86SPI_SINGLE_MODE = 0x01
87SPI_DUAL_MODE = 0x03
88SPI_QUAD_MODE = 0x04
89
90# Flash related values
91FLASH_SIZE_1_MBYTES_VAL = "1"
92FLASH_SIZE_2_MBYTES_VAL = "2"
93FLASH_SIZE_4_MBYTES_VAL = "4"
94FLASH_SIZE_8_MBYTES_VAL = "8"
95FLASH_SIZE_16_MBYTES_VAL = "16"
96
97FLASH_SIZE_1_MBYTES = 0x01
98FLASH_SIZE_2_MBYTES = 0x03
99FLASH_SIZE_4_MBYTES = 0x07
100FLASH_SIZE_8_MBYTES = 0x0f
101FLASH_SIZE_8_MBYTES = 0x0f
102FLASH_SIZE_16_MBYTES = 0x1f
103
104MAX_FLASH_SIZE = 0x03ffffff
105
106# Header fields default values.
107ADDR_16_BYTES_ALIGNED_MASK = 0x0000000f
108ADDR_4_BYTES_ALIGNED_MASK = 0x00000003
109ADDR_4K_BYTES_ALIGNED_MASK = 0x00000fff
110
111NUM_OF_BYTES = 32
112INVALID_INPUT = -1
113HEADER_SIZE = 64
114PAD_BYTE = b'\x00'
115BYTES_TO_PAD = HDR_FW_HEADER_SIG_OFFSET - RESERVED_BYTES_OFFSET
116
117# Verbose related values
118NO_VERBOSE = 0
119REG_VERBOSE = 1
120SUPER_VERBOSE = 1
121
122# Success/failure codes
123EXIT_SUCCESS_STATUS = 0
124EXIT_FAILURE_STATUS = 1
125
126def _bt_mode_handler(ecst_args):
127    """creates the bootloader table using the provided arguments.
128
129    :param ecst_args: the object representing the command line arguments.
130    """
131
132    output_file = _set_input_and_output(ecst_args)
133    _check_chip(output_file, ecst_args)
134
135    if ecst_args.paste_firmware_header != 0:
136        _check_firmware_header_offset(output_file, ecst_args)
137
138    _copy_image(output_file, ecst_args)
139    _set_anchor(output_file, ecst_args)
140    _set_extended_anchor(output_file, ecst_args)
141    _set_spi_flash_maximum_clock(output_file, ecst_args)
142    _set_spi_flash_mode(output_file, ecst_args)
143    _set_error_detection_configuration(output_file, ecst_args)
144    _set_firmware_load_start_address(output_file, ecst_args)
145    _set_firmware_entry_point(output_file, ecst_args)
146    _set_firmware_crc_start_and_size(output_file, ecst_args)
147    _set_firmware_length(output_file, ecst_args)
148    _set_flash_size(output_file, ecst_args)
149    _set_reserved_bytes(output_file, ecst_args)
150    _set_firmware_header_crc_signature(output_file, ecst_args)
151    _set_firmware_image_crc_signature(output_file, ecst_args)
152
153    _exit_with_success()
154
155def _set_input_and_output(ecst_args):
156    """checks the input file and output and sets the output file.
157
158    checks input file existence, creates an output file according
159    to the 'output' argument.
160
161    Note: input file size has to be greater than 0, and named differently
162    from output file
163
164    :param ecst_args: the object representing the command line arguments.
165
166    :returns: output file path object, or -1 if fails
167    """
168    input_file = ecst_args.input
169    output = ecst_args.output
170    input_file_size = 0
171
172    if not input_file:
173        exit_with_failure("Define input file, using -i flag")
174
175    input_file_path = Path(input_file)
176
177    if not input_file_path.exists():
178        exit_with_failure(f'Cannot open {input_file}')
179    elif input_file_path.stat().st_size == 0:
180        exit_with_failure(f'BIN Input file ({input_file}) is empty')
181    else:
182        input_file_size = input_file_path.stat().st_size
183
184    if not output:
185        output_file = Path("out_" + input_file_path.name)
186    else:
187        output_file = Path(output)
188
189    if output_file.exists():
190        if output_file.samefile(input_file_path):
191            exit_with_failure(f'Input file name {input_file} '
192                              f'should be differed from'
193                              f' Output file name {output}')
194        output_file.unlink()
195
196    output_file.touch()
197
198    if ecst_args.verbose == REG_VERBOSE:
199        print(Fore.LIGHTCYAN_EX + f'\nBIN file: {input_file}, size:'
200              f' {input_file_size} bytes')
201        print(f'Output file name: {output_file.name} \n')
202
203    return output_file
204
205def _check_chip(output, ecst_args):
206    """checks if the chip entered is a legal chip, generates an error
207    and closes the application, deletes the output file if the chip name
208    is illegal.
209
210    :param output: the output file object,
211    :param ecst_args: the object representing the command line arguments.
212    """
213
214    if ecst_args.chip_name == INVALID_INPUT:
215        message = f'Invalid chip name, '
216        message += "should be npcx9m8, npcx9m7, npcx9m6, npcx7m7," \
217                   " npcx7m6, npcx7m5, npcx5m5 or npcx5m6."
218        _exit_with_failure_delete_file(output, message)
219
220def _set_anchor(output, ecst_args):
221    """writes the anchor value to the output file
222
223    :param output: the output file object.
224    :param ecst_args: the object representing the command line arguments.
225    """
226
227    with output.open("r+b") as output_file:
228        output_file.seek(HDR_ANCHOR_OFFSET + ecst_args.paste_firmware_header)
229        output_file.write(FW_HDR_ANCHOR.to_bytes(4, "little"))
230        if ecst_args.verbose == REG_VERBOSE:
231            print(f'- HDR - FW Header ANCHOR                 - Offset '
232                  f'{HDR_ANCHOR_OFFSET}  -  {_hex_print_format(FW_HDR_ANCHOR)}')
233
234    output_file.close()
235
236def _set_extended_anchor(output, ecst_args):
237    """writes the extended anchor value to the output file
238
239    :param output: the output file object,
240    :param ecst_args: the object representing the command line arguments.
241    """
242
243    with output.open("r+b") as output_file:
244        output_file.seek(HDR_EXTENDED_ANCHOR_OFFSET + \
245        ecst_args.paste_firmware_header)
246        if ecst_args.firmware_header_crc is FW_HDR_CRC_ENABLE:
247            output_file.write(FW_HDR_EXT_ANCHOR_ENABLE.to_bytes(2, "little"))
248            anchor_to_print = _hex_print_format(FW_HDR_EXT_ANCHOR_ENABLE)
249        else:
250            output_file.write(FW_HDR_EXT_ANCHOR_DISABLE.to_bytes(2, "little"))
251            anchor_to_print = _hex_print_format(FW_HDR_EXT_ANCHOR_DISABLE)
252
253        if ecst_args.verbose == REG_VERBOSE:
254            print(f'- HDR - Header EXTENDED ANCHOR           - Offset'
255                  f' {HDR_EXTENDED_ANCHOR_OFFSET}  -  {anchor_to_print}')
256        output_file.close()
257
258
259def _check_firmware_header_offset(output, ecst_args):
260    """checks if the firmware header offset entered is valid.
261    proportions:
262
263    firmware header offset is a non-negative integer.
264    firmware header offset is 16 bytes aligned
265    firmware header offset equals/smaller than input file minus
266    FW HEADER SIZE (64 KB)
267    input file size is bigger than FW HEADER SIZE (64 KB)
268
269    :param output: the output file object,
270    :param ecst_args: the object representing the command line arguments.
271    """
272
273    input_file = Path(ecst_args.input)
274    paste_fw_offset = ecst_args.paste_firmware_header
275    input_file_size = input_file.stat().st_size
276
277    if paste_fw_offset == INVALID_INPUT:
278        _exit_with_failure_delete_file(output, "Cannot read paste"
279                                               " firmware offset")
280
281    paste_fw_offset_to_print = _hex_print_format(paste_fw_offset)
282
283    if paste_fw_offset & ADDR_16_BYTES_ALIGNED_MASK != 0:
284        message = f'Paste firmware address ({paste_fw_offset_to_print}) ' \
285            f'is not 16 bytes aligned'
286        _exit_with_failure_delete_file(output, message)
287
288    if input_file_size <= HEADER_SIZE:
289        message = f' input file size ({input_file_size} bytes) ' \
290            f'should be bigger than fw header size ({HEADER_SIZE} bytes)'
291        _exit_with_failure_delete_file(output, message)
292
293    if input_file_size - HEADER_SIZE < paste_fw_offset:
294        message = f'FW offset ({paste_fw_offset_to_print})should be less ' \
295            f'than input file size ({input_file_size}) minus fw header size' \
296            f' {HEADER_SIZE}'
297        _exit_with_failure_delete_file(output, message)
298
299def _set_spi_flash_maximum_clock(output, ecst_args):
300    """Sets the maximum allowable clock frequency (for firmware loading);
301    also sets the ratio between the Core clock
302    and the SPI clock for the specified mode.
303    writes the data into the output file.
304    the application is closed, and an error is generated
305    if the fields are not valid.
306
307    Bits 2-0 - SPI MAX Clock
308    Bit 3: SPI Clock Ratio
309
310    :param output: the output file object,
311    :param ecst_args: the object representing the command line arguments.
312    """
313    spi_max_clock_to_write = 0
314
315    with output.open("r+b") as output_file:
316        output_file.seek(HDR_SPI_MAX_CLK_OFFSET +
317                         ecst_args.paste_firmware_header)
318        spi_max_clock = ecst_args.spi_flash_maximum_clock
319        spi_clock_ratio = ecst_args.spi_flash_clock_ratio
320
321        if spi_clock_ratio == SPI_CLOCK_RATIO_1_VAL:
322            spi_clock_ratio = SPI_CLOCK_RATIO_1
323        elif spi_clock_ratio == SPI_CLOCK_RATIO_2_VAL:
324            spi_clock_ratio = SPI_CLOCK_RATIO_2
325        elif spi_clock_ratio == INVALID_INPUT:
326            message = f'Cannot read SPI Clock Ratio'
327            output_file.close()
328            _exit_with_failure_delete_file(output, message)
329        else:
330            message = f'Invalid SPI Core Clock Ratio (3) - it should be 1 or 2'
331            output_file.close()
332            _exit_with_failure_delete_file(output, message)
333
334        if spi_max_clock == SPI_MAX_CLOCK_20_MHZ_VAL:
335            spi_max_clock_to_write = SPI_MAX_CLOCK_20_MHZ | spi_clock_ratio
336            output_file.write(spi_max_clock_to_write.to_bytes(1, "little"))
337
338        elif spi_max_clock == SPI_MAX_CLOCK_25_MHZ_VAL:
339            spi_max_clock_to_write = SPI_MAX_CLOCK_25_MHZ | spi_clock_ratio
340            output_file.write(spi_max_clock_to_write.to_bytes(1, "little"))
341
342        elif spi_max_clock == SPI_MAX_CLOCK_33_MHZ_VAL:
343            spi_max_clock_to_write = SPI_MAX_CLOCK_33_MHZ | spi_clock_ratio
344            output_file.write(spi_max_clock_to_write.to_bytes(1, "little"))
345
346        elif spi_max_clock == SPI_MAX_CLOCK_40_MHZ_VAL:
347            spi_max_clock_to_write = SPI_MAX_CLOCK_40_MHZ | spi_clock_ratio
348            output_file.write(spi_max_clock_to_write.to_bytes(1, "little"))
349
350        elif spi_max_clock == SPI_MAX_CLOCK_50_MHZ_VAL:
351            spi_max_clock_to_write = SPI_MAX_CLOCK_50_MHZ | spi_clock_ratio
352            output_file.write(spi_max_clock_to_write.to_bytes(1, "little"))
353
354        elif not str(spi_max_clock).isdigit():
355            output_file.close()
356            _exit_with_failure_delete_file(output,
357                                           "Cannot read SPI Flash Max Clock")
358        else:
359            message = f'Invalid SPI Flash MAX Clock size ({spi_max_clock}) '
360            message += '- it should be 20, 25, 33, 40 or 50 MHz'
361            output_file.close()
362            _exit_with_failure_delete_file(output, message)
363
364        if ecst_args.verbose == REG_VERBOSE:
365            print(f'- HDR - SPI flash MAX Clock              - Offset '
366                  f'{HDR_SPI_MAX_CLK_OFFSET}  -  '
367                  f'{_hex_print_format(spi_max_clock_to_write)}')
368        output_file.close()
369
370def _set_spi_flash_mode(output, ecst_args):
371    """Sets the read mode used for firmware loading and enables
372    the Unlimited Burst functionality.
373    writes the data into the output file.
374    the application is closed, and an error is generated
375    if the fields are not valid.
376
377    Bits 2-0 - SPI Flash Read Mode
378    Bit 3: Unlimited Burst Mode
379
380    Note: unlimburst is not relevant for npcx5mn chips family.
381
382    :param output: the output file object,
383    :param ecst_args: the object representing the command line arguments.
384    """
385
386    spi_flash_read_mode = ecst_args.spi_flash_read_mode
387    spi_unlimited_burst_mode = ecst_args.unlimited_burst_mode
388    spi_read_mode_to_write = 0
389
390    with output.open("r+b") as output_file:
391        output_file.seek(HDR_SPI_READ_MODE_OFFSET +
392                         ecst_args.paste_firmware_header)
393        if spi_flash_read_mode == SPI_NORMAL_MODE_VAL:
394            spi_read_mode_to_write = SPI_NORMAL_MODE | spi_unlimited_burst_mode
395            output_file.write(spi_read_mode_to_write.to_bytes(1, "little"))
396        elif spi_flash_read_mode == SPI_SINGLE_MODE_VAL:
397            spi_read_mode_to_write = SPI_SINGLE_MODE | spi_unlimited_burst_mode
398            output_file.write(spi_read_mode_to_write.to_bytes(1, "little"))
399        elif spi_flash_read_mode == SPI_DUAL_MODE_VAL:
400            spi_read_mode_to_write = SPI_DUAL_MODE | spi_unlimited_burst_mode
401            output_file.write(spi_read_mode_to_write.to_bytes(1, "little"))
402        elif spi_flash_read_mode == SPI_QUAD_MODE_VAL:
403            spi_read_mode_to_write = SPI_QUAD_MODE | spi_unlimited_burst_mode
404            output_file.write(spi_read_mode_to_write.to_bytes(1, "little"))
405        else:
406            message = f'Invalid SPI Flash Read Mode ({spi_flash_read_mode}),'
407            message += 'it should be normal, fast, dual, quad'
408            output_file.close()
409            _exit_with_failure_delete_file(output, message)
410
411        if ecst_args.verbose == REG_VERBOSE:
412            print(f'- HDR - SPI flash Read Mode              - Offset '
413                  f'{HDR_SPI_READ_MODE_OFFSET}  -  '
414                  f'{_hex_print_format(spi_read_mode_to_write)}')
415
416    output_file.close()
417
418def _set_error_detection_configuration(output, ecst_args):
419    """writes the error detection configuration value (enabled/disabled)
420    to the output file
421
422    :param output: the output file object,
423    :param ecst_args: the object representing the command line arguments.
424    """
425    with output.open("r+b") as output_file:
426        output_file.seek(HDR_ERR_DETECTION_CONF_OFFSET +
427                         ecst_args.paste_firmware_header)
428        if ecst_args.firmware_crc == FW_CRC_ENABLE:
429            err_detect_config_to_print = _hex_print_format(FW_CRC_ENABLE)
430            output_file.write(FW_CRC_ENABLE.to_bytes(1, "little"))
431        else:
432            err_detect_config_to_print = _hex_print_format(FW_CRC_DISABLE)
433            output_file.write(FW_CRC_DISABLE.to_bytes(1, "little"))
434
435        if ecst_args.verbose == REG_VERBOSE:
436            print(f'- HDR - FW CRC Enabled                   - Offset '
437                  f'{HDR_ERR_DETECTION_CONF_OFFSET}  -  '
438                  f'{err_detect_config_to_print}')
439
440        output_file.close()
441
442def _set_firmware_load_start_address(output, ecst_args):
443    """writes the fw load address to the output file
444
445    :param output: the output file object,
446    :param ecst_args: the object representing the command line arguments.
447    """
448    start_ram = ecst_args.chip_ram_address
449    end_ram = start_ram + ecst_args.chip_ram_size
450    fw_load_addr = ecst_args.firmware_load_address
451    fw_length = ecst_args.firmware_length
452    fw_end_addr = fw_load_addr + fw_length
453
454    start_ram_to_print = _hex_print_format(start_ram)
455    end_ram_to_print = _hex_print_format(end_ram)
456    fw_load_addr_to_print = _hex_print_format(fw_load_addr)
457    fw_length_to_print = _hex_print_format(fw_length)
458    fw_end_addr_to_print = _hex_print_format(fw_end_addr)
459
460    if fw_length == INVALID_INPUT:
461        message = f'Cannot read firmware length'
462        _exit_with_failure_delete_file(output, message)
463
464    if fw_length & ADDR_16_BYTES_ALIGNED_MASK != 0:
465        message = f'Firmware length ({fw_length_to_print}) ' \
466            f'is not 16 bytes aligned'
467        _exit_with_failure_delete_file(output, message)
468
469    if fw_load_addr is INVALID_INPUT:
470        message = f'Cannot read FW Load start address'
471        _exit_with_failure_delete_file(output, message)
472
473    if fw_load_addr & ADDR_16_BYTES_ALIGNED_MASK != 0:
474        message = f'Firmware load address ({fw_load_addr_to_print}) ' \
475            f'is not 16 bytes aligned'
476        _exit_with_failure_delete_file(output, message)
477
478    if (fw_load_addr > end_ram) or (fw_load_addr < start_ram):
479        message = f'Firmware load address ({fw_load_addr_to_print}) ' \
480            f'should be between start ({start_ram_to_print}) '\
481            f'and end ({end_ram_to_print}) of RAM'
482        _exit_with_failure_delete_file(output, message)
483
484    if fw_end_addr > end_ram:
485        message = f'Firmware end address ({fw_end_addr_to_print}) should be '
486        message += f'less than end of RAM address ({end_ram_to_print})'
487        _exit_with_failure_delete_file(output, message)
488
489    with output.open("r+b") as output_file:
490        output_file.seek(HDR_FW_LOAD_START_ADDR_OFFSET +
491                         ecst_args.paste_firmware_header)
492        output_file.write(ecst_args.firmware_load_address.
493                          to_bytes(4, "little"))
494
495        output_file.seek(HDR_FW_LENGTH_OFFSET +
496                         ecst_args.paste_firmware_header)
497        output_file.write(fw_length.to_bytes(4, "little"))
498
499        if ecst_args.verbose == REG_VERBOSE:
500            print(f'- HDR - FW load start address            - Offset '
501                  f'{HDR_FW_LOAD_START_ADDR_OFFSET}  -  '
502                  f'{fw_load_addr_to_print}')
503    output_file.close()
504
505def _set_firmware_entry_point(output, ecst_args):
506    """writes the fw entry point to the output file.
507    proportions:
508
509    :param output: the output file object,
510    :param ecst_args: the object representing the command line arguments.
511    """
512    entry_pt_none = False
513    input_file_path = Path(ecst_args.input)
514    fw_entry_pt = ecst_args.firmware_entry_point
515    fw_use_arm_reset = ecst_args.use_arm_reset
516    fw_length = ecst_args.firmware_length
517    fw_load_addr = ecst_args.firmware_load_address
518    fw_end_addr = fw_load_addr + fw_length
519
520    # check if fwep flag wasn't set and set it to fw load address if needed
521    if fw_entry_pt is None:
522        entry_pt_none = True
523        fw_entry_pt = ecst_args.firmware_load_address
524
525    if not entry_pt_none and fw_use_arm_reset:
526        message = f'-usearmrst not allowed, FW entry point already set using '\
527            f'-fwep !'
528        _exit_with_failure_delete_file(output, message)
529
530    with input_file_path.open("r+b") as input_file:
531        with output.open("r+b") as output_file:
532            if fw_use_arm_reset:
533                input_file.seek(ARM_FW_ENTRY_POINT_OFFSET +
534                                ecst_args.paste_firmware_header)
535                fw_entry_byte = input_file.read(4)
536                fw_entry_pt = int.from_bytes(fw_entry_byte, "little")
537
538            if fw_entry_pt < fw_load_addr or fw_entry_pt > fw_end_addr:
539                output_file.close()
540                input_file.close()
541                message = f'Firmware entry point ' \
542                    f'({_hex_print_format(fw_entry_pt)}) ' \
543                    f'should be between the FW load address ' \
544                    f'({_hex_print_format(fw_load_addr)})' \
545                    f' and FW end address ({_hex_print_format(fw_end_addr)})'
546
547                _exit_with_failure_delete_file(output, message)
548
549            output_file.seek(HDR_FW_ENTRY_POINT_OFFSET +
550                             ecst_args.paste_firmware_header)
551            output_file.write(fw_entry_pt.to_bytes(4, "little"))
552        output_file.close()
553    input_file.close()
554
555    if ecst_args.verbose == REG_VERBOSE:
556        print(f'- HDR - FW Entry point                   - Offset '
557              f'{HDR_FW_ENTRY_POINT_OFFSET} -  '
558              f'{_hex_print_format(fw_entry_pt)}')
559
560def _set_firmware_crc_start_and_size(output, ecst_args):
561    """writes the fw crc start address and the crc size to the output file.
562    proportions:
563    --crc start address should be 4 byte aligned, bigger than crc end address
564    --crc size should be 4 byte aligned, and be set to firmware length minus
565    crc start offset by default
566    --crc end address is crc start address + crc size bytes
567
568    the application is closed, and an error is generated if the mentioned
569    fields not comply with the proportions.
570
571    :param output: the output file object,
572    :param ecst_args: the object representing the command line arguments.
573    """
574    fw_crc_size = ecst_args.firmware_crc_size
575    fw_crc_start = ecst_args.firmware_crc_start
576
577    if fw_crc_size is None:
578        fw_crc_end = ecst_args.firmware_length - 1
579        # default value for crc size
580        fw_crc_size = ecst_args.firmware_length - fw_crc_start
581        ecst_args.firmware_crc_size = fw_crc_size
582    else:
583        fw_crc_end = fw_crc_start + fw_crc_size - 1
584
585    fw_crc_start_to_print = _hex_print_format(fw_crc_start)
586    fw_crc_end_to_print = _hex_print_format(fw_crc_end)
587    fw_length_to_print = _hex_print_format(ecst_args.firmware_length)
588    fw_crc_size_to_print = _hex_print_format(fw_crc_size)
589
590    if fw_crc_start & ADDR_4_BYTES_ALIGNED_MASK != 0:
591        message = f'Firmware crc offset address ' \
592            f'({fw_crc_start_to_print}) is not 4 bytes aligned'
593        _exit_with_failure_delete_file(output, message)
594
595    if fw_crc_start > fw_crc_end:
596        message = f'CRC start address ({fw_crc_start_to_print}) should' \
597            f' be less or equal to CRC end address ({fw_crc_end_to_print}) \n'
598        _exit_with_failure_delete_file(output, message)
599
600    if fw_crc_size & ADDR_4_BYTES_ALIGNED_MASK != 0:
601        message = f'Firmware crc size ({fw_crc_size_to_print}) ' \
602            f'is not 4 bytes aligned'
603        _exit_with_failure_delete_file(output, message)
604
605    if fw_crc_end > ecst_args.firmware_length - 1:
606        message = f'CRC end address ({fw_crc_end_to_print}) should be less' \
607            f' than the FW length ({fw_length_to_print}) \n'
608        _exit_with_failure_delete_file(output, message)
609
610    with output.open("r+b") as output_file:
611        output_file.seek(HDR_FW_ERR_DETECT_START_ADDR_OFFSET +
612                         ecst_args.paste_firmware_header)
613        output_file.write(fw_crc_start.to_bytes(4, "little"))
614
615        output_file.seek(HDR_FW_ERR_DETECT_END_ADDR_OFFSET +
616                         ecst_args.paste_firmware_header)
617        output_file.write(fw_crc_end.to_bytes(4, "little"))
618        output_file.close()
619
620    if ecst_args.verbose == REG_VERBOSE:
621        print(f'- HDR - FW CRC Start                     - Offset '
622              f'{HDR_FW_ERR_DETECT_START_ADDR_OFFSET} -  '
623              f'{fw_crc_start_to_print}')
624
625        print(f'- HDR - FW CRC End                       - Offset '
626              f'{HDR_FW_ERR_DETECT_END_ADDR_OFFSET} -  '
627              f'{fw_crc_end_to_print}')
628
629def _set_firmware_length(output, ecst_args):
630    """writes the flash size value to the output file
631    Note: the firmware length value has already been checked before
632    this method
633
634    :param output: the output file object,
635    :param ecst_args: the object representing the command line arguments.
636    """
637
638    fw_length = ecst_args.firmware_length
639    fw_length_to_print = _hex_print_format(fw_length)
640
641    with output.open("r+b") as output_file:
642        output_file.seek(HDR_FW_LENGTH_OFFSET +
643                         ecst_args.paste_firmware_header)
644        output_file.write(fw_length.to_bytes(4, "little"))
645        if ecst_args.verbose == REG_VERBOSE:
646            print(f'- HDR - FW Length                        - Offset '
647                  f'{HDR_FW_LENGTH_OFFSET} -  '
648                  f'{fw_length_to_print}')
649    output_file.close()
650
651def _set_flash_size(output, ecst_args):
652    """writes the flash size value to the output file
653    valid values are 1,2,4,8 or 16 (default is 16).
654    the application is closed and the output file is deleted
655    if the flash size is invalid
656
657    :param output: the output file object,
658    :param ecst_args: the object representing the command line arguments.
659    """
660    flash_size_to_print = ""
661
662    with output.open("r+b") as output_file:
663        output_file.seek(HDR_FLASH_SIZE_OFFSET +
664                         ecst_args.paste_firmware_header)
665        flash_size = ecst_args.flash_size
666
667        if flash_size == FLASH_SIZE_1_MBYTES_VAL:
668            output_file.write(FLASH_SIZE_1_MBYTES.to_bytes(4, "little"))
669            flash_size_to_print = _hex_print_format(FLASH_SIZE_1_MBYTES)
670        elif flash_size == FLASH_SIZE_2_MBYTES_VAL:
671            output_file.write(FLASH_SIZE_2_MBYTES.to_bytes(4, "little"))
672            flash_size_to_print = _hex_print_format(FLASH_SIZE_2_MBYTES)
673        elif flash_size == FLASH_SIZE_4_MBYTES_VAL:
674            output_file.write(FLASH_SIZE_4_MBYTES.to_bytes(4, "little"))
675            flash_size_to_print = _hex_print_format(FLASH_SIZE_4_MBYTES)
676        elif flash_size == FLASH_SIZE_8_MBYTES_VAL:
677            output_file.write(FLASH_SIZE_8_MBYTES.to_bytes(4, "little"))
678            flash_size_to_print = _hex_print_format(FLASH_SIZE_8_MBYTES)
679        elif flash_size == FLASH_SIZE_16_MBYTES_VAL:
680            output_file.write(FLASH_SIZE_16_MBYTES.to_bytes(4, "little"))
681            flash_size_to_print = _hex_print_format(FLASH_SIZE_16_MBYTES)
682        elif not flash_size.isdigit():
683            output_file.close()
684            _exit_with_failure_delete_file(output, "Cannot read Flash size")
685        else:
686            message = f'Invalid flash size ({flash_size} MBytes), ' \
687                f' it should be 0.5, 1, 2, 4, 8, 16 MBytes \n' \
688                f' please note - for 0.5 MBytes flash, enter \'1\' '
689            output_file.close()
690            _exit_with_failure_delete_file(output, message)
691
692    if ecst_args.verbose == REG_VERBOSE:
693        print(f'- HDR - Flash size                       - Offset '
694              f'{HDR_FLASH_SIZE_OFFSET} - '
695              f' {flash_size_to_print}')
696    output_file.close()
697
698def _set_reserved_bytes(output, ecst_args):
699    """fills the reserved part of the image at the relevant offset
700    with the PAD_BYTE
701
702    :param output: the output file object
703    :param ecst_args: the object representing the command line arguments.
704    """
705    with output.open("r+b") as output_file:
706        for i in range(BYTES_TO_PAD):
707            output_file.seek(RESERVED_BYTES_OFFSET +
708                             ecst_args.paste_firmware_header + i)
709            output_file.write(PAD_BYTE)
710    output_file.close()
711
712def _set_firmware_header_crc_signature(output, ecst_args):
713    """writes the firmware header crc signature (4 bytes)
714    to the output file
715
716    :param output: the output file object
717    :param ecst_args: the object representing the command line arguments.
718    """
719    crc_to_print = _hex_print_format(0)
720
721    # calculating crc only if the header crc check is enabled
722    if ecst_args.firmware_header_crc != FW_HDR_CRC_DISABLE:
723
724        with output.open("r+b") as output_file:
725            crc_calc = 0xffffffff
726            table = _create_table()
727
728            for i in range(HDR_FW_HEADER_SIG_OFFSET):
729                output_file.seek(ecst_args.paste_firmware_header + i)
730                current = output_file.read(1)
731                crc_calc = _crc_update(
732                    int.from_bytes(current, "little"), crc_calc, table)
733
734            crc = _finalize_crc(crc_calc)
735            crc_to_write = crc.to_bytes(4, "little")
736            crc_to_print = _hex_print_format(crc)
737            output_file.seek(ecst_args.paste_firmware_header +
738                             HDR_FW_HEADER_SIG_OFFSET)
739            output_file.write(crc_to_write)
740
741        output_file.close()
742
743    if ecst_args.verbose == REG_VERBOSE:
744        print(f'- HDR - Header CRC                       - Offset '
745              f'{HDR_FW_HEADER_SIG_OFFSET} - '
746              f' {crc_to_print}')
747
748def _set_firmware_image_crc_signature(output, ecst_args):
749    """writes the firmware image crc signature (4 bytes)
750    to the output file.
751
752    :param output: the output file object,
753    :param ecst_args: the object representing the command line arguments.
754    """
755    # calculating crc only if the image crc check is enabled
756    crc_to_print = _hex_print_format(0)
757    if ecst_args.firmware_crc != FW_CRC_DISABLE:
758
759        with output.open("r+b") as output_file:
760            crc_calc = 0xffffffff
761            table = _create_table()
762
763            output_file.seek(FW_IMAGE_OFFSET + ecst_args.paste_firmware_header +
764                             ecst_args.firmware_crc_start)
765            for _ in range(ecst_args.firmware_crc_size):
766                current = output_file.read(1)
767                crc_calc = _crc_update(int.from_bytes(current, "little"), \
768                crc_calc, table)
769
770            crc = _finalize_crc(crc_calc)
771            crc_to_write = crc.to_bytes(4, "little")
772            crc_to_print = _hex_print_format(crc)
773            output_file.seek(HDR_FW_IMAGE_SIG_OFFSET +
774                             ecst_args.paste_firmware_header)
775            output_file.write(crc_to_write)
776
777        output_file.close()
778
779    if ecst_args.verbose == REG_VERBOSE:
780        print(f'- HDR - Header CRC                       - Offset '
781              f'{HDR_FW_IMAGE_SIG_OFFSET} - '
782              f' {crc_to_print}')
783
784def _copy_image(output, ecst_args):
785    """copies the fw image from the input file to the output file
786    if firmware header offset is defined, just copies the input file to the
787    output file
788
789    :param output: the output file object,
790    :param ecst_args: the object representing the command line arguments.
791    """
792    with open(ecst_args.input, "rb") as firmware_image:
793        with open(output, "r+b") as output_file:
794            if ecst_args.paste_firmware_header == 0:
795                output_file.seek(FW_IMAGE_OFFSET)
796            else:
797                output_file.seek(0)
798            for line in firmware_image:
799                output_file.write(line)
800        output_file.close()
801    firmware_image.close()
802
803    # pad fw image to be 16 byte aligned if needed
804    input_file_size = Path(ecst_args.input).stat().st_size
805    bytes_to_pad_num = abs((16 - input_file_size) % 16)
806
807    with open(output, "r+b") as output_file:
808        i = bytes_to_pad_num
809        while i != 0:
810            output_file.seek(0, 2)  # seek end of file
811            output_file.write(PAD_BYTE)
812            i -= 1
813    output_file.close()
814
815    # update firmware length if needed
816    fw_length = ecst_args.firmware_length
817    if fw_length is None:
818        if ecst_args.paste_firmware_header == 0:
819            ecst_args.firmware_length = input_file_size + bytes_to_pad_num
820        else:
821            ecst_args.firmware_length = input_file_size - HEADER_SIZE - \
822                                                ecst_args.paste_firmware_header
823
824def _merge_file_with_bt_header(ecst_args):
825    """Merge the BT with the BH according to the bhoffset and pointer flags
826
827    :param ecst_args: the object representing the command line arguments.
828    """
829    bh_index = ecst_args.bh_offset
830    file_name_to_print = ""
831
832    if not ecst_args.input:
833        exit_with_failure('No input BIN file selected for'
834                          'Bootloader header file.')
835    else:
836        input_file = Path(ecst_args.input)
837        if not input_file.exists():
838            exit_with_failure(f'Cannot open {ecst_args.input}')
839
840        if input_file.stat().st_size < bh_index:
841
842            message = f'Bootloader header offset ({bh_index} bytes) should be '
843            message += f'less than file size ' \
844                f'({input_file.stat().st_size } bytes)'
845            exit_with_failure(message)
846
847        # create a file if the output parameter is not None,
848        # otherwise write to the input file
849        if ecst_args.output is not None:
850            output_file = Path(ecst_args.output)
851            output_file.touch()
852            with input_file.open("r+b") as firmware_image:
853                with output_file.open("r+b") as output_file:
854                    file_name_to_print = output_file.name
855                    for line in firmware_image:
856                        output_file.write(line)
857                    output_file.seek(bh_index)
858                    output_file.write(HDR_PTR_SIGNATURE.to_bytes(4, "little"))
859                    output_file.seek(bh_index + 4)
860                    output_file.write(ecst_args.pointer.to_bytes(4, "little"))
861                output_file.close()
862            firmware_image.close()
863
864        else:
865            with input_file.open("r+b") as file_to_merge:
866                file_name_to_print = file_to_merge.name
867                file_to_merge.seek(bh_index + SIGNATURE_OFFSET)
868                file_to_merge.write(HDR_PTR_SIGNATURE.to_bytes(4, "little"))
869                file_to_merge.seek(bh_index + POINTER_OFFSET)
870                file_to_merge.write(ecst_args.pointer.to_bytes(4, "little"))
871            file_to_merge.close()
872
873    if ecst_args.verbose == REG_VERBOSE:
874        print(Fore.LIGHTCYAN_EX + f'BootLoader Header file:'
875              f' {file_name_to_print}')
876        print(f'Offset: {_hex_print_format(ecst_args.bh_offset)},'
877              f' Signature: {_hex_print_format(HDR_PTR_SIGNATURE)},'
878              f' Pointer: {_hex_print_format(ecst_args.pointer)}')
879
880def _create_bt_header(ecst_args):
881    """create bootloader table header, consist of 4 bytes signature and
882    4 bytes pointer
883
884    :param ecst_args: the object representing the command line arguments.
885    """
886    if not ecst_args.output:
887        exit_with_failure("No output file selected for "
888                          "Bootloader header file.")
889    else:
890        output_file = Path(ecst_args.output)
891        if not output_file.exists():
892            output_file.touch()
893        with open(output_file, "r+b") as boot_loader_header_file:
894            boot_loader_header_file.seek(SIGNATURE_OFFSET)
895            boot_loader_header_file.write(HDR_PTR_SIGNATURE.to_bytes(4, \
896            "little"))
897            boot_loader_header_file.seek(POINTER_OFFSET)
898            boot_loader_header_file.write(ecst_args.pointer.to_bytes(4, \
899            "little"))
900        boot_loader_header_file.close()
901        if ecst_args.verbose == REG_VERBOSE:
902            print(Fore.LIGHTCYAN_EX + f'BootLoader Header file:'
903                  f' {output_file.name}')
904            print(f'Signature: {_hex_print_format(HDR_PTR_SIGNATURE)}, '
905                  f'Pointer: {_hex_print_format(ecst_args.pointer)}')
906
907def _create_table():
908    """helper for crc calculation"""
909    table = []
910    for i in range(256):
911        k = i
912        for _ in range(8):
913            if k & 1:
914                k = (k >> 1) ^ 0xEDB88320
915            else:
916                k >>= 1
917        table.append(k)
918    return table
919
920def _crc_update(cur, crc, table):
921    """helper for crc calculation
922
923    :param cur
924    :param crc
925    :param table
926    """
927    l_crc = (0x000000ff & cur)
928
929    tmp = crc ^ l_crc
930    crc = (crc >> 8) ^ table[(tmp & 0xff)]
931    return crc
932
933def _finalize_crc(crc):
934    """helper for crc calculation
935
936    :param crc
937    """
938    final_crc = 0
939    for j in range(NUM_OF_BYTES):
940        current_bit = crc & (1 << j)
941        current_bit = current_bit >> j
942        final_crc |= current_bit << (NUM_OF_BYTES - 1) - j
943    return final_crc
944
945def _hex_print_format(value):
946    """hex representation of an integer
947
948    :param value: an integer to be represented in hex
949    """
950    return "0x{:08x}".format(value)
951
952def _exit_with_failure_delete_file(output, message):
953    """formatted failure message printer, prints the
954    relevant error message, deletes the output file,
955    and exits the application.
956
957    :param message: the error message to be printed
958    """
959    output_file = Path(output)
960    if output_file.exists():
961        output_file.unlink()
962
963    message = '\n' + message
964    message += '\n'
965    message += '******************************\n'
966    message += '***        FAILED          ***\n'
967    message += '******************************\n'
968    print(Fore.RED + message)
969
970    sys.exit(EXIT_FAILURE_STATUS)
971
972def _exit_with_success():
973    """formatted success message printer, prints the
974    success message and exits the application.
975    """
976    message = '\n'
977    message += '******************************\n'
978    message += '***        SUCCESS         ***\n'
979    message += '******************************\n'
980    print(Fore.GREEN + message)
981
982    sys.exit(EXIT_SUCCESS_STATUS)
983
984def main():
985    """main of the application
986    """
987    init()  # colored print initialization for windows
988
989    if len(sys.argv) < 2:
990        sys.exit(EXIT_FAILURE_STATUS)
991
992    ecst_obj = EcstArgs()
993
994    if ecst_obj.error_args:
995        for err_arg in ecst_obj.error_args:
996            message = f'unKnown flag: {err_arg}'
997            exit_with_failure(message)
998        sys.exit(EXIT_SUCCESS_STATUS)
999
1000    # Start to handle booter header table
1001    _bt_mode_handler(ecst_obj)
1002
1003if __name__ == '__main__':
1004    main()
1005