Files
avrdude/src/serialadapter.c
Stefan Rueger 8730b145c8 Suppress gcc compiler warning
For some reason gcc thinks q+3 might be out of bounds below. It isn't, but
adding the strlen(q) >= 3 condition suppresses the warning.

In function ‘sa_snmatch’,
    inlined from ‘sa_snmatch’ at /home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:56:12,
    inlined from ‘sa_num_matches_by_ids’ at /home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:190:48,
    inlined from ‘sa_unique_by_ids’ at /home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:203:61,
    inlined from ‘sa_list_specs’ at /home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:239:8,
    inlined from ‘sa_print_specs’ at /home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:253:19:
/home/srueger/etc/clock/avrdude/gcc-warning/src/serialadapter.c:57:63: warning: array subscript 3 is outside array bounds of ‘char[1]’ [-Warray-bounds=]
   57 |   return sn && (str_starts(sn, q) || (str_starts(q, "...") && str_ends(sn, q + 3)));
      |                                                               ^~~~~~~~~~~~~~~~~~~
2026-06-13 01:31:21 +01:00

469 lines
14 KiB
C

/*
* 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
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <ac_cfg.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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)
mmt_free(*portp);
*portp = mmt_strdup(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, "...") && strlen(q) >= 3 /* suppress gcc warning */ && str_ends(sn, q + 3)));
}
#define null_len(s) ((s)? strlen(s): 0)
// 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 = null_len(a) - null_len(na), lb = null_len(b) - null_len(nb);
// 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 accordingly
for(n = 0; port_list[n]; n++)
continue;
SERPORT *sp = mmt_malloc(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 = mmt_strdup(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 = mmt_strdup((q = sp_get_port_usb_serial(p))? q: "");
j++;
}
}
if(j > 0)
qsort(sp, j, sizeof *sp, sa_portcmp);
else
mmt_free(sp), sp = NULL;
sp_free_port_list(port_list);
if(np)
*np = j;
return sp;
}
// Free memory allocated from get_libserialport_data()
static void free_libserialport_data(SERPORT *sp, int n) {
for(int k = 0; sp && k < n; k++)
mmt_free(sp[k].sernum), mmt_free(sp[k].port);
mmt_free(sp);
}
// Returns a NULL-terminated mmt_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 = mmt_malloc((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 an mmt_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 = mmt_malloc(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++] = mmt_strdup(id);
else if(*sn && sa_unique_by_sea(sea, sn, sp, n, i))
Plist[Pi++] = mmt_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 = mmt_realloc(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++] = mmt_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++] = mmt_sprintf("usb:%04x:%04x:%s", sp[i].vid, sp[i].pid, sn);
else if(via && Pi == 0)
Plist[Pi++] = mmt_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);
mmt_free(*Ps);
}
msg_warning("\n");
mmt_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);
}
free_libserialport_data(sp, n);
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);
}
free_libserialport_data(sp, n);
return rv;
}
// Potentially change port *portp after opening & closing it with baudrate
int touch_serialport(char **portp, int baudrate, int nwaits) {
int i, n1, n2;
SERPORT *sp1, *sp2, **diff;
sp1 = get_libserialport_data(&n1);
if(!sp1 || n1 <= 0 || !portp)
return -1;
pmsg_notice("touching serial port %s at %d baud\n", *portp, baudrate);
union pinfo pinfo;
union filedescriptor fd;
pinfo.serialinfo.baud = baudrate;
pinfo.serialinfo.cflags = SERIAL_8N1;
if(serial_open(*portp, pinfo, &fd) == -1) {
pmsg_error("%s() failed to open port %s at %d baud\n", __func__, *portp, baudrate);
return -1;
}
serial_set_dtr_rts(&fd, 1);
usleep(100);
serial_set_dtr_rts(&fd, 0);
serial_rawclose(&fd);
const int nloops = 32, nap = 50;
#if (defined(__arm__) || defined(__aarch64__)) && !defined(__APPLE__)
nwaits += 2;
#endif
pmsg_notice("waiting for new port...");
usleep(400*1000*nwaits);
for(i = nloops; i > 0; i--) {
usleep(nap*1000);
if((sp2 = get_libserialport_data(&n2))) {
diff = sa_spa_not_spb(sp2, n2, sp1, n1);
if(*diff && diff[0]->port && !diff[1]) { // Exactly one new port sprung up
pmsg_notice2("new port %s discovered\n", (*diff)->port);
if(*portp)
mmt_free(*portp);
*portp = mmt_strdup((*diff)->port);
msg_notice(" %d ms:", (nloops - i + 1)*nap + nwaits*400);
i = -1; // Leave loop
}
mmt_free(diff);
free_libserialport_data(sp2, n2);
}
}
free_libserialport_data(sp1, n1);
msg_notice(" using %s port %s\n", i < 0? "new": "same", *portp);
return 0;
}
// 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++)
mmt_free(sp[k].sernum), mmt_free(sp[k].port);
mmt_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;
}
int touch_serialport(char **portp, int baudrate, int nwaits) {
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;
int maxlen = 0, len;
sort_programmers(programmers);
// Compute max length of serial adapter names
for(ln1 = lfirst(programmers); ln1; ln1 = lnext(ln1)) {
sea = ldata(ln1);
if(!is_serialadapter(sea))
continue;
for(ln2 = lfirst(sea->id); ln2; ln2 = lnext(ln2)) {
const char *id = ldata(ln2);
if(*id == 0 || *id == '.')
continue;
if((len = strlen(id)) > maxlen)
maxlen = len;
}
}
for(ln1 = lfirst(programmers); ln1; ln1 = lnext(ln1)) {
sea = ldata(ln1);
if(!is_serialadapter(sea))
continue;
for(ln2 = lfirst(sea->id); ln2; ln2 = lnext(ln2)) {
const char *id = ldata(ln2);
if(*id == 0 || *id == '.')
continue;
fprintf(fp, "%s%-*s = [usbvid 0x%04x, usbpid", prefix, maxlen, id, sea->usbvid);
for(ln3 = lfirst(sea->usbpid); ln3; ln3 = lnext(ln3))
fprintf(fp, " 0x%04x", *(int *) ldata(ln3));
if(sea->usbsn && *sea->usbsn)
fprintf(fp, ", usbsn %s", sea->usbsn);
fprintf(fp, "]\n");
}
}
}
void serialadapter_not_found(const char *sea_id) {
if(sea_id && *sea_id)
pmsg_error("cannot find serial adapter id %s\n", sea_id);
lmsg_error("\nValid serial adapters are:\n");
list_serialadapters(stderr, " ", programmers);
msg_error("\n");
}