Files
2026-08-20 20:28:40 +01:00

727 lines
21 KiB
C

/* General USB I/O support */
/*
* Argyll Color Management 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 icoms_nt.c and icoms_ux.c */
/* with common and USB specific routines */
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <signal.h>
#if defined(UNIX)
#include <termios.h>
#include <errno.h>
#include <unistd.h>
#endif
#ifndef SALONEINSTLIB
#include "copyright.h"
#include "aconfig.h"
#else
#include "sa_config.h"
#endif
#include "numsup.h"
#include "cgats.h"
#include "xspect.h"
#include "insttypes.h"
#include "conv.h"
#include "icoms.h"
#include "inst.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);
amutex_init(p->condx);
}
void usb_uninit_cancel(usb_cancelt *p) {
amutex_del(p->cmtx);
amutex_del(p->condx);
}
/* Used by caller of icoms to re-init for wait_io */
/* Must be called before icoms_usb_wait_io() */
void usb_reinit_cancel(usb_cancelt *cancelt) {
amutex_lock(cancelt->cmtx);
cancelt->state = 0;
amutex_lock(cancelt->condx); /* Block until IO is started */
amutex_unlock(cancelt->cmtx);
}
/* Wait for the given transaction to be pending or complete. */
static int icoms_usb_wait_io(
icoms *p,
usb_cancelt *cancelt
) {
amutex_lock(cancelt->condx); /* Wait for unlock */
amutex_unlock(cancelt->condx); /* Free it up for next time */
return ICOM_OK;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Include the USB implementation dependent function implementations */
#ifdef NT
# if !defined(EN_LIBUSB0) && !defined(EN_USBDK)
# error "One or both of USE_LIBUSB0 and USE_USBDK must be set in Jamtop!"
# endif
# ifdef EN_LIBUSB0
# include "usbio_w0.c" /* libusb0.sys based */
# endif
# ifdef EN_USBDK
# include "usbio_dk.c" /* UsbDk based */
# endif
# include "usbio_nt.c" /* multiplexing shims */
#endif
#if defined(UNIX_APPLE)
# include "usbio_ox.c"
#endif
#if defined(UNIX_X11)
# if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__FreeBSD_kernel__)
# include "usbio_bsd.c"
# else
# include "usbio_lx.c"
# endif
#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, /* Size to write/max bytes to read */
int *xferred, /* Bytes written or read (may be NULL) */
double tout /* Timeout in seconds */
) {
int rv = 0; /* Return value */
int 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); /* Timeout 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, rwbytes));
if (xferred != NULL)
*xferred = rwbytes;
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, timeout %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) {
static amutex_static(lock);
a1logd(g_log, 6, "icoms_sighandler: invoked with arg = %d\n",arg);
/* Make sure we don't re-enter */
if (amutex_trylock(lock)) {
return;
}
if (in_usb_rw != 0)
in_usb_rw = -1;
icoms_cleanup();
/* Call through to previous 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");
amutex_unlock(lock);
exit(0);
}
/* - - - - - - - - - - - - - - - - - - - */
/* Use sigaction() if we can, to get SA_RESTART behavior */
#if defined(UNIX)
//sighandler_t signal_x(int signum, sighandler_t handler) {
static void (*signal_x(int signum, void (*handler)(int)))(int) {
struct sigaction new, old;
int rv = 0;
new.sa_handler = handler;
sigemptyset(&new.sa_mask);
new.sa_flags = SA_RESTART;
rv = sigaction(signum, &new, &old);
return old.sa_handler;
}
#else
# define signal_x signal
#endif
/* 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_x(SIGHUP, icoms_sighandler);
#endif /* UNIX */
usbio_int = signal_x(SIGINT, icoms_sighandler);
usbio_term = signal_x(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_x(SIGHUP, usbio_hup);
#endif /* UNIX */
signal_x(SIGINT, usbio_int);
signal_x(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, /* null terminated unless nch > 0 */
int nwch, /* if > 0, number of characters to write */
double tout)
{
int len, wbytes;
long ttop, top; /* Total timeout period, timeout period */
unsigned int stime, etime; /* Start and end times of USB operation */
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",
nwch > 0 ? icoms_tohex((unsigned char *)wbuf, nwch) : 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;
if (nwch != 0)
len = nwch;
else
len = strlen(wbuf);
ttop = (int)(tout * 1000.0 + 0.5); /* Total timeout period in msecs */
a1logd(p->log, 8, "\nicoms_usb_ser_write: ep 0x%x, bytes %d, ttop %d, quant %d\n", p->rd_ep, len, ttop, p->rd_qa);
etime = stime = msec_time();
/* Until data is all written, we time out, or the user aborts */
for (top = ttop; top > 0 && len > 0;) {
int rv;
a1logd(p->log, 8, "icoms_usb_ser_write: attempting to write %d bytes to usb top = %d\n",len,top);
rv = icoms_usb_transaction(p, NULL, &wbytes, type, (unsigned char)ep, (unsigned char *)wbuf, len, top);
etime = msec_time();
if (rv != ICOM_OK) {
if (rv != ICOM_TO) {
retrv |= rv;
break;
}
} else { /* Account for bytes written */
a1logd(p->log, 8, "icoms_usb_ser_write: wrote %d bytes\n",wbytes);
wbuf += wbytes;
len -= wbytes;
}
top = ttop - (etime - stime); /* Remaining time */
}
if (top <= 0) { /* Must have timed out */
a1logd(p->log, 8, "icoms_usb_ser_write: timeout, took %d msec out of %d\n",etime - stime,ttop);
retrv |= ICOM_TO;
}
a1logd(p->log, 8, "icoms_usb_ser_write: took %d msec, returning ICOM err 0x%x\n",etime - stime,retrv);
return retrv;
}
/* Read characters into the buffer */
/* Return string will be terminated with a nul */
/* Read only in packet sized chunks, and retry if */
/* the bytes requested aren't 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 */
int *pbread, /* Bytes read (not including forced '\000') */
char *tc, /* Terminating characers, NULL for none or char count mode */
int ntc, /* Number of terminating characters or char count needed */
double tout) /* Time out in seconds */
{
int j, rbytes;
long ttop, top; /* Total timeout period, timeout period */
unsigned int stime, etime; /* Start and end times of USB operation */
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;
int nreads; /* Number of reads performed */
int fastserial = 0; /* If fast serial type */
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;
}
if (p->dctype & icomt_fastserial)
fastserial = 1;
for (j = 0; j < bsize; j++)
rbuf[j] = 0;
/* The DTP94 doesn't cope with a timeout on OS X, so we need to avoid */
/* them by giving each read the largest timeout period possible. */
/* This also reduces the problem of libusb 0.1 not returning the */
/* number of characters read on a timeout. */
ttop = (int)(tout * 1000.0 + 0.5); /* Total timeout period in msecs */
a1logd(p->log, 8, "\nicoms_usb_ser_read: ep 0x%x, bytes %d, ttop %d, ntc %d, quant %d\n", p->rd_ep, bsize, ttop, ntc, p->rd_qa);
bsize -= 1; /* Allow space for null */
bsize -= p->ms_bytes; /* Allow space for modem status bytes */
j = (tc == NULL && ntc <= 0) ? -1 : 0;
etime = stime = msec_time();
/* Until data is all read, we time out, or the user aborts */
for (top = ttop, nreads = 0; top > 0 && bsize > 0 && j < ntc ;) {
int c, rv;
int rsize = bsize;
/* If not a fast USB/BT serial port, read in quanta size chunks */
if (!fastserial && rsize > p->rd_qa)
rsize = p->rd_qa;
a1logd(p->log, 8, "icoms_usb_ser_read: attempting to read %d bytes from usb, top = %d, j = %d\n",rsize,top,j);
rv = icoms_usb_transaction(p, NULL, &rbytes, type, (unsigned char)ep, (unsigned char *)rbuf, rsize, top);
etime = msec_time();
nreads++;
if (rbytes > 0) { /* Account for bytes read */
/* Account for modem status bytes. Modem bytes are per usb read, */
/* or every p->rd_qa bytes. */
if (p->ms_bytes) { /* Throw away modem bytes */
char *bp = rbuf;
int rb = rbytes;
for (; rb > 0; ) {
int nb = rb < p->ms_bytes ? rb : p->ms_bytes; /* Bytes to shift */
if (p->interp_ms != NULL && nb >= p->ms_bytes)
retrv |= p->interp_ms(p, (unsigned char *)bp); /* Deal with error flags in ms bytes */
a1logd(p->log, 8, "icoms_usb_ser_read: discarded %d modem bytes 0x%02x 0x%02x\n",nb,nb >= 1 ? (bp[0] & 0xff) : 0, nb >= 2 ? (bp[1] & 0xff) : 0);
rb -= nb;
rbytes -= nb;
memmove(bp, bp+nb, rb);
bp += p->rd_qa - p->ms_bytes;
rb -= p->rd_qa - p->ms_bytes;
}
rbuf[rbytes] = 0;
}
a1logd(p->log, 8, "icoms_usb_ser_read: read %d bytes, rbuf = '%s'\n",rbytes,icoms_fix(rrbuf));
bsize -= rbytes;
if (tc != NULL) {
while(rbytes--) { /* Count termination characters */
char ch = *rbuf++, *tcp = tc;
while(*tcp != '\000') {
if (ch == *tcp)
j++;
tcp++;
}
}
a1logd(p->log, 8, "icoms_usb_ser_read: tc count %d\n",j);
} else {
if (ntc > 0)
j += rbytes;
rbuf += rbytes;
}
}
/* Deal with any errors */
if (rv != ICOM_OK && rv != ICOM_SHORT) {
a1logd(p->log, 8, "icoms_usb_ser_read: read failed with 0x%x, rbuf = '%s'\n",rv,icoms_fix(rrbuf));
retrv |= rv;
break;
}
top = ttop - (etime - stime); /* Remaining time */
}
*rbuf = '\000';
a1logd(p->log, 8, "icoms_usb_ser_read: read %d total bytes with %d reads\n",rbuf - rrbuf, nreads);
if (pbread != NULL)
*pbread = (rbuf - rrbuf);
/* If ran out of time and not completed */
a1logd(p->log, 8, "icoms_usb_ser_read: took %d msec\n",etime - stime);
if (top <= 0 && bsize > 0 && j < ntc) {
a1logd(p->log, 8, "icoms_usb_ser_read: timeout, took %d msec out of %d\n",etime - stime,ttop);
retrv |= ICOM_TO;
}
a1logd(p->log, 8, "icoms_usb_ser_read: took %d msec, returning '%s' ICOM err 0x%x\n",
etime - stime, tc == NULL && ntc > 0 ? icoms_tohex((unsigned char*)rrbuf, rbuf - rrbuf) : 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;
} else {
a1logd(p->log, 8, "icoms_set_usb_port: Not a USB port!\n");
return ICOM_NOTS;
}
a1logd(p->log, 6, "icoms_set_usb_port: usb port characteristics set ok\n");
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_wait_io = icoms_usb_wait_io;
p->usb_cancel_io = icoms_usb_cancel_io;
p->usb_resetep = icoms_usb_resetep;
p->usb_clearhalt = icoms_usb_clearhalt;
}
/* ---------------------------------------------------------------------------------*/
/* Utility code */
/* USB descriptors are little endian */
/* Take a word sized return buffer, and convert it to an unsigned int */
unsigned int usb2uint(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 */
unsigned int usb2ushort(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 */
void int2usb(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 */
void short2usb(unsigned char *buf, int inv) {
buf[0] = (inv >> 0) & 0xff;
buf[1] = (inv >> 8) & 0xff;
}
/* ---------------------------------------------------------------------------------*/
#endif /* ENABLE_USB */