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())))