Skip to content

Commit

Permalink
west: Add west rtt command
Browse files Browse the repository at this point in the history
Currently only implemented in the pyocd runner, awaiting feedback.

This command runs separately from a debug session. We should
probably also support attaching a rtt client to a running
debugserver. However, this might work differently for each runner.
pyocd is easy, as it supports a separate RTT command, while openocd
and jlink will always need a running server.

See the FIXME's for other points i would like input on.

Signed-off-by: Tobias Pisani <[email protected]>
  • Loading branch information
topisani committed Sep 9, 2024
1 parent 669be61 commit e6256de
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 6 deletions.
3 changes: 3 additions & 0 deletions scripts/west-commands.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ west-commands:
- name: attach
class: Attach
help: interactively debug a board
- name: rtt
class: Rtt
help: open an rtt shell
- file: scripts/west_commands/export.py
commands:
- name: zephyr-export
Expand Down
18 changes: 18 additions & 0 deletions scripts/west_commands/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,21 @@ def do_add_parser(self, parser_adder):

def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args)


class Rtt(WestCommand):

def __init__(self):
super(Rtt, self).__init__(
'rtt',
# Keep this in sync with the string in west-commands.yml.
'open an rtt shell',
"",
accepts_unknown_args=True)
self.runner_key = 'rtt-runner' # in runners.yaml

def do_add_parser(self, parser_adder):
return add_parser_common(self, parser_adder)

def do_run(self, my_args, runner_args):
do_run_common(self, my_args, runner_args)
3 changes: 2 additions & 1 deletion scripts/west_commands/run_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -628,7 +628,8 @@ def filetype(attr):
filetype('file_type'),
config('gdb'),
config('openocd'),
config('openocd_search', []))
config('openocd_search', []),
config('rtt_address'))

def dump_traceback():
# Save the current exception to a file and return its path.
Expand Down
60 changes: 58 additions & 2 deletions scripts/west_commands/runners/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@
from typing import Dict, List, NamedTuple, NoReturn, Optional, Set, Type, \
Union

try:
from elftools.elf.elffile import ELFFile
ELFTOOLS_MISSING = False
except ImportError:
ELFTOOLS_MISSING = True


# Turn on to enable just logging the commands that would be run (at
# info rather than debug level), without actually running them. This
# can break runners that are expecting output or if one command
Expand All @@ -37,6 +44,27 @@

_logger = logging.getLogger('runners')

# FIXME: I assume this code belongs somewhere else, but i couldn't figure out
# a good location for it, so i put it here for now
# We could potentially search for RTT blocks in hex or bin files as well,
# but since the magic string is "SEGGER RTT", i thought it might be better
# to avoid, at the risk of false positives.
def find_rtt_block(elf_file: str) -> Optional[int]:
if ELFTOOLS_MISSING:
raise RuntimeError('the Python dependency elftools was missing; '
'see the getting started guide for details on '
'how to fix')

with open(elf_file, 'rb') as f:
elffile = ELFFile(f)
for sect in elffile.iter_sections('SHT_SYMTAB'):
symbols = sect.get_symbol_by_name('_SEGGER_RTT')
if symbols is None:
continue
for s in symbols:
return s.entry.get('st_value')
return None


class _DebugDummyPopen:

Expand Down Expand Up @@ -219,7 +247,7 @@ def __init__(self, program):
super().__init__(errno.ENOENT, os.strerror(errno.ENOENT), program)


_RUNNERCAPS_COMMANDS = {'flash', 'debug', 'debugserver', 'attach', 'simulate', 'robot'}
_RUNNERCAPS_COMMANDS = {'flash', 'debug', 'debugserver', 'attach', 'simulate', 'robot', 'rtt'}

