1# Copyright (c) 2021, Telink Semiconductor
2#
3# SPDX-License-Identifier: Apache-2.0
4
5import os
6import re
7import subprocess
8import time
9
10from runners.core import BuildConfiguration, RunnerCaps, ZephyrBinaryRunner
11
12
13class SpiBurnBinaryRunner(ZephyrBinaryRunner):
14    '''Runner front-end for SPI_burn.'''
15
16    def __init__(self, cfg, addr, spiburn, iceman, timeout, gdb_port, gdb_ex, erase=False):
17        super().__init__(cfg)
18
19        self.spiburn = spiburn
20        self.iceman = iceman
21        self.addr = addr
22        self.timeout = int(timeout)
23        self.erase = bool(erase)
24        self.gdb_port = gdb_port
25        self.gdb_ex = gdb_ex
26
27    @classmethod
28    def name(cls):
29        return 'spi_burn'
30
31    @classmethod
32    def capabilities(cls):
33        return RunnerCaps(commands={'flash', 'debug'}, erase=True, flash_addr=True)
34
35    @classmethod
36    def do_add_parser(cls, parser):
37        parser.add_argument('--addr', default='0x0',
38                            help='start flash address to write')
39        parser.add_argument('--timeout', default=10,
40                            help='ICEman connection establishing timeout in seconds')
41        parser.add_argument('--telink-tools-path', help='path to Telink flash tools')
42        parser.add_argument('--gdb-port', default='1111', help='Port to connect for gdb-client')
43        parser.add_argument('--gdb-ex', default='', nargs='?',
44                            help='Additional gdb commands to run')
45
46    @classmethod
47    def do_create(cls, cfg, args):
48
49        if args.telink_tools_path:
50            spiburn = f'{args.telink_tools_path}/flash/bin/SPI_burn'
51            iceman = f'{args.telink_tools_path}/ice/ICEman'
52        else:
53            # If telink_tools_path arg is not specified then
54            # pass to tools shall be specified in PATH
55            spiburn = 'SPI_burn'
56            iceman  = 'ICEman'
57
58        # Get flash address offset
59        if args.dt_flash == 'y':
60            build_conf = BuildConfiguration(cfg.build_dir)
61            address = hex(
62                cls.get_flash_address(args, build_conf) - build_conf['CONFIG_FLASH_BASE_ADDRESS']
63            )
64        else:
65            address = args.addr
66
67        return SpiBurnBinaryRunner(
68            cfg, address, spiburn, iceman, args.timeout, args.gdb_port, args.gdb_ex, args.erase
69        )
70
71    def do_run(self, command, **kwargs):
72
73        self.require(self.spiburn)
74
75        # Find path to ICEman with require call
76        self.iceman_path = self.require(self.iceman)
77
78        if command == "flash":
79            self._flash()
80
81        elif command == "debug":
82            self._debug()
83
84        else:
85            self.logger.error(f'{command} not supported!')
86
87    def start_iceman(self):
88
89        # Start ICEman as background process
90        self.ice_process = self.popen_ignore_int(["./ICEman", '-Z', 'v5', '-l', 'aice_sdp.cfg'],
91                                                            cwd=os.path.dirname(self.iceman_path),
92                                                            stdout=subprocess.PIPE)
93
94        # Wait till it ready or exit by timeout
95        start = time.time()
96        while True:
97            out = self.ice_process.stdout.readline()
98            if b'ICEman is ready to use.' in out:
99                break
100            if time.time() - start > self.timeout:
101                raise RuntimeError("TIMEOUT: ICEman is not ready")
102
103    def stop_iceman(self):
104        # Kill ICEman subprocess
105        self.ice_process.terminate()
106
107    def _flash(self):
108
109        try:
110
111            # Start ICEman
112            self.start_iceman()
113
114            # Compose flash command
115            cmd_flash = [self.spiburn, '--addr', str(self.addr), '--image', self.cfg.bin_file]
116
117            if self.erase:
118                cmd_flash += ["--erase-all"]
119
120            # Run SPI burn flash tool
121            self.check_call(cmd_flash)
122
123        finally:
124            self.stop_iceman()
125
126    def _debug(self):
127
128        try:
129
130            # Start ICEman
131            self.start_iceman()
132
133            # format -ex commands
134            gdb_ex = re.split("(-ex) ", self.gdb_ex)[1::]
135
136            # Compose gdb command
137            client_cmd = [
138                self.cfg.gdb,
139                self.cfg.elf_file,
140                '-ex',
141                f'target remote :{self.gdb_port}',
142            ] + gdb_ex
143
144            # Run gdb
145            self.run_client(client_cmd)
146
147        finally:
148            self.stop_iceman()
149