zephyr/scripts/west_commands/runners/pyocd.py
Tobias Pisani b3b8360f39 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 <mail@topisani.dev>
2024-09-10 12:39:42 -04:00

239 lines
8.4 KiB
Python

# Copyright (c) 2017 Linaro Limited.
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for pyOCD .'''
import os
from os import path
from runners.core import ZephyrBinaryRunner, RunnerCaps, BuildConfiguration
DEFAULT_PYOCD_GDB_PORT = 3333
DEFAULT_PYOCD_TELNET_PORT = 4444
class PyOcdBinaryRunner(ZephyrBinaryRunner):
'''Runner front-end for pyOCD.'''
def __init__(self, cfg, target,
pyocd='pyocd',
dev_id=None, flash_addr=0x0, erase=False, flash_opts=None,
gdb_port=DEFAULT_PYOCD_GDB_PORT,
telnet_port=DEFAULT_PYOCD_TELNET_PORT, tui=False,
pyocd_config=None,
daparg=None, frequency=None, tool_opt=None):
super().__init__(cfg)
default = path.join(cfg.board_dir, 'support', 'pyocd.yaml')
if path.exists(default):
self.pyocd_config = default
else:
self.pyocd_config = None
self.target_args = ['-t', target]
self.pyocd = pyocd
self.flash_addr_args = ['-a', hex(flash_addr)] if flash_addr else []
self.erase = erase
self.gdb_cmd = [cfg.gdb] if cfg.gdb is not None else None
self.gdb_port = gdb_port
self.telnet_port = telnet_port
self.tui_args = ['-tui'] if tui else []
self.hex_name = cfg.hex_file
self.bin_name = cfg.bin_file
self.elf_name = cfg.elf_file
pyocd_config_args = []
if self.pyocd_config is not None:
pyocd_config_args = ['--config', self.pyocd_config]
self.pyocd_config_args = pyocd_config_args
board_args = []
if dev_id is not None:
board_args = ['-u', dev_id]
self.board_args = board_args
daparg_args = []
if daparg is not None:
daparg_args = ['-da', daparg]
self.daparg_args = daparg_args
frequency_args = []
if frequency is not None:
frequency_args = ['-f', frequency]
self.frequency_args = frequency_args
self.tool_opt_args = tool_opt or []
self.flash_extra = flash_opts if flash_opts else []
@classmethod
def name(cls):
return 'pyocd'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'debugserver', 'attach', 'rtt'},
dev_id=True, flash_addr=True, erase=True,
tool_opt=True, rtt=True)
@classmethod
def dev_id_help(cls) -> str:
return '''Device identifier. Use it to select the probe's unique ID
or substring thereof.'''
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--target', required=True,
help='target override')
parser.add_argument('--daparg',
help='Additional -da arguments to pyocd tool')
parser.add_argument('--pyocd', default='pyocd',
help='path to pyocd tool, default is pyocd')
parser.add_argument('--flash-opt', default=[], action='append',
help='''Additional options for pyocd flash,
e.g. --flash-opt="-e=chip" to chip erase''')
parser.add_argument('--frequency',
help='SWD clock frequency in Hz')
parser.add_argument('--gdb-port', default=DEFAULT_PYOCD_GDB_PORT,
help='pyocd gdb port, defaults to {}'.format(
DEFAULT_PYOCD_GDB_PORT))
parser.add_argument('--telnet-port', default=DEFAULT_PYOCD_TELNET_PORT,
help='pyocd telnet port, defaults to {}'.format(
DEFAULT_PYOCD_TELNET_PORT))
parser.add_argument('--tui', default=False, action='store_true',
help='if given, GDB uses -tui')
parser.add_argument('--board-id', dest='dev_id',
help='obsolete synonym for -i/--dev-id')
@classmethod
def tool_opt_help(cls) -> str:
return """Additional options for pyocd commander,
e.g. '--script=user.py'"""
@classmethod
def do_create(cls, cfg, args):
build_conf = BuildConfiguration(cfg.build_dir)
flash_addr = cls.get_flash_address(args, build_conf)
ret = PyOcdBinaryRunner(
cfg, args.target,
pyocd=args.pyocd,
flash_addr=flash_addr, erase=args.erase, flash_opts=args.flash_opt,
gdb_port=args.gdb_port, telnet_port=args.telnet_port, tui=args.tui,
dev_id=args.dev_id, daparg=args.daparg,
frequency=args.frequency,
tool_opt=args.tool_opt)
daparg = os.environ.get('PYOCD_DAPARG')
if not ret.daparg_args and daparg:
ret.logger.warning('PYOCD_DAPARG is deprecated; use --daparg')
ret.logger.debug('--daparg={} via PYOCD_DAPARG'.format(daparg))
ret.daparg_args = ['-da', daparg]
return ret
def port_args(self):
return ['-p', str(self.gdb_port), '-T', str(self.telnet_port)]
def do_run(self, command, **kwargs):
self.require(self.pyocd)
if command == 'rtt':
self.rtt(**kwargs)
elif command == 'flash':
self.flash(**kwargs)
else:
self.debug_debugserver(command, **kwargs)
def flash(self, **kwargs):
# Use hex, bin or elf file provided by the buildsystem.
# Preferring .hex over .bin and .elf
if self.hex_name is not None and os.path.isfile(self.hex_name):
fname = self.hex_name
# Preferring .bin over .elf
elif self.bin_name is not None and os.path.isfile(self.bin_name):
fname = self.bin_name
elif self.elf_name is not None and os.path.isfile(self.elf_name):
fname = self.elf_name
else:
raise ValueError(
'Cannot flash; no hex ({}), bin ({}) or elf ({}) files found. '.format(
self.hex_name, self.bin_name, self.elf_name))
erase_method = 'chip' if self.erase else 'sector'
cmd = ([self.pyocd] +
['flash'] +
self.pyocd_config_args +
['-e', erase_method] +
self.flash_addr_args +
self.daparg_args +
self.target_args +
self.board_args +
self.frequency_args +
self.tool_opt_args +
self.flash_extra +
[fname])
self.logger.info('Flashing file: {}'.format(fname))
self.check_call(cmd)
def log_gdbserver_message(self):
self.logger.info('pyOCD GDB server running on port {}'.
format(self.gdb_port))
def debug_debugserver(self, command, **kwargs):
server_cmd = ([self.pyocd] +
['gdbserver'] +
self.daparg_args +
self.port_args() +
self.target_args +
self.board_args +
self.frequency_args +
self.tool_opt_args)
if command == 'debugserver':
self.log_gdbserver_message()
self.check_call(server_cmd)
else:
if self.gdb_cmd is None:
raise ValueError('Cannot debug; gdb is missing')
if self.elf_name is None:
raise ValueError('Cannot debug; elf is missing')
client_cmd = (self.gdb_cmd +
self.tui_args +
[self.elf_name] +
['-ex', 'target remote :{}'.format(self.gdb_port)])
if command == 'debug':
client_cmd += ['-ex', 'monitor halt',
'-ex', 'monitor reset',
'-ex', 'load']
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)