scripts: Blackmagic probe flash and debug scripts - Also includes Linux udev rules for both. - Fix applications/system_example/BUILD.gn on Mac - Python helper script to find serial port device paths Bug: b/310955626 Change-Id: Ib3297eae7af53d585ac637fd29d2eed2c4646f6b Reviewed-on: https://pigweed-review.googlesource.com/c/gonk/+/177898 Commit-Queue: Anthony DiGirolamo <tonymd@google.com> Reviewed-by: Carlos Chinchilla <cachinchilla@google.com> Reviewed-by: Rob Mohr <mohrr@google.com>
diff --git a/applications/system_example/BUILD.gn b/applications/system_example/BUILD.gn index 851a7e7..5892b83 100644 --- a/applications/system_example/BUILD.gn +++ b/applications/system_example/BUILD.gn
@@ -30,5 +30,7 @@ "$dir_pw_unit_test:rpc_service", ] - ldflags = [ "-Wl,--print-memory-usage" ] + if (pw_build_EXECUTABLE_TARGET_TYPE == "//targets/stm32f769i_disc0_stm32cube:stm32f769i_disc0_stm32cube.size_optimized") { + ldflags = [ "-Wl,--print-memory-usage" ] + } }
diff --git a/scripts/debug.sh b/scripts/debug.sh new file mode 100755 index 0000000..8b08206 --- /dev/null +++ b/scripts/debug.sh
@@ -0,0 +1,97 @@ +#!/bin/bash +# Copyright 2023 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Launch a debug session with gdb-dashboard and miniterm in separate Tmux +# splits. + +# Linux Blackmagic Probe +DEVBMP="/dev/ttyBmpGdb" +FOUND_BMP=$(python -m gonk_tools.find_serial_port -p 'Black Magic Probe' -1) && DEVBMP=$FOUND_BMP + +DEVGONK="/dev/ttyGonk" +FOUND_GONK=$(python -m gonk_tools.find_serial_port -p 'GENERIC_F730R8TX' -1) && DEVGONK=$FOUND_GONK + +GONK_BAUDRATE=115200 + +# GDB binary +GDB=arm-none-eabi-gdb +which gdb-multiarch && GDB=gdb-multiarch + +# List all active panes in this tab. For example: +# +# 0 /dev/pts/4 (active) +# 1 /dev/pts/7 +# 2 /dev/pts/6 +# 3 /dev/pts/5 +# +tmux_list_panes () { + tmux list-panes -F '#P #{pane_tty} #{?pane_active,(active),}' +} + +# Get the current tmux pane in focus +tmux_get_active_pane_id () { + tmux_list_panes | awk '/(active)/ { print $1}' +} + +# Get the pane ID for a given TTY. +# Useful for performing an action on a pane not in focus. +tmux_get_pane_id () { + tmux_list_panes | awk "\$0~\"${1}\" { print \$1}" +} + +# Get the current pane TTY for a given TTY +tmux_get_active_pane_tty () { + tmux_list_panes | awk '/(active)/ { print $2}' +} + + +split_tmux_panes () { + ORIGINAL_PANE=$(tmux_get_active_pane_id) + + # Make a split for gdb-dashboard + tmux select-pane -t $ORIGINAL_PANE + tmux split-window -h + DASHBOARD_TTY=$(tmux_get_active_pane_tty) + + # Make a split for serial output + tmux select-pane -t $ORIGINAL_PANE + tmux split-window -v "sleep 4; python -m serial.tools.miniterm --raw ${DEVGONK} ${GONK_BAUDRATE}" + SERIAL_TTY=$(tmux_get_active_pane_tty) + + # Select the original pane and run gdb + tmux select-pane -t $ORIGINAL_PANE +} + +tmux_cleanup () { + tmux kill-pane -t $(tmux_get_pane_id $SERIAL_TTY) + tmux kill-pane -t $(tmux_get_pane_id $DASHBOARD_TTY) +} + +which tmux && split_tmux_panes + +$GDB \ + -ex "set confirm off" \ + -ex "dashboard -output ${DASHBOARD_TTY}" \ + -ex "target extended-remote ${DEVBMP}" \ + -ex "monitor version" \ + -ex "monitor tpwr enable" \ + -ex "monitor swdp_scan" \ + -ex "attach 1" \ + -ex "load" \ + -ex "compare-sections" \ + -ex "run" \ + $@ + +which tmux && tmux_cleanup
diff --git a/scripts/flash-with-blackmagic-probe.sh b/scripts/flash-with-blackmagic-probe.sh new file mode 100755 index 0000000..df3248b --- /dev/null +++ b/scripts/flash-with-blackmagic-probe.sh
@@ -0,0 +1,31 @@ +#!/bin/bash +# Copyright 2023 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. + +# Flash an elf using the Blackmagic probe. + +DEVBMP="/dev/ttyBmpGdb" +FOUND_BMP=$(python -m gonk_tools.find_serial_port -p 'Black Magic Probe' -1) && DEVBMP=$FOUND_BMP + +arm-none-eabi-gdb -nx --batch \ + -ex "set confirm off" \ + -ex "target extended-remote ${DEVBMP}" \ + -ex "monitor version" \ + -ex "monitor tpwr enable" \ + -ex "monitor swdp_scan" \ + -ex "attach 1" \ + -ex "load" \ + -ex "compare-sections" \ + -ex "kill" \ + $@
diff --git a/scripts/udev-rules/99-blackmagic-plugdev.rules b/scripts/udev-rules/99-blackmagic-plugdev.rules new file mode 100644 index 0000000..4b24035 --- /dev/null +++ b/scripts/udev-rules/99-blackmagic-plugdev.rules
@@ -0,0 +1,16 @@ +# Black Magic Probe +# There are two connections, one for GDB and one for UART debugging. +# +# Copy this to /usr/lib/udev/rules.d/99-blackmagic.rules +# and run: +# sudo udevadm control --reload-rules +# sudo udevadm trigger + +ACTION!="add|change", GOTO="blackmagic_rules_end" +SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb" +SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg" +SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic GDB Server", SYMLINK+="ttyBmpGdb%E{ID_SERIAL_SHORT}" +SUBSYSTEM=="tty", ACTION=="add", ATTRS{interface}=="Black Magic UART Port", SYMLINK+="ttyBmpTarg%E{ID_SERIAL_SHORT}" +SUBSYSTEM=="usb", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6017", MODE="0666", GROUP="plugdev", TAG+="uaccess" +SUBSYSTEM=="usb", ATTR{idVendor}=="1d50", ATTR{idProduct}=="6018", MODE="0666", GROUP="plugdev", TAG+="uaccess" +LABEL="blackmagic_rules_end"
diff --git a/scripts/udev-rules/99-gonk.rules b/scripts/udev-rules/99-gonk.rules new file mode 100644 index 0000000..d546903 --- /dev/null +++ b/scripts/udev-rules/99-gonk.rules
@@ -0,0 +1,7 @@ +# Copy this to /usr/lib/udev/rules.d/99-gonk.rules +# and run: +# sudo udevadm control --reload-rules +# sudo udevadm trigger + +SUBSYSTEMS=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE:="0666", GROUP="plugdev" +KERNEL=="ttyACM*", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="5740", MODE:="0666", GROUP="plugdev", SYMLINK+="ttyGonk"
diff --git a/tools/BUILD.gn b/tools/BUILD.gn index d37ec86..90dc3d4 100644 --- a/tools/BUILD.gn +++ b/tools/BUILD.gn
@@ -24,6 +24,7 @@ sources = [ "gonk_tools/__init__.py", "gonk_tools/build_project.py", + "gonk_tools/find_serial_port.py", "gonk_tools/presubmit_checks.py", ] python_deps = [
diff --git a/tools/gonk_tools/find_serial_port.py b/tools/gonk_tools/find_serial_port.py new file mode 100644 index 0000000..abf678e --- /dev/null +++ b/tools/gonk_tools/find_serial_port.py
@@ -0,0 +1,108 @@ +#!/usr/bin/env python3 +# Copyright 2023 The Pigweed Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may not +# use this file except in compliance with the License. You may obtain a copy of +# the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations under +# the License. +"""Find serial ports.""" + +import argparse +import operator +import sys +from typing import Optional, Sequence + +from serial.tools.list_ports import comports +from serial.tools.list_ports_common import ListPortInfo + + +def _parse_args(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + '-l', + '--list-ports', + action='store_true', + help='List all port info.', + ) + parser.add_argument( + '-p', + '--product', + help='Print ports matching this product name.', + ) + parser.add_argument( + '-s', + '--serial-number', + help='Print ports matching this serial number.', + ) + parser.add_argument( + '-1', + '--print-first-match', + action='store_true', + help='Print the first port found sorted by device path.', + ) + return parser.parse_args() + + +def _print_ports(ports: Sequence[ListPortInfo]): + for cp in ports: + for line in [ + f"device = {cp.device}", + f"name = {cp.name}", + f"description = {cp.description}", + f"vid = {cp.vid}", + f"pid = {cp.pid}", + f"serial_number = {cp.serial_number}", + f"location = {cp.location}", + f"manufacturer = {cp.manufacturer}", + f"product = {cp.product}", + f"interface = {cp.interface}", + ]: + print(line) + print() + + +def main( + list_ports: bool = False, + product: Optional[str] = None, + serial_number: Optional[str] = None, + print_first_match: bool = False, +) -> int: + """List all device info or print matches.""" + ports = sorted(comports(), key=operator.attrgetter('device')) + + if list_ports: + _print_ports(ports) + return 0 + + any_match_found = False + + # Print matching devices + for port in ports: + if (product is not None and port.product is not None + and product in port.product): + any_match_found = True + print(port.device) + + if (serial_number is not None and port.serial_number is not None + and serial_number in port.serial_number): + any_match_found = True + print(port.device) + + if any_match_found and print_first_match: + return 0 + + if not any_match_found: + return 1 + + return 0 + + +if __name__ == '__main__': + sys.exit(main(**vars(_parse_args())))