zephyr/scripts/west_commands/runners/blackmagicprobe.py
Fabio Baltieri 79856e7637 west: runners: blackmagicprobe: guess the gdb port based on the OS
Add some logic in blackmagicprobe.py to guess the GDB serial port based
on the OS instead of just trying the flag or /dev/ttyACM0.

The new logic explicitly looks for the well known path for BMP on Linux
(/dev/ttyBmpGdb), so that we can remove it from the board files that
have it set explicitly right now. At the same time it still allows
setting the port from the flag and also checks an environment variable,
so that it can be used for systems with multiple probes.

As a fallback, it looks for a valid serial port, picking the first one
(the probe creates two, the GDB one is the first one), and raise an
exception if nothing is found.

Signed-off-by: Fabio Baltieri <fabio.baltieri@gmail.com>
2023-01-19 10:16:00 +01:00

221 lines
7.7 KiB
Python

# 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)