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