diff --git a/dist/tools/bmp/README.md b/dist/tools/bmp/README.md index 375087ee3107..640257a86999 100644 --- a/dist/tools/bmp/README.md +++ b/dist/tools/bmp/README.md @@ -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: diff --git a/dist/tools/bmp/bmp.py b/dist/tools/bmp/bmp.py index e43a1e24013a..1c8ec3acf900 100755 --- a/dist/tools/bmp/bmp.py +++ b/dist/tools/bmp/bmp.py @@ -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 @@ -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) \ @@ -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('') @@ -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 in a list of ports +def search_port(prt, ports): + for port in ports: + if prt == port.device: + return port # parse GDB output for targets @@ -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 @@ -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") @@ -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) @@ -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 @@ -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: @@ -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): @@ -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: @@ -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(): @@ -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') @@ -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':