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