blob: 54fc22a7eda3c134e271a7bd7910548c58dcdefc [file] [log] [blame]
# Copyright (c) 2018 Roman Tataurov <diytronic@yandex.ru>
# Modified 2018 Tavish Naruka <tavishnaruka@gmail.com>
#
# SPDX-License-Identifier: Apache-2.0
'''Runner for flashing with Black Magic Probe.'''
# https://github.com/blacksphere/blackmagic/wiki
import glob
import os
import signal
import sys
from pathlib import Path
from runners.core import ZephyrBinaryRunner, RunnerCaps
try:
import serial.tools.list_ports
MISSING_REQUIREMENTS = False
except ImportError:
MISSING_REQUIREMENTS = True
# Default path for linux, based on the project udev.rules file.
DEFAULT_LINUX_BMP_PATH = '/dev/ttyBmpGdb'
# Interface descriptor for the GDB port as defined in the BMP firmware.
BMP_GDB_INTERFACE = 'Black Magic GDB Server'
# Product string as defined in the BMP firmware.
BMP_GDB_PRODUCT = "Black Magic Probe"
# BMP vendor and product ID.
BMP_GDB_VID = 0x1d50
BMP_GDB_PID = 0x6018
LINUX_SERIAL_GLOB = '/dev/ttyACM*'
DARWIN_SERIAL_GLOB = '/dev/cu.usbmodem*'
def blackmagicprobe_gdb_serial_linux():
'''Guess the GDB port on Linux platforms.'''
if os.path.exists(DEFAULT_LINUX_BMP_PATH):
return DEFAULT_LINUX_BMP_PATH
if not MISSING_REQUIREMENTS:
for port in serial.tools.list_ports.comports():
if port.interface == BMP_GDB_INTERFACE:
return port.device
ports = glob.glob(LINUX_SERIAL_GLOB)
if not ports:
raise RuntimeError(
f'cannot find any valid port matching {LINUX_SERIAL_GLOB}')
return sorted(ports)[0]
def blackmagicprobe_gdb_serial_darwin():
'''Guess the GDB port on Darwin platforms.'''
if not MISSING_REQUIREMENTS:
bmp_ports = []
for port in serial.tools.list_ports.comports():
if port.description and port.description.startswith(
BMP_GDB_PRODUCT):
bmp_ports.append(port.device)
if bmp_ports:
return sorted(bmp_ports)[0]
ports = glob.glob(DARWIN_SERIAL_GLOB)
if not ports:
raise RuntimeError(
f'cannot find any valid port matching {DARWIN_SERIAL_GLOB}')
return sorted(ports)[0]
def blackmagicprobe_gdb_serial_win32():
'''Guess the GDB port on Windows platforms.'''
if not MISSING_REQUIREMENTS:
bmp_ports = []
for port in serial.tools.list_ports.comports():
if port.vid == BMP_GDB_VID and port.pid == BMP_GDB_PID:
bmp_ports.append(port.device)
if bmp_ports:
return sorted(bmp_ports)[0]
return 'COM1'
def blackmagicprobe_gdb_serial(port):
'''Guess the GDB port for the probe.
Return the port to use, in order of priority:
- the port specified manually
- the port in the BMP_GDB_SERIAL environment variable
- a guessed one depending on the host
'''
if port:
return port
if 'BMP_GDB_SERIAL' in os.environ:
return os.environ['BMP_GDB_SERIAL']
platform = sys.platform
if platform.startswith('linux'):
return blackmagicprobe_gdb_serial_linux()
elif platform.startswith('darwin'):
return blackmagicprobe_gdb_serial_darwin()
elif platform.startswith('win32'):
return blackmagicprobe_gdb_serial_win32()
else:
raise RuntimeError(f'unsupported platform: {platform}')
class BlackMagicProbeRunner(ZephyrBinaryRunner):
'''Runner front-end for Black Magic probe.'''
def __init__(self, cfg, gdb_serial, connect_rst=False):
super().__init__(cfg)
self.gdb = [cfg.gdb] if cfg.gdb else None
# as_posix() because gdb doesn't recognize backslashes as path
# separators for the 'load' command we execute in bmp_flash().
#
# https://github.com/zephyrproject-rtos/zephyr/issues/50789
self.elf_file = Path(cfg.elf_file).as_posix()
self.gdb_serial = blackmagicprobe_gdb_serial(gdb_serial)
self.logger.info(f'using GDB serial: {self.gdb_serial}')
if connect_rst:
self.connect_rst_enable_arg = [
'-ex', "monitor connect_rst enable",
'-ex', "monitor connect_srst enable",
]
self.connect_rst_disable_arg = [
'-ex', "monitor connect_rst disable",
'-ex', "monitor connect_srst disable",
]
else:
self.connect_rst_enable_arg = []
self.connect_rst_disable_arg = []
@classmethod
def name(cls):
return 'blackmagicprobe'
@classmethod
def capabilities(cls):
return RunnerCaps(commands={'flash', 'debug', 'attach'})
@classmethod
def do_create(cls, cfg, args):
return BlackMagicProbeRunner(cfg, args.gdb_serial, args.connect_rst)
@classmethod
def do_add_parser(cls, parser):
parser.add_argument('--gdb-serial', help='GDB serial port')
parser.add_argument('--connect-rst', '--connect-srst', action='store_true',
help='Assert SRST during connect? (default: no)')
def bmp_flash(self, command, **kwargs):
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial)] +
self.connect_rst_enable_arg +
['-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "load {}".format(self.elf_file),
'-ex', "kill",
'-ex', "quit",
'-silent'])
self.check_call(command)
def check_call_ignore_sigint(self, command):
previous = signal.signal(signal.SIGINT, signal.SIG_IGN)
try:
self.check_call(command)
finally:
signal.signal(signal.SIGINT, previous)
def bmp_attach(self, command, **kwargs):
if self.elf_file is None:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial)] +
self.connect_rst_disable_arg +
['-ex', "monitor swdp_scan",
'-ex', "attach 1"])
else:
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial)] +
self.connect_rst_disable_arg +
['-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file)])
self.check_call_ignore_sigint(command)
def bmp_debug(self, command, **kwargs):
if self.elf_file is None:
raise ValueError('Cannot debug; elf file is missing')
command = (self.gdb +
['-ex', "set confirm off",
'-ex', "target extended-remote {}".format(
self.gdb_serial)] +
self.connect_rst_enable_arg +
['-ex', "monitor swdp_scan",
'-ex', "attach 1",
'-ex', "file {}".format(self.elf_file),
'-ex', "load {}".format(self.elf_file)])
self.check_call_ignore_sigint(command)
def do_run(self, command, **kwargs):
if self.gdb is None:
raise ValueError('Cannot execute; gdb not specified')
self.require(self.gdb[0])
if command == 'flash':
self.bmp_flash(command, **kwargs)
elif command == 'debug':
self.bmp_debug(command, **kwargs)
elif command == 'attach':
self.bmp_attach(command, **kwargs)
else:
self.bmp_flash(command, **kwargs)