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 c56f50a2eb
dist/tools/bmp: fix target parsing
Either GDB or pygdbmi changed behavior to trim off whitespace following
the message, resulting in the `\\n` at the end of the regex no longer
matching. This replaces the `\\n` with `\s*` to match both old and new
output.

Fixes: https://github.com/RIOT-OS/RIOT/issues/20604
2024-05-09 12:56:01 +02:00

293 lines
11 KiB
Python
Executable File

#!/usr/bin/env 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 a list of ports <ports>
def search_serial(snr, ports):
for port in ports:
if snr in port.serial_number:
return port.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*(.*)\s*", 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')