From 3d3f0f9623b9fa291995af4cd8316136a4bbf8c0 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Thu, 9 Jan 2025 22:16:56 +0100 Subject: [PATCH 1/4] dist/tools/bmp: set argument type --- dist/tools/bmp/bmp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dist/tools/bmp/bmp.py b/dist/tools/bmp/bmp.py index e43a1e24013a..9ac85293b590 100755 --- a/dist/tools/bmp/bmp.py +++ b/dist/tools/bmp/bmp.py @@ -258,7 +258,7 @@ 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('--term-cmd', help='serial terminal command', From 7a1128fb4a8cc309fa52b661e6f0e47682289b3a Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Thu, 9 Jan 2025 22:28:34 +0100 Subject: [PATCH 2/4] dist/tools/bmp: use f-strings in stead of interpolation --- dist/tools/bmp/bmp.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/dist/tools/bmp/bmp.py b/dist/tools/bmp/bmp.py index 9ac85293b590..c498023fafd3 100755 --- a/dist/tools/bmp/bmp.py +++ b/dist/tools/bmp/bmp.py @@ -35,7 +35,7 @@ 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) @@ -62,9 +62,9 @@ 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 i == 0: print("<- default", end=' ') print('') @@ -131,7 +131,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 +141,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,12 +158,12 @@ 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) @@ -181,7 +179,7 @@ def choose_port(args, ports): else: assert len(ports) > 0, "no ports found" port = ports[0].device - print('connecting to [%s]...' % port) + print(f'connecting to [{port}]...') return port @@ -193,7 +191,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 +206,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 +218,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: @@ -244,7 +242,7 @@ def connect_to_target(args, port): assert len(targets) > 0, "no targets found" print("found following targets:") for t in targets: - print("\t%s" % t) + print(f"\t{t}") print("") return gdbmi @@ -298,7 +296,7 @@ def main(): if args.action == 'list': sys.exit(0) - assert gdb_write_and_wait_for_result(gdbmi, '-target-attach %s' % args.attach, 'attaching to target') + 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': From b07f32d2abc639aa8d50323d4d86ba480b66947b Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sat, 11 Jan 2025 20:09:08 +0100 Subject: [PATCH 3/4] dist/tools/bmp: improve auto-detection of firmware Parse the version number from the product description as returned by libusb. This works for stable firmware releases, and ensures that older firmwares work out of the box. --- dist/tools/bmp/README.md | 12 ++++--- dist/tools/bmp/bmp.py | 77 +++++++++++++++++++++++++++++++--------- 2 files changed, 68 insertions(+), 21 deletions(-) diff --git a/dist/tools/bmp/README.md b/dist/tools/bmp/README.md index 375087ee3107..6307283c13d0 100644 --- a/dist/tools/bmp/README.md +++ b/dist/tools/bmp/README.md @@ -51,13 +51,17 @@ 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. ## Examples (tested with BluePill STM32F103F8C6) * test connection: diff --git a/dist/tools/bmp/bmp.py b/dist/tools/bmp/bmp.py index c498023fafd3..aa1599ef58a6 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 @@ -41,12 +42,24 @@ def find_suitable_gdb(gdb_path): 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) \ @@ -65,6 +78,8 @@ def enumerate_probes(ports): print(f"\t[{s.device}]", end=' ') if len(s.serial_number) > 1: 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 @@ -167,20 +189,41 @@ def check_flash(gdbmi): 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(f'connecting to [{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 @@ -258,7 +301,7 @@ def parse_args(): parser.add_argument('--port', help='choose specific probe by port (overrides auto selection)') 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') @@ -277,14 +320,14 @@ 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': From b27eb50a19264e1ea50ed0caca0922789335bfa5 Mon Sep 17 00:00:00 2001 From: Bas Stottelaar Date: Sat, 11 Jan 2025 23:31:58 +0100 Subject: [PATCH 4/4] dist/tools/bmp: detect unsupported targets Instead of listing 'no targets found', detect unsupported targets as well. Once we need to attach, assert that the target is supported. Starting with firmware 2.0.0 of the BMP, not all targets are supported by defaults (depends on the firmware flavor). Adding support for this case therefore makes sense. --- dist/tools/bmp/README.md | 4 ++++ dist/tools/bmp/bmp.py | 20 ++++++++++++++------ 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/dist/tools/bmp/README.md b/dist/tools/bmp/README.md index 6307283c13d0..640257a86999 100644 --- a/dist/tools/bmp/README.md +++ b/dist/tools/bmp/README.md @@ -63,6 +63,10 @@ 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 aa1599ef58a6..1c8ec3acf900 100755 --- a/dist/tools/bmp/bmp.py +++ b/dist/tools/bmp/bmp.py @@ -105,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 @@ -284,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(f"\t{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(): @@ -334,11 +339,14 @@ def main(): 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 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