From 0be6568b76e853d6abdcbcd326dd89175237b944 Mon Sep 17 00:00:00 2001 From: Hans Date: Sat, 23 Sep 2023 00:21:57 +0200 Subject: [PATCH] Add serial port discovery (#1498) Co-authored-by: Stefan Rueger --- CMakeLists.txt | 18 +++ src/Makefile.am | 2 +- src/avrdude.1 | 56 ++++++-- src/avrdude.conf.in | 44 ++++++ src/cmake_config.h.in | 3 + src/configure.ac | 19 +++ src/doc/avrdude.texi | 80 +++++++++-- src/libavrdude.h | 5 + src/main.c | 204 ++++++++++++++++++-------- src/serialadapter.c | 323 +++++++++++++++++++++++++++++++++++++++++- src/strutil.c | 13 ++ 11 files changed, 682 insertions(+), 85 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9ab5a832d..73bb07147 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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() diff --git a/src/Makefile.am b/src/Makefile.am index 5a994fb4a..bd172b3cb 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -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 diff --git a/src/avrdude.1 b/src/avrdude.1 index 72c354bf3..c2e41f38f 100644 --- a/src/avrdude.1 +++ b/src/avrdude.1 @@ -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. diff --git a/src/avrdude.conf.in b/src/avrdude.conf.in index 675828e4d..726edf37e 100644 --- a/src/avrdude.conf.in +++ b/src/avrdude.conf.in @@ -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 # diff --git a/src/cmake_config.h.in b/src/cmake_config.h.in index fc78d3cff..88f6f39e8 100644 --- a/src/cmake_config.h.in +++ b/src/cmake_config.h.in @@ -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 diff --git a/src/configure.ac b/src/configure.ac index c9d549de5..be34c6a03 100644 --- a/src/configure.ac +++ b/src/configure.ac @@ -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 diff --git a/src/doc/avrdude.texi b/src/doc/avrdude.texi index 9011bbfa1..31034e222 100644 --- a/src/doc/avrdude.texi +++ b/src/doc/avrdude.texi @@ -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 diff --git a/src/libavrdude.h b/src/libavrdude.h index f52b7793a..0f9d59380 100644 --- a/src/libavrdude.h +++ b/src/libavrdude.h @@ -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); diff --git a/src/main.c b/src/main.c index 985861474..5b5eb6995 100644 --- a/src/main.c +++ b/src/main.c @@ -224,19 +224,19 @@ static void usage(void) msg_error( "Usage: %s [options]\n" "Options:\n" - " -p Specify AVR device\n" + " -p Specify AVR device; -p ? lists all known parts\n" " -p / Run developer options for matched AVR devices,\n" " e.g., -p ATmega328P/s or /S for part definition\n" " -b Override RS-232 baud rate\n" " -B Specify bit clock period (us)\n" " -C Specify location of configuration file\n" - " -c Specify programmer type\n" + " -c Specify programmer; -c ? and -c ?type list all\n" " -c / 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 ISP Clock Delay [in microseconds]\n" - " -P Specify connection port\n" + " -P 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 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; - - 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); + 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 'k': + bitclock = 1E3 / bitclock; + 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); 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 [:] + * 2) -P usb::[:] + * 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; diff --git a/src/serialadapter.c b/src/serialadapter.c index ef81cc4f1..b57f679ec 100644 --- a/src/serialadapter.c +++ b/src/serialadapter.c @@ -1,6 +1,7 @@ /* * AVRDUDE - A Downloader/Uploader for AVR device programmers * Copyright (C) 2023 Stefan Rueger + * 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 +#include + +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) diff --git a/src/strutil.c b/src/strutil.c index 05d23eb22..308cc691e 100644 --- a/src/strutil.c +++ b/src/strutil.c @@ -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) {