Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

makefiles/tools/serial.inc.mk: Allow detection of debug adapter #19119

Merged
merged 3 commits into from
Feb 24, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions boards/arduino-mega2560/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ ARDUINO_MEGA2560_COMPAT_WITH_CLONES ?= 1
ifeq (1,$(ARDUINO_MEGA2560_COMPAT_WITH_CLONES))
TTY_SELECT_CMD := $(RIOTTOOLS)/usb-serial/ttys.py \
--most-recent \
--format path \
--format path serial \
$(TTY_BOARD_FILTER) || \
$(RIOTTOOLS)/usb-serial/ttys.py \
--most-recent \
--format path \
--format path serial \
$(TTY_BOARD_FILTER_CLONE)
endif

Expand Down
4 changes: 4 additions & 0 deletions boards/common/nucleo/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ include $(RIOTMAKE)/boards/stm32.inc.mk
# USB serials to only select the UART bridge of embedded STLink debuggers.
TTY_BOARD_FILTER := --model 'STM32 STLink'

# The TTY serial also is the ID of the debug adapter, as the TTY is provided by
# the debug adapter
DEBUG_ADAPTER_ID_IS_TTY_SERIAL := 1

# variable needed by cpy2remed PROGRAMMER
# it contains name of ST-Link removable media

Expand Down
4 changes: 4 additions & 0 deletions boards/hifive1b/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,9 @@ endif
# the UART bridge to the ESP32-SOLO-1 MCU instead of the FE310 MCU on the board.
TTY_BOARD_FILTER := --model HiFive --iface-num 0

# The TTY serial also is the ID of the debug adapter, as the TTY is provided by
# the debug adapter
DEBUG_ADAPTER_ID_IS_TTY_SERIAL := 1

TESTRUNNER_RESET_DELAY = 1
$(call target-export-variables,test,TESTRUNNER_RESET_DELAY)
4 changes: 4 additions & 0 deletions boards/microbit-v2/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ PROGRAMMERS_SUPPORTED += pyocd
# programmer firmware revisions "fix" that.
TTY_BOARD_FILTER := --model ".?BBC micro:bit CMSIS-DAP.?"

# The TTY serial also is the ID of the debug adapter, as the TTY is provided by
# the debug adapter
DEBUG_ADAPTER_ID_IS_TTY_SERIAL := 1

# The board is not recognized automatically by pyocd, so the CPU target
# option is passed explicitly
PYOCD_FLASH_TARGET_TYPE ?= -t $(CPU)
Expand Down
4 changes: 4 additions & 0 deletions boards/nrf52840dk/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,8 @@
# USB serials to only select the UART bridge of integrated J-Link debugger.
TTY_BOARD_FILTER := --model J-Link

# The TTY serial also is the ID of the debug adapter, as the TTY is provided by
# the debug adapter
DEBUG_ADAPTER_ID_IS_TTY_SERIAL := 1

include $(RIOTBOARD)/common/nrf52xxxdk/Makefile.include
4 changes: 4 additions & 0 deletions boards/qn9080dk/Makefile.include
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,9 @@ CFLAGS += \
QN908X_JLINK ?= $(QN9080DK_JLINK)
JLINK_DEVICE ?= QN9080A

# The TTY serial also is the ID of the debug adapter, as the TTY is provided by
# the debug adapter
DEBUG_ADAPTER_ID_IS_TTY_SERIAL := 1

# Include default QN908x board config
include $(RIOTBOARD)/common/qn908x/Makefile.include
14 changes: 13 additions & 1 deletion dist/tools/usb-serial/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,19 @@ With the parameter `--format FORMAT` a different format than the default
markdown table can be selected, e.g. `json` results in JSON output and `path`
will print the paths of the matching TTYs without any formatting (useful for
scripting). The full list of formats can be obtained by running the script with
the `--help` parameter
the `--help` parameter.

Note: Formats other than `json` and `table` can be combined. A script that
required both path and serial of TTYs could use:

```
./ttys.py --format path serial
```

This will output one TTY per line with the selected fields separated by space.
To use a different separator than space (e.g. to create CSV files), the option
`--format-sep` can be used. If a field value contains the separator, it will
be quoted and quotation chars inside will be escaped.

### Filtering

