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 ZephyrBinaryRunner, RunnerCaps 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', "target extended-remote {}".format( 174 self.gdb_serial)] + 175 self.connect_rst_enable_arg + 176 ['-ex', "monitor swdp_scan", 177 '-ex', "attach 1", 178 '-ex', "load {}".format(flash_file), 179 '-ex', "kill", 180 '-ex', "quit", 181 '-silent']) 182 self.check_call(command) 183 184 def check_call_ignore_sigint(self, command): 185 previous = signal.signal(signal.SIGINT, signal.SIG_IGN) 186 try: 187 self.check_call(command) 188 finally: 189 signal.signal(signal.SIGINT, previous) 190 191 def bmp_attach(self, command, **kwargs): 192 if self.elf_file is None: 193 command = (self.gdb + 194 ['-ex', "set confirm off", 195 '-ex', "target extended-remote {}".format( 196 self.gdb_serial)] + 197 self.connect_rst_disable_arg + 198 ['-ex', "monitor swdp_scan", 199 '-ex', "attach 1"]) 200 else: 201 command = (self.gdb + 202 ['-ex', "set confirm off", 203 '-ex', "target extended-remote {}".format( 204 self.gdb_serial)] + 205 self.connect_rst_disable_arg + 206 ['-ex', "monitor swdp_scan", 207 '-ex', "attach 1", 208 '-ex', "file {}".format(self.elf_file)]) 209 self.check_call_ignore_sigint(command) 210 211 def bmp_debug(self, command, **kwargs): 212 if self.elf_file is None: 213 raise ValueError('Cannot debug; elf file is missing') 214 command = (self.gdb + 215 ['-ex', "set confirm off", 216 '-ex', "target extended-remote {}".format( 217 self.gdb_serial)] + 218 self.connect_rst_enable_arg + 219 ['-ex', "monitor swdp_scan", 220 '-ex', "attach 1", 221 '-ex', "file {}".format(self.elf_file), 222 '-ex', "load {}".format(self.elf_file)]) 223 self.check_call_ignore_sigint(command) 224 225 def do_run(self, command, **kwargs): 226 if self.gdb is None: 227 raise ValueError('Cannot execute; gdb not specified') 228 self.require(self.gdb[0]) 229 230 if command == 'flash': 231 self.bmp_flash(command, **kwargs) 232 elif command == 'debug': 233 self.bmp_debug(command, **kwargs) 234 elif command == 'attach': 235 self.bmp_attach(command, **kwargs) 236 else: 237 self.bmp_flash(command, **kwargs) 238