Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dist/tools/bmp: additional improvements to auto-detection #21128

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions dist/tools/bmp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,21 @@ The probe is auto discovered based on the USB VID (0x1D50) and PID (0x6018,
`--serial` is provided.

If `--port` is provided, then that port will be used as the GDB port for all
actions, except for the `term` action.
actions, except for the `term` action. `--port` also accepts values such as
network addresses.

## Supported firmwares
This tool assumes firmware version 1.10 of the Black Magic debugger.
There are minor differences in the available firmwares of the Black Magic
debugger. Compatibility has been tested with version 1.8+.

Compatibility for older versions is limited, but can be selected by providing
`--bmp-version x.y.z`.
This tool will try to determine which version of the firmware is installed,
unless the probe is accessed remotely (e.g. `--port` is a network address). If
the firmware version cannot be determined, it will assume version 1.10.2. This
can be overridden using the `--bmp-version` flag.

As of firmware version 2.0.0 of the Black Magic debugger, support for targets
depend on the 'flavor' of firmware selected. This tool will indicate if that
is the case.

## Examples (tested with BluePill STM32F103F8C6)
* test connection:
Expand Down
127 changes: 88 additions & 39 deletions dist/tools/bmp/bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
from pygdbmi.gdbcontroller import GdbController

TIMEOUT = 100 # seconds
DEFAULT_VERSION = Version('1.10.2')


# find a suitable gdb executable, falling back to defaults if needed
Expand All @@ -35,18 +36,30 @@ def find_suitable_gdb(gdb_path):
for p in ['arm-none-eabi-gdb', 'gdb-multiarch']:
p = shutil.which(p)
if p:
print("GDB EXECUTABLE NOT FOUND! FALLING BACK TO %s" % p, file=sys.stderr)
print(f"GDB EXECUTABLE NOT FOUND! FALLING BACK TO {p}", file=sys.stderr)
return p
print("CANNOT LOCATE SUITABLE GDB EXECUTABLE!", file=sys.stderr)
sys.exit(-1)


# detect the firmware version by parsing the product description for something like v1.10.2
def detect_firmware_version(port):
matches = re.search(r"v[0-9]+.[0-9]+.[0-9]+", port.product)

if not matches:
return None

return Version(matches.group(0)[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}:
p.firmware_version = detect_firmware_version(p)

if re.fullmatch(r'COM\d\d', p.device):
p.device = '//./' + p.device
if 'GDB' in str(p.interface) \
Expand All @@ -62,9 +75,11 @@ def detect_probes():
def enumerate_probes(ports):
print("found following Black Magic GDB servers:")
for i, s in enumerate(ports):
print("\t[%s]" % s.device, end=' ')
print(f"\t[{s.device}]", end=' ')
if len(s.serial_number) > 1:
print("Serial:", s.serial_number, end=' ')
print(f"Serial: {s.serial_number}", end=' ')
if s.firmware_version:
print(f"Firmware: {s.firmware_version}", end=' ')
if i == 0:
print("<- default", end=' ')
print('')
Expand All @@ -74,7 +89,14 @@ def enumerate_probes(ports):
def search_serial(snr, ports):
for port in ports:
if snr in port.serial_number:
return port.device
return port


# search device with specific port number <prt> in a list of ports <ports>
def search_port(prt, ports):
for port in ports:
if prt == port.device:
return port


# parse GDB output for targets
Expand All @@ -83,9 +105,11 @@ def detect_targets(gdbmi, res):
while True:
for msg in res:
if msg['type'] == 'target':
m = re.fullmatch(pattern=r"\s*(\d+)\s*(.*)\s*", string=msg['payload'])
m = re.fullmatch(pattern=r"([\s\*]*)(\d+)\s*(.*)\s*", string=msg['payload'])
if m:
targets.append(m.group(2))
supported = "***" not in m.group(1)
description = m.group(3)
targets.append((supported, description))
elif msg['type'] == 'result':
assert msg['message'] == 'done', str(msg)
return targets
Expand Down Expand Up @@ -131,7 +155,7 @@ def download_to_flash(gdbmi):
while True:
for msg in res:
if msg['type'] == 'result':
assert msg['message'] == 'done', "download failed: %s" % str(msg)
assert msg['message'] == 'done', f"download failed: {msg}"
if pbar.start_time:
pbar.finish()
print("downloading finished")
Expand All @@ -141,14 +165,12 @@ def download_to_flash(gdbmi):
if section_name:
if first:
first = False
print("downloading... total size: %s"
% humanize.naturalsize(total_size, gnu=True))
print(f"downloading... total size: {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)))
print(f"downloading section [{section_name}] ({humanize.naturalsize(section_size, gnu=True)})")
pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=section_size).start()
if section_sent:
pbar.update(section_sent)
Expand All @@ -160,29 +182,50 @@ def check_flash(gdbmi):
while True:
for msg in res:
if msg['type'] == 'result':
assert msg['message'] == 'done', "checking failed: %s" % str(msg)
assert msg['message'] == 'done', f"checking failed: {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)
f"checking failed: {msg}"
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)


# choose GDB or UART port, based on available ports and application arguments
def choose_port(args, ports):
if args.port:
port = args.port
# choose GDB or UART port, based on available ports and application arguments.
def choose_probe(args, ports):
if args.serial:
descriptor = search_serial(args.serial, ports)
assert descriptor, "no BMP with this serial found"
elif args.port:
descriptor = search_port(args.port, ports)

# bail out if no descriptor found, because port could be a network address, a pipe or
# something else.
if not descriptor:
return (args.port, None)
else:
enumerate_probes(ports)
if args.serial:
port = search_serial(args.serial, ports)
assert port, "no BMP with this serial found"
assert len(ports) > 0, "no ports found"
descriptor = ports[0]

enumerate_probes(ports)
print(f'connecting to [{descriptor.device}]...')
return (descriptor.device, descriptor)


# choose firmware version, based on available descriptors and application arguments.
def choose_firmware_version(args, descriptor):
if args.bmp_version == "auto":
if descriptor and descriptor.firmware_version:
version = descriptor.firmware_version
print(f"auto-detected firmware version {version}")
else:
assert len(ports) > 0, "no ports found"
port = ports[0].device
print('connecting to [%s]...' % port)
return port
version = DEFAULT_VERSION
print(f"unable to detect firmware version, assuming {version} or later")
else:
version = Version(args.bmp_version)
print(f"using firmware version {version}")

return version


# terminal mode, opens TTY program
Expand All @@ -193,7 +236,7 @@ def term_mode(args, uart_port):

# debug mode, opens GDB shell with options
def debug_mode(args, port):
gdb_args = ['-ex \'target extended-remote %s\'' % port]
gdb_args = [f'-ex \'target extended-remote {port}\'']
if args.tpwr:
gdb_args.append('-ex \'monitor tpwr enable\'')
if args.connect_srst:
Expand All @@ -208,8 +251,8 @@ def debug_mode(args, port):
gdb_args.append('-ex \'monitor swd_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]))
gdb_args.append(f'-ex \'attach {args.attach}\'')
os.system(" ".join([f'\"{args.gdb_path}\"'] + gdb_args + [args.file]))


def connect_to_target(args, port):
Expand All @@ -220,7 +263,7 @@ def connect_to_target(args, port):
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',
assert gdb_write_and_wait_for_result(gdbmi, f'-target-select extended-remote {port}', 'connecting',
expected_result='connected')
# set options
if args.connect_srst:
Expand All @@ -243,10 +286,13 @@ def connect_to_target(args, port):
targets = detect_targets(gdbmi, res)
assert len(targets) > 0, "no targets found"
print("found following targets:")
for t in targets:
print("\t%s" % t)
for s, t in targets:
if not s:
print(f"\t{t} (unsupported)")
else:
print(f"\t{t}")
print("")
return gdbmi
return (gdbmi, targets)


def parse_args():
Expand All @@ -258,9 +304,9 @@ def parse_args():
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 (overrides auto selection)')
parser.add_argument('--attach', help='choose specific target by number', default='1')
parser.add_argument('--attach', help='choose specific target by number', type=int, default=1)
parser.add_argument('--gdb-path', help='path to GDB', default='gdb-multiarch')
parser.add_argument('--bmp-version', help='choose specific firmware version', default='1.10.0')
parser.add_argument('--bmp-version', help='choose specific firmware version', default='auto')
parser.add_argument('--term-cmd', help='serial terminal command',
default='picocom --nolock --imap lfcrlf --baud 115200 %s')

Expand All @@ -279,26 +325,29 @@ def main():
g, u = detect_probes()

if args.action == 'term':
port = choose_port(args, u)
(port, _) = choose_probe(args, u)

term_mode(args, port)
else:
port = choose_port(args, g)
(port, descriptor) = choose_probe(args, g)

args.file = args.file if args.file else ''
args.bmp_version = Version(args.bmp_version)
args.bmp_version = choose_firmware_version(args, descriptor)
args.gdb_path = find_suitable_gdb(args.gdb_path)

if args.action == 'debug':
debug_mode(args, port)
sys.exit(0)

gdbmi = connect_to_target(args, port)
(gdbmi, targets) = connect_to_target(args, port)

if args.action == 'list':
sys.exit(0)

assert gdb_write_and_wait_for_result(gdbmi, '-target-attach %s' % args.attach, 'attaching to target')
assert len(targets) >= args.attach, "attach greater than number of targets"
assert targets[args.attach - 1][0], "target unsupported by probe"

assert gdb_write_and_wait_for_result(gdbmi, f'-target-attach {args.attach}', 'attaching to target')

# reset mode: reset device using reset pin
if args.action == 'reset':
Expand Down
Loading