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

997 lines
32 KiB
C

/* General USB I/O support, OS X native implementation. */
/* This file is conditionaly #included into usbio.c */
/*
* 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.
*/
/* OS X I/O error codes are in IOKit/IOReturn.h */
#include <sys/time.h>
#include <CoreFoundation/CoreFoundation.h>
#include <IOKit/IOTypes.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/usb/IOUSBLib.h>
#include <IOKit/IOCFPlugIn.h>
//#include <IOKit/IOCFBundle.h>
#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
# include <objc/objc-auto.h>
#endif
/* Add paths to USB connected instruments */
/* Return an icom error */
int usb_get_paths(
icompaths *p
) {
kern_return_t kstat;
CFMutableDictionaryRef sdict; /* USB Device dictionary */
io_iterator_t mit; /* Matching itterator */
int vid, pid;
struct usb_idevice *usbd = NULL;
a1logd(p->log, 6, "usb_get_paths: about to look through devices:\n");
/* Get dictionary of USB devices */
if ((sdict = IOServiceMatching(kIOUSBDeviceClassName)) == NULL) {
a1loge(p->log, ICOM_SYS, "usb_get_paths() IOServiceMatching returned a NULL dictionary\n");
return ICOM_SYS;
}
/* Init itterator to find matching types. Consumes sdict reference */
if ((kstat = IOServiceGetMatchingServices(kIOMasterPortDefault, sdict, &mit))
!= KERN_SUCCESS) {
a1loge(p->log, ICOM_SYS, "usb_get_paths() IOServiceGetMatchingServices returned %d\n", kstat);
return ICOM_SYS;
}
/* Find all the matching USB devices */
for (;;) {
io_object_t ioob; /* USB object found */
io_iterator_t it1; /* Child level 1 */
CFMutableDictionaryRef usbprops = 0; /* USB Device properties */
CFNumberRef vref, pref; /* USB Vendor and Product ID propeties */
CFNumberRef nconfref; /* No configurations */
CFNumberRef nepref, lidpref; /* No ep's, Location ID properties */
unsigned int vid = 0, pid = 0, nep, tnep, nconfig = 0, lid = 0;
devType itype;
if ((ioob = IOIteratorNext(mit)) == 0)
break;
/* Get the two properies we need. */
if ((vref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kUSBVendorID),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(vref, kCFNumberIntType, &vid);
CFRelease(vref);
}
if ((pref = IORegistryEntryCreateCFProperty(ioob, CFSTR(kUSBProductID),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(pref, kCFNumberIntType, &pid);
CFRelease(pref);
}
if ((nconfref = IORegistryEntryCreateCFProperty(ioob, CFSTR("bNumConfigurations"),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(nconfref, kCFNumberIntType, &nconfig);
CFRelease(nconfref);
}
if ((lidpref = IORegistryEntryCreateCFProperty(ioob, CFSTR("locationID"),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(lidpref, kCFNumberIntType, &lid);
CFRelease(lidpref);
}
a1logd(p->log, 6, "usb_get_paths: checking vid 0x%04x, pid 0x%04x, lid 0x%x\n",vid,pid,lid);
/* Do a preliminary match */
if ((itype = inst_usb_match(vid, pid, 0)) == instUnknown) {
a1logd(p->log, 6 , "usb_get_paths: 0x%04x 0x%04x not reconized\n",vid,pid);
IOObjectRelease(ioob); /* Release found object */
continue;
}
/* 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(p->log, ICOM_SYS, "usb_get_paths: calloc failed!\n");
return ICOM_SYS;
}
usbd->nconfig = nconfig;
usbd->config = 1; /* We are only interested in config 1 */
/* Can we get Manufacturer, Product, SerialNumber using IORegistryEntryCreateCFProperty ? */
/* Go through all the Interfaces, adding up the number of end points */
tnep = 0;
if ((kstat = IORegistryEntryCreateIterator(ioob, kIOServicePlane,
kIORegistryIterateRecursively, &it1)) != KERN_SUCCESS) {
IOObjectRelease(ioob);
IOObjectRelease(mit);
a1loge(p->log, kstat, "usb_get_paths: IORegistryEntryCreateIterator() with %d\n",kstat);
return ICOM_SYS;
}
usbd->nifce = 0;
for (;;) {
io_object_t ch1; /* Child object 1 */
if ((ch1 = IOIteratorNext(it1)) == 0)
break;
if (IOObjectConformsTo(ch1, "IOUSBInterface")) {
unsigned int config = 0;
/* Get the configuration number */
if ((nconfref = IORegistryEntryCreateCFProperty(ch1, CFSTR(kUSBConfigurationValue),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(nconfref, kCFNumberIntType, &config);
CFRelease(nconfref);
}
if (config != 1)
continue;
usbd->nifce++;
/* Get the number of end points */
if ((nepref = IORegistryEntryCreateCFProperty(ch1, CFSTR(kUSBNumEndpoints),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(nepref, kCFNumberIntType, &nep);
CFRelease(nepref);
tnep += nep;
}
IOObjectRelease(ch1);
}
}
IOObjectRelease(it1);
if ((itype = inst_usb_match(vid, pid, tnep)) != instUnknown) {
int i;
char pname[400];
int rv;
/* If this device is HID, it will have already added to the paths list, */
/* so check for this and skip this device if it is already there. */
for (i = 0; i < p->ndpaths[dtix_combined]; i++) {
if (p->dpaths[i][dtix_combined]->vid == vid
&& p->dpaths[i][dtix_combined]->pid == pid
&& p->dpaths[i][dtix_combined]->hidd != NULL
&& p->dpaths[i][dtix_combined]->hidd->lid == lid) {
a1logd(p->log, 1, "usb_get_paths: Ignoring device because it is already in list as HID\n");
break;
}
}
if (i < p->ndpaths[dtix_combined]) {
IOObjectRelease(ioob); /* Release found object */
free(usbd);
} else {
a1logd(p->log, 1, "usb_get_paths: found instrument vid 0x%04x, pid 0x%04x\n",vid,pid);
usbd->lid = lid;
usbd->ioob = ioob;
/* Create a path/identification */
sprintf(pname,"usb%d: (%s)", lid >> 20, inst_name(itype));
/* Add the path and ep info to the list */
if ((rv = p->add_usb(p, pname, vid, pid, tnep, usbd, itype)) != ICOM_OK) {
IOObjectRelease(ioob); /* Release found object */
free(usbd);
IOObjectRelease(mit); /* Release the itterator */
return rv;
}
}
} else {
IOObjectRelease(ioob); /* Release found object */
free(usbd);
}
}
IOObjectRelease(mit); /* Release the itterator */
a1logd(p->log, 8, "usb_get_paths: returning %d paths and ICOM_OK\n",p->ndpaths[dtix_combined]);
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 (s->usbd->SerialNumber != NULL
&& (d->usbd->SerialNumber = strdup(s->usbd->SerialNumber)) == NULL) {
a1loge(d->log, ICOM_SYS, "usb_copy_usb_idevice: malloc\n");
return ICOM_SYS;
}
d->nconfig = s->usbd->nconfig;
d->config = s->usbd->config;
d->nifce = s->usbd->nifce;
d->usbd->ioob = s->usbd->ioob;
IOObjectRetain(d->usbd->ioob);
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->SerialNumber != NULL)
free(usbd->SerialNumber);
if (usbd->ioob != 0)
IOObjectRelease(usbd->ioob);
free(usbd);
}
/* Cleanup any USB specific icoms state */
void usb_del_usb(icoms *p) {
usb_del_usb_idevice(p->usbd);
}
/* Clean up anything allocated/created in p->usbd, but don't free p->usbd itself */
static void cleanup_device(icoms *p) {
if (p->usbd != NULL) {
if (p->usbd->device != NULL) { /* device is open */
int i;
/* Release all the interfaces */
for (i = 0; i < p->usbd->nifce; i++) {
if (p->usbd->interfaces[i] != NULL) {
(*p->usbd->interfaces[i])->USBInterfaceClose(p->usbd->interfaces[i]);
(*p->usbd->interfaces[i])->Release(p->usbd->interfaces[i]);
}
}
/* Close the device */
(*(p->usbd->device))->USBDeviceClose(p->usbd->device);
(*(p->usbd->device))->Release(p->usbd->device);
}
memset(p->usbd, 0, sizeof(struct usb_idevice));
}
}
/* 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) {
/* Workaround for some bugs - reset device on close */
if (p->uflags & icomuf_reset_before_close) {
IOReturn rv;
a1logd(p->log, 6, "usb_close_port: icomuf_reset_before_close\n");
// OS X >= 10.11 ignores ResetDevice, so use alternative... */
if ((rv = (*(p->usbd->device))->USBDeviceReEnumerate(p->usbd->device, 0))
!= kIOReturnSuccess)
a1logd(p->log, 1, "usb_close_port: USBDeviceReEnumerate failed with 0x%x\n",rv);
}
/* Close down and free everything in p->usbd */
cleanup_device(p);
if (p->usbd != NULL) {
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 int icoms_usb_control_msg(icoms *p, int *transferred, int requesttype, int request,
int value, int index, unsigned char *bytes, int size, int timeout);
static void *io_runloop(void *context);
/* 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, (1 based) */
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 */
) {
IOReturn rv;
int 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) {
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++) {
IOCFPlugInInterface **piif = NULL;
SInt32 score;
a1logd(p->log, 8, "usb_open_port: About to open USB port '%s'\n",p->name);
if (tries > 0) {
//msec_sleep(i_rand(50,100));
msec_sleep(77);
}
if (tries > 0 && pnames != NULL && kpc == NULL) {
if ((kpc = kkill_nprocess(pnames, p->log)) == NULL) {
a1logd(p->log, 1, "kkill_nprocess returned error!\n");
return ICOM_SYS;
}
}
if ((rv = IOCreatePlugInInterfaceForService(p->usbd->ioob,
kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID,
&piif, &score)) != kIOReturnSuccess || piif == NULL) {
a1loge(p->log, rv, "usb_open_port: Failed to get piif for "
"USB device '%s', result 0x%x, piif 0x%x\n",p->name,rv,piif);
return ICOM_SYS;
}
p->usbd->device = NULL;
if ((rv = (*piif)->QueryInterface(piif,
CFUUIDGetUUIDBytes(DeviceInterfaceID), (void *)&p->usbd->device))
!= kIOReturnSuccess || p->usbd->device == NULL) {
a1loge(p->log, rv, "usb_open_port: QueryInterface '%s' failed with 0%x\n",p->name, rv);
(*piif)->Release(piif);
return ICOM_SYS;
}
(*piif)->Release(piif); /* delete intermediate object */
if ((rv = (*p->usbd->device)->USBDeviceOpenSeize(p->usbd->device)) != kIOReturnSuccess) {
a1logd(p->log, 8, "usb_open_port: open '%s' config %d failed (0x%x) (Device being used ?)\n",p->name,config,rv);
if (retries <= 0) {
if (kpc != NULL) {
if (kpc->th->result < 0)
a1logw(p->log, "usb_open_port: killing competing processes failed\n");
kpc->del(kpc);
}
a1loge(p->log, rv, "usb_open_port: open '%s' config %d failed (0x%x) (Device being used ?)\n",p->name, config, rv);
(*p->usbd->device)->Release(p->usbd->device);
return ICOM_SYS;
}
continue;
} else if (p->debug)
a1logd(p->log, 1, "usb_open_port: open port '%s' succeeded\n",p->name);
p->uflags = usbflags;
/* We're done retries */
break;
}
if (kpc != NULL)
kpc->del(kpc);
/* Try and get the USB serial number */
#ifdef kUSBSerialNumberString /* Easy way */
{
CFStringRef sref; /* Serial number propety */
char serno[IUSB_DESC_TYPE_STRING_MAX_SIZE/2];
if ((sref = IORegistryEntryCreateCFProperty(p->usbd->ioob, CFSTR(kUSBSerialNumberString),
kCFAllocatorDefault,kNilOptions)) != 0) {
/* Convert from CF string to C string */
if (CFStringGetCString(sref, serno, IUSB_DESC_TYPE_STRING_MAX_SIZE/2,
kCFStringEncodingUTF8)) {
if ((p->usbd->SerialNumber = strdup(serno)) == NULL) {
a1loge(p->log, rv, "usb_open_port: open '%s' malloc failed\n",p->name);
cleanup_device(p);
return ICOM_SYS;
}
} else {
a1logd(p->log, 1, "usb_open_port: CFStringGetCString failed\n");
}
CFRelease(sref);
} else {
a1logd(p->log, 1, "usb_open_port: IORegistryEntryCreateCFProperty kUSBSerialNumberString failed\n");
}
}
#else /* The hard way... */
{
CFNumberRef siref; /* Serial number index */
unsigned int iserialno = 0;
if ((siref = IORegistryEntryCreateCFProperty(p->usbd->ioob, CFSTR(kUSBSerialNumberStringIndex),
kCFAllocatorDefault,kNilOptions)) != 0) {
CFNumberGetValue(siref, kCFNumberIntType, &iserialno);
CFRelease(siref);
} else {
a1logd(p->log, 6, "usb_open_port: failed to get kUSBSerialNumberStringIndex\n");
}
if (iserialno != 0) {
unsigned char buf[IUSB_DESC_TYPE_STRING_MAX_SIZE];
int se = 0;
int retsz = 0;
if (((se = icoms_usb_control_msg(p, &retsz,
IUSB_REQ_DEV_TO_HOST | IUSB_REQ_TYPE_STANDARD | IUSB_REQ_RECIP_DEVICE,
IUSB_REQ_GET_DESCRIPTOR,
(IUSB_DESC_TYPE_STRING << 8) | iserialno, 0,
buf, IUSB_DESC_TYPE_STRING_MAX_SIZE, 200)) != ICOM_OK && se != ICOM_SHORT)
|| retsz < IUSB_DESC_TYPE_STRING_MIN_SIZE) {
a1logd(p->log, 1, "usb_open_port failed to read device serial no with ICOM err 0x%x and retsz %d\n",se,retsz);
} else {
unsigned int slen = buf[0];
if (slen > 2 && (slen & 1) == 0) {
icmUTF16 buf2[IUSB_DESC_TYPE_STRING_MAX_SIZE/2];
unsigned int j;
size_t s8len;
/* Convert to 16 bit native endian and add nul terminator */
slen = (slen-2)/2;
for (j = 0; j < slen; j++) {
buf2[j] = usb2ushort(buf + 2 + j * 2);
}
buf2[j] = 0;
s8len =icmUTF16toUTF8(NULL, NULL, buf2); /* Compute UTF8 size */
if ((p->usbd->SerialNumber = (char *) calloc(s8len, 1)) == NULL) {
a1loge(p->log, ICOM_SYS, "usb_get_paths_w0 calloc failed!\n");
cleanup_device(p);
return ICOM_SYS;
}
icmUTF16toUTF8(NULL, (unsigned char *)p->usbd->SerialNumber, buf2); /* Convert to UTF8 */
a1logd(p->log, 6, "usb_open_port got USB serial no '%s'\n",p->usbd->SerialNumber);
}
}
}
}
#endif /* !kUSBSerialNumberString */
/* 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. */
/* OS X doesn't do a set_configuration() by default, so force one. */
p->cconfig = 0;
if (p->cconfig != config) {
if ((rv = (*(p->usbd->device))->SetConfiguration(
p->usbd->device, config)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: SetConfiguration failed with 0x%x\n",rv);
cleanup_device(p);
return ICOM_SYS;
}
p->cconfig = config;
a1logd(p->log, 6, "usb_open_port: SetConfiguration %d OK\n",config);
}
/* For some devices (Spyder 3/4 ?) on some versions of OS X (Ventura ?), */
/* we seem to need to let the device recover from the set configuration. */
msec_sleep(50);
/* Get interfaces */
{
int i, j;
IOUSBFindInterfaceRequest req;
io_iterator_t ioit;
io_service_t iface;
IOCFPlugInInterface **pluginref = NULL;
SInt32 score;
req.bInterfaceClass =
req.bInterfaceSubClass =
req.bInterfaceProtocol =
req.bAlternateSetting = kIOUSBFindInterfaceDontCare;
if ((rv = (*(p->usbd->device))->CreateInterfaceIterator(
p->usbd->device, &req, &ioit)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: CreateInterfaceIterator failed with 0x%x\n",rv);
cleanup_device(p);
return ICOM_SYS;
}
iface = IOIteratorNext(ioit);
for (i = 0; iface != 0; i++, iface = IOIteratorNext(ioit)) {
u_int8_t nep;
if ((rv = IOCreatePlugInInterfaceForService(iface,
kIOUSBInterfaceUserClientTypeID,
kIOCFPlugInInterfaceID,
&pluginref, &score)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: IOCreatePlugInInterfaceForService failed with 0x%x\n",rv);
IOObjectRelease(iface);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
IOObjectRelease(iface);
if ((rv = (*pluginref)->QueryInterface(pluginref,
CFUUIDGetUUIDBytes(InterfaceInterfaceID),
(void *)&p->usbd->interfaces[i])) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: QueryInterface failed with 0x%x\n",rv);
(*pluginref)->Stop(pluginref);
IODestroyPlugInInterface(pluginref);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
(*pluginref)->Stop(pluginref);
IODestroyPlugInInterface(pluginref); // this stops IOServices though ??
/* This can fail if the device has multiple interfaces, and */
/* one of them is grabbed by a system interface (i.e. debug serial port) */
/* So soft fail, and rely on ep use to fail for invalid interface */
if ((rv = (*(p->usbd->interfaces[i]))->USBInterfaceOpen(
p->usbd->interfaces[i])) != kIOReturnSuccess) {
#ifdef NEVER
a1loge(p->log, rv, "usb_open_port: USBInterfaceOpen %d failed with 0x%x\n",i, rv);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
#else
a1logw(p->log, "usb_open_port: USBInterfaceOpen %d failed with 0x%x\n",i,rv);
continue;
#endif
}
/* Get the end point details, and set reference to pipe no and interfece ix */
if ((rv = (*(p->usbd->interfaces[i]))->GetNumEndpoints(
p->usbd->interfaces[i], &nep)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: GetNumEndpoints failed with 0x%x\n",rv);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
for (j = 0; j < nep; j++) {
UInt8 direction, pnumber, transferType, interval;
UInt16 maxPacketSize;
int ad;
if ((rv = (*(p->usbd->interfaces[i]))->GetPipeProperties(
p->usbd->interfaces[i], (UInt8)(j+1), &direction, &pnumber,
&transferType, &maxPacketSize, &interval)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: GetPipeProperties failed with 0x%x\n",rv);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
ad = ((direction << IUSB_ENDPOINT_DIR_SHIFT) & IUSB_ENDPOINT_DIR_MASK)
| (pnumber & IUSB_ENDPOINT_NUM_MASK);
p->EPINFO(ad).valid = 1;
p->EPINFO(ad).addr = ad;
p->EPINFO(ad).packetsize = maxPacketSize;
p->EPINFO(ad).type = transferType & IUSB_ENDPOINT_TYPE_MASK;
p->EPINFO(ad).ifaceno = i;
p->EPINFO(ad).pipe = j+1;
a1logd(p->log, 6, "set ep ad 0x%x packetsize %d type %d, if %d, pipe %d\n",ad,maxPacketSize,transferType & IUSB_ENDPOINT_TYPE_MASK,i,j);
}
}
p->usbd->nifce = i; /* Just in case.. */
IOObjectRelease(ioit);
}
/* Clear any errors */
/* (Some I/F seem to hang if we do this, some seem to hang if we don't !) */
if (!(p->uflags & icomuf_no_open_clear)) {
int i;
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);
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 */
typedef struct _usbio_req {
icoms *p;
int iix; /* Interface index */
UInt8 pno; /* pipe index */
volatile int done; /* Done flag */
int xlength; /* Bytes transferred */
IOReturn result; /* Result of transaction */
CFRunLoopRef rlr; /* RunLoop of calling thread */
} usbio_req;
/* Async completion callback - called by RunLoop */
static void io_callback(void *refcon, IOReturn result, void *arg0) {
usbio_req *req = (usbio_req *)refcon;
a1logd(req->p->log, 1, "io_callback: result 0x%x, length %d\n",result,(int)(long)arg0);
req->xlength = (int)(long)arg0;
req->result = result;
req->done = 1;
CFRunLoopStop(req->rlr); /* We're done */
}
/* Our universal (non-control) 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;
int dirw = (endpoint & IUSB_ENDPOINT_DIR_MASK) == IUSB_ENDPOINT_OUT ? 1 : 0;
usbio_req req;
IOReturn result;
CFRunLoopSourceRef cfsource; /* Device event sources */
int iix = p->EPINFO(endpoint).ifaceno;
UInt8 pno = (UInt8)p->EPINFO(endpoint).pipe;
in_usb_rw++;
a1logd(p->log, 8, "icoms_usb_transaction: req type 0x%x ep 0x%x size %d\n",ttype,endpoint,length);
if (transferred != NULL)
*transferred = 0;
if (ttype != icom_usb_trantype_interrutpt
&& ttype != icom_usb_trantype_bulk) {
/* We only handle interrupt & bulk, not control */
return ICOM_SYS;
}
req.p = p;
req.iix = iix;
req.pno = pno;
req.xlength = 0;
req.done = 0;
req.rlr = CFRunLoopGetCurrent(); /* Get this threads RunLoop */
CFRetain(req.rlr);
/* Create an async event source for the interface */
if ((result = (*(p->usbd->interfaces[iix]))->CreateInterfaceAsyncEventSource(
p->usbd->interfaces[iix], &cfsource)) != kIOReturnSuccess) {
a1logw(p->log, "icoms_usb_transaction: CreateInterfaceAsyncEventSource failed with 0x%x\n",result);
CFRelease(req.rlr);
return ICOM_SYS;
}
CFRetain(cfsource);
/* Add it to the RunLoop */
CFRunLoopAddSource(req.rlr, cfsource, kCFRunLoopDefaultMode);
if (dirw)
result = (*p->usbd->interfaces[iix])->WritePipeAsync(p->usbd->interfaces[iix],
pno, buffer, length, io_callback, &req);
else
result = (*p->usbd->interfaces[iix])->ReadPipeAsync(p->usbd->interfaces[iix],
pno, buffer, length, io_callback, &req);
if (result != kIOReturnSuccess) {
a1loge(p->log, ICOM_SYS, "icoms_usb_transaction: %sPipeAsync failed with 0x%x\n",dirw ? "Write" : "Read", result);
reqrv = ICOM_SYS;
goto done;
}
if (cancelt != NULL) {
amutex_lock(cancelt->cmtx);
cancelt->hcancel = (void *)&req;
cancelt->state = 1;
amutex_unlock(cancelt->condx); /* Signal any thread waiting for IO start */
amutex_unlock(cancelt->cmtx);
}
/* Wait for the callback to complete or for a timeout */
result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout / 1000.0, false);
// kCFRunLoopRunFinished = 1
// kCFRunLoopRunStopped = 2
// kCFRunLoopRunTimedOut = 3
// kCFRunLoopRunHandledSource = 4
if (result == kCFRunLoopRunTimedOut) {
/* Timed out - so abort i/o */
a1logd(p->log, 8, "coms_usb_transaction: time out - aborting io\n");
(*p->usbd->interfaces[iix])->AbortPipe(p->usbd->interfaces[iix], pno);
CFRunLoopRun(); /* Wait for abort */
reqrv = ICOM_TO;
} else if (result != kCFRunLoopRunStopped) {
a1logd(p->log, 8, "coms_usb_transaction: unexpected result 0x%x\n",result);
reqrv = ICOM_USBR;
}
done:;
CFRunLoopRemoveSource(req.rlr, cfsource, kCFRunLoopDefaultMode);
CFRelease(cfsource);
CFRelease(req.rlr);
a1logd(p->log, 8, "coms_usb_transaction: completed with reqrv 0x%x and xlength %d\n",req.result,req.xlength);
/* If io was aborted, ClearPipeStall */
if (req.result == kIOReturnAborted) {
a1logd(p->log, 8, "coms_usb_transaction: io was aborted\n");
#if (InterfaceVersion > 182)
(*p->usbd->interfaces[iix])->ClearPipeStallBothEnds(p->usbd->interfaces[iix], pno);
#else
(*p->usbd->interfaces[iix])->ClearPipeStall(p->usbd->interfaces[iix], pno);
// icoms_usb_control_msg(p, NULL, IUSB_REQ_HOST_TO_DEV | IUSB_REQ_TYPE_STANDARD
// | IUSB_REQ_RECIP_ENDPOINT, IUSB_REQ_CLEAR_FEATURE,
// IUSB_FEATURE_EP_HALT, endpoint, NULL, 0, 200);
#endif
if (reqrv == ICOM_OK) /* If not aborted for a known reason, must be cancelled */
reqrv = ICOM_CANC;
}
/* If normal completion - not timed out or aborted */
if (reqrv == ICOM_OK) { /* Completed OK */
if (req.result == kIOReturnSuccess) {
if (req.xlength != length)
reqrv = ICOM_SHORT;
} else {
if (dirw)
reqrv = ICOM_USBW;
else
reqrv = ICOM_USBR;
}
}
if (transferred != NULL)
*transferred = req.xlength;
if (cancelt != NULL) {
amutex_lock(cancelt->cmtx);
cancelt->hcancel = (void *)NULL;
if (cancelt->state == 0)
amutex_unlock(cancelt->condx);
cancelt->state = 2;
amutex_unlock(cancelt->cmtx);
}
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, req.xlength);
return reqrv;
}
/* Our control message routine */
/* 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;
IOReturn rv;
IOUSBDevRequestTO req;
a1logd(p->log, 8, "icoms_usb_control_msg: type 0x%x req 0x%x size %d\n",requesttype,request,size);
bzero(&req, sizeof(req));
req.bmRequestType = requesttype;
req.bRequest = request;
req.wValue = value;
req.wIndex = index;
req.wLength = size;
req.pData = bytes;
req.completionTimeout = timeout;
req.noDataTimeout = timeout;
if (transferred != NULL)
*transferred = 0;
if ((rv = (*(p->usbd->device))->DeviceRequestTO(p->usbd->device, &req)) != kIOReturnSuccess) {
if (rv == kIOUSBTransactionTimeout) {
reqrv = ICOM_TO;
} else {
if (dirw)
reqrv = ICOM_USBW;
else
reqrv = ICOM_USBR;
}
} else {
if (transferred != NULL)
*transferred = req.wLenDone;
/* The requested size wasn't transferred */
if (req.wLenDone != size)
reqrv |= ICOM_SHORT;
}
a1logd(p->log, 8, "icoms_usb_control_msg: returning err 0x%x and %d bytes\n",reqrv, transferred != NULL ? *transferred : -1);
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 reqrv = ICOM_OK;
amutex_lock(cancelt->cmtx);
usbio_req *req = (usbio_req *)cancelt->hcancel;
if (req != NULL) {
IOReturn rv;
if ((rv = (*p->usbd->interfaces[req->iix])->AbortPipe(
p->usbd->interfaces[req->iix], req->pno)) != kIOReturnSuccess) {
reqrv = ICOM_USBW;
}
}
amutex_unlock(cancelt->cmtx);
return reqrv;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Reset and end point data toggle to 0 */
int icoms_usb_resetep(
icoms *p,
int ep /* End point address */
) {
int reqrv = ICOM_OK;
int iix = p->EPINFO(ep).ifaceno;
UInt8 pno = (UInt8)p->EPINFO(ep).pipe;
IOReturn rv;
if ((rv = (*p->usbd->interfaces[iix])->ResetPipe(
p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
a1logd(p->log, 1, "icoms_usb_resetep failed with 0x%x\n",rv);
reqrv = ICOM_USBW;
}
return reqrv;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - */
/* Clear a halt on an end point */
int icoms_usb_clearhalt(
icoms *p,
int ep /* End point address */
) {
int reqrv = ICOM_OK;
int iix = p->EPINFO(ep).ifaceno;
UInt8 pno = (UInt8)p->EPINFO(ep).pipe;
IOReturn rv;
int irv;
#if (InterfaceVersion > 182)
if ((rv = (*p->usbd->interfaces[iix])->ClearPipeStallBothEnds(
p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
a1logd(p->log, 1, "icoms_usb_clearhalt failed with 0x%x\n",rv);
reqrv = ICOM_USBW;
}
#else
if ((rv = (*p->usbd->interfaces[iix])->ClearPipeStall(
p->usbd->interfaces[iix], pno)) != kIOReturnSuccess) {
a1logd(p->log, 1, "icoms_usb_clearhalt failed with 0x%x\n",rv);
reqrv = ICOM_USBW;
}
if ((irv = icoms_usb_control_msg(p, NULL, IUSB_REQ_HOST_TO_DEV | IUSB_REQ_TYPE_STANDARD
| IUSB_REQ_RECIP_ENDPOINT, IUSB_REQ_CLEAR_FEATURE,
IUSB_FEATURE_EP_HALT, ep, NULL, 0, 200)) != ICOM_OK) {
a1logd(p->log, 1, "icoms_usb_clearhalt far end failed with 0x%x\n",irv);
reqrv = ICOM_USBW;
}
#endif
return reqrv;
}
/* - - - - - - - - - - - - - - - - - - - - - - - - - */
#ifdef NEVER
{
IOUSBFindInterfaceRequest req;
io_iterator_t ioit;
/* See if we can find any interfaces */
req.bInterfaceClass =
req.bInterfaceSubClass =
req.bInterfaceProtocol =
req.bAlternateSetting = kIOUSBFindInterfaceDontCare;
if ((rv = (*(p->usbd->device))->CreateInterfaceIterator(
p->usbd->device, &req, &ioit)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: CreateInterfaceIterator failed with 0x%x\n",rv);
cleanup_device(p);
return ICOM_SYS;
}
if (IOIteratorNext(ioit) == 0) { /* Configure the device */
IOUSBConfigurationDescriptorPtr confdesc;
if ((rv = (*(p->usbd->device))->GetConfigurationDescriptorPtr(
p->usbd->device, config-1, &confdesc)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: GetConfigurationDescriptorPtr failed with 0x%x\n",rv);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
if ((rv = (*(p->usbd->device))->SetConfiguration(
p->usbd->device, confdesc->bConfigurationValue)) != kIOReturnSuccess) {
a1loge(p->log, rv, "usb_open_port: SetConfiguration failed with 0x%x\n",rv);
IOObjectRelease(ioit);
cleanup_device(p);
return ICOM_SYS;
}
a1logd(p->log, 6, "usb_open_port: SetConfiguration %d OK\n",confdesc->bConfigurationValue);
} else { /* Some diagnostics */
UInt8 confno;
if ((rv = (*(p->usbd->device))->GetConfiguration(
p->usbd->device, &confno)) != kIOReturnSuccess) {
a1logd(p->log, 6, "usb_open_port: GetConfiguration failed with 0x%x\n",rv);
} else {
a1logd(p->log, 6, "usb_open_port: Device didn't need configuring - currently %d\n",confno);
}
}
IOObjectRelease(ioit);
}
#endif /* NEVER */