Add serial port discovery (#1498)

Co-authored-by: Stefan Rueger <stefan.rueger@urclocks.com>
This commit is contained in:
Hans
2023-09-23 00:21:57 +02:00
committed by GitHub
parent 431fe61ffa
commit 0be6568b76
11 changed files with 678 additions and 81 deletions

View File

@@ -125,6 +125,7 @@ if(USE_STATIC_LIBS)
set(PREFERRED_LIBFTDI libftdi.a ftdi)
set(PREFERRED_LIBFTDI1 libftdi1.a ftdi1)
set(PREFERRED_LIBREADLINE libreadline.a)
set(PREFERRED_LIBSERIALPORT libserialport.a)
set(PREFERRED_LIBGPIOD libgpiod.a gpiod)
else()
set(PREFERRED_LIBELF elf)
@@ -134,6 +135,7 @@ else()
set(PREFERRED_LIBFTDI ftdi)
set(PREFERRED_LIBFTDI1 ftdi1)
set(PREFERRED_LIBREADLINE readline)
set(PREFERRED_LIBSERIALPORT serialport)
set(PREFERRED_LIBGPIOD gpiod)
endif()
@@ -225,6 +227,15 @@ elseif(MSVC)
set(HAVE_LIBREADLINE 1)
endif()
#-------------------------------------
# Find libserialport
find_library(HAVE_LIBSERIALPORT NAMES ${PREFERRED_LIBSERIALPORT})
if(HAVE_LIBSERIALPORT)
set(LIB_LIBSERIALPORT ${HAVE_LIBSERIALPORT})
set(HAVE_LIBSERIALPORT 1)
endif()
# -------------------------------------
# Find libgpiod, if needed
if(HAVE_LINUXGPIO)
@@ -319,6 +330,7 @@ if (DEBUG_CMAKE)
message(STATUS "HAVE_LIBFTDI: ${HAVE_LIBFTDI}")
message(STATUS "HAVE_LIBFTDI1: ${HAVE_LIBFTDI1}")
message(STATUS "HAVE_LIBREADLINE: ${HAVE_LIBREADLINE}")
message(STATUS "HAVE_LIBSERIALPORT: ${HAVE_LIBSERIALPORT}")
message(STATUS "HAVE_LIBELF_H: ${HAVE_LIBELF_H}")
message(STATUS "HAVE_LIBELF_LIBELF_H: ${HAVE_LIBELF_LIBELF_H}")
message(STATUS "HAVE_USB_H: ${HAVE_USB_H}")
@@ -377,6 +389,12 @@ else()
message(STATUS "DON'T HAVE libreadline")
endif()
if(HAVE_LIBSERIALPORT)
message(STATUS "DO HAVE libserialport")
else()
message(STATUS "DON'T HAVE libserialport")
endif()
if(BUILD_DOC)
message(STATUS "ENABLED doc")
else()

View File

@@ -63,7 +63,7 @@ avrdude_CFLAGS = @ENABLE_WARNINGS@
libavrdude_a_CFLAGS = @ENABLE_WARNINGS@
libavrdude_la_CFLAGS = $(libavrdude_a_CFLAGS)
avrdude_LDADD = $(top_builddir)/$(noinst_LIBRARIES) @LIBUSB_1_0@ @LIBHIDAPI@ @LIBUSB@ @LIBFTDI1@ @LIBFTDI@ @LIBHID@ @LIBELF@ @LIBPTHREAD@ -lm
avrdude_LDADD = $(top_builddir)/$(noinst_LIBRARIES) @LIBUSB_1_0@ @LIBHIDAPI@ @LIBUSB@ @LIBFTDI1@ @LIBFTDI@ @LIBHID@ @LIBELF@ @LIBPTHREAD@ @LIBSERIALPORT@ -lm
bin_PROGRAMS = avrdude

View File

@@ -601,20 +601,53 @@ Note that the result will be stored in the EEPROM cell at address 0.
.It Fl P Ar port
Use
.Ar port
to identify the device to which the programmer is attached. By
default the
.Pa /dev/ppi0
port is used, but if the programmer type normally connects to the
serial port, the
.Pa /dev/cuaa0
port is the default. If you need to use a different parallel or
serial port, use this option to specify the alternate port name.
to identify the connection through which the programmer is attached. This
can be a parallel, serial, spi or linuxgpio connection. The programmer
normally specifies the connection type; in absence of a -P specification,
system-dependent default values
.Pa default_parallel ,
.Pa default_serial ,
.Pa default_spi ,
or
.Pa default_linuxgpio
from the configuration file are used. If you need to use a different port,
use this option to specify the alternate port name.
.Pp
If
.Nm
has been configured with libserialport support, a serial port can be specified
using a predefined serial adapter type in avrdude.conf or .avrduderc, e.g.,
.Ar ch340
or
.Ar ft232r .
If more than one serial adapter of the same type is connected, they can be
distinguished by appending a serial number, e.g.,
.Ar ft232r:12345678 .
Note that the USB to serial chip has to have a serial number for this to work.
.Nm Avrdude
can check for leading and trailing serial number matches as well.
In the above example,
.Ar ft232r:1234
would also result in a match, and so would
.Ar ft232r:...5678 .
If the USB to serial chip is not known to
.Nm ,
it can be specified using the hexadecimal USB vendor ID, hexadecimal
product ID and an optional serial number, following the serial number
matching rules described above, e.g.,
.Ar usb:0x2341:0x0043
or
.Ar usb:2341:0043:12345678 .
To see a list of currently plugged-in serial ports use -P ?s. In order to
see a list of all possible serial adapters known to
.Nm
use -P ?sa.
.Pp
On Win32 operating systems, the parallel ports are referred to as lpt1
through lpt3, referring to the addresses 0x378, 0x278, and 0x3BC,
respectively. If the parallel port can be accessed through a different
address, this address can be specified directly, using the common C
language notation (i. e., hexadecimal values are prefixed by
language notation (i.e., hexadecimal values are prefixed by
.Ql 0x
).
.Pp
@@ -984,7 +1017,6 @@ See below for a list of programmers accepting extended parameters
or issue
.Nm
-x help ... to see the extended options of the chosen programmer.
.El
.Ss Terminal mode
In this mode,
@@ -1873,7 +1905,7 @@ avrdude: Target prepared for ISP, signed off.
avrdude: Please restart avrdude without power-cycling the target.
.Ed
.Pp
If the target AVR has been set up for debugWire mode (i. e. the
If the target AVR has been set up for debugWire mode (i.e., the
.Em DWEN
fuse is programmed), normal ISP connection attempts will fail as
the
@@ -1919,7 +1951,7 @@ one byte at a time.
For that reason, updating the flash ROM from terminal mode does not
work.
.Pp
Page-mode programming the EEPROM through JTAG (i.e. through an
Page-mode programming the EEPROM through JTAG (i.e., through an
.Fl U
option) requires a prior chip erase.
This is an inherent feature of the way JTAG EEPROM programming works.

View File

@@ -2967,6 +2967,50 @@ serialadapter
usbpid = 0x7523;
;
#------------------------------------------------------------
# ch9102
#------------------------------------------------------------
serialadapter
id = "ch9102";
desc = "WCH CH9102 USB to serial adapter";
usbvid = 0x1a86;
usbpid = 0x55d4;
;
#------------------------------------------------------------
# cp210x
#------------------------------------------------------------
serialadapter
id = "cp210x";
desc = "Silabs CP210x USB to serial adapter";
usbvid = 0x10c4;
usbpid = 0xea60, 0xea70, 0xea71;
;
#------------------------------------------------------------
# ft231x / ft234x / ft230x
#------------------------------------------------------------
serialadapter
id = "ft231x", "ft234x", "ft230x";
desc = "FTDI FT23X series USB to serial adapter";
usbvid = 0x0403;
usbpid = 0x6015;
;
#------------------------------------------------------------
# pl2303
#------------------------------------------------------------
serialadapter
id = "pl2303";
desc = "Profilic PL2303 USB to serial adapter";
usbvid = 0x067b;
usbpid = 0x2303;
;
#
# PART DEFINITIONS
#

View File

@@ -99,3 +99,6 @@
/* Define to 1 if you have the `readline' library (-lreadline). */
#cmakedefine HAVE_LIBREADLINE 1
/* Define to 1 if you have the `serialport' library */
#cmakedefine HAVE_LIBSERIALPORT 1

View File

@@ -185,6 +185,19 @@ if test x$have_libhidapi = xyes; then
fi
AC_SUBST(LIBHIDAPI, $LIBHIDAPI)
AH_TEMPLATE([HAVE_LIBSERIALPORT],
[Define if libserialport is found])
AC_CHECK_LIB([serialport], [sp_open], [have_libserialport=yes])
if test x$have_libserialport = xyes; then
case $target in
*)
LIBSERIALPORT="-lserialport"
;;
esac
AC_DEFINE([HAVE_LIBSERIALPORT])
AC_CHECK_HEADERS([libserialport.h])
fi
AC_SUBST(LIBSERIALPORT, $LIBSERIALPORT)
AH_TEMPLATE([HAVE_LIBFTDI1],
[Define if FTDI support is enabled via libftdi1])
@@ -604,6 +617,12 @@ else
echo "DON'T HAVE libreadline"
fi
if test x$have_libserialport = xyes; then
echo "DO HAVE libserialport"
else
echo "DON'T HAVE libserialport"
fi
if test x$have_pthread = xyes; then
echo "DO HAVE pthread"
else

View File

@@ -373,7 +373,7 @@ Windows programming software.
For many years, the AVRDUDE source resided in public repositories on
savannah.nongnu.org,
where it continued to be enhanced and ported to other systems. In
addition to FreeBSD, AVRDUDE now runs on Linux and Windows. The
addition to FreeBSD, AVRDUDE now runs on Linux, MacOS and Windows. The
developers behind the porting effort primarily were Ted Roth, Eric
Weddington, and J@"org Wunsch.
@@ -680,12 +680,30 @@ hardware.
Note that the result will be stored in the EEPROM cell at address 0.
@item -P @var{port}
Use port to identify the device to which the programmer is attached.
Normally, the default parallel port is used, but if the programmer type
normally connects to the serial port, the default serial port will be
used. See Appendix A, Platform Dependent Information, to find out the
default port names for your platform. If you need to use a different
parallel or serial port, use this option to specify the alternate port name.
Use @var{port} to identify the connection through which the programmer is
attached. This can be a parallel, serial, spi or linuxgpio connection. The
programmer normally specifies the connection type; in absence of a @code{-P}
specification, system-dependent default values @code{default_parallel},
@code{default_serial}, @code{default_spi}, or @code{default_linuxgpio} from
the configuration file are used. If you need to use a different port, use this
option to specify the alternate port name.
If avrdude has been configured with libserialport support, a serial port can
be specified using a predefined serial adapter type in @var{avrdude.conf} or
@var{.avrduderc}, e.g., @code{ch340} or @code{ft232r}. If more than one serial
adapter of the same type is connected, they can be distinguished by appending
a serial number, e.g., @code{ft232r:12345678}. Note that the USB to serial
chip has to have a serial number for this to work. Avrdude can check for
leading and trailing serial number matches as well. In the above example,
@code{ft232r:1234} would also result in a match, and so would
@code{ft232r:...5678}. If the USB to serial chip is not known to avrdude, it
can be specified using the hexadecimal USB vendor ID, hexadecimal product ID
and an optional serial number, following the serial number matching rules
described above, e.g., @code{usb:0x2341:0x0043} or
@code{usb:2341:0043:12345678}. To see a list of currently plugged-in serial
ports use @code{-P ?s}. In order to see a list of all possible serial adapters
known to avrdude use @code{-P ?sa}.
On Win32 operating systems, the parallel ports are referred to as lpt1
through lpt3, referring to the addresses 0x378, 0x278, and 0x3BC,
@@ -1762,7 +1780,7 @@ $ avrdude -cusbasp -pattiny13 -Ueeprom:r:-:i 2>/dev/null
$ avrdude -pattiny13 -qq -U flash:r:-:r | strings
Main menu
Distance: %dcm
Distance: %d cm
Exit
@end cartouche
@@ -1827,6 +1845,52 @@ AVR64EA48
@end smallexample
@page
@noindent
@strong{List of all curently plugged-in serial devices known to the libserialport library:}
@smallexample
@cartouche
$ avrdude -P ?s
Possible candidate serial ports are:
-P /dev/ttyUSB0 or -P ft232r:A600K203
-P /dev/ttyUSB1 or -P ft232r:MCU8
-P /dev/ttyUSB3, -P ch340 or -P ch340-115k
Note that above ports might not be connected to a target board or an AVR programmer.
Also note there may be other direct serial ports not listed above.
@end cartouche
@end smallexample
@noindent
@strong{List of all serial adapters known to AVRDUDE, i.e., defined in avrdude.conf:}
@smallexample
@cartouche
$ avrdude -P ?sa
Valid serial adapters are:
ch340 = [usbvid 0x1a86, usbpid 0x7523]
ch340-115k = [usbvid 0x1a86, usbpid 0x7523]
ch341a = [usbvid 0x1a86, usbpid 0x5512]
ch9102 = [usbvid 0x1a86, usbpid 0x55d4]
cp210x = [usbvid 0x10c4, usbpid 0xea60 0xea70 0xea71]
ft2232h = [usbvid 0x0403, usbpid 0x6010]
ft231x = [usbvid 0x0403, usbpid 0x6015]
ft234x = [usbvid 0x0403, usbpid 0x6015]
ft230x = [usbvid 0x0403, usbpid 0x6015]
ft232h = [usbvid 0x0403, usbpid 0x6014]
ft232r = [usbvid 0x0403, usbpid 0x6001]
ft4232h = [usbvid 0x0403, usbpid 0x6011]
pl2303 = [usbvid 0x067b, usbpid 0x2303]
@end cartouche
@end smallexample
@page
@noindent
@strong{AVRDUDE in a bash script creating terminal scripts that reset a part to factory settings:}
@smallexample

View File

@@ -1249,6 +1249,10 @@ typedef struct {
extern "C" {
#endif
int setport_from_serialadapter(char **portp, const SERIALADAPTER *ser, const char *sernum);
int setport_from_vid_pid(char **portp, int vid, int pid, const char *sernum);
int list_available_serialports(LISTID programmers);
int str_starts(const char *str, const char *starts);
int str_eq(const char *str1, const char *str2);
int str_contains(const char *str, const char *substr);
@@ -1265,6 +1269,7 @@ char *str_uc(char *s);
char *str_lcfirst(char *s);
char *str_ucfirst(char *s);
char *str_utoa(unsigned n, char *buf, int base);
char *str_endnumber(const char *str);
const char *str_plural(int x);
const char *str_inname(const char *fn);
const char *str_outname(const char *fn);

View File

@@ -224,19 +224,19 @@ static void usage(void)
msg_error(
"Usage: %s [options]\n"
"Options:\n"
" -p <partno> Specify AVR device\n"
" -p <partno> Specify AVR device; -p ? lists all known parts\n"
" -p <wildcard>/<flags> Run developer options for matched AVR devices,\n"
" e.g., -p ATmega328P/s or /S for part definition\n"
" -b <baudrate> Override RS-232 baud rate\n"
" -B <bitclock> Specify bit clock period (us)\n"
" -C <config-file> Specify location of configuration file\n"
" -c <programmer> Specify programmer type\n"
" -c <programmer> Specify programmer; -c ? and -c ?type list all\n"
" -c <wildcard>/<flags> Run developer options for matched programmers,\n"
" e.g., -c 'ur*'/s for programmer info/definition\n"
" -A Disable trailing-0xff removal for file/AVR read\n"
" -D Disable auto erase for flash memory; implies -A\n"
" -i <delay> ISP Clock Delay [in microseconds]\n"
" -P <port> Specify connection port\n"
" -P <port> Connection; -P ?s or -P ?sa lists serial ones\n"
" -F Override invalid signature or initial checks\n"
" -e Perform a chip erase\n"
" -O Perform RC oscillator calibration (see AVR053)\n"
@@ -646,58 +646,58 @@ int main(int argc, char * argv [])
}
break;
case 'B': /* specify JTAG ICE bit clock period */
bitclock = strtod(optarg, &e);
if (*e != 0) {
/* trailing unit of measure present */
int suffixlen = strlen(e);
switch (suffixlen) {
case 2:
if ((e[0] != 'h' && e[0] != 'H') || e[1] != 'z')
bitclock = 0.0;
else
/* convert from Hz to microseconds */
bitclock = 1E6 / bitclock;
break;
case 'B': /* specify JTAG ICE bit clock period */
bitclock = strtod(optarg, &e);
if (*e != 0) {
/* trailing unit of measure present */
int suffixlen = strlen(e);
switch (suffixlen) {
case 2:
if ((e[0] != 'h' && e[0] != 'H') || e[1] != 'z')
bitclock = 0.0;
else
/* convert from Hz to microseconds */
bitclock = 1E6 / bitclock;
break;
case 3:
if ((e[1] != 'h' && e[1] != 'H') || e[2] != 'z')
bitclock = 0.0;
else {
switch (e[0]) {
case 'M':
case 'm': /* no Millihertz here :) */
bitclock = 1.0 / bitclock;
break;
case 3:
if ((e[1] != 'h' && e[1] != 'H') || e[2] != 'z')
bitclock = 0.0;
else {
switch (e[0]) {
case 'M':
case 'm': /* no Millihertz here :) */
bitclock = 1.0 / bitclock;
break;
case 'k':
bitclock = 1E3 / bitclock;
break;
case 'k':
bitclock = 1E3 / bitclock;
break;
default:
bitclock = 0.0;
break;
}
}
break;
default:
bitclock = 0.0;
break;
}
}
break;
default:
bitclock = 0.0;
break;
}
if (bitclock == 0.0)
pmsg_error("invalid bit clock unit of measure '%s'\n", e);
}
if ((e == optarg) || bitclock == 0.0) {
pmsg_error("invalid bit clock period specified '%s'\n", optarg);
default:
bitclock = 0.0;
break;
}
if (bitclock == 0.0)
pmsg_error("invalid bit clock unit of measure '%s'\n", e);
}
if ((e == optarg) || bitclock == 0.0) {
pmsg_error("invalid bit clock period specified '%s'\n", optarg);
exit(1);
}
break;
case 'i': /* specify isp clock delay */
ispdelay = str_int(optarg, STR_INT32, &errstr);
if(errstr || ispdelay == 0) {
pmsg_error("invalid isp clock delay %s specified", optarg);
case 'i': /* specify isp clock delay */
ispdelay = str_int(optarg, STR_INT32, &errstr);
if(errstr || ispdelay == 0) {
pmsg_error("invalid isp clock delay %s specified", optarg);
if(errstr)
msg_error(": %s\n", errstr);
else
@@ -743,23 +743,23 @@ int main(int argc, char * argv [])
break;
case 'l':
logfile = optarg;
break;
logfile = optarg;
break;
case 'n':
uflags |= UF_NOWRITE;
break;
case 'O': /* perform RC oscillator calibration */
calibrate = 1;
break;
calibrate = 1;
break;
case 'p' : /* specify AVR part */
partdesc = optarg;
break;
case 'P':
port = optarg;
port = cfg_strdup(__func__, optarg);
break;
case 'q' : /* Quell progress output */
@@ -1031,6 +1031,17 @@ int main(int argc, char * argv [])
}
}
if(port) {
if(str_eq(port, "?s")) {
list_available_serialports(programmers);
exit(0);
} else if(str_eq(port, "?sa")) {
msg_error("\vValid serial adapters are:\n");
list_serialadapters(stderr, " ", programmers);
exit(0);
}
}
if(partdesc) {
if(str_eq(partdesc, "?")) {
if(pgmid && *pgmid && explicit_c) {
@@ -1134,30 +1145,92 @@ int main(int argc, char * argv [])
switch (pgm->conntype)
{
case CONNTYPE_PARALLEL:
port = cfg_strdup("main()", default_parallel);
port = cfg_strdup(__func__, default_parallel);
break;
case CONNTYPE_SERIAL:
port = cfg_strdup("main()", default_serial);
port = cfg_strdup(__func__, default_serial);
break;
case CONNTYPE_USB:
port = DEFAULT_USB;
port = cfg_strdup(__func__, DEFAULT_USB);
break;
case CONNTYPE_SPI:
port = cfg_strdup(__func__,
#ifdef HAVE_LINUXSPI
port = cfg_strdup("main()", *default_spi? default_spi: "unknown");
*default_spi? default_spi:
#endif
"unknown");
break;
case CONNTYPE_LINUXGPIO:
port = cfg_strdup("main()", default_linuxgpio);
port = cfg_strdup(__func__, default_linuxgpio);
break;
default:
port = cfg_strdup(__func__, "unknown");
break;
}
}
/*
* Divide a serialadapter port string into tokens separated by colons.
* There are two ways such a port string can be presented:
* 1) -P <serialadapter>[:<sernum>]
* 2) -P usb:<usbvid>:<usbpid>[:<sernum>]
* In either case the serial number is optional. The USB vendor and
* product ids are hexadecimal numbers.
*/
bool print_ports = true;
SERIALADAPTER *ser = NULL;
if (pgm->conntype == CONNTYPE_SERIAL) {
char *portdup = cfg_strdup(__func__, port);
char *port_tok[4], *tok = portdup;
for(int t = 0, maxt = str_starts(portdup, DEFAULT_USB ":")? 4: 2; t < 4; t++) {
char *save = tok && t < maxt? tok: "";
if(t < maxt-1 && tok && (tok = strchr(tok, ':')))
*tok++ = 0;
port_tok[t] = cfg_strdup(__func__, save);
}
free(portdup);
// Use libserialport to find the actual serial port
ser = locate_programmer(programmers, port_tok[0]);
if (is_serialadapter(ser)) {
int rv = setport_from_serialadapter(&port, ser, port_tok[1]);
if (rv == -1) {
pmsg_warning("serial adapter %s", port_tok[0]);
if (port_tok[1][0])
msg_warning(" with serial number %s", port_tok[1]);
else if (ser->usbsn && ser->usbsn[0])
msg_warning(" with serial number %s", ser->usbsn);
msg_warning(" not connected to host\n");
}
else if (rv == -2)
print_ports = false;
if(rv)
ser = NULL;
} else if(str_eq(port_tok[0], DEFAULT_USB)) {
// Port or usb:[vid]:[pid]
int vid, pid;
if (sscanf(port_tok[1], "%x", &vid) > 0 && sscanf(port_tok[2], "%x", &pid) > 0) {
int rv = setport_from_vid_pid(&port, vid, pid, port_tok[3]);
if (rv == -1) {
if (port_tok[3][0])
pmsg_warning("serial adapter with USB VID %s and PID %s and serial number %s not connected\n", port_tok[1], port_tok[2], port_tok[3]);
else
pmsg_warning("serial adapter with USB VID %s and PID %s not connected\n", port_tok[1], port_tok[2]);
}
else if (rv == -2)
print_ports = false;
}
}
for (int i = 0; i < 4; i++)
free(port_tok[i]);
}
/*
* open the programmer
*/
@@ -1177,20 +1250,25 @@ int main(int argc, char * argv [])
imsg_notice("Overriding Baud Rate : %d\n", baudrate);
pgm->baudrate = baudrate;
}
else if (ser && ser->baudrate) {
imsg_notice("Default Baud Rate : %d\n", ser->baudrate);
pgm->baudrate = ser->baudrate;
}
if (bitclock != 0.0) {
imsg_notice("Setting bit clk period : %.1f\n", bitclock);
pgm->bitclock = bitclock * 1e-6;
}
if (ispdelay != 0) {
imsg_notice("Setting isp clock delay : %3i\n", ispdelay);
imsg_notice("Setting isp clock delay : %3i\n", ispdelay);
pgm->ispdelay = ispdelay;
}
rc = pgm->open(pgm, port);
if (rc < 0) {
pmsg_error("unable to open programmer %s on port %s\n", pgmid, port);
if (print_ports && pgm->conntype == CONNTYPE_SERIAL)
list_available_serialports(programmers);
exitrc = 1;
pgm->ppidata = 0; /* clear all bits at exit */
goto main_exit;

View File

@@ -1,6 +1,7 @@
/*
* AVRDUDE - A Downloader/Uploader for AVR device programmers
* Copyright (C) 2023 Stefan Rueger <stefan.rueger@urclocks.com>
* Copyright (C) 2023 Hans Eirik Bull
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -24,6 +25,327 @@
#include "avrdude.h"
#include "libavrdude.h"
#ifdef HAVE_LIBSERIALPORT
#include <libserialport.h>
#include <ctype.h>
typedef struct {
int vid, pid;
char *sernum, *port;
} SERPORT;
// Set new port string freeing any previously set one
static int sa_setport(char **portp, const char *sp_port) {
if(!sp_port) {
pmsg_warning("port string to be assigned is NULL\n");
return -1;
}
if(portp) {
if(*portp)
free(*portp);
*portp = cfg_strdup(__func__, sp_port);
}
return 0;
}
// Is the actual serial number sn matched by the query q?
static int sa_snmatch(const char *sn, const char *q) {
return sn && (str_starts(sn, q) || (str_starts(q , "...") && str_ends(sn, q+3)));
}
// Order two SERPORTs port strings: base first then trailing numbers, if any
static int sa_portcmp(const void *p, const void *q) {
int ret;
const char *a = ((SERPORT *) p)->port, *b = ((SERPORT *) q)->port;
const char *na = str_endnumber(a), *nb = str_endnumber(b);
size_t la = strlen(a) - (na? strlen(na): 0), lb = strlen(b) - (nb? strlen(nb): 0);
// Compare string bases first
if(la && lb && (ret = strncasecmp(a, b, la < lb? la: lb)))
return ret;
if((ret = la-lb))
return ret;
// If string bases are the same then compare trailing numbers
if(na && nb) {
long long d;
if((d = atoll(na)-atoll(nb)))
return d < 0? -1: 1;
} else if(na)
return 1;
else if(nb)
return -1;
// Ports are the same (this should not happen) but still compare vid, pid and sn
if((ret = ((SERPORT *) p)->vid - ((SERPORT *) q)->vid))
return ret;
if((ret = ((SERPORT *) p)->pid - ((SERPORT *) q)->pid))
return ret;
return strcmp(((SERPORT *) p)->sernum, ((SERPORT *) q)->sernum);
}
// Get serial port data; allocate a SERPORT array sp, store data and return it
static SERPORT *get_libserialport_data(int *np) {
struct sp_port **port_list = NULL;
enum sp_return result = sp_list_ports(&port_list);
if(result != SP_OK) {
pmsg_error("sp_list_ports() failed!\n");
sp_free_port_list(port_list);
return NULL;
}
int i, j, n;
// Count the number of available ports and allocate space according to the needed size
for(n = 0; port_list[n]; n++)
continue;
SERPORT *sp = cfg_malloc(__func__, n*sizeof*sp);
for(j = 0, i = 0; i < n; i++) { // j counts the number of valid ports
struct sp_port *p = port_list[i];
char *q;
// Fill sp struct with port information
if((q = sp_get_port_name(p))) {
sp[j].port = cfg_strdup(__func__, q);
if(sp_get_port_usb_vid_pid(p, &sp[j].vid, &sp[j].pid) != SP_OK)
sp[j].vid = sp[j].pid = 0;
sp[j].sernum = cfg_strdup(__func__, (q = sp_get_port_usb_serial(p))? q: "");
j++;
}
}
if(j > 0)
qsort(sp, j, sizeof*sp, sa_portcmp);
else
free(sp), sp = NULL;
sp_free_port_list(port_list);
if(np)
*np = j;
return sp;
}
// Returns a NULL-terminated malloc'd list of items in SERPORT list spa that are not in spb
SERPORT **sa_spa_not_spb(SERPORT *spa, int na, SERPORT *spb, int nb) {
SERPORT **ret = cfg_malloc(__func__, (na+1)*sizeof*ret);
int ia = 0, ib = 0, ir = 0;
// Use the comm algorithm on two sorted SERPORT lists
while(ia < na && ib < nb) {
int d = sa_portcmp(spa+ia, spb+ib);
if(d < 0)
ret[ir++] = spa+ia++;
else if(d > 0)
ib++;
else
ia++, ib++;
}
while(ia < na)
ret[ir++] = spa+ia++;
ret[ir] = NULL;
return ret;
}
// Return number of SERPORTs that a serial adapter matches
static int sa_num_matches_by_sea(const SERIALADAPTER *sea, const char *sernum, const SERPORT *sp, int n) {
const char *sn = *sernum? sernum: sea->usbsn;
int matches = 0;
for(int i = 0; i < n; i++)
if(sp[i].vid == sea->usbvid)
for(LNODEID usbpid = lfirst(sea->usbpid); usbpid; usbpid = lnext(usbpid))
if(sp[i].pid == *(int *) ldata(usbpid) && sa_snmatch(sp[i].sernum, sn)) {
matches++;
break;
}
return matches;
}
// Return number of SERPORTs that a (vid, pid, sernum) triple matches
static int sa_num_matches_by_ids(int vid, int pid, const char *sernum, const SERPORT *sp, int n) {
int matches = 0;
for(int i = 0; i < n; i++)
if(sp[i].vid == vid && sp[i].pid == pid && sa_snmatch(sp[i].sernum, sernum))
matches++;
return matches;
}
// Is the i-th SERPORT the only match with the serial adapter wrt all plugged-in ones?
static int sa_unique_by_sea(const SERIALADAPTER *sea, const char *sn, const SERPORT *sp, int n, int i) {
return sa_num_matches_by_sea(sea, sn, sp, n) == 1 && sa_num_matches_by_sea(sea, sn, sp+i, 1);
}
// Is the i-th SERPORT the only match with (vid, pid, sn) wrt all plugged-in ones?
static int sa_unique_by_ids(int vid, int pid, const char *sn, const SERPORT *sp, int n, int i) {
return sa_num_matches_by_ids(vid, pid, sn, sp, n) == 1 && sa_num_matches_by_ids(vid, pid, sn, sp+i, 1);
}
// Return a malloc'd list of -P specifications that uniquely address sp[i]
static char **sa_list_specs(const SERPORT *sp, int n, int i) {
int Pn = 4, Pi = 0;
char **Plist = cfg_malloc(__func__, Pn*sizeof*Plist);
const char *sn = sp[i].sernum, *via = NULL;
// Loop though all serial adapters in avrdude.conf
for(LNODEID ln1 = lfirst(programmers); ln1; ln1=lnext(ln1)) {
SERIALADAPTER *sea = ldata(ln1);
if(!is_serialadapter(sea))
continue;
for(LNODEID sid = lfirst(sea->id); sid; sid = lnext(sid)) {
char *id = ldata(sid);
// Put id or id:sn into list if it uniquely matches sp[i]
if(sa_unique_by_sea(sea, "", sp, n, i))
Plist[Pi++] = cfg_strdup(__func__, id);
else if(*sn && sa_unique_by_sea(sea, sn, sp, n, i))
Plist[Pi++] = str_sprintf("%s:%s", id, sn);
else if(!via && sa_num_matches_by_sea(sea, "", sp+i, 1))
via = id;
if(Pi >= Pn-1) { // Ensure there is space for one more and NULL
if(Pn >= INT_MAX/2)
break;
Pn *= 2;
Plist = cfg_realloc(__func__, Plist, Pn*sizeof*Plist);
}
}
}
if(Pi == 0 && sp[i].vid) { // No unique serial adapter, so maybe vid:pid[:sn] works?
if(sa_unique_by_ids(sp[i].vid, sp[i].pid, "", sp, n, i))
Plist[Pi++] = str_sprintf("usb:%04x:%04x", sp[i].vid, sp[i].pid);
else if(*sn && sa_unique_by_ids(sp[i].vid, sp[i].pid, sn, sp, n, i))
Plist[Pi++] = str_sprintf("usb:%04x:%04x:%s", sp[i].vid, sp[i].pid, sn);
else if(via && Pi == 0)
Plist[Pi++] = str_sprintf("(via %s serial adapter)", via);
}
Plist[Pi] = NULL;
return Plist;
}
// Print possible ways SERPORT sp[i] might be specified
static void sa_print_specs(const SERPORT *sp, int n, int i) {
char **Pspecs = sa_list_specs(sp, n, i);
msg_warning(" -P %s", sp[i].port);
for(char **Ps = Pspecs; *Ps; Ps++) {
msg_warning("%s %s", str_starts(*Ps, "(via ")? "": Ps[1]? ", -P": " or -P", *Ps);
free(*Ps);
}
msg_warning("\n");
free(Pspecs);
}
// Set the port specs to the port iff sea matches one and only one of the connected SERPORTs
int setport_from_serialadapter(char **portp, const SERIALADAPTER *sea, const char *sernum) {
int rv, m, n;
SERPORT *sp = get_libserialport_data(&n);
if(!sp || n <= 0)
return -1;
m = sa_num_matches_by_sea(sea, sernum, sp, n);
if(m == 1) { // Unique result, set port string
rv = -1;
for(int i = 0; i < n; i++)
if(sa_num_matches_by_sea(sea, sernum, sp+i, 1))
rv = sa_setport(portp, sp[i].port);
} else {
rv = -2;
pmsg_warning("-P %s is %s; consider\n", *portp, m? "ambiguous": "not connected");
for(int i = 0; i < n; i++)
if(m == 0 || sa_num_matches_by_sea(sea, sernum, sp+i, 1) == 1)
sa_print_specs(sp, n, i);
}
for(int k = 0; k < n; k++)
free(sp[k].sernum), free(sp[k].port);
free(sp);
return rv;
}
// Set the port specs to the port iff the ids match one and only one of the connected SERPORTs
int setport_from_vid_pid(char **portp, int vid, int pid, const char *sernum) {
int rv, m, n;
SERPORT *sp = get_libserialport_data(&n);
if(!sp || n <= 0)
return -1;
m = sa_num_matches_by_ids(vid, pid, sernum, sp, n);
if(m == 1) { // Unique result, set port string
rv = -1;
for(int i = 0; i < n; i++)
if(sa_num_matches_by_ids(vid, pid, sernum, sp+i, 1))
rv = sa_setport(portp, sp[i].port);
} else {
rv = -2;
pmsg_warning("-P %s is %s; consider\n", *portp, m? "ambiguous": "not connected");
for(int i = 0; i < n; i++)
if(m == 0 || sa_num_matches_by_ids(vid, pid, sernum, sp+i, 1) == 1)
sa_print_specs(sp, n, i);
}
for(int k = 0; k < n; k++)
free(sp[k].sernum), free(sp[k].port);
free(sp);
return rv;
}
// List available serial ports
int list_available_serialports(LISTID programmers) {
// Get serial port information from libserialport
int n;
SERPORT *sp = get_libserialport_data(&n);
if(!sp || n <= 0)
return -1;
msg_warning("%sossible candidate serial port%s:\n",
n>1? "P": "A p", n>1? "s are": " is");
for(int i = 0; i < n; i++)
sa_print_specs(sp, n, i);
msg_warning("Note that above port%s might not be connected to a target board or an AVR programmer.\n",
str_plural(n));
msg_warning("Also note there may be other direct serial ports not listed above.\n");
for(int k = 0; k < n; k++)
free(sp[k].sernum), free(sp[k].port);
free(sp);
return 0;
}
#else
int setport_from_serialadapter(char **portp, const SERIALADAPTER *ser, const char *sernum) {
pmsg_error("avrdude built without libserialport support; please compile again with libserialport installed\n");
return -1;
}
int setport_from_vid_pid(char **portp, int vid, int pid, const char *sernum) {
pmsg_error("avrdude built without libserialport support; please compile again with libserialport installed\n");
return -1;
}
int list_available_serialports(LISTID programmers) {
pmsg_error("avrdude built without libserialport support; please compile again with libserialport installed\n");
return -1;
}
#endif
void list_serialadapters(FILE *fp, const char *prefix, LISTID programmers) {
LNODEID ln1, ln2, ln3;
SERIALADAPTER *sea;
@@ -63,7 +385,6 @@ void list_serialadapters(FILE *fp, const char *prefix, LISTID programmers) {
}
}
void serialadapter_not_found(const char *sea_id) {
msg_error("\v");
if(sea_id && *sea_id)

View File

@@ -363,6 +363,19 @@ char *str_utoa(unsigned n, char *buf, int base) {
return buf;
}
// Returns a pointer to the start of a trailing number in the string or NULL if not there
char *str_endnumber(const char *str) {
const char *ret = NULL;
for(const char *end = str + strlen(str)-1; end >= str; end--)
if(isdigit((unsigned char) *end))
ret = end;
else
break;
return (char *) ret;
}
// Convenience functions for printing
const char *str_plural(int x) {