/* 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 */