Skip to content

Commit 18f45b5

Browse files
topisaninashif
authored andcommitted
west: runners: Implement the west rtt command for openocd
This was non-trivial, as openocd is a bit weird to work with. Using only commands passed with '-c' arguments, I couldn't get it to reliably resume (or just not halt) the target when started. I tried using the 'sleep' command, and various 'configure -event XX { resume }' events, but nothing panned out, as it seems to always halt after all `-c` commands have been run. To avoid that, this waits for the TCL RPC port to be up, and sends a resume command there. This works reliably. Signed-off-by: Tobias Pisani <[email protected]>
1 parent b3b8360 commit 18f45b5

File tree

1 file changed

+113
-14
lines changed

1 file changed

+113
-14
lines changed

scripts/west_commands/runners/openocd.py

Lines changed: 113 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,13 @@
66

77
'''Runner for openocd.'''
88

9-
import subprocess
109
import re
10+
import selectors
11+
import shutil
12+
import socket
13+
import subprocess
14+
import sys
15+
import time
1116

1217
from os import path
1318
from pathlib import Path
@@ -23,6 +28,7 @@
2328
DEFAULT_OPENOCD_TCL_PORT = 6333
2429
DEFAULT_OPENOCD_TELNET_PORT = 4444
2530
DEFAULT_OPENOCD_GDB_PORT = 3333
31+
DEFAULT_OPENOCD_RTT_PORT = 5555
2632
DEFAULT_OPENOCD_RESET_HALT_CMD = 'reset init'
2733
DEFAULT_OPENOCD_TARGET_HANDLE = "_TARGETNAME"
2834

@@ -39,7 +45,8 @@ def __init__(self, cfg, pre_init=None, reset_halt_cmd=DEFAULT_OPENOCD_RESET_HALT
3945
gdb_port=DEFAULT_OPENOCD_GDB_PORT,
4046
gdb_client_port=DEFAULT_OPENOCD_GDB_PORT,
4147
gdb_init=None, no_load=False,
42-
target_handle=DEFAULT_OPENOCD_TARGET_HANDLE):
48+
target_handle=DEFAULT_OPENOCD_TARGET_HANDLE,
49+
rtt_port=DEFAULT_OPENOCD_RTT_PORT):
4350
super().__init__(cfg)
4451

4552
if not path.exists(cfg.board_dir):
@@ -97,14 +104,15 @@ def __init__(self, cfg, pre_init=None, reset_halt_cmd=DEFAULT_OPENOCD_RESET_HALT
97104
self.gdb_init = gdb_init
98105
self.load_arg = [] if no_load else ['-ex', 'load']
99106
self.target_handle = target_handle
107+
self.rtt_port = rtt_port
100108

101109
@classmethod
102110
def name(cls):
103111
return 'openocd'
104112

105113
@classmethod
106114
def capabilities(cls):
107-
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'})
115+
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach', 'rtt'}, rtt=True)
108116

109117
@classmethod
110118
def do_add_parser(cls, parser):
@@ -165,6 +173,8 @@ def do_add_parser(cls, parser):
165173
help=f'''Internal handle used in openocd targets cfg
166174
files, defaults to "{DEFAULT_OPENOCD_TARGET_HANDLE}".
167175
''')
176+
parser.add_argument('--rtt-port', default=DEFAULT_OPENOCD_RTT_PORT,
177+
help='openocd rtt port, defaults to 5555')
168178

169179

170180
@classmethod
@@ -180,7 +190,8 @@ def do_create(cls, cfg, args):
180190
no_targets=args.no_targets, tcl_port=args.tcl_port,
181191
telnet_port=args.telnet_port, gdb_port=args.gdb_port,
182192
gdb_client_port=args.gdb_client_port, gdb_init=args.gdb_init,
183-
no_load=args.no_load, target_handle=args.target_handle)
193+
no_load=args.no_load, target_handle=args.target_handle,
194+
rtt_port=args.rtt_port)
184195

185196
def print_gdbserver_message(self):
186197
if not self.thread_info_enabled:
@@ -242,6 +253,8 @@ def do_run(self, command, **kwargs):
242253
self.do_attach_debug(command, **kwargs)
243254
elif command == 'load':
244255
self.do_load(**kwargs)
256+
elif command == 'rtt':
257+
self.do_rtt(**kwargs)
245258
else:
246259
self.do_debugserver(**kwargs)
247260

@@ -256,7 +269,7 @@ def do_flash(self, **kwargs):
256269
# them to POSIX style just to be sure.
257270
hex_name = Path(self.cfg.hex_file).as_posix()
258271

259-
self.logger.info('Flashing file: {}'.format(hex_name))
272+
self.logger.info(f'Flashing file: {hex_name}')
260273

261274
pre_init_cmd = []
262275
pre_load_cmd = []
@@ -347,17 +360,17 @@ def do_attach_debug(self, command, **kwargs):
347360

348361
if self.thread_info_enabled and self.supports_thread_info():
349362
pre_init_cmd.append("-c")
350-
rtos_command = '${} configure -rtos Zephyr'.format(self.target_handle)
363+
rtos_command = f'${self.target_handle} configure -rtos Zephyr'
351364
pre_init_cmd.append(rtos_command)
352365