@dataclass
class RunnerCaps:
Expand All @@ -231,7 +259,7 @@ class RunnerCaps:
Available capabilities:
- commands: set of supported commands; default is {'flash',
'debug', 'debugserver', 'attach', 'simulate', 'robot'}.
'debug', 'debugserver', 'attach', 'simulate', 'robot', 'rtt'}.
- dev_id: whether the runner supports device identifiers, in the form of an
-i, --dev-id option. This is useful when the user has multiple debuggers
Expand Down Expand Up @@ -268,6 +296,9 @@ class RunnerCaps:
discovered in the build directory.
- hide_load_files: whether the elf/hex/bin file arguments should be hidden.
- rtt: whether the runner supports SEGGER RTT. This adds a --rtt-address
option.
'''

commands: Set[str] = field(default_factory=lambda: set(_RUNNERCAPS_COMMANDS))
Expand All @@ -279,6 +310,8 @@ class RunnerCaps:
tool_opt: bool = False
file: bool = False
hide_load_files: bool = False
rtt: bool = False # This capability exists separately from the rtt command
# to allow other commands to use the rtt address

def __post_init__(self):
if not self.commands.issubset(_RUNNERCAPS_COMMANDS):
Expand Down Expand Up @@ -319,6 +352,7 @@ class RunnerConfig(NamedTuple):
gdb: Optional[str] = None # path to a usable gdb
openocd: Optional[str] = None # path to a usable openocd
openocd_search: List[str] = [] # add these paths to the openocd search path
rtt_address: Optional[int] = None # address of the rtt control block


_YN_CHOICES = ['Y', 'y', 'N', 'n', 'yes', 'no', 'YES', 'NO']
Expand Down Expand Up @@ -572,6 +606,13 @@ def add_parser(cls, parser):
help=(cls.tool_opt_help() if caps.tool_opt
else argparse.SUPPRESS))

if caps.rtt:
parser.add_argument('--rtt-address', dest='rtt_address',
type=lambda x: int(x, 0),
help="address of RTT control block. If not supplied, it will be autodetected if possible")
else:
parser.add_argument('--rtt-address', help=argparse.SUPPRESS)

# Runner-specific options.
cls.do_add_parser(parser)

Expand Down Expand Up @@ -607,6 +648,8 @@ def create(cls, cfg: RunnerConfig,
raise ValueError("--file-type requires --file")
if args.file_type and not caps.file:
_missing_cap(cls, '--file-type')
if args.rtt_address and not caps.rtt:
_missing_cap(cls, '--rtt-address')

ret = cls.do_create(cfg, args)
if args.erase:
Expand Down Expand Up @@ -731,6 +774,19 @@ def require(program: str, path: Optional[str] = None) -> str:
raise MissingProgram(program)
return ret

def get_rtt_address(self) -> int | None:
'''Helper method for extracting a the RTT control block address.
If args.rtt_address was supplied, returns that.
Otherwise, attempt to locate an rtt block in the elf file.
If this is not found, None is returned'''
if self.cfg.rtt_address is not None:
return self.cfg.rtt_address
elif self.cfg.elf_file is not None:
return find_rtt_block(self.cfg.elf_file)
return None

def run_server_and_client(self, server, client, **kwargs):
'''Run a server that ignores SIGINT, and a client that handles it.
Expand Down
28 changes: 25 additions & 3 deletions scripts/west_commands/runners/pyocd.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,9 @@ def name(cls):

@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach'},
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach', 'rtt'},
dev_id=True, flash_addr=True, erase=True,
tool_opt=True)
tool_opt=True, rtt=True)

@classmethod
def dev_id_help(cls) -> str:
Expand Down Expand Up @@ -142,7 +142,9 @@ def port_args(self):

def do_run(self, command, **kwargs):
self.require(self.pyocd)
if command == 'flash':
if command == 'rtt':
self.rtt(**kwargs)
elif command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
Expand Down Expand Up @@ -214,3 +216,23 @@ def debug_debugserver(self, command, **kwargs):
self.require(client_cmd[0])
self.log_gdbserver_message()
self.run_server_and_client(server_cmd, client_cmd)


def rtt(self):
rtt_addr = self.get_rtt_address()
if rtt_addr is None:
raise ValueError('RTT control block not found')

self.logger.debug(f'rtt address: 0x{rtt_addr:x}')

cmd = ([self.pyocd] +
['rtt'] +
self.pyocd_config_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.tool_opt_args +
['-a', f'0x{rtt_addr:x}'])

self.check_call(cmd)

0 comments on commit e6256de

Please sign in to comment.