997 lines
32 KiB
C
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 */
|