From 22f703cab05b7cd368f4de9e03991b7664dc5022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Frings-F=C3=BCrst?= Date: Mon, 1 Sep 2014 13:56:46 +0200 Subject: Initial import of argyll version 1.5.1-8 --- spectro/usbio_lx.c | 1069 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1069 insertions(+) create mode 100644 spectro/usbio_lx.c (limited to 'spectro/usbio_lx.c') 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 +#include +#include +#include +#include +#include +#include + +/* select() defined, but not poll(), so emulate poll() */ +#if defined(FD_CLR) && !defined(POLLIN) +#include "pollem.h" +#define poll_x pollem +#else +#include /* 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; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - */ + -- cgit v1.2.3