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.c | 585 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 585 insertions(+) create mode 100644 spectro/usbio.c (limited to 'spectro/usbio.c') diff --git a/spectro/usbio.c b/spectro/usbio.c new file mode 100644 index 0000000..3db1535 --- /dev/null +++ b/spectro/usbio.c @@ -0,0 +1,585 @@ + + /* General USB I/O support */ + +/* + * 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. + */ + +/* These routines supliement the class code in ntio.c and unixio.c */ +/* with common and USB specific routines */ + +#include +#include +#include +#include +#if defined(UNIX) +#include +#include +#include +#endif +#ifndef SALONEINSTLIB +#include "copyright.h" +#include "aconfig.h" +#else +#include "sa_config.h" +#endif +#include "numsup.h" +#include "xspect.h" +#include "insttypes.h" +#include "conv.h" +#include "icoms.h" + +#ifdef ENABLE_USB + +/* Counter set when we're in a USB read or write */ +/* Note - this isn't perfectly thread safe */ +int in_usb_rw = 0; + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* Cancel token utility functions */ + +/* Used by caller of icoms to init and uninit token */ +void usb_init_cancel(usb_cancelt *p) { + + amutex_init(p->cmtx); + +#ifdef NATIVE_USB + p->hcancel = NULL; +#else +# ifdef USE_LIBUSB1 + p->hcancel = NULL; +# else + p->hcancel = (void *)-1; +# endif +#endif +} + +void usb_uninit_cancel(usb_cancelt *p) { + amutex_del(p->cmtx); +} + +/* Used by implementation */ +static void usb_lock_cancel(usb_cancelt *p) { + amutex_lock(p->cmtx); +} + +static void usb_unlock_cancel(usb_cancelt *p) { + amutex_unlock(p->cmtx); +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Include the USB implementation dependent function implementations */ +#ifdef NATIVE_USB +# ifdef NT +# include "usbio_nt.c" +# endif +# if defined(__APPLE__) +# include "usbio_ox.c" +# endif +# if defined(UNIX_X11) +# include "usbio_lx.c" +# endif +#else /* Using libusb */ +# include "usbio_lusb.c" +#endif + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ +/* I/O routines supported by icoms - uses platform independent */ +/* USB routines implemented by code in usbio_*.c above */ + +/* USB control message */ +static int +icoms_usb_control( +icoms *p, +int requesttype, /* 8 bit request type (USB bmRequestType) */ +int request, /* 8 bit request code (USB bRequest) */ +int value, /* 16 bit value (USB wValue) */ +int index, /* 16 bit index (USB wIndex) */ +unsigned char *rwbuf, /* Write or read buffer */ +int rwsize, /* Bytes to read or write */ +double tout /* Timeout in seconds */ +) { + int rv = 0; /* Return value */ + int c, rwbytes; /* Data bytes read or written */ + long top; /* timeout in msec */ + + if (p->log->debug >= 8) { + a1logd(p->log, 8, "icoms_usb_control: message %02x, %02x %04x %04x %04x\n", + requesttype, request, value, index, rwsize); + if ((requesttype & IUSB_ENDPOINT_IN) == 0) + a1logd(p->log, 8, " writing data %s\n",icoms_tohex(rwbuf, rwsize)); + } + + if (!p->is_open) { + a1loge(p->log, ICOM_SYS, "icoms_usb_control: device not open\n"); + return ICOM_SYS; + } + + top = (int)(tout * 1000.0 + 0.5); /* Timout in msec */ + +#ifdef QUIET_MEMCHECKERS + if (requesttype & IUSB_ENDPOINT_IN) + memset(rwbuf, 0, rwsize); +#endif + + /* Call back end implementation */ + rv = icoms_usb_control_msg(p, &rwbytes, requesttype, request, value, index, + rwbuf, rwsize, top); + a1logd(p->log, 8, "icoms_usb_control: returning ICOM err 0x%x\n",rv); + + if (p->log->debug >= 8 && (requesttype & IUSB_ENDPOINT_IN)) + a1logd(p->log, 8, " read data %s\n",icoms_tohex(rwbuf, rwsize)); + + return rv; +} + +/* - - - - - - - - - - - - - */ +/* USB Read/Write. Direction is set by ep. */ +/* Don't retry on a short read, return ICOM_SHORT. */ +static int +icoms_usb_rw(icoms *p, + usb_cancelt *cancelt, /* Cancel handle */ + int ep, /* End point address */ + unsigned char *rbuf, /* Read/Write buffer */ + int bsize, /* Bytes to read */ + int *breadp, /* Bytes read/written */ + double tout /* Timeout in seconds */ +) { + int lerr; /* Last error */ + int bread, qa; + long top; /* Timeout period */ + icom_usb_trantype type; /* bulk or interrupt */ + +#ifdef QUIET_MEMCHECKERS + memset(rbuf, 0, bsize); +#endif + + if (!p->is_open) { + a1loge(p->log, ICOM_SYS, "icoms_usb_rw: device not initialised\n"); + return ICOM_SYS; + } + + if (p->EPINFO(ep).valid == 0) { + a1loge(p->log, ICOM_SYS, "icoms_usb_rw: invalid end point 0x%02x\n",ep); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK + && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) { + a1loge(p->log, ICOM_SYS, "icoms_usb_rw: unhandled end point type %d\n",p->EPINFO(ep).type); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK) + type = icom_usb_trantype_bulk; + else + type = icom_usb_trantype_interrutpt; + + qa = bsize; /* For simpler tracing */ + + lerr = 0; + bread = 0; + + top = (int)(tout * 1000 + 0.5); /* Timeout period in msecs */ + + /* Bug workaround - on some OS's for some devices */ + if (p->uflags & icomuf_resetep_before_read + && (ep & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_IN) { + msec_sleep(1); /* Let device recover ? */ + p->usb_resetep(p, ep); + msec_sleep(1); /* Let device recover (ie. Spyder 3) */ + } + + /* Until data is all read/written, we get a short read/write, we time out, or the user aborts */ +// a1logd(p->log, 8, "icoms_usb_rw: read/write of %d bytes, timout %f\n",bsize,tout); + while (bsize > 0) { + int rv, rbytes; + int rsize = bsize > qa ? qa : bsize; + +// a1logd(p->log, 8, "icoms_usb_rw: read/write %d bytes this time\n",rsize); + rv = icoms_usb_transaction(p, cancelt, &rbytes, type, (unsigned char)ep, rbuf, rsize, top); + if (rv != 0 && rv != ICOM_SHORT) { + lerr = rv; + break; + } else { /* Account for bytes read/written */ + bsize -= rbytes; + rbuf += rbytes; + bread += rbytes; + } + if (rbytes != rsize) { + lerr |= ICOM_SHORT; + break; + } + } + + if (breadp != NULL) + *breadp = bread; + + a1logd(p->log, 8, "icoms_usb_rw: returning %d bytes, ICOM err 0x%x\n",bread, lerr); + + return lerr; +} + +/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ + +/* Static list so that all open USB/HID connections can be closed on a SIGKILL */ +static icoms *icoms_list = NULL; + +/* Clean up any open USB ports ready for exit on signal */ +static void icoms_cleanup() { + icoms *pp, *np; + +#if defined(UNIX) + /* This is a bit of a hack to compensate for the fact */ + /* that a ^C will kill the program while ICANON is off. */ + /* It's really better to restore the original attributes, */ + /* even when USB is not compiled in. */ + struct termios news; + if (tcgetattr(STDIN_FILENO, &news) >= 0) { + news.c_lflag |= (ICANON | ECHO); + tcsetattr(STDIN_FILENO,TCSANOW, &news); + } +#endif + + for (pp = icoms_list; pp != NULL; pp = np) { + np = pp->next; + a1logd(pp->log, 6, "icoms_cleanup: closing usb port 0x%x\n",pp); + /* There's a problem here if have more than one USB port */ + /* open - win32 doesn't return from the system call. */ + /* Should we depend on usb read/write routines to call cleanup ? */ + pp->close_port(pp); + } +} + +#ifdef NT +void (__cdecl *usbio_int)(int sig) = SIG_DFL; +void (__cdecl *usbio_term)(int sig) = SIG_DFL; +#endif +#ifdef UNIX +void (*usbio_hup)(int sig) = SIG_DFL; +void (*usbio_int)(int sig) = SIG_DFL; +void (*usbio_term)(int sig) = SIG_DFL; +#endif + +/* On something killing our process, deal with USB cleanup */ +static void icoms_sighandler(int arg) { + a1logd(g_log, 6, "icoms_sighandler: invoked with arg = %d\n",arg); + if (in_usb_rw != 0) + in_usb_rw = -1; + icoms_cleanup(); + /* Call the existing handlers */ +#ifdef UNIX + if (arg == SIGHUP && usbio_hup != SIG_DFL && usbio_hup != SIG_IGN) + usbio_hup(arg); +#endif /* UNIX */ + if (arg == SIGINT && usbio_int != SIG_DFL && usbio_int != SIG_IGN) + usbio_int(arg); + if (arg == SIGTERM && usbio_term != SIG_DFL && usbio_term != SIG_IGN) + usbio_term(arg); + + a1logd(g_log, 6, "icoms_sighandler: calling exit()\n"); + exit(0); +} + +/* - - - - - - - - - - - - - - - - - - - */ + +/* Install the cleanup signal handlers */ +void usb_install_signal_handlers(icoms *p) { + if (icoms_list == NULL) { + a1logd(g_log, 6, "usb_install_signal_handlers: called\n"); +#if defined(UNIX) + usbio_hup = signal(SIGHUP, icoms_sighandler); +#endif /* UNIX */ + usbio_int = signal(SIGINT, icoms_sighandler); + usbio_term = signal(SIGTERM, icoms_sighandler); + } + + /* Add it to our static list, to allow automatic cleanup on signal */ + p->next = icoms_list; + icoms_list = p; + a1logd(g_log, 6, "usb_install_signal_handlers: done\n"); +} + +/* Delete an icoms from our static signal cleanup list */ +void usb_delete_from_cleanup_list(icoms *p) { + + /* Find it and delete it from our static cleanup list */ + if (icoms_list != NULL) { + if (icoms_list == p) { + icoms_list = p->next; + if (icoms_list == NULL) { +#if defined(UNIX) + signal(SIGHUP, usbio_hup); +#endif /* UNIX */ + signal(SIGINT, usbio_int); + signal(SIGTERM, usbio_term); + } + } else { + icoms *pp; + for (pp = icoms_list; pp != NULL; pp = pp->next) { + if (pp->next == p) { + pp->next = p->next; + break; + } + } + } + } +} + +/* ========================================================= */ +/* USB write/read "serial" imitation */ + +/* Write the characters in the buffer out */ +/* Data will be written up to the terminating nul */ +/* Return relevant error status bits */ +static int +icoms_usb_ser_write( +icoms *p, +char *wbuf, +double tout) +{ + int len, wbytes; + long toc, i, top; /* Timout count, counter, timeout period */ + int ep = p->wr_ep; /* End point */ + icom_usb_trantype type; /* bulk or interrupt */ + int retrv = ICOM_OK; + + a1logd(p->log, 8, "\nicoms_usb_ser_write: writing '%s'\n",icoms_fix(wbuf)); + + if (!p->is_open) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: device is not open\n"); + return ICOM_SYS; + } + + if (p->EPINFO(ep).valid == 0) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: invalid end point 0x%02x\n",ep); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK + && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_write: unhandled end point type %d",p->EPINFO(ep).type); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK) + type = icom_usb_trantype_bulk; + else + type = icom_usb_trantype_interrutpt; + + len = strlen(wbuf); + tout *= 1000.0; /* Timout in msec */ + + top = (int)(tout + 0.5); /* Timeout period in msecs */ + toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */ + if (toc < 1) + toc = 1; + + /* Until data is all written, we time out, or the user aborts */ + for (i = toc; i > 0 && len > 0;) { + int c, rv; + a1logd(p->log, 8, "icoms_usb_ser_write: attempting to write %d bytes to usb top = %d, i = %d\n",len,top,i); + + rv = icoms_usb_transaction(p, NULL, &wbytes, type, (unsigned char)ep, (unsigned char *)wbuf, len, top); + if (rv != ICOM_OK) { + if (rv != ICOM_TO) { + retrv |= rv; + break; + } + i--; /* timeout */ + } else { /* Account for bytes written */ + a1logd(p->log, 8, "icoms_usb_ser_write: wrote %d bytes\n",wbytes); + i = toc; + wbuf += wbytes; + len -= wbytes; + } + } + if (i <= 0) /* Must have timed out */ + retrv |= ICOM_TO; + + a1logd(p->log, 8, "icoms_usb_ser_write: returning ICOM err 0x%x\n",retrv); + + return retrv; +} + + +/* Read characters into the buffer */ +/* Return string will be terminated with a nul */ +/* Read only in paket sized chunks, and retry if */ +/* the bytes requested aren'r read, untill we get a */ +/* timeout or a terminating char is read */ +static int +icoms_usb_ser_read(icoms *p, +char *rbuf, /* Buffer to store characters read */ +int bsize, /* Buffer size */ +char tc, /* Terminating characer */ +int ntc, /* Number of terminating characters */ +double tout) /* Time out in seconds */ +{ + int j, rbytes; + long toc, i, top; /* Timout count, counter, timeout period */ + char *rrbuf = rbuf; /* Start of return buffer */ + int ep = p->rd_ep; /* End point */ + icom_usb_trantype type; /* bulk or interrupt */ + int retrv = ICOM_OK; + +#ifdef QUIET_MEMCHECKERS + memset(rbuf, 0, bsize); +#endif + + if (!p->is_open) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: device is not open\n"); + return ICOM_SYS; + } + + if (p->EPINFO(ep).valid == 0) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: invalid end point 0x%02x\n",ep); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type != ICOM_EP_TYPE_BULK + && p->EPINFO(ep).type != ICOM_EP_TYPE_INTERRUPT) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read: unhandled end point type %d",p->EPINFO(ep).type); + return ICOM_SYS; + } + + if (p->EPINFO(ep).type == ICOM_EP_TYPE_BULK) + type = icom_usb_trantype_bulk; + else + type = icom_usb_trantype_interrutpt; + + if (bsize < 3) { + a1loge(p->log, ICOM_SYS, "icoms_usb_ser_read given too small a buffer (%d)", bsize); + return ICOM_SYS; + } + + for (i = 0; i < bsize; i++) rbuf[i] = 0; + + tout *= 1000.0; /* Timout in msec */ + bsize--; /* Allow space for null */ + + /* Have to do this in one go, because libusb has no way */ + /* of timing out and returning the number of characters read */ + /* up to the timeout, and it looses characters. */ + top = (int)(tout + 0.5); /* Timeout period in msecs */ + toc = (int)(tout/top + 0.5); /* Number of timout periods in timeout */ + if (toc < 1) + toc = 1; + + a1logd(p->log, 8, "\nicoms_usb_ser_read: end point 0x%x, read quanta %d\n",p->rd_ep,p->rd_qa); + /* Until data is all read, we time out, or the user aborts */ + for (i = toc, j = 0; i > 0 && bsize > 1 && j < ntc ;) { + int c, rv; + int rsize = p->rd_qa < bsize ? p->rd_qa : bsize; + + a1logd(p->log, 8, "icoms_usb_ser_read: attempting to read %d bytes from usb, top = %d, i = %d, j = %d\n",bsize > p->rd_qa ? p->rd_qa : bsize,top,i,j); + /* We read one read quanta at a time (usually 8 bytes), to avoid */ + /* problems with libusb loosing characters whenever it times out. */ + rv = icoms_usb_transaction(p, NULL, &rbytes, type, (unsigned char)ep, (unsigned char *)rbuf, rsize, top); + if (rv != 0 && rv != ICOM_SHORT) { + a1logd(p->log, 8, "icoms_usb_ser_read: read failed with 0x%x, rbuf = '%s'\n",rv,icoms_fix(rrbuf)); + if (rv != ICOM_TO) { + retrv |= rv; + break; + } + i--; /* Timeout */ + } else { /* Account for bytes read */ + a1logd(p->log, 8, "icoms_usb_ser_read: read read %d bytes, rbuf = '%s'\n",rbytes,icoms_fix(rrbuf)); + i = toc; + bsize -= rbytes; + while(rbytes) { /* Count termination characters */ + if (*rbuf++ == tc) + j++; + rbytes--; + } + } + } + + if (i <= 0) /* Must have timed out */ + retrv |= ICOM_TO; + + *rbuf = '\000'; + + a1logd(p->log, 8, "icoms_usb_ser_read: returning '%s' ICOM err 0x%x\n",icoms_fix(rrbuf),retrv); + + return retrv; +} + + +/* ------------------------------------------------- */ + +/* Set the usb port number and characteristics. */ +/* This may be called to re-establish a connection that has failed */ +/* return an icom error */ +static int +icoms_set_usb_port( +icoms *p, +int config, /* Configuration */ +int wr_ep, /* "Serial" Write end point */ +int rd_ep, /* "Serial" 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 */ +) { + a1logd(p->log, 8, "icoms_set_usb_port: About to set usb port characteristics\n"); + + if (p->port_type(p) == icomt_usb) { + int rv; + + if (p->is_open) + p->close_port(p); + + if ((rv = usb_open_port(p, config, wr_ep, rd_ep, usbflags, retries, pnames)) != ICOM_OK) { + return rv; + } + + p->write = icoms_usb_ser_write; + p->read = icoms_usb_ser_read; + + } + a1logd(p->log, 6, "icoms_set_usb_port: usb port characteristics set ok\n"); + +#ifndef NATIVE_USB + /* libusb doesn't have any facility for re-directing its */ + /* debug messages. Since we're moving away from it, */ + /* ignore the problem. */ + if (p->log->debug >= 8) { /* Could this go inside usb_open_port ? */ +# ifdef USE_LIBUSB1 + libusb_set_debug(NULL, p->log->debug); +# else + usb_set_debug(p->debug); +# endif + } +#endif /* NATIVE_USB */ + + return ICOM_OK; +} + +/* ---------------------------------------------------------------------------------*/ + +/* Set the USB specific icoms methods */ +void usb_set_usb_methods( +icoms *p +) { + p->set_usb_port = icoms_set_usb_port; + p->usb_control = icoms_usb_control; + p->usb_read = icoms_usb_rw; + p->usb_write = icoms_usb_rw; + p->usb_cancel_io = icoms_usb_cancel_io; + p->usb_resetep = icoms_usb_resetep; + p->usb_clearhalt = icoms_usb_clearhalt; +} + +/* ---------------------------------------------------------------------------------*/ + +#endif /* ENABLE_USB */ -- cgit v1.2.3