From 7bbe4746761771e3e6a9ca62346d3324a1e7f297 Mon Sep 17 00:00:00 2001 From: Chris Friedt Date: Sat, 9 Nov 2024 10:56:04 -0500 Subject: [PATCH] west: runners: update the rtt implementation for openocd Previously, rtt start would always fail because the target (i.e. firmware) had not been started. ``` Info : rtt: Searching for control block 'SEGGER RTT' Info : rtt: No control block found ``` When the command is 'rtt', load and run the binary via gdb before calling 'rtt start'. Firmware calls `SEGGER_RTT_Init()` shortly after init which allows OpenOCD to find the RTT control block. Similarly, only start the 'rtt server' after calling 'rtt start' to avoid any potential race conditions internally within OpenOCD. Signed-off-by: Chris Friedt --- scripts/west_commands/runners/openocd.py | 136 ++++++++++------------- 1 file changed, 56 insertions(+), 80 deletions(-) diff --git a/scripts/west_commands/runners/openocd.py b/scripts/west_commands/runners/openocd.py index 2569371ff9b305..460a56b644b603 100644 --- a/scripts/west_commands/runners/openocd.py +++ b/scripts/west_commands/runners/openocd.py @@ -7,9 +7,7 @@ '''Runner for openocd.''' import re -import socket import subprocess -import time from os import path from pathlib import Path @@ -29,6 +27,19 @@ DEFAULT_OPENOCD_RESET_HALT_CMD = 'reset init' DEFAULT_OPENOCD_TARGET_HANDLE = "_TARGETNAME" + +def to_num(number): + dev_match = re.search(r"^\d*\+dev", number) + dev_version = dev_match is not None + + num_match = re.search(r"^\d*", number) + num = int(num_match.group(0)) + + if dev_version: + num += 1 + + return num + class OpenOcdBinaryRunner(ZephyrBinaryRunner): '''Runner front-end for openocd.''' @@ -200,18 +211,8 @@ def print_gdbserver_message(self): self.logger.info('OpenOCD GDB server running on port ' f'{self.gdb_port}{thread_msg}') - # pylint: disable=R0201 - def to_num(self, number): - dev_match = re.search(r"^\d*\+dev", number) - dev_version = not dev_match is None - - num_match = re.search(r"^\d*", number) - num = int(num_match.group(0)) - - if dev_version: - num += 1 - - return num + def print_rttserver_message(self): + self.logger.info(f'OpenOCD RTT server running on port {self.rtt_port}') def read_version(self): self.require(self.openocd_cmd[0]) @@ -223,7 +224,7 @@ def read_version(self): version_match = re.search(r"Open On-Chip Debugger (\d+.\d+.\d+)", out) version = version_match.group(1).split('.') - return [self.to_num(i) for i in version] + return [to_num(i) for i in version] def supports_thread_info(self): # Zephyr rtos was introduced after 0.11.0 @@ -246,12 +247,10 @@ def do_run(self, command, **kwargs): self.do_flash_elf(**kwargs) elif command == 'flash': self.do_flash(**kwargs) - elif command in ('attach', 'debug'): - self.do_attach_debug(command, **kwargs) + elif command in ('attach', 'debug', 'rtt'): + self.do_attach_debug_rtt(command, **kwargs) elif command == 'load': self.do_load(**kwargs) - elif command == 'rtt': - self.do_rtt(**kwargs) else: self.do_debugserver(**kwargs) @@ -344,7 +343,7 @@ def do_flash_elf(self, **kwargs): self.check_call(cmd) - def do_attach_debug(self, command, **kwargs): + def do_attach_debug_rtt(self, command, **kwargs): if self.gdb_cmd is None: raise ValueError('Cannot debug; no gdb specified') if self.elf_name is None: @@ -369,16 +368,51 @@ def do_attach_debug(self, command, **kwargs): gdb_cmd = (self.gdb_cmd + self.tui_arg + ['-ex', f'target extended-remote :{self.gdb_client_port}', self.elf_name]) - if command == 'debug': + + if command in ('debug', 'rtt'): gdb_cmd.extend(self.load_arg) if self.gdb_init is not None: for i in self.gdb_init: gdb_cmd.append("-ex") gdb_cmd.append(i) + if command == 'rtt': + rtt_address = self.get_rtt_address() + if rtt_address is None: + raise ValueError("RTT Control block not be found") + + # gdb is not intended to be used interactively with rtt. It is only used to load the + # binary, so we cannot prompt the user to press return to see more text. Disable + # pagination for rtt. + gdb_cmd.extend(['-ex', 'set pagination off']) + + # start the rtt server via gdb monitor commands (which are passed to openocd) + gdb_cmd.extend( + ['-ex', f'monitor rtt setup 0x{rtt_address:x} 0x10 "SEGGER RTT"']) + gdb_cmd.extend(['-ex', 'monitor reset run']) + gdb_cmd.extend(['-ex', 'monitor rtt start']) + gdb_cmd.extend( + ['-ex', f'monitor rtt server start {self.rtt_port} 0']) + + # detach from the target and quit the gdb session after running the above commands + # so that the rtt client can connect to the server + gdb_cmd.extend(['-ex', 'detach', '-ex', 'quit']) self.require(gdb_cmd[0]) self.print_gdbserver_message() - self.run_server_and_client(server_cmd, gdb_cmd) + + if command in ('attach', 'debug'): + self.run_server_and_client(server_cmd, gdb_cmd) + elif command == 'rtt': + self.print_rttserver_message() + server_proc = self.popen_ignore_int(server_cmd) + try: + # gdb is still necessary to load, setup rtt, etc, but it wll be detached and will + # quit so that rtt client can take over. + subprocess.run(gdb_cmd) + self.run_telnet_client('localhost', self.rtt_port) + finally: + server_proc.terminate() + server_proc.wait() def do_debugserver(self, **kwargs): pre_init_cmd = [] @@ -399,61 +433,3 @@ def do_debugserver(self, **kwargs): ['-c', self.reset_halt_cmd]) self.print_gdbserver_message() self.check_call(cmd) - - def do_rtt(self, **kwargs): - pre_init_cmd = [] - for i in self.pre_init: - pre_init_cmd.append("-c") - pre_init_cmd.append(i) - - if self.thread_info_enabled and self.supports_thread_info(): - pre_init_cmd.append("-c") - rtos_command = f'${self.target_handle} configure -rtos Zephyr' - pre_init_cmd.append(rtos_command) - - rtt_address = self.get_rtt_address() - if rtt_address is None: - raise ValueError("RTT Control block not be found") - - rtt_cmds = [ - '-c', f'rtt setup 0x{rtt_address:x} 0x10 "SEGGER RTT"', - '-c', f'rtt server start {self.rtt_port} 0', - '-c', 'rtt start', - ] - - server_cmd = (self.openocd_cmd + self.cfg_cmd + - ['-c', f'tcl_port {self.tcl_port}', - '-c', f'telnet_port {self.telnet_port}', - '-c', f'gdb_port {self.gdb_port}'] + - pre_init_cmd + self.init_arg + self.targets_arg + - ['-c', self.reset_halt_cmd] + - rtt_cmds - ) - self.print_gdbserver_message() - server_proc = self.popen_ignore_int(server_cmd) - # The target gets halted after all commands passed on the commandline are run. - # The only way to run resume here, to not have to connect a GDB, is to connect - # to the tcl port and run the command. When the TCL port comes up, initialization - # is done. - try: - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - # As long as the server process is still running, keep retrying the connection - while server_proc.poll() is None: - try: - sock.connect(('localhost', self.tcl_port)) - break - except ConnectionRefusedError: - time.sleep(0.1) - # \x1a is the command terminator for the openocd tcl rpc - sock.send(b'resume\x1a') - sock.shutdown(socket.SHUT_RDWR) - # Run the client. Since rtt is initialized before the tcl rpc comes up, - # the port is open now. - self.logger.info("Opening RTT") - time.sleep(0.1) # Give the server a moment to output log messages first - self.run_telnet_client('localhost', self.rtt_port) - except Exception as e: - self.logger.error(e) - finally: - server_proc.terminate() - server_proc.wait()