99import time
1010import traceback
1111
12+ import tty
1213import pexpect
1314import yaml
1415
@@ -308,20 +309,16 @@ def __init__(self, server_cmd=None, config=None, debug=False, timeout=60,
308309 cmd = shlex .split (server_cmd )
309310 else :
310311 cmd = ["openocd" ]
311- if debug :
312- cmd .append ("-d" )
313312
314313 # This command needs to come before any config scripts on the command
315314 # line, since they are executed in order.
316315 cmd += [
317316 # Tell OpenOCD to bind gdb to an unused, ephemeral port.
318317 "--command" , "gdb_port 0" ,
319- # We don't use the TCL server.
320- "--command" , "tcl_port disabled" ,
321- # Put the regular command prompt in stdin. Don't listen on a port
322- # because it will conflict if multiple OpenOCD instances are running
323- # at the same time.
324- "--command" , "telnet_port pipe" ,
318+ # We create a socket for OpenOCD command line (TCL-RPC)
319+ "--command" , "tcl_port 0" ,
320+ # don't use telnet
321+ "--command" , "telnet_port disabled" ,
325322 ]
326323
327324 if config :
@@ -331,6 +328,7 @@ def __init__(self, server_cmd=None, config=None, debug=False, timeout=60,
331328 sys .exit (1 )
332329
333330 cmd += ["-f" , self .config_file ]
331+
334332 if debug :
335333 cmd .append ("-d" )
336334
@@ -366,28 +364,47 @@ def __init__(self, server_cmd=None, config=None, debug=False, timeout=60,
366364 logfile .flush ()
367365
368366 self .gdb_ports = []
367+ self .tclrpc_port = None
369368 self .start (cmd , logfile , extra_env )
370369
370+ self .openocd_cli = pexpect .spawn (f"nc localhost { self .tclrpc_port } " )
371+ # TCL-RPC uses \x1a as a watermark for end of message. We set raw
372+ # pty mode to disable translation of \x1a to EOF
373+ tty .setraw (self .openocd_cli .child_fd )
374+
371375 def start (self , cmd , logfile , extra_env ):
372376 combined_env = {** os .environ , ** extra_env }
373377 # pylint: disable-next=consider-using-with
374- self .process = subprocess .Popen (cmd , stdin = subprocess . PIPE ,
378+ self .process = subprocess .Popen (cmd , stdin = None ,
375379 stdout = logfile , stderr = logfile , env = combined_env )
376380
377381 try :
378382 # Wait for OpenOCD to have made it through riscv_examine(). When
379383 # using OpenOCD to communicate with a simulator this may take a
380384 # long time, and gdb will time out when trying to connect if we
381385 # attempt too early.
382-
383386 while True :
384387 m = self .expect (
385- rb"( Listening on port (\d+) for gdb connections| "
386- rb"tcl server disabled) " ,
388+ rb"Listening on port (?P<port> \d+) for "
389+ rb"(?P< server>(?:gdb)|(?:tcl)) connections " ,
387390 message = "Waiting for OpenOCD to start up..." )
388- if b"gdb" in m .group (1 ):
389- self .gdb_ports .append (int (m .group (2 )))
390- else :
391+ if m ["server" ] == b"gdb" :
392+ self .gdb_ports .append (int (m ["port" ]))
393+ elif m ["server" ] == b"tcl" :
394+ if self .tclrpc_port :
395+ raise TestLibError (
396+ "unexpected re-definition of TCL-RPC port" )
397+ self .tclrpc_port = int (m ["port" ])
398+ # WARNING! WARNING! WARNING!
399+ # The condition below works properly only if OpenOCD reports
400+ # gdb/tcl ports in a specific order. Namely, it requires the
401+ # gdb ports to be reported before the tcl one. At the moment
402+ # this comment was written OpenOCD reports these ports in the
403+ # required order if we have a call to `init` statement in
404+ # either target configuration file or command-line parameter.
405+ # All configuration files used in testing include a call to
406+ # `init`
407+ if self .tclrpc_port and (len (self .gdb_ports ) > 0 ):
391408 break
392409
393410 if self .debug_openocd :
@@ -420,20 +437,12 @@ def smp(self):
420437 return False
421438
422439 def command (self , cmd ):
423- """Write the command to OpenOCD's stdin. Return the output of the
424- command, minus the prompt."""
425- self .process .stdin .write (f"{ cmd } \n " .encode ())
426- self .process .stdin .flush ()
427- m = self .expect (re .escape (f"{ cmd } \n " .encode ()))
428-
429- # The prompt isn't flushed to the log, so send a unique command that
430- # lets us find where output of the last command ends.
431- magic = f"# { self .command_count } x" .encode ()
432- self .command_count += 1
433- self .process .stdin .write (magic + b"\n " )
434- self .process .stdin .flush ()
435- m = self .expect (rb"(.*)^>\s*" + re .escape (magic ))
436- return m .group (1 )
440+ """Send the command to OpenOCD's TCL-RPC server. Return the output of
441+ the command, minus the prompt."""
442+ self .openocd_cli .write (f"{ cmd } \n \x1a " )
443+ self .openocd_cli .expect (rb"(.*)\x1a" )
444+ m = self .openocd_cli .match .group (1 )
445+ return m
437446
438447 def expect (self , regex , message = None ):
439448 """Wait for the regex to match the log, and return the match object. If
0 commit comments