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