From b3b8360f39930d14316fab9981798e5f004f578c Mon Sep 17 00:00:00 2001 From: Tobias Pisani Date: Fri, 5 Jul 2024 12:42:12 +0200 Subject: [PATCH] west: runners: Add `west rtt` command with pyocd implementation This command runs separately from a debug server, instead of attaching to a running server. This is both the easiest out of the box experience, and also should be possible to implement consistently for most runners. This commit includes an initial implementation for pyocd. Signed-off-by: Tobias Pisani --- scripts/west-commands.yml | 3 ++ scripts/west_commands/debug.py | 18 ++++++++ scripts/west_commands/run_common.py | 3 +- scripts/west_commands/runners/core.py | 60 +++++++++++++++++++++++++- scripts/west_commands/runners/pyocd.py | 28 ++++++++++-- 5 files changed, 106 insertions(+), 6 deletions(-) diff --git a/scripts/west-commands.yml b/scripts/west-commands.yml index 6b7835e7562..aceebef092d 100644 --- a/scripts/west-commands.yml +++ b/scripts/west-commands.yml @@ -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 diff --git a/scripts/west_commands/debug.py b/scripts/west_commands/debug.py index 288363d2e0b..156222aa332 100644 --- a/scripts/west_commands/debug.py +++ b/scripts/west_commands/debug.py @@ -74,3 +74,21 @@ class Attach(WestCommand): 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) diff --git a/scripts/west_commands/run_common.py b/scripts/west_commands/run_common.py index 581e848b5d3..772d736c183 100644 --- a/scripts/west_commands/run_common.py +++ b/scripts/west_commands/run_common.py @@ -628,7 +628,8 @@ def get_runner_config(build_dir, yaml_path, runners_yaml, args=None): 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. diff --git a/scripts/west_commands/runners/core.py b/scripts/west_commands/runners/core.py index 10e740a4230..eea2a6c486e 100644 --- a/scripts/west_commands/runners/core.py +++ b/scripts/west_commands/runners/core.py @@ -29,6 +29,13 @@ from inspect import isabstract 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 @@ -37,6 +44,27 @@ _DRY_RUN = False _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: @@ -219,7 +247,7 @@ class MissingProgram(FileNotFoundError): 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: @@ -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 @@ -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)) @@ -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): @@ -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'] @@ -572,6 +606,13 @@ class ZephyrBinaryRunner(abc.ABC): 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) @@ -607,6 +648,8 @@ class ZephyrBinaryRunner(abc.ABC): 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: @@ -731,6 +774,19 @@ class ZephyrBinaryRunner(abc.ABC): 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. diff --git a/scripts/west_commands/runners/pyocd.py b/scripts/west_commands/runners/pyocd.py index d754291d915..40519c6f2c1 100644 --- a/scripts/west_commands/runners/pyocd.py +++ b/scripts/west_commands/runners/pyocd.py @@ -76,9 +76,9 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner): @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: @@ -142,7 +142,9 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner): 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) @@ -214,3 +216,23 @@ class PyOcdBinaryRunner(ZephyrBinaryRunner): 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)