Skip to content

Commit 2416dc3

Browse files
authored
Merge pull request RIOT-OS#21128 from basilfx/bmp-auto-detect
dist/tools/bmp: additional improvements to auto-detection
2 parents c5d23ba + b27eb50 commit 2416dc3

File tree

2 files changed

+100
-43
lines changed

2 files changed

+100
-43
lines changed

dist/tools/bmp/README.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,21 @@ The probe is auto discovered based on the USB VID (0x1D50) and PID (0x6018,
5151
`--serial` is provided.
5252

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

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

59-
Compatibility for older versions is limited, but can be selected by providing
60-
`--bmp-version x.y.z`.
61+
This tool will try to determine which version of the firmware is installed,
62+
unless the probe is accessed remotely (e.g. `--port` is a network address). If
63+
the firmware version cannot be determined, it will assume version 1.10.2. This
64+
can be overridden using the `--bmp-version` flag.
65+
66+
As of firmware version 2.0.0 of the Black Magic debugger, support for targets
67+
depend on the 'flavor' of firmware selected. This tool will indicate if that
68+
is the case.
6169

6270
## Examples (tested with BluePill STM32F103F8C6)
6371
* test connection:

dist/tools/bmp/bmp.py

Lines changed: 88 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from pygdbmi.gdbcontroller import GdbController
2626

2727
TIMEOUT = 100 # seconds
28+
DEFAULT_VERSION = Version('1.10.2')
2829

2930

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

4344

45+
# detect the firmware version by parsing the product description for something like v1.10.2
46+
def detect_firmware_version(port):
47+
matches = re.search(r"v[0-9]+.[0-9]+.[0-9]+", port.product)
48+
49+
if not matches:
50+
return None
51+
52+
return Version(matches.group(0)[1:])
53+
54+
4455
# find all connected BMPs and store both GDB and UART interfaces
4556
def detect_probes():
4657
gdb_ports = []
4758
uart_ports = []
4859
for p in serial.tools.list_ports.comports():
4960
if p.vid == 0x1D50 and p.pid in {0x6018, 0x6017}:
61+
p.firmware_version = detect_firmware_version(p)
62+
5063
if re.fullmatch(r'COM\d\d', p.device):
5164
p.device = '//./' + p.device
5265
if 'GDB' in str(p.interface) \
@@ -62,9 +75,11 @@ def detect_probes():
6275
def enumerate_probes(ports):
6376
print("found following Black Magic GDB servers:")
6477
for i, s in enumerate(ports):
65-
print("\t[%s]" % s.device, end=' ')
78+
print(f"\t[{s.device}]", end=' ')
6679
if len(s.serial_number) > 1:
67-
print("Serial:", s.serial_number, end=' ')
80+
print(f"Serial: {s.serial_number}", end=' ')
81+
if s.firmware_version:
82+
print(f"Firmware: {s.firmware_version}", end=' ')
6883
if i == 0:
6984
print("<- default", end=' ')
7085
print('')
@@ -74,7 +89,14 @@ def enumerate_probes(ports):
7489
def search_serial(snr, ports):
7590
for port in ports:
7691
if snr in port.serial_number:
77-
return port.device
92+
return port
93+
94+
95+
# search device with specific port number <prt> in a list of ports <ports>
96+
def search_port(prt, ports):
97+
for port in ports:
98+
if prt == port.device:
99+
return port
78100

79101

80102
# parse GDB output for targets
@@ -83,9 +105,11 @@ def detect_targets(gdbmi, res):
83105
while True:
84106
for msg in res:
85107
if msg['type'] == 'target':
86-
m = re.fullmatch(pattern=r"\s*(\d+)\s*(.*)\s*", string=msg['payload'])
108+
m = re.fullmatch(pattern=r"([\s\*]*)(\d+)\s*(.*)\s*", string=msg['payload'])
87109
if m:
88-
targets.append(m.group(2))
110+
supported = "***" not in m.group(1)
111+
description = m.group(3)
112+
targets.append((supported, description))
89113
elif msg['type'] == 'result':
90114
assert msg['message'] == 'done', str(msg)
91115
return targets
@@ -131,7 +155,7 @@ def download_to_flash(gdbmi):
131155
while True:
132156
for msg in res:
133157
if msg['type'] == 'result':
134-
assert msg['message'] == 'done', "download failed: %s" % str(msg)
158+
assert msg['message'] == 'done', f"download failed: {msg}"
135159
if pbar.start_time:
136160
pbar.finish()
137161
print("downloading finished")
@@ -141,14 +165,12 @@ def download_to_flash(gdbmi):
141165
if section_name:
142166
if first:
143167
first = False
144-
print("downloading... total size: %s"
145-
% humanize.naturalsize(total_size, gnu=True))
168+
print(f"downloading... total size: {humanize.naturalsize(total_size, gnu=True)}")
146169
if section_name != current_sec:
147170
if pbar.start_time:
148171
pbar.finish()
149172
current_sec = section_name
150-
print("downloading section [%s] (%s)" % (
151-
section_name, humanize.naturalsize(section_size, gnu=True)))
173+
print(f"downloading section [{section_name}] ({humanize.naturalsize(section_size, gnu=True)})")
152174
pbar = ProgressBar(widgets=[Percentage(), Bar()], maxval=section_size).start()
153175
if section_sent:
154176
pbar.update(section_sent)
@@ -160,29 +182,50 @@ def check_flash(gdbmi):
160182
while True:
161183
for msg in res:
162184
if msg['type'] == 'result':
163-
assert msg['message'] == 'done', "checking failed: %s" % str(msg)
185+
assert msg['message'] == 'done', f"checking failed: {msg}"
164186
print("checking successful")
165187
return
166188
elif msg['type'] == 'console':
167189
assert 'matched' in msg['payload'] and 'MIS-MATCHED' not in msg['payload'], \
168-
"checking failed: %s" % str(msg)
190+
f"checking failed: {msg}"
169191
res = gdbmi.get_gdb_response(timeout_sec=TIMEOUT)
170192

171193

172-
# choose GDB or UART port, based on available ports and application arguments
173-
def choose_port(args, ports):
174-
if args.port:
175-
port = args.port
194+
# choose GDB or UART port, based on available ports and application arguments.
195+
def choose_probe(args, ports):
196+
if args.serial:
197+
descriptor = search_serial(args.serial, ports)
198+
assert descriptor, "no BMP with this serial found"
199+
elif args.port:
200+
descriptor = search_port(args.port, ports)
201+
202+
# bail out if no descriptor found, because port could be a network address, a pipe or
203+
# something else.
204+
if not descriptor:
205+
return (args.port, None)
176206
else:
177-
enumerate_probes(ports)
178-
if args.serial:
179-
port = search_serial(args.serial, ports)
180-
assert port, "no BMP with this serial found"
207+
assert len(ports) > 0, "no ports found"
208+
descriptor = ports[0]
209+
210+
enumerate_probes(ports)
211+
print(f'connecting to [{descriptor.device}]...')
212+
return (descriptor.device, descriptor)
213+
214+
215+
# choose firmware version, based on available descriptors and application arguments.
216+
def choose_firmware_version(args, descriptor):
217+
if args.bmp_version == "auto":
218+
if descriptor and descriptor.firmware_version:
219+
version = descriptor.firmware_version
220+
print(f"auto-detected firmware version {version}")
181221
else:
182-
assert len(ports) > 0, "no ports found"
183-
port = ports[0].device
184-
print('connecting to [%s]...' % port)
185-
return port
222+
version = DEFAULT_VERSION
223+
print(f"unable to detect firmware version, assuming {version} or later")
224+
else:
225+
version = Version(args.bmp_version)
226+
print(f"using firmware version {version}")
227+
228+
return version
186229

187230

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

194237
# debug mode, opens GDB shell with options
195238
def debug_mode(args, port):
196-
gdb_args = ['-ex \'target extended-remote %s\'' % port]
239+
gdb_args = [f'-ex \'target extended-remote {port}\'']
197240
if args.tpwr:
198241
gdb_args.append('-ex \'monitor tpwr enable\'')
199242
if args.connect_srst:
@@ -208,8 +251,8 @@ def debug_mode(args, port):
208251
gdb_args.append('-ex \'monitor swd_scan\'')
209252
else:
210253
gdb_args.append('-ex \'monitor swdp_scan\'')
211-
gdb_args.append('-ex \'attach %s\'' % args.attach)
212-
os.system(" ".join(['\"' + args.gdb_path + '\"'] + gdb_args + [args.file]))
254+
gdb_args.append(f'-ex \'attach {args.attach}\'')
255+
os.system(" ".join([f'\"{args.gdb_path}\"'] + gdb_args + [args.file]))
213256

214257

215258
def connect_to_target(args, port):
@@ -220,7 +263,7 @@ def connect_to_target(args, port):
220263
except TypeError:
221264
# and then new API
222265
gdbmi = GdbController(command=[args.gdb_path, "--nx", "--quiet", "--interpreter=mi2", args.file])
223-
assert gdb_write_and_wait_for_result(gdbmi, '-target-select extended-remote %s' % port, 'connecting',
266+
assert gdb_write_and_wait_for_result(gdbmi, f'-target-select extended-remote {port}', 'connecting',
224267
expected_result='connected')
225268
# set options
226269
if args.connect_srst:
@@ -243,10 +286,13 @@ def connect_to_target(args, port):
243286
targets = detect_targets(gdbmi, res)
244287
assert len(targets) > 0, "no targets found"
245288
print("found following targets:")
246-
for t in targets:
247-
print("\t%s" % t)
289+
for s, t in targets:
290+
if not s:
291+
print(f"\t{t} (unsupported)")
292+
else:
293+
print(f"\t{t}")
248294
print("")
249-
return gdbmi
295+
return (gdbmi, targets)
250296

251297

252298
def parse_args():
@@ -258,9 +304,9 @@ def parse_args():
258304
parser.add_argument('--tpwr', action='store_true', help='enable target power')
259305
parser.add_argument('--serial', help='choose specific probe by serial number')
260306
parser.add_argument('--port', help='choose specific probe by port (overrides auto selection)')
261-
parser.add_argument('--attach', help='choose specific target by number', default='1')
307+
parser.add_argument('--attach', help='choose specific target by number', type=int, default=1)
262308
parser.add_argument('--gdb-path', help='path to GDB', default='gdb-multiarch')
263-
parser.add_argument('--bmp-version', help='choose specific firmware version', default='1.10.0')
309+
parser.add_argument('--bmp-version', help='choose specific firmware version', default='auto')
264310
parser.add_argument('--term-cmd', help='serial terminal command',
265311
default='picocom --nolock --imap lfcrlf --baud 115200 %s')
266312

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

281327
if args.action == 'term':
282-
port = choose_port(args, u)
328+
(port, _) = choose_probe(args, u)
283329

284330
term_mode(args, port)
285331
else:
286-
port = choose_port(args, g)
332+
(port, descriptor) = choose_probe(args, g)
287333

288334
args.file = args.file if args.file else ''
289-
args.bmp_version = Version(args.bmp_version)
335+
args.bmp_version = choose_firmware_version(args, descriptor)
290336
args.gdb_path = find_suitable_gdb(args.gdb_path)
291337

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

296-
gdbmi = connect_to_target(args, port)
342+
(gdbmi, targets) = connect_to_target(args, port)
297343

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

301-
assert gdb_write_and_wait_for_result(gdbmi, '-target-attach %s' % args.attach, 'attaching to target')
347+
assert len(targets) >= args.attach, "attach greater than number of targets"
348+
assert targets[args.attach - 1][0], "target unsupported by probe"
349+
350+
assert gdb_write_and_wait_for_result(gdbmi, f'-target-attach {args.attach}', 'attaching to target')
302351

303352
# reset mode: reset device using reset pin
304353
if args.action == 'reset':

0 commit comments

Comments
 (0)