Expand Down
60 changes: 42 additions & 18 deletions dist/tools/usb-serial/ttys.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,7 @@ def parse_args(args):
Parse the given command line style arguments with argparse
"""
desc = "List and filter TTY interfaces that might belong to boards"
supported_formats = {
"table",
"json",
formats_combinable = {
"path",
"serial",
"vendor",
Expand All @@ -74,13 +72,20 @@ def parse_args(args):
"ctime",
"iface_num",
}
formats_uncombinable = {
"table",
"json",
}
supported_formats = formats_combinable.union(formats_uncombinable)
parser = argparse.ArgumentParser(description=desc)
parser.add_argument("--most-recent", action="store_true",
help="Print only the most recently connected matching "
+ "TTY")
parser.add_argument("--format", default="table", type=str,
parser.add_argument("--format", default=["table"], type=str, nargs='+',
help=f"How to format the TTYs. Supported formats: "
f"{sorted(supported_formats)}")
parser.add_argument("--format-sep", default=" ", type=str,
help="Separator between formats (default: space)")
parser.add_argument("--serial", default=None, type=str,
help="Print only devices matching this serial")
parser.add_argument("--driver", default=None, type=str,
Expand Down Expand Up @@ -108,8 +113,17 @@ def parse_args(args):

args = parser.parse_args()

if args.format not in supported_formats:
sys.exit(f"Format \"{args.format}\" not supported")
if len(args.format) == 1:
if args.format[0] not in supported_formats:
sys.exit(f"Format \"{args.format[0]}\" not supported")
else:
for fmt in args.format:
if fmt not in formats_combinable:
if fmt in formats_uncombinable:
sys.exit(f"Format \"{fmt}\" cannot be combined with " +
"other formats")
else:
sys.exit(f"Format \"{fmt}\" not supported")

if args.exclude_serial is None:
if "EXCLUDE_TTY_SERIAL" in os.environ:
Expand Down Expand Up @@ -154,21 +168,31 @@ def print_results(args, ttys):
"""
Print the given TTY devices according to the given args
"""
if args.format == "json":
print(json.dumps(ttys, indent=2))
return
if len(args.format) == 1:
if args.format[0] == "json":
print(json.dumps(ttys, indent=2))
return

if args.format == "table":
for tty in ttys:
tty["ctime"] = time.strftime("%H:%M:%S",
time.localtime(tty["ctime"]))
headers = ["path", "driver", "vendor", "model", "model_db", "serial",
"ctime", "iface_num"]
print_table(ttys, headers)
return
if args.format[0] == "table":
for tty in ttys:
tty["ctime"] = time.strftime("%H:%M:%S",
time.localtime(tty["ctime"]))
headers = ["path", "driver", "vendor", "model", "model_db",
"serial", "ctime", "iface_num"]
print_table(ttys, headers)
return

for tty in ttys:
print(tty[args.format])
line = ""
for fmt in args.format:
item = tty[fmt]
if item.rfind(args.format_sep) >= 0:
# item contains separator --> quote it
# using json.dumps to also escape quotation chars and other
# unsafe stuff
item = json.dumps(item)
line += f"{args.format_sep}{item}"
print(line[len(args.format_sep):])


def generate_filters(args):
Expand Down
14 changes: 7 additions & 7 deletions doc/doxygen/src/flashing.md
Original file line number Diff line number Diff line change
Expand Up @@ -478,20 +478,20 @@ In most cases, just adding a simple `TTY_BOARD_FILTER` is sufficient. If we
however have wildly different flavors of the same board (e.g. genuine Arduino
Mega 2560 with an ATmega16U2 and clones with a cheap USB to UART bridge) that we
all want to support, we have to instead provide a `TTY_SELECT_CMD` that prints
the path to the TTY and exists with `0` if a TTY was found, or that exists with
`1` and prints nothing when no TTY was found. We can still use the `ttys.py`
script to detect all Arduino Mega 2560 versions: We first try to detect a
genuine Arduino Mega and fall back to selecting cheap USB UART bridges when that
fails using the `||` shell operator:
the path to and the serial of the TTY (separated by a space) and exists with
`0` if a TTY was found, or that exists with `1` and prints nothing when no TTY
was found. We can still use the `ttys.py` script to detect all Arduino Mega
2560 versions: We first try to detect a genuine Arduino Mega and fall back to
selecting cheap USB UART bridges when that fails using the `||` shell operator:

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
TTY_SELECT_CMD := $(RIOTTOOLS)/usb-serial/ttys.py \
--most-recent \
--format path \
--format path serial \
--vendor 'Arduino' \
--model-db 'Mega 2560|Mega ADK' || \
$(RIOTTOOLS)/usb-serial/ttys.py \
--most-recent \
--format path \
--format path serial \
--driver 'cp210x'
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
11 changes: 8 additions & 3 deletions makefiles/tools/serial.inc.mk
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@ ifeq (1,$(MOST_RECENT_PORT))
endif
TTY_SELECT_CMD ?= $(RIOTTOOLS)/usb-serial/ttys.py \
--most-recent \
--format path \
--format path serial \
$(TTY_BOARD_FILTER)
PORT_DETECTED := $(shell $(TTY_SELECT_CMD) || echo 'no-tty-detected')
PORT ?= $(PORT_DETECTED)
TTY_DETECTED := $(shell $(TTY_SELECT_CMD) || echo 'no-tty-detected no-serial-detected')
PORT_DETECTED := $(firstword $(TTY_DETECTED))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we ever expect OSes to assign TTY names that contain whitespace, or devices to expose serials that do?

I don't see any direct security concerns, so I'll just leave that as a note and not a blocker.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ttys.py will quote any output that would contain the "seperation char", which by default is the space. I have to admit that I don't know for sure if $(firstword "foo bar" "blah blah") does indeed yield "foo bar", or just "foo.

PORT_SERIAL_DETECTED := $(lastword $(TTY_DETECTED))
PORT ?= $(firstword $(TTY_DETECTED))
ifeq (1,$(DEBUG_ADAPTER_ID_IS_TTY_SERIAL))
DEBUG_ADAPTER_ID ?= $(PORT_SERIAL_DETECTED)
endif
endif
# Otherwise, use as default the most commonly used ports on Linux and OSX
PORT_LINUX ?= /dev/ttyACM0
Expand Down