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)