1# Copyright (c) 2018 Roman Tataurov <diytronic@yandex.ru> 2# Modified 2018 Tavish Naruka <tavishnaruka@gmail.com> 3# 4# SPDX-License-Identifier: Apache-2.0 5'''Runner for flashing with Black Magic Probe.''' 6# https://github.com/blacksphere/blackmagic/wiki 7 8import glob 9import os 10import signal 11import sys 12from pathlib import Path 13 14from runners.core import RunnerCaps, ZephyrBinaryRunner 15 16try: 17 import serial.tools.list_ports 18 MISSING_REQUIREMENTS = False 19except ImportError: 20 MISSING_REQUIREMENTS = True 21 22# Default path for linux, based on the project udev.rules file. 23DEFAULT_LINUX_BMP_PATH = '/dev/ttyBmpGdb' 24 25# Interface descriptor for the GDB port as defined in the BMP firmware. 26BMP_GDB_INTERFACE = 'Black Magic GDB Server' 27 28# Product string as defined in the BMP firmware. 29BMP_GDB_PRODUCT = "Black Magic Probe" 30 31# BMP vendor and product ID. 32BMP_GDB_VID = 0x1d50 33BMP_GDB_PID = 0x6018 34 35LINUX_SERIAL_GLOB = '/dev/ttyACM*' 36DARWIN_SERIAL_GLOB = '/dev/cu.usbmodem*' 37 38def blackmagicprobe_gdb_serial_linux(): 39 '''Guess the GDB port on Linux platforms.''' 40 if os.path.exists(DEFAULT_LINUX_BMP_PATH): 41 return DEFAULT_LINUX_BMP_PATH 42 43 if not MISSING_REQUIREMENTS: 44 for port in serial.tools.list_ports.comports(): 45 if port.interface == BMP_GDB_INTERFACE: 46 return port.device 47 48 ports = glob.glob(LINUX_SERIAL_GLOB) 49 if not ports: 50 raise RuntimeError( 51 f'cannot find any valid port matching {LINUX_SERIAL_GLOB}') 52 return sorted(ports)[0] 53 54def blackmagicprobe_gdb_serial_darwin(): 55 '''Guess the GDB port on Darwin platforms.''' 56 if not MISSING_REQUIREMENTS: 57 bmp_ports = [] 58 for port in serial.tools.list_ports.comports(): 59 if port.description and port.description.startswith( 60 BMP_GDB_PRODUCT): 61 bmp_ports.append(port.device) 62 if bmp_ports: 63 return sorted(bmp_ports)[0] 64 65 ports = glob.glob(DARWIN_SERIAL_GLOB) 66 if not ports: 67 raise RuntimeError( 68 f'cannot find any valid port matching {DARWIN_SERIAL_GLOB}') 69 return sorted(ports)[0] 70 71def blackmagicprobe_gdb_serial_win32(): 72 '''Guess the GDB port on Windows platforms.''' 73 if not MISSING_REQUIREMENTS: 74 bmp_ports = [] 75 for port in serial.tools.list_ports.comports(): 76 if port.vid == BMP_GDB_VID and port.pid == BMP_GDB_PID: 77 bmp_ports.append(port.device) 78 if bmp_ports: 79 return sorted(bmp_ports)[0] 80 81 return 'COM1' 82 83def blackmagicprobe_gdb_serial(port): 84 '''Guess the GDB port for the probe. 85 86 Return the port to use, in order of priority: 87 - the port specified manually 88 - the port in the BMP_GDB_SERIAL environment variable 89 - a guessed one depending on the host 90 ''' 91 if port: 92 return port 93 94 if 'BMP_GDB_SERIAL' in os.environ: 95 return os.environ['BMP_GDB_SERIAL'] 96 97 platform = sys.platform 98 if platform.startswith('linux'): 99 return blackmagicprobe_gdb_serial_linux() 100 elif platform.startswith('darwin'): 101 return blackmagicprobe_gdb_serial_darwin() 102 elif platform.startswith('win32'): 103 return blackmagicprobe_gdb_serial_win32() 104 else: 105 raise RuntimeError(f'unsupported platform: {platform}') 106 107 108class BlackMagicProbeRunner(ZephyrBinaryRunner): 109 '''Runner front-end for Black Magic probe.''' 110 111 def __init__(self, cfg, gdb_serial, connect_rst=False): 112 super().__init__(cfg) 113 self.gdb = [cfg.gdb] if cfg.gdb else None 114 # as_posix() because gdb doesn't recognize backslashes as path 115 # separators for the 'load' command we execute in bmp_flash(). 116 # 117 # https://github.com/zephyrproject-rtos/zephyr/issues/50789 118 self.elf_file = Path(cfg.elf_file).as_posix() 119 if cfg.hex_file is not None: 120 self.hex_file = Path(cfg.hex_file).as_posix() 121 else: 122 self.hex_file = None 123 self.gdb_serial = blackmagicprobe_gdb_serial(gdb_serial) 124 self.logger.info(f'using GDB serial: {self.gdb_serial}') 125 if connect_rst: 126 self.connect_rst_enable_arg = [ 127 '-ex', "monitor connect_rst enable", 128 '-ex', "monitor connect_srst enable", 129 ] 130 self.connect_rst_disable_arg = [ 131 '-ex', "monitor connect_rst disable", 132 '-ex', "monitor connect_srst disable", 133 ] 134 else: 135 self.connect_rst_enable_arg = [] 136 self.connect_rst_disable_arg = [] 137 138 @classmethod 139 def name(cls): 140 return 'blackmagicprobe' 141 142 @classmethod 143 def capabilities(cls): 144 return RunnerCaps(commands={'flash', 'debug', 'attach'}) 145 146 @classmethod 147 def do_create(cls, cfg, args): 148 return BlackMagicProbeRunner(cfg, args.gdb_serial, args.connect_rst) 149 150 @classmethod 151 def do_add_parser(cls, parser): 152 parser.add_argument('--gdb-serial', help='GDB serial port') 153 parser.add_argument('--connect-rst', '--connect-srst', action='store_true', 154 help='Assert SRST during connect? (default: no)') 155 156 def bmp_flash(self, command, **kwargs): 157 # if hex file is present and signed, use it else use elf file 158 if self.hex_file: 159 split = self.hex_file.split('.') 160 # eg zephyr.signed.hex 161 if len(split) >= 3 and split[-2] == 'signed': 162 flash_file = self.hex_file 163 else: 164 flash_file = self.elf_file 165 else: 166 flash_file = self.elf_file 167 168 if flash_file is None: 169 raise ValueError('Cannot flash; elf file is missing') 170 171 command = (self.gdb + 172 ['-ex', "set confirm off", 173 '-ex', f"target extended-remote {self.gdb_serial}"] + 174 self.connect_rst_enable_arg + 175 ['-ex', "monitor swdp_scan", 176 '-ex', "attach 1", 177 '-ex', f"load {flash_file}", 178 '-ex', "kill", 179 '-ex', "quit", 180 '-silent']) 181 self.check_call(command) 182 183 def check_call_ignore_sigint(self, command): 184 previous = signal.signal(signal.SIGINT, signal.SIG_IGN) 185 try: 186 self.check_call(command) 187 finally: 188 signal.signal(signal.SIGINT, previous) 189 190 def bmp_attach(self, command, **kwargs): 191 if self.elf_file is None: 192 command = (self.gdb + 193 ['-ex', "set confirm off", 194 '-ex', f"target extended-remote {self.gdb_serial}"] + 195 self.connect_rst_disable_arg + 196 ['-ex', "monitor swdp_scan", 197 '-ex', "attach 1"]) 198 else: 199 command = (self.gdb + 200 ['-ex', "set confirm off", 201 '-ex', f"target extended-remote {self.gdb_serial}"] + 202 self.connect_rst_disable_arg + 203 ['-ex', "monitor swdp_scan", 204 '-ex', "attach 1", 205 '-ex', f"file {self.elf_file}"]) 206 self.check_call_ignore_sigint(command) 207 208 def bmp_debug(self, command, **kwargs): 209 if self.elf_file is None: 210 raise ValueError('Cannot debug; elf file is missing') 211 command = (self.gdb + 212 ['-ex', "set confirm off", 213 '-ex', f"target extended-remote {self.gdb_serial}"] + 214 self.connect_rst_enable_arg + 215 ['-ex', "monitor swdp_scan", 216 '-ex', "attach 1", 217 '-ex', f"file {self.elf_file}", 218 '-ex', f"load {self.elf_file}"]) 219 self.check_call_ignore_sigint(command) 220 221 def do_run(self, command, **kwargs): 222 if self.gdb is None: 223 raise ValueError('Cannot execute; gdb not specified') 224 self.require(self.gdb[0]) 225 226 if command == 'flash': 227 self.bmp_flash(command, **kwargs) 228 elif command == 'debug': 229 self.bmp_debug(command, **kwargs) 230 elif command == 'attach': 231 self.bmp_attach(command, **kwargs) 232 else: 233 self.bmp_flash(command, **kwargs) 234