Skip to content

Commit

Permalink
west: runners: update the rtt implementation for openocd
Browse files Browse the repository at this point in the history
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 <cfriedt@tenstorrent.com>
  • Loading branch information
Chris Friedt committed Nov 15, 2024
1 parent 0fa8731 commit 7bbe474
Showing 1 changed file with 56 additions and 80 deletions.
136 changes: 56 additions & 80 deletions scripts/west_commands/runners/openocd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
'''Runner for openocd.'''

import re
import socket
import subprocess
import time

from os import path
from pathlib import Path
Expand All @@ -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.'''

Expand Down Expand Up @@ -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])
Expand All @@ -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
Expand All @@ -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)

Expand Down Expand Up @@ -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:
Expand All @@ -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 = []
Expand All @@ -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()

0 comments on commit 7bbe474

Please sign in to comment.