66
77'''Runner for openocd.'''
88
9- import subprocess
109import re
10+ import selectors
11+ import shutil
12+ import socket
13+ import subprocess
14+ import sys
15+ import time
1116
1217from os import path
1318from pathlib import Path
2328DEFAULT_OPENOCD_TCL_PORT = 6333
2429DEFAULT_OPENOCD_TELNET_PORT = 4444
2530DEFAULT_OPENOCD_GDB_PORT = 3333
31+ DEFAULT_OPENOCD_RTT_PORT = 5555
2632DEFAULT_OPENOCD_RESET_HALT_CMD = 'reset init'
2733DEFAULT_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