1# Copyright (c) 2017 Linaro Limited.
2#
3# SPDX-License-Identifier: Apache-2.0
4
5'''Modified openocd and gdb runner for Cyclone V SoC DevKit.'''
6
7import os
8import re
9import subprocess
10from os import path
11from pathlib import Path
12
13from runners.core import RunnerCaps, ZephyrBinaryRunner
14
15DEFAULT_OPENOCD_TCL_PORT = 6333
16DEFAULT_OPENOCD_TELNET_PORT = 4444
17DEFAULT_OPENOCD_GDB_PORT = 3333
18DEFAULT_OPENOCD_RESET_HALT_CMD = 'reset halt'
19
20class IntelCycloneVBinaryRunner(ZephyrBinaryRunner):
21    '''Runner front-end for openocd.'''
22
23    def __init__(self, cfg, pre_init=None, reset_halt_cmd=DEFAULT_OPENOCD_RESET_HALT_CMD,
24                 pre_load=None, load_cmd=None, verify_cmd=None, post_verify=None,
25                 do_verify=False, do_verify_only=False,
26                 tui=None, config=None, serial=None, use_elf=True,
27                 no_halt=False, no_init=False, no_targets=False,
28                 tcl_port=DEFAULT_OPENOCD_TCL_PORT,
29                 telnet_port=DEFAULT_OPENOCD_TELNET_PORT,
30                 gdb_port=DEFAULT_OPENOCD_GDB_PORT,
31                 gdb_init=None, no_load=False):
32        super().__init__(cfg)
33
34        support = path.join(cfg.board_dir, 'support')
35
36        gdb_commands = None
37        gdb_commands2 = None
38        gdb_commands_deb = None
39
40        if not config:
41            default = path.join(support, 'openocd.cfg')
42            default2 = path.join(support, 'download_all.gdb')
43            default3 = path.join(support, 'appli_dl_cmd.gdb')
44            default4 = path.join(support, 'appli_debug_cmd.gdb')
45            if path.exists(default):
46                config = [default]
47                gdb_commands = [default2]
48                gdb_commands2 = [default3]
49                gdb_commands_deb = [default4]
50        self.openocd_config = config
51        self.gdb_cmds = gdb_commands
52        self.gdb_cmds2 = gdb_commands2
53        self.gdb_cmds_deb = gdb_commands_deb
54
55        search_args = []
56        if path.exists(support):
57            search_args.append('-s')
58            search_args.append(support)
59
60        if self.openocd_config is not None:
61            for i in self.openocd_config:
62                if path.exists(i) and not path.samefile(path.dirname(i), support):
63                    search_args.append('-s')
64                    search_args.append(path.dirname(i))
65
66        if cfg.openocd_search is not None:
67            for p in cfg.openocd_search:
68                search_args.extend(['-s', p])
69        self.openocd_cmd = [cfg.openocd or 'openocd'] + search_args
70        # openocd doesn't cope with Windows path names, so convert
71        # them to POSIX style just to be sure.
72        self.elf_name = Path(cfg.elf_file).as_posix()
73        self.pre_init = pre_init or []
74        self.reset_halt_cmd = reset_halt_cmd
75        self.pre_load = pre_load or []
76        self.load_cmd = load_cmd
77        self.verify_cmd = verify_cmd
78        self.post_verify = post_verify or []
79        self.do_verify = do_verify or False
80        self.do_verify_only = do_verify_only or False
81        self.tcl_port = tcl_port
82        self.telnet_port = telnet_port
83        self.gdb_port = gdb_port
84        self.gdb_cmd = [cfg.gdb] if cfg.gdb else None
85        self.tui_arg = ['-tui'] if tui else []
86        self.halt_arg = [] if no_halt else ['-c halt']
87        self.init_arg = [] if no_init else ['-c init']
88        self.targets_arg = [] if no_targets else ['-c targets']
89        self.serial = ['-c set _ZEPHYR_BOARD_SERIAL ' + serial] if serial else []
90        self.use_elf = use_elf
91        self.gdb_init = gdb_init
92        self.load_arg = [] if no_load else ['-ex', 'load']
93
94    @classmethod
95    def name(cls):
96        return 'intel_cyclonev'
97
98    @classmethod
99    def capabilities(cls):
100        return RunnerCaps(commands={'flash', 'debug', 'attach'},
101                          dev_id=False, flash_addr=False, erase=False)
102
103    @classmethod
104    def do_add_parser(cls, parser):
105        parser.add_argument('--config', action='append',
106                            help='''if given, override default config file;
107                            may be given multiple times''')
108        parser.add_argument('--serial', default="",
109                            help='''if given, selects FTDI instance by its serial number,
110                            defaults to empty''')
111        parser.add_argument('--use-elf', default=False, action='store_true',
112                            help='if given, Elf file will be used for loading instead of HEX image')
113        # Options for flashing:
114        parser.add_argument('--cmd-pre-init', action='append',
115                            help='''Command to run before calling init;
116                            may be given multiple times''')
117        parser.add_argument('--cmd-reset-halt', default=DEFAULT_OPENOCD_RESET_HALT_CMD,
118                            help=f'''Command to run for resetting and halting the target,
119                            defaults to "{DEFAULT_OPENOCD_RESET_HALT_CMD}"''')
120        parser.add_argument('--cmd-pre-load', action='append',
121                            help='''Command to run before flashing;
122                            may be given multiple times''')
123        parser.add_argument('--cmd-load',
124                            help='''Command to load/flash binary
125                            (required when flashing)''')
126        parser.add_argument('--cmd-verify',
127                            help='''Command to verify flashed binary''')
128        parser.add_argument('--cmd-post-verify', action='append',
129                            help='''Command to run after verification;
130                            may be given multiple times''')
131        parser.add_argument('--verify', action='store_true',
132                            help='if given, verify after flash')
133        parser.add_argument('--verify-only', action='store_true',
134                            help='if given, do verify and verify only. No flashing')
135
136        # Options for debugging:
137        parser.add_argument('--tui', default=False, action='store_true',
138                            help='if given, GDB uses -tui')
139        parser.add_argument('--tcl-port', default=DEFAULT_OPENOCD_TCL_PORT,
140                            help='openocd TCL port, defaults to 6333')
141        parser.add_argument('--telnet-port',
142                            default=DEFAULT_OPENOCD_TELNET_PORT,
143                            help='openocd telnet port, defaults to 4444')
144        parser.add_argument('--gdb-port', default=DEFAULT_OPENOCD_GDB_PORT,
145                            help='openocd gdb port, defaults to 3333')
146        parser.add_argument('--gdb-init', action='append',
147                            help='if given, add GDB init commands')
148        parser.add_argument('--no-halt', action='store_true',
149                            help='if given, no halt issued in gdb server cmd')
150        parser.add_argument('--no-init', action='store_true',
151                            help='if given, no init issued in gdb server cmd')
152        parser.add_argument('--no-targets', action='store_true',
153                            help='if given, no target issued in gdb server cmd')
154        parser.add_argument('--no-load', action='store_true',
155                            help='if given, no load issued in gdb server cmd')
156
157    @classmethod
158    def do_create(cls, cfg, args):
159        return IntelCycloneVBinaryRunner(
160            cfg,
161            pre_init=args.cmd_pre_init, reset_halt_cmd=args.cmd_reset_halt,
162            pre_load=args.cmd_pre_load, load_cmd=args.cmd_load,
163            verify_cmd=args.cmd_verify, post_verify=args.cmd_post_verify,
164            do_verify=args.verify, do_verify_only=args.verify_only,
165            tui=args.tui, config=args.config, serial=args.serial,
166            use_elf=args.use_elf, no_halt=args.no_halt, no_init=args.no_init,
167            no_targets=args.no_targets, tcl_port=args.tcl_port,
168            telnet_port=args.telnet_port, gdb_port=args.gdb_port,
169            gdb_init=args.gdb_init, no_load=args.no_load)
170
171    def print_gdbserver_message(self):
172        if not self.thread_info_enabled:
173            thread_msg = '; no thread info available'
174        elif self.supports_thread_info():
175            thread_msg = '; thread info enabled'
176        else:
177            thread_msg = '; update OpenOCD software for thread info'
178        self.logger.info('OpenOCD GDB server running on port '
179                         f'{self.gdb_port}{thread_msg}')
180
181    def to_num(self, number):
182        dev_match = re.search(r"^\d*\+dev", number)
183        dev_version = dev_match is not None
184
185        num_match = re.search(r"^\d*", number)
186        num = int(num_match.group(0))
187
188        if dev_version:
189            num += 1
190
191        return num
192
193    def read_version(self):
194        self.require(self.openocd_cmd[0])
195
196        # OpenOCD prints in stderr, need redirect to get output
197        out = self.check_output([self.openocd_cmd[0], '--version'],
198                                stderr=subprocess.STDOUT).decode()
199
200        version_match = re.search(r"Open On-Chip Debugger (\d+.\d+.\d+)", out)
201        version = version_match.group(1).split('.')
202
203        return [self.to_num(i) for i in version]
204
205    def supports_thread_info(self):
206        # Zephyr rtos was introduced after 0.11.0
207        (major, minor, rev) = self.read_version()
208        return (major, minor, rev) > (0, 11, 0)
209
210    def do_run(self, command, **kwargs):
211        self.require(self.openocd_cmd[0])
212
213        self.cfg_cmd = []
214        if self.openocd_config is not None:
215            for i in self.openocd_config:
216                self.cfg_cmd.append('-f')
217                self.cfg_cmd.append(i)
218
219        if command == 'flash':
220            self.do_flash_elf(**kwargs)
221        elif command in ('attach', 'debug'):
222            self.do_attach_debug(command, **kwargs)
223
224    def do_flash_elf(self, **kwargs):
225        if self.gdb_cmd is None:
226            raise ValueError('Cannot debug; no gdb specified')
227        if self.elf_name is None:
228            raise ValueError('Cannot debug; no .elf specified')
229
230        pre_init_cmd = []
231        for i in self.pre_init:
232            pre_init_cmd.append("-c")
233            pre_init_cmd.append(i)
234            pre_init_cmd.append("-q")
235
236        if self.thread_info_enabled and self.supports_thread_info():
237            pre_init_cmd.append("-c")
238            pre_init_cmd.append("$_TARGETNAME configure -rtos Zephyr")
239
240        server_cmd = (self.openocd_cmd + self.serial + self.cfg_cmd +        #added mevalver
241                      pre_init_cmd)
242        temp_str = '--cd=' + os.environ.get('ZEPHYR_BASE') #Go to Zephyr base Dir
243        # Execute First Script in Zephyr Base Dir
244        gdb_cmd = (self.gdb_cmd + self.tui_arg +
245                   [temp_str,'-ex', f'target extended-remote localhost:{self.gdb_port}' , '-batch'])
246        # Execute Second Script in Build Dir
247        gdb_cmd2 = (self.gdb_cmd + self.tui_arg +
248                   ['-ex', f'target extended-remote localhost:{self.gdb_port}' , '-batch'])
249        echo = ['echo']
250        if self.gdb_init is not None:
251            for i in self.gdb_init:
252                gdb_cmd.append("-ex")
253                gdb_cmd.append(i)
254                gdb_cmd2.append("-ex")
255                gdb_cmd2.append(i)
256
257        if self.gdb_cmds is not None:
258            for i in self.gdb_cmds:
259                gdb_cmd.append("-x")
260                gdb_cmd.append(i)
261
262        if self.gdb_cmds2 is not None:
263            for i in self.gdb_cmds2:
264                gdb_cmd2.append("-x")
265                gdb_cmd2.append(i)
266
267        self.require(gdb_cmd[0])
268        self.print_gdbserver_message()
269
270        cmd1 = echo + server_cmd
271        self.check_call(cmd1)
272        cmd2 = echo + gdb_cmd
273        self.check_call(cmd2)
274        cmd3 = echo + gdb_cmd2
275        self.check_call(cmd3)
276
277        self.run_server_and_client(server_cmd, gdb_cmd)
278        self.run_server_and_client(server_cmd, gdb_cmd2)
279
280    def do_attach_debug(self, command, **kwargs):
281        if self.gdb_cmd is None:
282            raise ValueError('Cannot debug; no gdb specified')
283        if self.elf_name is None:
284            raise ValueError('Cannot debug; no .elf specified')
285
286        pre_init_cmd = []
287        for i in self.pre_init:
288            pre_init_cmd.append("-c")
289            pre_init_cmd.append(i)
290
291        if self.thread_info_enabled and self.supports_thread_info():
292            pre_init_cmd.append("-c")
293            pre_init_cmd.append("$_TARGETNAME configure -rtos Zephyr")
294            pre_init_cmd.append("-q")
295
296        server_cmd = (self.openocd_cmd + self.serial + self.cfg_cmd +
297                      pre_init_cmd)
298
299        gdb_attach = (self.gdb_cmd + self.tui_arg +
300                   ['-ex', f'target extended-remote :{self.gdb_port}',
301                    self.elf_name, '-q'])
302
303        temp_str = '--cd=' + os.environ.get('ZEPHYR_BASE') #Go to Zephyr base Dir
304
305        # Execute First Script in Zephyr Base Dir
306        gdb_cmd = (self.gdb_cmd + self.tui_arg +
307                   [temp_str,'-ex', f'target extended-remote localhost:{self.gdb_port}' , '-batch'])
308
309        # Execute Second Script in Build Dir
310        gdb_cmd2 = (self.gdb_cmd + self.tui_arg +
311                   ['-ex', f'target extended-remote :{self.gdb_port}' , '-batch'])
312
313
314        if self.gdb_init is not None:
315            for i in self.gdb_init:
316                gdb_cmd.append("-ex")
317                gdb_cmd.append(i)
318                gdb_cmd2.append("-ex")
319                gdb_cmd2.append(i)
320
321        if self.gdb_cmds is not None:
322            for i in self.gdb_cmds:
323                gdb_cmd.append("-x")
324                gdb_cmd.append(i)
325
326        if self.gdb_cmds_deb is not None:
327            for i in self.gdb_cmds_deb:
328                gdb_cmd2.append("-x")
329                gdb_cmd2.append(i)
330
331        self.require(gdb_cmd[0])
332        self.print_gdbserver_message()
333
334        if command == 'attach':
335            self.run_server_and_client(server_cmd, gdb_attach)
336        elif command == 'debug':
337            self.run_server_and_client(server_cmd, gdb_cmd)
338            self.run_server_and_client(server_cmd, gdb_cmd2)
339            self.run_server_and_client(server_cmd, gdb_attach)
340