353366
server_cmd = (self.openocd_cmd + self.serial + self.cfg_cmd +
354-
['-c', 'tcl_port {}'.format(self.tcl_port),
355-
'-c', 'telnet_port {}'.format(self.telnet_port),
356-
'-c', 'gdb_port {}'.format(self.gdb_port)] +
367+
['-c', f'tcl_port {self.tcl_port}',
368+
'-c', f'telnet_port {self.telnet_port}',
369+
'-c', f'gdb_port {self.gdb_port}'] +
357370
pre_init_cmd + self.init_arg + self.targets_arg +
358371
self.halt_arg)
359372
gdb_cmd = (self.gdb_cmd + self.tui_arg +
360-
['-ex', 'target extended-remote :{}'.format(self.gdb_client_port),
373+
['-ex', f'target extended-remote :{self.gdb_client_port}',
361374
self.elf_name])
362375
if command == 'debug':
363376
gdb_cmd.extend(self.load_arg)
@@ -378,14 +391,100 @@ def do_debugserver(self, **kwargs):
378391

379392
if self.thread_info_enabled and self.supports_thread_info():
380393
pre_init_cmd.append("-c")
381-
rtos_command = '${} configure -rtos Zephyr'.format(self.target_handle)
394+
rtos_command = f'${self.target_handle} configure -rtos Zephyr'
382395
pre_init_cmd.append(rtos_command)
383396

384397
cmd = (self.openocd_cmd + self.cfg_cmd +
385-
['-c', 'tcl_port {}'.format(self.tcl_port),
386-
'-c', 'telnet_port {}'.format(self.telnet_port),
387-
'-c', 'gdb_port {}'.format(self.gdb_port)] +
398+
['-c', f'tcl_port {self.tcl_port}',
399+
'-c', f'telnet_port {self.telnet_port}',
400+
'-c', f'gdb_port {self.gdb_port}'] +
388401
pre_init_cmd + self.init_arg + self.targets_arg +
389402
['-c', self.reset_halt_cmd])
390403
self.print_gdbserver_message()
391404
self.check_call(cmd)
405+
406+
def do_rtt(self, **kwargs):
407+
pre_init_cmd = []
408+
for i in self.pre_init:
409+
pre_init_cmd.append("-c")
410+
pre_init_cmd.append(i)
411+
412+
if self.thread_info_enabled and self.supports_thread_info():
413+
pre_init_cmd.append("-c")
414+
rtos_command = f'${self.target_handle} configure -rtos Zephyr'
415+
pre_init_cmd.append(rtos_command)
416+
417+
rtt_address = self.get_rtt_address()
418+
if rtt_address is None:
419+
raise ValueError("RTT Control block not be found")
420+
421+
rtt_cmds = [
422+
'-c', f'rtt setup 0x{rtt_address:x} 0x10 "SEGGER RTT"',
423+
'-c', f'rtt server start {self.rtt_port} 0',
424+
'-c', 'rtt start',
425+
]
426+
427+
server_cmd = (self.openocd_cmd + self.cfg_cmd +
428+
['-c', f'tcl_port {self.tcl_port}',
429+
'-c', f'telnet_port {self.telnet_port}',
430+
'-c', f'gdb_port {self.gdb_port}'] +
431+
pre_init_cmd + self.init_arg + self.targets_arg +
432+
['-c', self.reset_halt_cmd] +
433+
rtt_cmds
434+
)
435+
self.print_gdbserver_message()
436+
server_proc = self.popen_ignore_int(server_cmd)
437+
# The target gets halted after all commands passed on the commandline are run.
438+
# The only way to run resume here, to not have to connect a GDB, is to connect
439+
# to the tcl port and run the command. When the TCL port comes up, initialization
440+
# is done.
441+
try:
442+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
443+
# As long as the server process is still running, keep retrying the connection
444+
while server_proc.poll() is None:
445+
try:
446+
sock.connect(('localhost', self.tcl_port))
447+
break
448+
except ConnectionRefusedError:
449+
time.sleep(0.1)
450+
# \x1a is the command terminator for the openocd tcl rpc
451+
sock.send(b'resume\x1a')
452+
sock.shutdown(socket.SHUT_RDWR)
453+
# Run the client. Since rtt is initialized before the tcl rpc comes up,
454+
# the port is open now.
455+
self.logger.info("Opening RTT")
456+
time.sleep(0.1) # Give the server a moment to output log messages first
457+
self._run_rtt_client()
458+
except Exception as e:
459+
self.logger.error(e)
460+
finally:
461+
server_proc.terminate()
462+
server_proc.wait()
463+
464+
def _run_rtt_client(self):
465+
# If a `nc` command is available, run it, as it will provide the best support for
466+
# CONFIG_SHELL_VT100_COMMANDS etc.
467+
if shutil.which('nc') is not None:
468+
client_cmd = ['nc', 'localhost', str(self.rtt_port)]
469+
self.run_client(client_cmd)
470+
return
471+
472+
# Otherwise, use a pure python implementation. This will work well for logging,
473+
# but input is line based only.
474+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
475+
sock.connect(('localhost', self.rtt_port))
476+
sel = selectors.DefaultSelector()
477+
sel.register(sys.stdin, selectors.EVENT_READ)
478+
sel.register(sock, selectors.EVENT_READ)
479+
while True:
480+
events = sel.select()
481+
for key, _ in events:
482+
if key.fileobj == sys.stdin:
483+
text = sys.stdin.readline()
484+
if text:
485+
sock.send(text.encode())
486+
487+
elif key.fileobj == sock:
488+
resp = sock.recv(2048)
489+
if resp:
490+
print(resp.decode())

0 commit comments

Comments
 (0)