1# Copyright (c) 2022 Renesas Electronics Corporation
2#
3# SPDX-License-Identifier: Apache-2.0
4
5'''Runner for flashing with ezFlashCLI.'''
6import shlex
7
8from runners.core import RunnerCaps, ZephyrBinaryRunner
9
10DEFAULT_EZFLASHCLI = "ezFlashCLI"
11
12class EzFlashCliBinaryRunner(ZephyrBinaryRunner):
13    '''Runner front-end for ezFlashCLI'''
14
15    def __init__(self, cfg, tool, dev_id=None, tool_opt=None, erase=False, reset=True):
16        super().__init__(cfg)
17        self.bin_ = cfg.bin_file
18
19        self.tool = tool
20        self.dev_id = dev_id
21        self.erase = bool(erase)
22        self.reset = bool(reset)
23
24        self.tool_opt = []
25        if tool_opt is not None:
26            for opts in [shlex.split(opt) for opt in tool_opt]:
27                self.tool_opt += opts
28
29    @classmethod
30    def name(cls):
31        return 'ezflashcli'
32
33    @classmethod
34    def capabilities(cls):
35        return RunnerCaps(commands={'flash'}, dev_id=True, tool_opt=True, erase=True, reset=True)
36
37    @classmethod
38    def dev_id_help(cls) -> str:
39        return '''Device identifier. Use it to select the J-Link Serial Number
40                  of the device connected over USB.'''
41
42    @classmethod
43    def tool_opt_help(cls) -> str:
44        return "Additional options for ezFlashCLI e.g. '--verbose'"
45
46    @classmethod
47    def do_add_parser(cls, parser):
48        parser.add_argument('--tool', default=DEFAULT_EZFLASHCLI,
49                            help='ezFlashCLI path, default is '
50                                f'{DEFAULT_EZFLASHCLI}')
51        parser.set_defaults(reset=True)
52
53    @classmethod
54    def do_create(cls, cfg, args):
55        return EzFlashCliBinaryRunner(cfg, tool=args.tool, dev_id=args.dev_id,
56                                      tool_opt=args.tool_opt, erase=args.erase)
57
58    def needs_product_header(self):
59        # Applications linked to code partition are meant to be run by MCUboot
60        # and do not require product header. Other applications and MCUboot itself
61        # are run by internal bootloader and thus require valid product header.
62
63        is_mcuboot = self.build_conf.getboolean('CONFIG_MCUBOOT')
64        uses_code_partition = self.build_conf.getboolean('CONFIG_USE_DT_CODE_PARTITION')
65
66        return is_mcuboot or not uses_code_partition
67
68    def get_options(self):
69        device_args = []
70        if self.dev_id is not None:
71            device_args = ['-j', f'{self.dev_id}']
72        return device_args + self.tool_opt
73
74    def program_bin(self):
75        options = self.get_options()
76
77        if self.erase:
78            self.logger.info("Erasing flash...")
79            self.check_call([self.tool] + options + ["erase_flash"])
80
81        self.logger.info(f"Flashing {self.bin_}...")
82        if self.needs_product_header():
83            # Write product header and application image at fixed offset as required
84            # by internal bootloader.
85            self.check_call([self.tool] + options + ["image_flash", self.bin_])
86        else:
87            load_offset = self.build_conf['CONFIG_FLASH_LOAD_OFFSET']
88            self.check_call(
89                [self.tool] + options + ["write_flash", f'0x{load_offset:x}', self.bin_]
90            )
91
92    def reset_device(self):
93        self.logger.info("Resetting...")
94        options = self.get_options()
95        self.check_call([self.tool] + options + ["go"])
96
97    def do_run(self, command, **kwargs):
98        self.require(self.tool)
99        self.ensure_output('bin')
100        self.program_bin()
101        if self.reset:
102            self.reset_device()
103