summaryrefslogtreecommitdiff
path: root/spectro/usbio_lx.c
diff options
context:
space:
mode:
Diffstat (limited to 'spectro/usbio_lx.c')
-rw-r--r--spectro/usbio_lx.c1069
1 files changed, 1069 insertions, 0 deletions
diff --git a/spectro/usbio_lx.c b/spectro/usbio_lx.c
new file mode 100644
index 0000000..359f483
--- /dev/null
+++ b/spectro/usbio_lx.c
@@ -0,0 +1,1069 @@
+
+/* General USB I/O support, Linux native implementation. */
+/* ("libusbx ? We don't need no stinking libusbx..." :-) */
+/* This file is conditionaly #included into usbio.c */
+
+/*
+ * Argyll Color Correction System
+ *
+ * Author: Graeme W. Gill
+ * Date: 2006/22/4
+ *
+ * Copyright 2006 - 2013 Graeme W. Gill
+ * All rights reserved.
+ *
+ * This material is licenced under the GNU GENERAL PUBLIC LICENSE Version 2 or later :-
+ * see the License2.txt file for licencing details.
+ */
+
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+/* select() defined, but not poll(), so emulate poll() */
+#if defined(FD_CLR) && !defined(POLLIN)
+#include "pollem.h"
+#define poll_x pollem
+#else
+#include <sys/poll.h> /* Else assume poll() is native */
+#define poll_x poll
+#endif
+
+/* USB descriptors are little endian */
+
+/* Take a word sized return buffer, and convert it to an unsigned int */
+static unsigned int buf2uint(unsigned char *buf) {
+ unsigned int val;
+ val = buf[3];
+ val = ((val << 8) + (0xff & buf[2]));
+ val = ((val << 8) + (0xff & buf[1]));
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take a short sized return buffer, and convert it to an int */
+static unsigned int buf2ushort(unsigned char *buf) {
+ unsigned int val;
+ val = buf[1];
+ val = ((val << 8) + (0xff & buf[0]));
+ return val;
+}
+
+/* Take an int, and convert it into a byte buffer */
+static void int2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+ buf[2] = (inv >> 16) & 0xff;
+ buf[3] = (inv >> 24) & 0xff;
+}
+
+/* Take a short, and convert it into a byte buffer */
+static void short2buf(unsigned char *buf, int inv) {
+ buf[0] = (inv >> 0) & 0xff;
+ buf[1] = (inv >> 8) & 0xff;
+}
+
+
+/* Check a USB Vendor and product ID by reading the device descriptors, */
+/* and add the device to the icoms path if it is supported. */
+/* Return icom nz error code on fatal error */
+int usb_check_and_add_fd(
+a1log *log,
+icompaths *pp, /* icompaths to add to, or if NULL */
+icompath *p, /* icompath to set. */
+char *dpath, /* path to device - may be NULL */
+int fd /* device file descriptor */
+) {
+ int rv;
+ unsigned char buf[IUSB_DESC_TYPE_DEVICE_SIZE];
+ unsigned vid, pid, nep10 = 0xffff;
+ unsigned int configix, nconfig, totlen;
+ instType itype;
+ struct usb_idevice *usbd = NULL;
+
+ a1logd(log, 6, "usb_check_and_add_fd: with fd %d\n",fd);
+
+ /* Read the device descriptor */
+ if ((rv = read(fd, buf, IUSB_DESC_TYPE_DEVICE_SIZE)) < 0
+ || rv != IUSB_DESC_TYPE_DEVICE_SIZE
+ || buf[0] != IUSB_DESC_TYPE_DEVICE_SIZE
+ || buf[1] != IUSB_DESC_TYPE_DEVICE) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device descriptor\n");
+ return ICOM_OK;
+ }
+
+ /* Extract the vid and pid */
+ vid = buf2ushort(buf + 8);
+ pid = buf2ushort(buf + 10);
+ nconfig = buf[17];
+
+ a1logd(log, 6, "usb_check_and_add: checking vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Do a preliminary match */
+ if ((itype = inst_usb_match(vid, pid, 0)) == instUnknown) {
+ a1logd(log, 6 , "usb_check_and_add: instrument not reconized\n");
+ return ICOM_OK;
+ }
+
+ /* Allocate an idevice so that we can fill in the end point information */
+ if ((usbd = (struct usb_idevice *) calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(log, ICOM_SYS, "icoms: calloc failed!\n");
+ return ICOM_SYS;
+ }
+
+ usbd->nconfig = nconfig;
+
+ /* Read the configuration descriptors looking for the first configuration, first interface, */
+ /* and extract the number of end points */
+ for (configix = 0; configix < nconfig; configix++) {
+ int configno;
+ unsigned int ninfaces, inface, nep;
+ unsigned char *buf2, *bp, *zp;
+
+ /* Read the first 4 bytes to get the type and size */
+ if ((rv = read(fd, buf, 4)) < 0
+ || rv != 4
+ || buf[1] != IUSB_DESC_TYPE_CONFIG) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device config\n");
+ free(usbd);
+ return ICOM_OK;
+ }
+
+ if ((totlen = buf2ushort(buf + 2)) < 6) {
+ a1logd(log, 1, "usb_check_and_add: config desc size strange\n");
+ free(usbd);
+ return ICOM_OK;;
+ }
+ if ((buf2 = calloc(1, totlen)) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: calloc of descriptor failed!\n");
+ return ICOM_SYS;
+ }
+
+ memcpy(buf2, buf, 4); /* First 4 bytes read */
+ if ((rv = read(fd, buf2 + 4, totlen - 4)) < 0 /* Read the remainder */
+ || rv != (totlen - 4)) {
+ a1logd(log, 1, "usb_check_and_add: failed to read device config details\n");
+ free(buf2);
+ free(usbd);
+ return ICOM_SYS;
+ }
+
+ bp = buf2 + buf2[0]; /* Skip coniguration tag */
+ zp = buf2 + totlen; /* Past last bytes */
+
+ /* We are at the first configuration. */
+ /* Just read tags and keep track of where we are */
+ ninfaces = 0;
+ nep = 0;
+ usbd->nifce = buf2[4]; /* number of interfaces */
+ usbd->config = configno = buf2[5]; /* current configuration */
+ for (;bp < zp; bp += bp[0]) {
+ int ifaceno;
+ if ((bp + 1) >= zp)
+ break; /* Hmm - bodgy, give up */
+ if (bp[1] == IUSB_DESC_TYPE_INTERFACE) {
+ ninfaces++;
+ if ((bp + 2) >= zp)
+ break; /* Hmm - bodgy, give up */
+ ifaceno = bp[2]; /* Get bInterfaceNumber */
+ } else if (bp[1] == IUSB_DESC_TYPE_ENDPOINT) {
+ nep++;
+ if ((bp + 5) >= zp)
+ break; /* Hmm - bodgy */
+ /* At first config - */
+ /* record current nep and end point details */
+ if (configno == 1) {
+ int ad = bp[2];
+ nep10 = nep;
+ usbd->EPINFO(ad).valid = 1;
+ usbd->EPINFO(ad).addr = ad;
+ usbd->EPINFO(ad).packetsize = buf2ushort(bp + 4);
+ usbd->EPINFO(ad).type = bp[3] & IUSB_ENDPOINT_TYPE_MASK;
+ usbd->EPINFO(ad).interface = ifaceno;
+ a1logd(log, 6, "set ep ad 0x%x packetsize %d type %d\n",ad,usbd->EPINFO(ad).packetsize,usbd->EPINFO(ad).type);
+ }
+ }
+ /* Ignore other tags */
+ }
+ free(buf2);
+ }
+ if (nep10 == 0xffff) { /* Hmm. Failed to find number of end points */
+ a1logd(log, 1, "usb_check_and_add: failed to find number of end points\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+
+ a1logd(log, 6, "usb_check_and_add: found nep10 %d\n",nep10);
+
+ /* Found a known instrument ? */
+ if ((itype = inst_usb_match(vid, pid, nep10)) != instUnknown) {
+ char pname[400];
+
+ a1logd(log, 2, "usb_check_and_add: found instrument vid 0x%04x, pid 0x%04x\n",vid,pid);
+
+ /* Create a path/identification */
+ /* (devnum doesn't seem valid ?) */
+ if (dpath == NULL) {
+ sprintf(pname,"%s", inst_name(itype));
+ if ((usbd->dpath = strdup("no_path")) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: strdup path failed!\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+ } else {
+ sprintf(pname,"%s (%s)", dpath, inst_name(itype));
+ if ((usbd->dpath = strdup(dpath)) == NULL) {
+ a1loge(log, ICOM_SYS, "usb_check_and_add: strdup path failed!\n");
+ free(usbd);
+ return ICOM_SYS;
+ }
+ }
+
+ /* Add the path and ep info to the list */
+ if (pp != NULL) {
+ if ((rv = pp->add_usb(pp, pname, vid, pid, nep10, usbd, itype)) != ICOM_OK)
+ return rv;
+ } else {
+ usbd->fd = fd;
+ if ((rv = icompath_set_usb(log, p, pname, vid, pid, nep10, usbd, itype)) != ICOM_OK)
+ return rv;
+ }
+ } else {
+ free(usbd);
+ }
+ return ICOM_OK;
+}
+
+/* Same as above, starting with the path */
+static int usb_check_and_add(
+icompaths *p,
+char *dpath
+) {
+ int fd;
+ int rv;
+
+ a1logd(p->log, 6, "usb_check_and_add: givem '%s'\n",dpath);
+
+ /* Open the device so that we can read it */
+ if ((fd = open(dpath, O_RDONLY)) < 0) {
+ a1logd(p->log, 1, "usb_check_and_add: failed to open '%s'\n",dpath);
+ return ICOM_OK;
+ }
+
+ rv = usb_check_and_add_fd(p->log, p, NULL, dpath, fd);
+
+ close(fd);
+ return rv;
+}
+
+/* Add paths to USB connected instruments */
+/* Return an icom error */
+int usb_get_paths(
+icompaths *p
+) {
+ int vid, pid;
+
+ a1logd(p->log, 6, "usb_get_paths: about to look through buses:\n");
+
+ {
+ int j;
+ char *paths[3] = { "/dev/bus/usb", /* current, from udev */
+ "/proc/bus/usb", /* old, deprecated */
+ "/dev" }; /* CONFIG_USB_DEVICE_CLASS, embeded, deprecated ? */
+
+ /* See what device names to look for */
+ for (j = 0; j < 3; j++) {
+ DIR *d1;
+ struct dirent *e1;
+ int rv, found = 0;
+
+ if ((d1 = opendir(paths[j])) == NULL)
+ continue;
+
+ while ((e1 = readdir(d1)) != NULL) {
+ if (e1->d_name[0] == '.')
+ continue;
+ found = 1;
+ if (j < 2) { /* Directory structure */
+ char path1[PATH_MAX];
+ char path2[PATH_MAX];
+ DIR *d2;
+ struct dirent *e2;
+ struct stat statbuf;
+
+ snprintf(path1, PATH_MAX, "%s/%s", paths[j], e1->d_name);
+
+ if ((d2 = opendir(path1)) == NULL)
+ continue;
+ while ((e2 = readdir(d2)) != NULL) {
+ if (e2->d_name[0] == '.')
+ continue;
+
+ snprintf(path2, PATH_MAX, "%s/%s/%s", paths[j], e1->d_name, e2->d_name);
+ a1logd(p->log, 8, "usb_get_paths: about to stat %s\n",path2);
+ if (stat(path2, &statbuf) == 0 && S_ISCHR(statbuf.st_mode)) {
+ found = 1;
+ if ((rv = usb_check_and_add(p, path2)) != ICOM_OK) {
+ closedir(d1);
+ return rv;
+ }
+ }
+ }
+ closedir(d2);
+ } else { /* Flat */
+ char path2[PATH_MAX];
+ struct stat statbuf;
+
+ /* Hmm. This will go badly wrong if this is a /dev/usbdev%d.%d_ep%d, */
+ /* since we're not expecting the end points to be separate devices. */
+ /* Maybe that's deprectated though... */
+ snprintf(path2, PATH_MAX, "%s/%s", paths[j], e1->d_name);
+ a1logd(p->log, 8, "usb_get_paths: about to stat %s\n",path2);
+ if (stat(path2, &statbuf) == 0 && S_ISCHR(statbuf.st_mode)) {
+ found = 1;
+ if ((rv = usb_check_and_add(p, path2)) != ICOM_OK) {
+ closedir(d1);
+ return rv;
+ }
+ }
+ }
+ }
+ closedir(d1);
+ if (found)
+ break;
+ }
+ }
+
+ a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->npaths);
+ return ICOM_OK;
+}
+
+
+/* Copy usb_idevice contents from icompaths to icom */
+/* return icom error */
+int usb_copy_usb_idevice(icoms *d, icompath *s) {
+ int i;
+ if (s->usbd == NULL) {
+ d->usbd = NULL;
+ return ICOM_OK;
+ }
+ if ((d->usbd = calloc(sizeof(struct usb_idevice), 1)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ if ((d->usbd->dpath = strdup(s->usbd->dpath)) == NULL) {
+ a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
+ return ICOM_SYS;
+ }
+ /* Copy the ep info */
+ d->nconfig = s->usbd->nconfig;
+ d->nifce = s->usbd->nifce;
+ d->config = s->usbd->config;
+ for (i = 0; i < 32; i++)
+ d->ep[i] = s->usbd->ep[i]; /* Struct copy */
+ return ICOM_OK;
+}
+
+/* Cleanup and then free a usb dev entry */
+void usb_del_usb_idevice(struct usb_idevice *usbd) {
+
+ if (usbd == NULL)
+ return;
+
+ if (usbd->dpath != NULL)
+ free(usbd->dpath);
+ free(usbd);
+}
+
+/* Cleanup any USB specific icoms state */
+void usb_del_usb(icoms *p) {
+
+ usb_del_usb_idevice(p->usbd);
+}
+
+/* Close an open USB port */
+/* If we don't do this, the port and/or the device may be left in an unusable state. */
+void usb_close_port(icoms *p) {
+
+ a1logd(p->log, 6, "usb_close_port: called\n");
+
+ if (p->is_open && p->usbd != NULL) {
+ struct usbdevfs_urb urb;
+ unsigned char buf[8+IUSB_DESC_TYPE_DEVICE_SIZE];
+ int iface, rv;
+
+ /* Release all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++)
+ ioctl(p->usbd->fd, USBDEVFS_RELEASEINTERFACE, &iface);
+
+ /* Workaround for some bugs - reset device on close */
+ if (p->uflags & icomuf_reset_before_close) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_RESET, NULL)) != 0) {
+ a1logd(p->log, 1, "usb_close_port: reset returned %d\n",rv);
+ }
+ }
+
+ if (p->usbd->running) { /* If reaper is still running */
+ unsigned char buf[1] = { 0 };
+
+ a1logd(p->log, 6, "usb_close_port: waking reaper thread to trigger exit\n");
+ p->usbd->shutdown = 1;
+
+ if (write(p->usbd->sd_pipe[1], buf, 1) < 1) {
+ a1logd(p->log, 1, "usb_close_port: writing to sd_pipe failed with '%s'\n", strerror(errno));
+ /* Hmm. We could be in trouble ? */
+ }
+ }
+ a1logd(p->log, 6, "usb_close_port: waiting for reaper thread\n");
+ pthread_join(p->usbd->thread, NULL); /* Wait for urb reaper thread to exit */
+ close(p->usbd->fd);
+ pthread_mutex_destroy(&p->usbd->lock);
+ close(p->usbd->sd_pipe[0]);
+ close(p->usbd->sd_pipe[1]);
+ free(p->usbd->dpath);
+ free(p->usbd);
+ p->usbd = NULL;
+
+ a1logd(p->log, 6, "usb_close_port: usb port has been released and closed\n");
+ }
+ p->is_open = 0;
+
+ /* Find it and delete it from our static cleanup list */
+ usb_delete_from_cleanup_list(p);
+}
+
+static void *urb_reaper(void *context); /* Declare */
+
+/* Open a USB port for all our uses. */
+/* This always re-opens the port */
+/* return icom error */
+static int usb_open_port(
+icoms *p,
+int config, /* Configuration number */
+int wr_ep, /* Write end point */
+int rd_ep, /* Read end point */
+icomuflags usbflags,/* Any special handling flags */
+int retries, /* > 0 if we should retry set_configuration (100msec) */
+char **pnames /* List of process names to try and kill before opening */
+) {
+ int rv, tries = 0;
+ a1logd(p->log, 8, "usb_open_port: Make sure USB port is open, tries %d\n",retries);
+
+ if (p->is_open)
+ p->close_port(p);
+
+ /* Make sure the port is open */
+ if (!p->is_open) {
+ int rv, i, iface;
+ kkill_nproc_ctx *kpc = NULL;
+
+ if (config != 1) {
+ /* Nothing currently needs it, so we haven't implemented it yet... */
+ a1loge(p->log, ICOM_NOTS, "usb_open_port: native driver cant handle config %d\n",config);
+ return ICOM_NOTS;
+ }
+
+ /* Do open retries */
+ for (tries = 0; retries >= 0; retries--, tries++) {
+
+ a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->usbd->dpath);
+
+ if (tries > 0) {
+ //msec_sleep(i_rand(50,100));
+ msec_sleep(77);
+ }
+
+ if ((rv = p->usbd->fd = open(p->usbd->dpath, O_RDWR)) < 0) {
+ a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (%d) (Permissions ?)\n",p->usbd->dpath,config,rv);
+ if (retries <= 0) {
+ if (kpc != NULL)
+ kpc->del(kpc);
+ a1loge(p->log, ICOM_SYS, "usb_open_port: open '%s' config %d failed (%d) (Permissions ?)\n",p->usbd->dpath,config,rv);
+ return ICOM_SYS;
+ }
+ continue;
+ } else if (p->debug)
+ a1logd(p->log, 2, "usb_open_port: open port '%s' succeeded\n",p->usbd->dpath);
+
+ p->uflags = usbflags;
+
+ /* We should only do a set configuration if the device has more than one */
+ /* possible configuration and it is currently not the desired configuration, */
+ /* but we should avoid doing a set configuration if the OS has already */
+ /* selected the configuration we want, since two set configs seem to */
+ /* mess up the Spyder2, BUT we can't do a get config because this */
+ /* messes up the i1pro-D. */
+
+ /* Linux set_configuration(1) by default, so we don't need to do anything */
+ p->cconfig = 1;
+
+ if (p->cconfig != config) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_SETCONFIGURATION, &config)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_setconfig failed with %d\n",rv);
+ return ICOM_SYS;
+ }
+ p->cconfig = config;
+ a1logd(p->log, 6, "usb_open_port: set config %d OK\n",config);
+ }
+
+ /* We're done */
+ break;
+ }
+
+ if (kpc != NULL)
+ kpc->del(kpc);
+
+ /* Claim all the interfaces */
+ for (iface = 0; iface < p->nifce; iface++) {
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLAIMINTERFACE, &iface)) < 0) {
+ struct usbdevfs_getdriver getd;
+ getd.interface = iface;
+
+ a1logd(p->log, 1, "usb_open_port: Claiming USB port '%s' interface %d initally failed with %d\n",p->usbd->dpath,iface,rv);
+
+ /* Detatch the existing interface if kernel driver is active. */
+ if (p->uflags & icomuf_detach
+ && ioctl(p->usbd->fd, USBDEVFS_GETDRIVER, &getd) == 0) {
+ struct usbdevfs_ioctl cmd;
+ a1logd(p->log, 1, "usb_open_port: Attempting kernel detach\n");
+ cmd.ifno = iface;
+ cmd.ioctl_code = USBDEVFS_DISCONNECT;
+ cmd.data = NULL;
+ ioctl(p->usbd->fd, USBDEVFS_IOCTL, &cmd);
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLAIMINTERFACE, &iface)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed after detach with %d\n",p->usbd->dpath,iface,rv);
+ return ICOM_SYS;
+ }
+ } else {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: Claiming USB port '%s' interface %d failed with %d\n",p->usbd->dpath,iface,rv);
+ return ICOM_SYS;
+ }
+ }
+ }
+
+ /* Clear any errors. */
+ /* (Some I/F seem to hang if we do this, some seem to hang if we don't !) */
+ /* The ColorMunki on Linux only starts every second time if we don't do this. */
+ if (!(p->uflags & icomuf_no_open_clear)) {
+ for (i = 0; i < 32; i++) {
+ if (!p->ep[i].valid)
+ continue;
+ p->usb_clearhalt(p, p->ep[i].addr);
+ }
+ }
+
+ /* Set "serial" coms values */
+ p->wr_ep = wr_ep;
+ p->rd_ep = rd_ep;
+ p->rd_qa = p->EPINFO(rd_ep).packetsize;
+ if (p->rd_qa == 0)
+ p->rd_qa = 8;
+ a1logd(p->log, 8, "usb_open_port: 'serial' read quanta = packet size = %d\n",p->rd_qa);
+
+ /* Start the reaper thread to handle URB completions */
+ if ((rv = pipe(p->usbd->sd_pipe)) < 0) {
+ a1loge(p->log, ICOM_SYS, "usb_open_port: creat pipe failed with %d\n",rv);
+ return ICOM_SYS;
+ }
+ pthread_mutex_init(&p->usbd->lock, NULL);
+
+ p->usbd->running = 1;
+ if ((rv = pthread_create(&p->usbd->thread, NULL, urb_reaper, (void*)p)) < 0) {
+ p->usbd->running = 0;
+ a1loge(p->log, ICOM_SYS, "usb_open_port: creating urb reaper thread failed with %s\n",rv);
+ return ICOM_SYS;
+ }
+
+ p->is_open = 1;
+ a1logd(p->log, 8, "usb_open_port: USB port is now open\n");
+ }
+
+ /* Install the cleanup signal handlers, and add to our cleanup list */
+ usb_install_signal_handlers(p);
+
+ return ICOM_OK;
+}
+
+/* -------------------------------------------------------------- */
+/* I/O structures */
+
+/* an icoms urb */
+typedef struct {
+ struct _usbio_req *req; /* Request we're part of */
+ int urbno; /* urb index within request */
+ struct usbdevfs_urb urb; /* Linux urb */
+} usbio_urb;
+
+/* an i/o request */
+typedef struct _usbio_req {
+
+ int nurbs; /* Number of urbs in urbs[] */
+ usbio_urb *urbs; /* Allocated */
+ volatile int nourbs; /* Number of outstanding urbs, 0 when done */
+ int cancelled; /* All the URB's have been cancelled */
+
+ pthread_mutex_t lock; /* Protect req & reaper access */
+ pthread_cond_t cond; /* Signal to thread waiting on req */
+
+ struct _usbio_req *next; /* Link to next in list */
+} usbio_req;
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancel a req's urbs from the last down to but not including thisurb. */
+/* return icom error */
+static int cancel_req(icoms *p, usbio_req *req, int thisurb) {
+ int i, rv = ICOM_OK;
+ for (i = req->nurbs-1; i > thisurb; i--) {
+ int ev;
+ // ~~99 can we skip done, errored or cancelled urbs ?
+ // Does it matter if there is a race between cancellers ? */
+ a1logd(p->log, 7, "cancel_req %d\n",i);
+ ev = ioctl(p->usbd->fd, USBDEVFS_DISCARDURB, &req->urbs[i].urb);
+ if (ev != 0 && ev != EINVAL) {
+ /* Hmmm */
+ a1loge(p->log, ICOM_SYS, "cancel_req: failed with %d\n",rv);
+ rv = ICOM_SYS;
+ }
+ req->urbs[i].urb.status = -ECONNRESET;
+ }
+ req->cancelled = 1; /* Don't cancel it again */
+ return ICOM_OK;
+}
+
+/* The reaper thread */
+static void *urb_reaper(void *context) {
+ icoms *p = (icoms *)context;
+ struct usbdevfs_urb *out = NULL;
+ int errc = 0;
+ int rv;
+ struct pollfd pa[2]; /* Poll array to monitor urb result or shutdown */
+
+ a1logd(p->log, 6, "urb_reaper: reap starting\n");
+
+ /* Wait for a URB, and signal the requester */
+ for (;;) {
+ usbio_urb *iurb;
+ usbio_req *req;
+
+ /* Setup to wait for serial output not block */
+ pa[0].fd = p->usbd->fd;
+ pa[0].events = POLLIN | POLLOUT;
+ pa[0].revents = 0;
+
+ pa[1].fd = p->usbd->sd_pipe[0];
+ pa[1].events = POLLIN;
+ pa[1].revents = 0;
+
+ /* Wait for fd to become ready or shutdown */
+ if ((rv = poll_x(pa, 2, -1)) < 0 || pa[1].revents || pa[0].revents == 0) {
+ a1logd(p->log, 6, "urb_reaper: poll returned %d and events %d %d\n",rv,pa[0].revents,pa[1].revents);
+ p->usbd->shutdown = 1;
+ break;
+ }
+
+ /* Not sure what this returns if there is nothing there */
+ rv = ioctl(p->usbd->fd, USBDEVFS_REAPURBNDELAY, &out);
+
+ if (rv < 0) {
+ a1logd(p->log, 2, "urb_reaper: reap failed with %d\n",rv);
+ if (errc++ < 5) {
+ continue;
+ }
+ p->usbd->shutdown = 1;
+ break;
+ }
+
+ errc = 0;
+
+ if (out == NULL) {
+ a1logd(p->log, 2, "urb_reaper: reap returned NULL URB\n");
+ continue;
+ }
+
+ /* Normal reap */
+ iurb = (usbio_urb *)out->usercontext;
+ req = iurb->req;
+
+ a1logd(p->log, 8, "urb_reaper: urb reap URB %d with status %d bytes %d, usrbs left %d\n",iurb->urbno, out->status, out->actual_length, req->nourbs-1);
+
+ pthread_mutex_lock(&req->lock); /* Stop requester from missing reap */
+ req->nourbs--; /* We're reaped one */
+
+ /* If urb failed or is done (but not cancelled), cancel all the following urbs */
+ if (req->nourbs > 0 && !req->cancelled
+ && ((out->actual_length < out->buffer_length)
+ || (out->status < 0 && out->status != -ECONNRESET))) {
+ a1logd(p->log, 6, "urb_reaper: reaper canceling failed or done urb's\n",rv);
+ if (cancel_req(p, req, iurb->urbno) != ICOM_OK) {
+ pthread_mutex_unlock(&req->lock);
+ /* Is this fatal ? Assume so for the moment ... */
+ break;
+ }
+ }
+ if (req->nourbs <= 0) /* Signal the requesting thread that we're done */
+ pthread_cond_signal(&req->cond);
+ pthread_mutex_unlock(&req->lock);
+ }
+
+ /* Clean up */
+ if (p->usbd->shutdown) {
+ usbio_req *req;
+
+ a1logd(p->log, 8, "urb_reaper: shutdown or too many failure\n");
+
+ /* Signal that any request should give up, and that the */
+ /* reaper thread is going to exit */
+ p->usbd->running = 0;
+
+ /* Go through the outstanding request list, and */
+ /* mark them as failed and signal them all */
+ pthread_mutex_lock(&p->usbd->lock);
+ req = p->usbd->reqs;
+ while(req != NULL) {
+ int i;
+
+ pthread_mutex_lock(&req->lock);
+ for (i = req->nourbs-1; i >= 0; i--) {
+ req->urbs[i].urb.status = ICOM_SYS;
+ }
+ req->nourbs = 0;
+ pthread_cond_signal(&req->cond);
+ pthread_mutex_unlock(&req->lock);
+ req = req->next;
+ }
+ pthread_mutex_unlock(&p->usbd->lock);
+ a1logd(p->log, 1, "urb_reaper: cleared requests\n");
+ }
+ p->usbd->running = 0;
+
+ a1logd(p->log, 6, "urb_reaper: thread done\n");
+ return NULL;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - */
+/* Our universal USB transfer function */
+static int icoms_usb_transaction(
+ icoms *p,
+ usb_cancelt *cancelt,
+ int *transferred,
+ icom_usb_trantype ttype, /* transfer type */
+ unsigned char endpoint, /* 0x80 for control write, 0x00 for control read */
+ unsigned char *buffer,
+ int length,
+ unsigned int timeout /* In msec */
+) {
+ int reqrv = ICOM_OK, rv = 0;
+ usbio_req req, **preq;
+ int type;
+ int remlen;
+ unsigned char *bp;
+ int xlength = 0;
+ int i;
+
+ in_usb_rw++;
+ a1logd(p->log, 8, "icoms_usb_transaction: req type 0x%x ep 0x%x size %d\n",ttype,endpoint,length);
+
+ if (!p->usbd->running) {
+ in_usb_rw--;
+ a1logv(p->log, 1, "icoms_usb_transaction: reaper thread is not running\n");
+ return ICOM_SYS;
+ }
+
+ /* Translate icoms transfer type of Linux */
+ switch (ttype) {
+ case icom_usb_trantype_command:
+ type = USBDEVFS_URB_TYPE_CONTROL;
+ break;
+ case icom_usb_trantype_interrutpt:
+ type = USBDEVFS_URB_TYPE_INTERRUPT;
+ break;
+ case icom_usb_trantype_bulk:
+ type = USBDEVFS_URB_TYPE_BULK;
+ break;
+ }
+
+ /* Setup the icom req and urbs */
+ req.urbs = NULL;
+ pthread_mutex_init(&req.lock, NULL);
+ pthread_cond_init(&req.cond, NULL);
+
+ /* Linux historically only copes with 16384 length urbs, */
+ /* so break up longer requests into multiple urbs */
+
+ req.cancelled = 0;
+ req.nourbs = req.nurbs = (length + (1 << 14)-1) >> 14;
+ if ((req.urbs = (usbio_urb *)calloc(sizeof(usbio_urb), req.nourbs)) == NULL) {
+ in_usb_rw--;
+ a1loge(p->log, ICOM_SYS, "icoms_usb_transaction: control transfer too big! (%d)\n",length);
+ return ICOM_SYS;
+ }
+
+ bp = buffer;
+ remlen = length;
+ for (i = 0; i < req.nurbs; i++) {
+ req.urbs[i].req = &req;
+ req.urbs[i].urbno = i;
+ /* Setup Linux URB */
+ req.urbs[i].urb.usercontext = &req.urbs[i];
+ req.urbs[i].urb.type = type;
+ if (type != USBDEVFS_URB_TYPE_CONTROL)
+ req.urbs[i].urb.endpoint = endpoint;
+ if (remlen > 16384)
+ req.urbs[i].urb.buffer_length = 16384;
+ else
+ req.urbs[i].urb.buffer_length = remlen;
+ req.urbs[i].urb.buffer = (void *)bp;
+ remlen -= req.urbs[i].urb.buffer_length;
+ bp += req.urbs[i].urb.buffer_length;
+ req.urbs[i].urb.status = -EINPROGRESS;
+ }
+a1logd(p->log, 8, "icoms_usb_transaction: reset req 0x%p nourbs to %d\n",&req,req.nourbs);
+
+ /* Add our request to the req list so that it can be cancelled on reap failure */
+ pthread_mutex_lock(&p->usbd->lock);
+ req.next = p->usbd->reqs;
+ p->usbd->reqs = &req;
+ pthread_mutex_unlock(&p->usbd->lock);
+
+ /* submit the URBs */
+ for (i = 0; i < req.nurbs; i++) {
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_SUBMITURB, &req.urbs[i].urb)) < 0) {
+ a1logd(p->log, 1, "coms_usb_transaction: Submitting urb to fd %d failed with %d\n",p->usbd->fd, rv);
+ req.urbs[i].urb.status = ICOM_SYS; /* Mark it as failed to submit */
+ req.nourbs--;
+ }
+ }
+
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)&req;
+ usb_unlock_cancel(cancelt);
+ }
+
+ /* Wait for the reaper to wake us, or for a timeout, */
+ /* or for the reaper to die. */
+ pthread_mutex_lock(&req.lock);
+ if (req.nourbs > 0) {
+ struct timeval tv;
+ struct timespec ts;
+
+ // this is unduly complicated...
+ gettimeofday(&tv, NULL);
+ ts.tv_sec = tv.tv_sec + timeout/1000;
+ ts.tv_nsec = (tv.tv_usec + (timeout % 1000) * 1000) * 1000L;
+ if (ts.tv_nsec > 1000000000L) {
+ ts.tv_nsec -= 1000000000L;
+ ts.tv_sec++;
+ }
+
+ for(;;) { /* Ignore spurious wakeups */
+ if ((rv = pthread_cond_timedwait(&req.cond, &req.lock, &ts)) != 0) {
+ if (rv != ETIMEDOUT) {
+ pthread_mutex_unlock(&req.lock);
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_timedwait failed with %d\n",rv);
+ rv = ICOM_SYS;
+ goto done;
+ }
+
+ /* Timed out - cancel the remaining URB's */
+ a1logd(p->log, 8, "coms_usb_transaction: time out - cancel remaining URB's\n");
+ reqrv = ICOM_TO;
+ if (!req.cancelled && (rv = cancel_req(p, &req, -1)) != ICOM_OK) {
+ pthread_mutex_unlock(&req.lock);
+ reqrv = ICOM_SYS;
+ /* Since cancelling failed, we can't wait for them to be reaped */
+ goto done;
+ }
+
+ /* Wait for the cancelled URB's to be reaped */
+ for (;req.nourbs > 0;) { /* Ignore spurious wakeups */
+ if ((rv = pthread_cond_wait(&req.cond, &req.lock)) != 0) {
+ pthread_mutex_unlock(&req.lock);
+ a1logd(p->log, 1, "coms_usb_transaction: pthread_cond_wait failed with %d\n",rv);
+ reqrv = ICOM_SYS;
+ /* Waiting for reap failed, so give up */
+ goto done;
+ }
+ }
+ } else {
+ a1logd(p->log, 8, "coms_usb_transaction: reap - %d left\n",req.nourbs);
+ }
+ if (req.nourbs <= 0)
+ break; /* All urbs's are done */
+ }
+ }
+ pthread_mutex_unlock(&req.lock);
+
+ /* Compute the overall result by going through the urbs. */
+ for (i = 0; i < req.nurbs; i++) {
+ int stat = req.urbs[i].urb.status;
+ xlength += req.urbs[i].urb.actual_length;
+
+ if (stat == ICOM_SYS) { /* Submit or cancel failed */
+ reqrv = ICOM_SYS;
+ } else if (reqrv == ICOM_OK && stat < 0 && stat != -ECONNRESET) { /* Error result */
+ if ((endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT)
+ reqrv = ICOM_USBW;
+ else
+ reqrv = ICOM_USBR;
+ } else if (reqrv == ICOM_OK && stat == -ECONNRESET) { /* Cancelled */
+ reqrv = ICOM_CANC;
+ } else if (reqrv == ICOM_OK
+ && req.urbs[i].urb.actual_length < req.urbs[i].urb.buffer_length) {
+ /* Disregard any following urb's status - they are probably cancelled */
+ break;
+ }
+ /* reqrv == ICOM_TO will ignore urb status */
+ }
+
+ if (ttype == icom_usb_trantype_command)
+ xlength += IUSB_REQ_HEADER_SIZE; /* Account for header - linux doesn't */
+
+ /* requested size wasn't transferred ? */
+ if (reqrv == ICOM_OK && xlength != length)
+ reqrv = ICOM_SHORT;
+
+ if (transferred != NULL)
+ *transferred = xlength;
+
+done:;
+ if (cancelt != NULL) {
+ usb_lock_cancel(cancelt);
+ cancelt->hcancel = (void *)NULL;
+ usb_unlock_cancel(cancelt);
+ }
+
+ /* Remove our request from the list */
+ pthread_mutex_lock(&p->usbd->lock);
+ preq = &p->usbd->reqs;
+ while (*preq != &req && *preq != NULL) /* Find it */
+ preq = &((*preq)->next);
+ if (*preq != NULL)
+ *preq = (*preq)->next;
+ pthread_mutex_unlock(&p->usbd->lock);
+
+ if (req.urbs != NULL)
+ free(req.urbs);
+ pthread_cond_destroy(&req.cond);
+ pthread_mutex_destroy(&req.lock);
+
+ if (in_usb_rw < 0)
+ exit(0);
+
+ in_usb_rw--;
+
+ a1logd(p->log, 8, "coms_usb_transaction: returning err 0x%x and %d bytes\n",reqrv, xlength);
+
+ return reqrv;
+}
+
+
+/* Return error icom error code */
+static int icoms_usb_control_msg(
+icoms *p,
+int *transferred,
+int requesttype, int request,
+int value, int index, unsigned char *bytes, int size,
+int timeout) {
+ int reqrv = ICOM_OK;
+ int dirw = (requesttype & IUSB_REQ_DIR_MASK) == IUSB_REQ_HOST_TO_DEV ? 1 : 0;
+ unsigned char *buf;
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x req 0x%x size %d\n",requesttype,request,size);
+
+ /* Allocate a buffer for the ctrl header + payload */
+ if ((buf = calloc(1, IUSB_REQ_HEADER_SIZE + size)) == NULL) {
+ a1loge(p->log, ICOM_SYS, "icoms_usb_control_msg: calloc failed\n");
+ return ICOM_SYS;
+ }
+
+ /* Setup the control header */
+ buf[0] = requesttype;
+ buf[1] = request;
+ short2buf(buf + 2, value);
+ short2buf(buf + 4, index);
+ short2buf(buf + 6, size);
+
+ /* If it's a write, copy the write data into the buffer */
+ if (dirw)
+ memcpy(buf + IUSB_REQ_HEADER_SIZE, bytes, size);
+
+ reqrv = icoms_usb_transaction(p, NULL, transferred, icom_usb_trantype_command,
+ dirw ? 0x80 : 0x00, buf, IUSB_REQ_HEADER_SIZE + size, timeout);
+
+ /* If read, copy the data back */
+ if (!dirw)
+ memcpy(bytes, buf + IUSB_REQ_HEADER_SIZE, size);
+
+ if (transferred != NULL) /* Adjust for header size requested */
+ *transferred -= IUSB_REQ_HEADER_SIZE;
+
+ free(buf);
+
+ a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",reqrv, *transferred);
+ return reqrv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Time out error return value */
+
+#define USBIO_ERROR_TIMEOUT -ETIMEDOUT
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
+
+/* Cancel i/o in another thread */
+int icoms_usb_cancel_io(
+ icoms *p,
+ usb_cancelt *cancelt
+) {
+ int rv = ICOM_OK;
+ a1logd(p->log, 8, "icoms_usb_cancel_io called\n");
+ usb_lock_cancel(cancelt);
+ if (cancelt->hcancel != NULL)
+ rv = cancel_req(p, (usbio_req *)cancelt->hcancel, -1);
+ usb_unlock_cancel(cancelt);
+
+ if (rv != ICOM_OK) /* Assume this could be because of faulty device */
+ rv = ICOM_USBW;
+
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Reset and end point data toggle to 0 */
+int icoms_usb_resetep(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv = ICOM_OK;
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_RESETEP, &ep)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_resetep failed with %d\n",rv);
+ rv = ICOM_USBW;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+/* Clear a halt on an end point */
+int icoms_usb_clearhalt(
+ icoms *p,
+ int ep /* End point address */
+) {
+ int rv = ICOM_OK;
+
+ if ((rv = ioctl(p->usbd->fd, USBDEVFS_CLEAR_HALT, &ep)) != 0) {
+ a1logd(p->log, 1, "icoms_usb_clearhalt failed with %d\n",rv);
+ rv = ICOM_USBW;
+ }
+ return rv;
+}
+
+/* - - - - - - - - - - - - - - - - - - - - - - - - - */
+