1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00
RIOT/dist/tools/bmp/bmp.py
Marian Buschsieweke 71b2860322
dist/tools/bmp: Fix flashing with pygdbmi 0.10.0
The flashing script for the black magic probe stopped working with pygdbmi in
version 0.10.0 due to an API change. This adapts the code to first try
initialization with the old pygdbmi API (as before), but tries again with the
new API if that fails.
2020-11-03 16:33:46 +01:00

293 lines
11 KiB
Python
Executable File

#!/usr/bin/python3
# Copyright (C) 2019 Otto-von-Guericke-Universität Magdeburg
#
# This file is subject to the terms and conditions of the GNU Lesser
# General Public License v2.1. See the file LICENSE in the top level
# directory for more details.
#
# @author Maximilian Deubel <maximilian.deubel@ovgu.de>
# Black Magic Probe helper script
# This script can detect connected Black Magic Probes and can be used as a flashloader and much more
import argparse
import os
import re
import sys
import humanize
import serial.tools.list_ports
from progressbar import Bar, Percentage, ProgressBar
from pygdbmi.gdbcontroller import GdbController
import distutils.spawn
parser = argparse.ArgumentParser(description='Black Magic Tool helper script.')
parser.add_argument('--jtag', action='store_true', help='use JTAG transport')
parser.add_argument('--swd', action='store_true', help='use SWD transport (default)')
parser.add_argument('--connect-srst', action='store_true', help='reset target while connecting')
parser.add_argument('--tpwr', action='store_true', help='enable target power')
parser.add_argument('--serial', help='choose specific probe by serial number')
parser.add_argument('--port', help='choose specific probe by port')
parser.add_argument('--attach', help='choose specific target by number', default='1')
parser.add_argument('--gdb-path', help='path to GDB', default='gdb-multiarch')
parser.add_argument('--term-cmd', help='serial terminal command',
default='picocom --nolock --imap lfcrlf --baud 115200 %s')
parser.add_argument('action', help='choose a task to perform', nargs='?',
choices=['list', 'flash', 'erase', 'debug', 'term', 'reset'],
default='list')
parser.add_argument('file', help='file to load to target (hex or elf)', nargs='?')
TIMEOUT = 100 # seconds
# find a suitable gdb executable, falling back to defaults if needed
def find_suitable_gdb(gdb_path):
if distutils.spawn.find_executable(gdb_path):
return gdb_path
else:
for p in ['arm-none-eabi-gdb', 'gdb-multiarch']:
p = distutils.spawn.find_executable(p)
if p:
print("GDB EXECUTABLE NOT FOUND! FALLING BACK TO %s" % p, file=sys.stderr)
return p
print("CANNOT LOCATE SUITABLE GDB EXECUTABLE!", file=sys.stderr)
sys.exit(-1)
# find all connected BMPs and store both GDB and UART interfaces
def detect_probes():
gdb_ports = []
uart_ports = []
for p in serial.tools.list_ports.comports():
if p.vid == 0x1D50 and p.pid in {0x6018, 0x6017}:
if re.fullmatch(r'COM\d\d', p.device):
p.device = '//./' + p.device
if 'GDB' in str(p.interface) \
or re.fullmatch(r'/dev/cu\.usbmodem([A-F0-9]*)1', p.device) \
or p.location[-1] == '0' and os.name == 'nt':
gdb_ports.append(p)
else:
uart_ports.append(p)
return gdb_ports, uart_ports
# search device with specific serial number <snr> in list <l>
def search_serial(snr, l):
for p in l:
if snr in p.serial_number:
return p.device
# parse GDB output for targets
def detect_targets(gdbmi, res):
targets = []
while True:
for msg in res:
if msg['type'] == 'target':
m = re.fullmatch(pattern=r"\s*(\d)+\s*(.*)\\n", string=msg['payload'])
if m:
targets.append(m.group(2))
elif msg['type'] == 'result':
assert msg['message'] == 'done', str(msg)
return targets
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
def gdb_write_and_wait_for_result(gdbmi, cmd, description, expected_result='done'):
res = gdbmi.write(cmd, timeout_sec=TIMEOUT)
while True:
for msg in res:
if msg['type'] == 'result':
if msg['message'] == expected_result:
print(description, "successful.")
return True
else:
print(description, "failed.", file=sys.stderr)
return False
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
def parse_download_msg(msg):
m = re.fullmatch(
pattern=r"\+download,"
r"\{(?:section=\"(.*?)\")?,?(?:section-sent=\"(.*?)\")?,?"
r"(?:section-size=\"(.*?)\")?,?(?:total-sent=\"(.*?)\")?,?"
r"(?:total-size=\"(.*?)\")?,?\}",
string=msg['payload'])
if m:
section_name = m.group(1)
section_sent = int(m.group(2)) if m.group(2) else None
section_size = int(m.group(3)) if m.group(3) else None
total_sent = int(m.group(4)) if m.group(4) else None
total_size = int(m.group(5)) if m.group(5) else None
return section_name, section_sent, section_size, total_sent, total_size
def download_to_flash(gdbmi):
res = gdbmi.write('-target-download', timeout_sec=TIMEOUT)
first = True # whether this is the first status message
current_sec = None # name of current section
pbar = ProgressBar()
while True:
for msg in res:
if msg['type'] == 'result':
assert msg['message'] == 'done', "download failed: %s" % str(msg)
if pbar.start_time:
pbar.finish()
print("downloading finished")
return
elif msg['type'] == 'output':
section_name, section_sent, section_size, total_sent, total_size = parse_download_msg(msg)
if section_name:
if first:
first = False
print("downloading... total size: %s"
% humanize.naturalsize(total_size, gnu=True))
if section_name != current_sec:
if pbar.start_time:
pbar.finish()
current_sec = section_name
print("downloading section [%s] (%s)" % (
section_name, humanize.naturalsize(section_size, gnu=True)))
pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=section_size).start()
if section_sent:
pbar.update(section_sent)
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
def check_flash(gdbmi):
res = gdbmi.write('compare-sections', timeout_sec=TIMEOUT)
while True:
for msg in res:
if msg['type'] == 'result':
assert msg['message'] == 'done', "checking failed: %s" % str(msg)
print("checking successful")
return
elif msg['type'] == 'console':
assert 'matched' in msg['payload'] and 'MIS-MATCHED' not in msg['payload'], \
"checking failed: %s" % str(msg)
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
def choose_bmp_port(gdb_ports):
print("found following Black Magic GDB servers:")
for i, s in enumerate(gdb_ports):
print("\t[%s]" % s.device, end=' ')
if len(s.serial_number) > 1:
print("Serial:", s.serial_number, end=' ')
if i == 0:
print("<- default", end=' ')
print('')
port = gdb_ports[0].device
if args.port:
port = args.port
elif args.serial:
port = search_serial(args.serial, gdb_ports)
assert port, "no BMP with this serial found"
print('connecting to [%s]...' % port)
return port
# terminal mode, opens TTY program
def term_mode(uart_ports):
port = uart_ports[0].device
if args.port:
port = args.port
elif args.serial:
port = search_serial(args.serial, uart_ports)
assert port, "no BMP with this serial found"
os.system(args.term_cmd % port)
sys.exit(0)
# debug mode, opens GDB shell with options
def debug_mode(port):
gdb_args = ['-ex \'target extended-remote %s\'' % port]
if args.tpwr:
gdb_args.append('-ex \'monitor tpwr enable\'')
if args.connect_srst:
gdb_args.append('-ex \'monitor connect_srst enable\'')
if args.jtag:
gdb_args.append('-ex \'monitor jtag_scan\'')
else:
gdb_args.append('-ex \'monitor swdp_scan\'')
gdb_args.append('-ex \'attach %s\'' % args.attach)
os.system(" ".join(['\"' + args.gdb_path + '\"'] + gdb_args + [args.file]))
def connect_to_target(port):
# open GDB in machine interface mode
try:
# try old API first
gdbmi = GdbController(gdb_path=args.gdb_path, gdb_args=["--nx", "--quiet", "--interpreter=mi2", args.file])
except TypeError:
# and then new API
gdbmi = GdbController(command=[args.gdb_path, "--nx", "--quiet", "--interpreter=mi2", args.file])
assert gdb_write_and_wait_for_result(gdbmi, '-target-select extended-remote %s' % port, 'connecting',
expected_result='connected')
# set options
if args.connect_srst:
gdbmi.write('monitor connect_srst enable', timeout_sec=TIMEOUT)
if args.tpwr:
gdbmi.write('monitor tpwr enable', timeout_sec=TIMEOUT)
# scan for targets
if not args.jtag:
print("scanning using SWD...")
res = gdbmi.write('monitor swdp_scan', timeout_sec=TIMEOUT)
else:
print("scanning using JTAG...")
res = gdbmi.write('monitor jtag_scan', timeout_sec=TIMEOUT)
targets = detect_targets(gdbmi, res)
assert len(targets) > 0, "no targets found"
print("found following targets:")
for t in targets:
print("\t%s" % t)
print("")
return gdbmi
if __name__ == '__main__':
args = parser.parse_args()
assert not (args.swd and args.jtag), "you may only choose one protocol"
assert not (args.serial and args.port), "you may only specify the probe by port or by serial"
g, u = detect_probes()
assert len(g) > 0, "no Black Magic Probes found 😔"
if args.action == 'term':
term_mode(u)
else:
port = choose_bmp_port(g)
args.file = args.file if args.file else ''
args.gdb_path = find_suitable_gdb(args.gdb_path)
if args.action == 'debug':
debug_mode(port)
sys.exit(0)
gdbmi = connect_to_target(port)
if args.action == 'list':
sys.exit(0)
assert gdb_write_and_wait_for_result(gdbmi, '-target-attach %s' % args.attach, 'attaching to target')
# reset mode: reset device using reset pin
if args.action == 'reset':
assert gdb_write_and_wait_for_result(gdbmi, 'monitor hard_srst', 'resetting target')
sys.exit(0)
# erase mode
elif args.action == 'erase':
print('erasing...')
assert gdb_write_and_wait_for_result(gdbmi, '-target-flash-erase', 'erasing target')
sys.exit(0)
# flashloader mode: flash, check and restart
elif args.action == 'flash':
download_to_flash(gdbmi)
check_flash(gdbmi)
# kill and reset
assert gdb_write_and_wait_for_result(gdbmi, 'kill', 'killing')