summaryrefslogtreecommitdiffstats
path: root/src/os/macos/darwin_usb.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/os/macos/darwin_usb.c')
-rw-r--r--src/os/macos/darwin_usb.c2611
1 files changed, 2611 insertions, 0 deletions
diff --git a/src/os/macos/darwin_usb.c b/src/os/macos/darwin_usb.c
new file mode 100644
index 0000000..903422c
--- /dev/null
+++ b/src/os/macos/darwin_usb.c
@@ -0,0 +1,2611 @@
+/* -*- Mode: C; indent-tabs-mode:nil -*- */
+/*
+ * darwin backend for libusb 1.0
+ * Copyright © 2008-2021 Nathan Hjelm <hjelmn@cs.unm.edu>
+ * Copyright © 2019-2021 Google LLC. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#include <config.h>
+#include <assert.h>
+#include <time.h>
+#include <ctype.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/sysctl.h>
+
+#include <mach/clock.h>
+#include <mach/clock_types.h>
+#include <mach/mach_host.h>
+#include <mach/mach_port.h>
+
+/* Suppress warnings about the use of the deprecated objc_registerThreadWithCollector
+ * function. Its use is also conditionalized to only older deployment targets. */
+#define OBJC_SILENCE_GC_DEPRECATIONS 1
+
+/* Default timeout to 10s for reenumerate. This is needed because USBDeviceReEnumerate
+ * does not return error status on macOS. */
+#define DARWIN_REENUMERATE_TIMEOUT_US 10000000
+
+#include <AvailabilityMacros.h>
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200
+ #include <objc/objc-auto.h>
+#endif
+
+#include "darwin_usb.h"
+
+static int init_count = 0;
+
+/* Both kIOMasterPortDefault or kIOMainPortDefault are synonyms for 0. */
+static const mach_port_t darwin_default_master_port = 0;
+
+/* async event thread */
+static pthread_mutex_t libusb_darwin_at_mutex = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t libusb_darwin_at_cond = PTHREAD_COND_INITIALIZER;
+
+#if !defined(HAVE_CLOCK_GETTIME)
+static clock_serv_t clock_realtime;
+static clock_serv_t clock_monotonic;
+#endif
+
+#define LIBUSB_DARWIN_STARTUP_FAILURE ((CFRunLoopRef) -1)
+
+static CFRunLoopRef libusb_darwin_acfl = NULL; /* event cf loop */
+static CFRunLoopSourceRef libusb_darwin_acfls = NULL; /* shutdown signal for event cf loop */
+
+static usbi_mutex_t darwin_cached_devices_lock = PTHREAD_MUTEX_INITIALIZER;
+static struct list_head darwin_cached_devices;
+static const char *darwin_device_class = "IOUSBDevice";
+
+#define DARWIN_CACHED_DEVICE(a) (((struct darwin_device_priv *)usbi_get_device_priv((a)))->dev)
+
+/* async event thread */
+static pthread_t libusb_darwin_at;
+
+static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len);
+static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface);
+static int darwin_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface);
+static int darwin_reenumerate_device(struct libusb_device_handle *dev_handle, bool capture);
+static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint);
+static int darwin_reset_device(struct libusb_device_handle *dev_handle);
+static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0);
+
+static enum libusb_error darwin_scan_devices(struct libusb_context *ctx);
+static enum libusb_error process_new_device (struct libusb_context *ctx, struct darwin_cached_device *cached_device,
+ UInt64 old_session_id);
+
+static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out,
+ UInt64 *old_session_id);
+
+#if defined(ENABLE_LOGGING)
+static const char *darwin_error_str (IOReturn result) {
+ static char string_buffer[50];
+ switch (result) {
+ case kIOReturnSuccess:
+ return "no error";
+ case kIOReturnNotOpen:
+ return "device not opened for exclusive access";
+ case kIOReturnNoDevice:
+ return "no connection to an IOService";
+ case kIOUSBNoAsyncPortErr:
+ return "no async port has been opened for interface";
+ case kIOReturnExclusiveAccess:
+ return "another process has device opened for exclusive access";
+ case kIOUSBPipeStalled:
+#if defined(kUSBHostReturnPipeStalled)
+ case kUSBHostReturnPipeStalled:
+#endif
+ return "pipe is stalled";
+ case kIOReturnError:
+ return "could not establish a connection to the Darwin kernel";
+ case kIOUSBTransactionTimeout:
+ return "transaction timed out";
+ case kIOReturnBadArgument:
+ return "invalid argument";
+ case kIOReturnAborted:
+ return "transaction aborted";
+ case kIOReturnNotResponding:
+ return "device not responding";
+ case kIOReturnOverrun:
+ return "data overrun";
+ case kIOReturnCannotWire:
+ return "physical memory can not be wired down";
+ case kIOReturnNoResources:
+ return "out of resources";
+ case kIOUSBHighSpeedSplitError:
+ return "high speed split error";
+ case kIOUSBUnknownPipeErr:
+ return "pipe ref not recognized";
+ default:
+ snprintf(string_buffer, sizeof(string_buffer), "unknown error (0x%x)", result);
+ return string_buffer;
+ }
+}
+#endif
+
+static enum libusb_error darwin_to_libusb (IOReturn result) {
+ switch (result) {
+ case kIOReturnUnderrun:
+ case kIOReturnSuccess:
+ return LIBUSB_SUCCESS;
+ case kIOReturnNotOpen:
+ case kIOReturnNoDevice:
+ return LIBUSB_ERROR_NO_DEVICE;
+ case kIOReturnExclusiveAccess:
+ return LIBUSB_ERROR_ACCESS;
+ case kIOUSBPipeStalled:
+#if defined(kUSBHostReturnPipeStalled)
+ case kUSBHostReturnPipeStalled:
+#endif
+ return LIBUSB_ERROR_PIPE;
+ case kIOReturnBadArgument:
+ return LIBUSB_ERROR_INVALID_PARAM;
+ case kIOUSBTransactionTimeout:
+ return LIBUSB_ERROR_TIMEOUT;
+ case kIOUSBUnknownPipeErr:
+ return LIBUSB_ERROR_NOT_FOUND;
+ case kIOReturnNotResponding:
+ case kIOReturnAborted:
+ case kIOReturnError:
+ case kIOUSBNoAsyncPortErr:
+ default:
+ return LIBUSB_ERROR_OTHER;
+ }
+}
+
+/* this function must be called with the darwin_cached_devices_lock held */
+static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) {
+ cached_dev->refcount--;
+ /* free the device and remove it from the cache */
+ if (0 == cached_dev->refcount) {
+ list_del(&cached_dev->list);
+
+ if (cached_dev->device) {
+ (*(cached_dev->device))->Release(cached_dev->device);
+ cached_dev->device = NULL;
+ }
+ IOObjectRelease (cached_dev->service);
+ free (cached_dev);
+ }
+}
+
+static void darwin_ref_cached_device(struct darwin_cached_device *cached_dev) {
+ cached_dev->refcount++;
+}
+
+static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, uint8_t *pipep, uint8_t *ifcp, struct darwin_interface **interface_out) {
+ struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle);
+
+ /* current interface */
+ struct darwin_interface *cInterface;
+
+ uint8_t i, iface;
+
+ struct libusb_context *ctx = HANDLE_CTX(dev_handle);
+
+ usbi_dbg (ctx, "converting ep address 0x%02x to pipeRef and interface", ep);
+
+ for (iface = 0 ; iface < USB_MAXINTERFACES ; iface++) {
+ cInterface = &priv->interfaces[iface];
+
+ if (dev_handle->claimed_interfaces & (1U << iface)) {
+ for (i = 0 ; i < cInterface->num_endpoints ; i++) {
+ if (cInterface->endpoint_addrs[i] == ep) {
+ *pipep = i + 1;
+
+ if (ifcp)
+ *ifcp = iface;
+
+ if (interface_out)
+ *interface_out = cInterface;
+
+ usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface);
+ return LIBUSB_SUCCESS;
+ }
+ }
+ }
+ }
+
+ /* No pipe found with the correct endpoint address */
+ usbi_warn (HANDLE_CTX(dev_handle), "no pipeRef found with endpoint address 0x%02x.", ep);
+
+ return LIBUSB_ERROR_NOT_FOUND;
+}
+
+static IOReturn usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 location) {
+ CFMutableDictionaryRef matchingDict = IOServiceMatching(darwin_device_class);
+
+ if (!matchingDict)
+ return kIOReturnError;
+
+ if (location) {
+ CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+ &kCFTypeDictionaryKeyCallBacks,
+ &kCFTypeDictionaryValueCallBacks);
+
+ /* there are no unsigned CFNumber types so treat the value as signed. the OS seems to do this
+ internally (CFNumberType of locationID is kCFNumberSInt32Type) */
+ CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location);
+
+ if (propertyMatchDict && locationCF) {
+ CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF);
+ CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict);
+ }
+ /* else we can still proceed as long as the caller accounts for the possibility of other devices in the iterator */
+
+ /* release our references as per the Create Rule */
+ if (propertyMatchDict)
+ CFRelease (propertyMatchDict);
+ if (locationCF)
+ CFRelease (locationCF);
+ }
+
+ return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator);
+}
+
+/* Returns 1 on success, 0 on failure. */
+static bool get_ioregistry_value_number (io_service_t service, CFStringRef property, CFNumberType type, void *p) {
+ CFTypeRef cfNumber = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0);
+ Boolean success = 0;
+
+ if (cfNumber) {
+ if (CFGetTypeID(cfNumber) == CFNumberGetTypeID()) {
+ success = CFNumberGetValue(cfNumber, type, p);
+ }
+
+ CFRelease (cfNumber);
+ }
+
+ return (success != 0);
+}
+
+/* Returns 1 on success, 0 on failure. */
+static bool get_ioregistry_value_data (io_service_t service, CFStringRef property, ssize_t size, void *p) {
+ CFTypeRef cfData = IORegistryEntryCreateCFProperty (service, property, kCFAllocatorDefault, 0);
+ bool success = false;
+
+ if (cfData) {
+ if (CFGetTypeID (cfData) == CFDataGetTypeID ()) {
+ CFIndex length = CFDataGetLength (cfData);
+ if (length < size) {
+ size = length;
+ }
+
+ CFDataGetBytes (cfData, CFRangeMake(0, size), p);
+ success = true;
+ }
+
+ CFRelease (cfData);
+ }
+
+ return success;
+}
+
+static usb_device_t **darwin_device_from_service (struct libusb_context *ctx, io_service_t service)
+{
+ io_cf_plugin_ref_t *plugInInterface = NULL;
+ usb_device_t **device;
+ IOReturn kresult;
+ SInt32 score;
+ const int max_retries = 5;
+
+ /* The IOCreatePlugInInterfaceForService function might consistently return
+ an "out of resources" error with certain USB devices the first time we run
+ it. The reason is still unclear, but retrying fixes the problem */
+ for (int count = 0; count < max_retries; count++) {
+ kresult = IOCreatePlugInInterfaceForService(service, kIOUSBDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID, &plugInInterface,
+ &score);
+ if (kIOReturnSuccess == kresult && plugInInterface) {
+ break;
+ }
+
+ usbi_dbg (ctx, "set up plugin for service retry: %s", darwin_error_str (kresult));
+
+ /* sleep for a little while before trying again */
+ nanosleep(&(struct timespec){.tv_sec = 0, .tv_nsec = 1000}, NULL);
+ }
+
+ if (kIOReturnSuccess != kresult || !plugInInterface) {
+ usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult));
+ return NULL;
+ }
+
+ (void)(*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(DeviceInterfaceID),
+ (LPVOID)&device);
+ /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */
+ (*plugInInterface)->Release (plugInInterface);
+
+ return device;
+}
+
+static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) {
+ UNUSED(ptr);
+ struct darwin_cached_device *cached_device;
+ UInt64 old_session_id;
+ struct libusb_context *ctx;
+ io_service_t service;
+ int ret;
+
+ usbi_mutex_lock(&active_contexts_lock);
+
+ while ((service = IOIteratorNext(add_devices))) {
+ ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id);
+ if (ret < 0 || !cached_device->can_enumerate) {
+ continue;
+ }
+
+ /* add this device to each active context's device list */
+ for_each_context(ctx) {
+ process_new_device (ctx, cached_device, old_session_id);
+ }
+
+ if (cached_device->in_reenumerate) {
+ usbi_dbg (NULL, "cached device in reset state. reset complete...");
+ cached_device->in_reenumerate = false;
+ }
+
+ IOObjectRelease(service);
+ }
+
+ usbi_mutex_unlock(&active_contexts_lock);
+}
+
+static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) {
+ UNUSED(ptr);
+ struct libusb_device *dev = NULL;
+ struct libusb_context *ctx;
+ struct darwin_cached_device *old_device;
+
+ io_service_t device;
+ UInt64 session, locationID;
+ int ret;
+
+ usbi_mutex_lock(&active_contexts_lock);
+
+ while ((device = IOIteratorNext (rem_devices)) != 0) {
+ bool is_reenumerating = false;
+
+ /* get the location from the i/o registry */
+ ret = get_ioregistry_value_number (device, CFSTR("sessionID"), kCFNumberSInt64Type, &session);
+ (void) get_ioregistry_value_number (device, CFSTR("locationID"), kCFNumberSInt32Type, &locationID);
+ IOObjectRelease (device);
+ if (!ret)
+ continue;
+
+ /* we need to match darwin_ref_cached_device call made in darwin_get_cached_device function
+ otherwise no cached device will ever get freed */
+ usbi_mutex_lock(&darwin_cached_devices_lock);
+ list_for_each_entry(old_device, &darwin_cached_devices, list, struct darwin_cached_device) {
+ if (old_device->session == session) {
+ if (old_device->in_reenumerate) {
+ /* device is re-enumerating. do not dereference the device at this time. libusb_reset_device()
+ * will deref if needed. */
+ usbi_dbg (NULL, "detected device detached due to re-enumeration. sessionID: 0x%" PRIx64 ", locationID: 0x%" PRIx64,
+ session, locationID);
+
+ /* the device object is no longer usable so go ahead and release it */
+ if (old_device->device) {
+ (*(old_device->device))->Release(old_device->device);
+ old_device->device = NULL;
+ }
+
+ is_reenumerating = true;
+ } else {
+ darwin_deref_cached_device (old_device);
+ }
+
+ break;
+ }
+ }
+
+ usbi_mutex_unlock(&darwin_cached_devices_lock);
+ if (is_reenumerating) {
+ continue;
+ }
+
+ for_each_context(ctx) {
+ usbi_dbg (ctx, "notifying context %p of device disconnect", ctx);
+
+ dev = usbi_get_device_by_session_id(ctx, (unsigned long) session);
+ if (dev) {
+ /* signal the core that this device has been disconnected. the core will tear down this device
+ when the reference count reaches 0 */
+ usbi_disconnect_device(dev);
+ libusb_unref_device(dev);
+ }
+ }
+ }
+
+ usbi_mutex_unlock(&active_contexts_lock);
+}
+
+static void darwin_hotplug_poll (void)
+{
+ /* not sure if 1 ms will be too long/short but it should work ok */
+ mach_timespec_t timeout = {.tv_sec = 0, .tv_nsec = 1000000ul};
+
+ /* since a kernel thread may notify the IOIterators used for
+ * hotplug notification we can't just clear the iterators.
+ * instead just wait until all IOService providers are quiet */
+ (void) IOKitWaitQuiet (darwin_default_master_port, &timeout);
+}
+
+static void darwin_clear_iterator (io_iterator_t iter) {
+ io_service_t device;
+
+ while ((device = IOIteratorNext (iter)) != 0)
+ IOObjectRelease (device);
+}
+
+static void darwin_fail_startup(void) {
+ pthread_mutex_lock (&libusb_darwin_at_mutex);
+ libusb_darwin_acfl = LIBUSB_DARWIN_STARTUP_FAILURE;
+ pthread_cond_signal (&libusb_darwin_at_cond);
+ pthread_mutex_unlock (&libusb_darwin_at_mutex);
+ pthread_exit (NULL);
+}
+
+static void *darwin_event_thread_main (void *arg0) {
+ IOReturn kresult;
+ struct libusb_context *ctx = (struct libusb_context *)arg0;
+ CFRunLoopRef runloop;
+ CFRunLoopSourceRef libusb_shutdown_cfsource;
+ CFRunLoopSourceContext libusb_shutdown_cfsourcectx;
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060
+ /* Set this thread's name, so it can be seen in the debugger
+ and crash reports. */
+ pthread_setname_np ("org.libusb.device-hotplug");
+#endif
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200
+ /* Tell the Objective-C garbage collector about this thread.
+ This is required because, unlike NSThreads, pthreads are
+ not automatically registered. Although we don't use
+ Objective-C, we use CoreFoundation, which does.
+ Garbage collection support was entirely removed in 10.12,
+ so don't bother there. */
+ objc_registerThreadWithCollector();
+#endif
+
+ /* hotplug (device arrival/removal) sources */
+ CFRunLoopSourceRef libusb_notification_cfsource;
+ io_notification_port_t libusb_notification_port;
+ io_iterator_t libusb_rem_device_iterator;
+ io_iterator_t libusb_add_device_iterator;
+
+ usbi_dbg (ctx, "creating hotplug event source");
+
+ runloop = CFRunLoopGetCurrent ();
+ CFRetain (runloop);
+
+ /* add the shutdown cfsource to the run loop */
+ memset(&libusb_shutdown_cfsourcectx, 0, sizeof(libusb_shutdown_cfsourcectx));
+ libusb_shutdown_cfsourcectx.info = runloop;
+ libusb_shutdown_cfsourcectx.perform = (void (*)(void *))CFRunLoopStop;
+ libusb_shutdown_cfsource = CFRunLoopSourceCreate(NULL, 0, &libusb_shutdown_cfsourcectx);
+ CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode);
+
+ /* add the notification port to the run loop */
+ libusb_notification_port = IONotificationPortCreate (darwin_default_master_port);
+ libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port);
+ CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode);
+
+ /* create notifications for removed devices */
+ kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification,
+ IOServiceMatching(darwin_device_class),
+ darwin_devices_detached,
+ ctx, &libusb_rem_device_iterator);
+
+ if (kresult != kIOReturnSuccess) {
+ usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
+ CFRelease (libusb_shutdown_cfsource);
+ CFRelease (runloop);
+ darwin_fail_startup ();
+ }
+
+ /* create notifications for attached devices */
+ kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification,
+ IOServiceMatching(darwin_device_class),
+ darwin_devices_attached,
+ ctx, &libusb_add_device_iterator);
+
+ if (kresult != kIOReturnSuccess) {
+ usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult));
+ CFRelease (libusb_shutdown_cfsource);
+ CFRelease (runloop);
+ darwin_fail_startup ();
+ }
+
+ /* arm notifiers */
+ darwin_clear_iterator (libusb_rem_device_iterator);
+ darwin_clear_iterator (libusb_add_device_iterator);
+
+ usbi_dbg (ctx, "darwin event thread ready to receive events");
+
+ /* signal the main thread that the hotplug runloop has been created. */
+ pthread_mutex_lock (&libusb_darwin_at_mutex);
+ libusb_darwin_acfl = runloop;
+ libusb_darwin_acfls = libusb_shutdown_cfsource;
+ pthread_cond_signal (&libusb_darwin_at_cond);
+ pthread_mutex_unlock (&libusb_darwin_at_mutex);
+
+ /* run the runloop */
+ CFRunLoopRun();
+
+ usbi_dbg (ctx, "darwin event thread exiting");
+
+ /* signal the main thread that the hotplug runloop has finished. */
+ pthread_mutex_lock (&libusb_darwin_at_mutex);
+ libusb_darwin_acfls = NULL;
+ libusb_darwin_acfl = NULL;
+ pthread_cond_signal (&libusb_darwin_at_cond);
+ pthread_mutex_unlock (&libusb_darwin_at_mutex);
+
+ /* remove the notification cfsource */
+ CFRunLoopRemoveSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode);
+
+ /* remove the shutdown cfsource */
+ CFRunLoopRemoveSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode);
+
+ /* delete notification port */
+ IONotificationPortDestroy (libusb_notification_port);
+
+ /* delete iterators */
+ IOObjectRelease (libusb_rem_device_iterator);
+ IOObjectRelease (libusb_add_device_iterator);
+
+ CFRelease (libusb_shutdown_cfsource);
+ CFRelease (runloop);
+
+ pthread_exit (NULL);
+}
+
+/* cleanup function to destroy cached devices */
+static void darwin_cleanup_devices(void) {
+ struct darwin_cached_device *dev, *next;
+
+ list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) {
+ darwin_deref_cached_device(dev);
+ }
+}
+
+static int darwin_init(struct libusb_context *ctx) {
+ bool first_init;
+ int rc;
+
+ first_init = (1 == ++init_count);
+
+ do {
+ if (first_init) {
+ if (NULL == darwin_cached_devices.next) {
+ list_init (&darwin_cached_devices);
+ }
+ assert(list_empty(&darwin_cached_devices));
+#if !defined(HAVE_CLOCK_GETTIME)
+ /* create the clocks that will be used if clock_gettime() is not available */
+ host_name_port_t host_self;
+
+ host_self = mach_host_self();
+ host_get_clock_service(host_self, CALENDAR_CLOCK, &clock_realtime);
+ host_get_clock_service(host_self, SYSTEM_CLOCK, &clock_monotonic);
+ mach_port_deallocate(mach_task_self(), host_self);
+#endif
+ }
+
+ rc = darwin_scan_devices (ctx);
+ if (LIBUSB_SUCCESS != rc)
+ break;
+
+ if (first_init) {
+ rc = pthread_create (&libusb_darwin_at, NULL, darwin_event_thread_main, ctx);
+ if (0 != rc) {
+ usbi_err (ctx, "could not create event thread, error %d", rc);
+ rc = LIBUSB_ERROR_OTHER;
+ break;
+ }
+
+ pthread_mutex_lock (&libusb_darwin_at_mutex);
+ while (!libusb_darwin_acfl)
+ pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
+ if (libusb_darwin_acfl == LIBUSB_DARWIN_STARTUP_FAILURE) {
+ libusb_darwin_acfl = NULL;
+ rc = LIBUSB_ERROR_OTHER;
+ }
+ pthread_mutex_unlock (&libusb_darwin_at_mutex);
+
+ if (0 != rc)
+ pthread_join (libusb_darwin_at, NULL);
+ }
+ } while (0);
+
+ if (LIBUSB_SUCCESS != rc) {
+ if (first_init) {
+ darwin_cleanup_devices ();
+#if !defined(HAVE_CLOCK_GETTIME)
+ mach_port_deallocate(mach_task_self(), clock_realtime);
+ mach_port_deallocate(mach_task_self(), clock_monotonic);
+#endif
+ }
+ --init_count;
+ }
+
+ return rc;
+}
+
+static void darwin_exit (struct libusb_context *ctx) {
+ UNUSED(ctx);
+
+ if (0 == --init_count) {
+ /* stop the event runloop and wait for the thread to terminate. */
+ pthread_mutex_lock (&libusb_darwin_at_mutex);
+ CFRunLoopSourceSignal (libusb_darwin_acfls);
+ CFRunLoopWakeUp (libusb_darwin_acfl);
+ while (libusb_darwin_acfl)
+ pthread_cond_wait (&libusb_darwin_at_cond, &libusb_darwin_at_mutex);
+ pthread_mutex_unlock (&libusb_darwin_at_mutex);
+ pthread_join (libusb_darwin_at, NULL);
+
+ darwin_cleanup_devices ();
+
+#if !defined(HAVE_CLOCK_GETTIME)
+ mach_port_deallocate(mach_task_self(), clock_realtime);
+ mach_port_deallocate(mach_task_self(), clock_monotonic);
+#endif
+ }
+}
+
+static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) {
+ struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev);
+ UInt8 i, numConfig;
+ IOUSBConfigurationDescriptorPtr desc;
+ IOReturn kresult;
+
+ /* is there a simpler way to determine the index? */
+ kresult = (*(priv->device))->GetNumberOfConfigurations (priv->device, &numConfig);
+ if (kresult != kIOReturnSuccess)
+ return darwin_to_libusb (kresult);
+
+ for (i = 0 ; i < numConfig ; i++) {
+ (*(priv->device))->GetConfigurationDescriptorPtr (priv->device, i, &desc);
+
+ if (desc->bConfigurationValue == config_value)
+ return i;
+ }
+
+ /* configuration not found */
+ return LIBUSB_ERROR_NOT_FOUND;
+}
+
+static int darwin_get_active_config_descriptor(struct libusb_device *dev, void *buffer, size_t len) {
+ struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev);
+ int config_index;
+
+ if (0 == priv->active_config)
+ return LIBUSB_ERROR_NOT_FOUND;
+
+ config_index = get_configuration_index (dev, priv->active_config);
+ if (config_index < 0)
+ return config_index;
+
+ assert(config_index >= 0 && config_index <= UINT8_MAX);
+ return darwin_get_config_descriptor (dev, (UInt8)config_index, buffer, len);
+}
+
+static int darwin_get_config_descriptor(struct libusb_device *dev, uint8_t config_index, void *buffer, size_t len) {
+ struct darwin_cached_device *priv = DARWIN_CACHED_DEVICE(dev);
+ IOUSBConfigurationDescriptorPtr desc;
+ IOReturn kresult;
+ int ret;
+
+ if (!priv || !priv->device)
+ return LIBUSB_ERROR_OTHER;
+
+ kresult = (*priv->device)->GetConfigurationDescriptorPtr (priv->device, config_index, &desc);
+ if (kresult == kIOReturnSuccess) {
+ /* copy descriptor */
+ if (libusb_le16_to_cpu(desc->wTotalLength) < len)
+ len = libusb_le16_to_cpu(desc->wTotalLength);
+
+ memmove (buffer, desc, len);
+ }
+
+ ret = darwin_to_libusb (kresult);
+ if (ret != LIBUSB_SUCCESS)
+ return ret;
+
+ return (int) len;
+}
+
+/* check whether the os has configured the device */
+static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, struct darwin_cached_device *dev) {
+ usb_device_t **darwin_device = dev->device;
+
+ IOUSBConfigurationDescriptorPtr configDesc;
+ IOUSBFindInterfaceRequest request;
+ IOReturn kresult;
+ io_iterator_t interface_iterator;
+ io_service_t firstInterface;
+
+ if (dev->dev_descriptor.bNumConfigurations < 1) {
+ usbi_err (ctx, "device has no configurations");
+ return LIBUSB_ERROR_OTHER; /* no configurations at this speed so we can't use it */
+ }
+
+ /* checking the configuration of a root hub simulation takes ~1 s in 10.11. the device is
+ not usable anyway */
+ if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) &&
+ 0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) {
+ usbi_dbg (ctx, "ignoring configuration on root hub simulation");
+ dev->active_config = 0;
+ return LIBUSB_SUCCESS;
+ }
+
+ /* find the first configuration */
+ kresult = (*darwin_device)->GetConfigurationDescriptorPtr (darwin_device, 0, &configDesc);
+ dev->first_config = (kIOReturnSuccess == kresult) ? configDesc->bConfigurationValue : 1;
+
+ /* check if the device is already configured. there is probably a better way than iterating over the
+ to accomplish this (the trick is we need to avoid a call to GetConfigurations since buggy devices
+ might lock up on the device request) */
+
+ /* Setup the Interface Request */
+ request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
+ request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
+ request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
+ request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+
+ kresult = (*(darwin_device))->CreateInterfaceIterator(darwin_device, &request, &interface_iterator);
+ if (kresult != kIOReturnSuccess)
+ return darwin_to_libusb (kresult);
+
+ /* iterate once */
+ firstInterface = IOIteratorNext(interface_iterator);
+
+ /* done with the interface iterator */
+ IOObjectRelease(interface_iterator);
+
+ if (firstInterface) {
+ IOObjectRelease (firstInterface);
+
+ /* device is configured */
+ if (dev->dev_descriptor.bNumConfigurations == 1)
+ /* to avoid problems with some devices get the configurations value from the configuration descriptor */
+ dev->active_config = dev->first_config;
+ else
+ /* devices with more than one configuration should work with GetConfiguration */
+ (*darwin_device)->GetConfiguration (darwin_device, &dev->active_config);
+ } else
+ /* not configured */
+ dev->active_config = 0;
+
+ usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config);
+
+ return LIBUSB_SUCCESS;
+}
+
+static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UInt8 desc_index, void *buffer, size_t buffer_size) {
+ IOUSBDevRequestTO req;
+
+ assert(buffer_size <= UINT16_MAX);
+
+ memset (buffer, 0, buffer_size);
+
+ /* Set up request for descriptor/ */
+ req.bmRequestType = USBmakebmRequestType(kUSBIn, kUSBStandard, kUSBDevice);
+ req.bRequest = kUSBRqGetDescriptor;
+ req.wValue = (UInt16)(desc << 8);
+ req.wIndex = desc_index;
+ req.wLength = (UInt16)buffer_size;
+ req.pData = buffer;
+ req.noDataTimeout = 20;
+ req.completionTimeout = 100;
+
+ return (*device)->DeviceRequestTO (device, &req);
+}
+
+static enum libusb_error darwin_cache_device_descriptor (struct libusb_context *ctx, struct darwin_cached_device *dev) {
+ usb_device_t **device = dev->device;
+ int retries = 1;
+ long delay = 30000; // microseconds
+ int unsuspended = 0, try_unsuspend = 1, try_reconfigure = 1;
+ int is_open = 0;
+ IOReturn ret = 0, ret2;
+ UInt8 bDeviceClass;
+ UInt16 idProduct, idVendor;
+
+ dev->can_enumerate = 0;
+
+ (*device)->GetDeviceClass (device, &bDeviceClass);
+ (*device)->GetDeviceProduct (device, &idProduct);
+ (*device)->GetDeviceVendor (device, &idVendor);
+
+ /* According to Apple's documentation the device must be open for DeviceRequest but we may not be able to open some
+ * devices and Apple's USB Prober doesn't bother to open the device before issuing a descriptor request. Still,
+ * to follow the spec as closely as possible, try opening the device */
+ is_open = ((*device)->USBDeviceOpenSeize(device) == kIOReturnSuccess);
+
+ do {
+ /**** retrieve device descriptor ****/
+ ret = darwin_request_descriptor (device, kUSBDeviceDesc, 0, &dev->dev_descriptor, sizeof(dev->dev_descriptor));
+
+ if (kIOReturnOverrun == ret && kUSBDeviceDesc == dev->dev_descriptor.bDescriptorType)
+ /* received an overrun error but we still received a device descriptor */
+ ret = kIOReturnSuccess;
+
+ if (kIOUSBVendorIDAppleComputer == idVendor) {
+ /* NTH: don't bother retrying or unsuspending Apple devices */
+ break;
+ }
+
+ if (kIOReturnSuccess == ret && (0 == dev->dev_descriptor.bNumConfigurations ||
+ 0 == dev->dev_descriptor.bcdUSB)) {
+ /* work around for incorrectly configured devices */
+ if (try_reconfigure && is_open) {
+ usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again...");
+
+ /* set the first configuration */
+ (*device)->SetConfiguration(device, 1);
+
+ /* don't try to reconfigure again */
+ try_reconfigure = 0;
+ }
+
+ ret = kIOUSBPipeStalled;
+ }
+
+ if (kIOReturnSuccess != ret && is_open && try_unsuspend) {
+ /* device may be suspended. unsuspend it and try again */
+#if DeviceVersion >= 320
+ UInt32 info = 0;
+
+ /* IOUSBFamily 320+ provides a way to detect device suspension but earlier versions do not */
+ (void)(*device)->GetUSBDeviceInformation (device, &info);
+
+ /* note that the device was suspended */
+ if (info & (1U << kUSBInformationDeviceIsSuspendedBit) || 0 == info)
+ try_unsuspend = 1;
+#endif
+
+ if (try_unsuspend) {
+ /* try to unsuspend the device */
+ ret2 = (*device)->USBDeviceSuspend (device, 0);
+ if (kIOReturnSuccess != ret2) {
+ /* prevent log spew from poorly behaving devices. this indicates the
+ os actually had trouble communicating with the device */
+ usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2));
+ } else
+ unsuspended = 1;
+
+ try_unsuspend = 0;
+ }
+ }
+
+ if (kIOReturnSuccess != ret) {
+ usbi_dbg(ctx, "kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000);
+ /* sleep for a little while before trying again */
+ nanosleep(&(struct timespec){delay / 1000000, (delay * 1000) % 1000000000}, NULL);
+ }
+ } while (kIOReturnSuccess != ret && retries--);
+
+ if (unsuspended)
+ /* resuspend the device */
+ (void)(*device)->USBDeviceSuspend (device, 1);
+
+ if (is_open)
+ (void) (*device)->USBDeviceClose (device);
+
+ if (ret != kIOReturnSuccess) {
+ /* a debug message was already printed out for this error */
+ if (LIBUSB_CLASS_HUB == bDeviceClass)
+ usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
+ idVendor, idProduct, darwin_error_str (ret), ret);
+ else
+ usbi_warn (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device",
+ idVendor, idProduct, darwin_error_str (ret), ret);
+ return darwin_to_libusb (ret);
+ }
+
+ /* catch buggy hubs (which appear to be virtual). Apple's own USB prober has problems with these devices. */
+ if (libusb_le16_to_cpu (dev->dev_descriptor.idProduct) != idProduct) {
+ /* not a valid device */
+ usbi_warn (NULL, "idProduct from iokit (%04x) does not match idProduct in descriptor (%04x). skipping device",
+ idProduct, libusb_le16_to_cpu (dev->dev_descriptor.idProduct));
+ return LIBUSB_ERROR_NO_DEVICE;
+ }
+
+ usbi_dbg (ctx, "cached device descriptor:");
+ usbi_dbg (ctx, " bDescriptorType: 0x%02x", dev->dev_descriptor.bDescriptorType);
+ usbi_dbg (ctx, " bcdUSB: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB));
+ usbi_dbg (ctx, " bDeviceClass: 0x%02x", dev->dev_descriptor.bDeviceClass);
+ usbi_dbg (ctx, " bDeviceSubClass: 0x%02x", dev->dev_descriptor.bDeviceSubClass);
+ usbi_dbg (ctx, " bDeviceProtocol: 0x%02x", dev->dev_descriptor.bDeviceProtocol);
+ usbi_dbg (ctx, " bMaxPacketSize0: 0x%02x", dev->dev_descriptor.bMaxPacketSize0);
+ usbi_dbg (ctx, " idVendor: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor));
+ usbi_dbg (ctx, " idProduct: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct));
+ usbi_dbg (ctx, " bcdDevice: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice));
+ usbi_dbg (ctx, " iManufacturer: 0x%02x", dev->dev_descriptor.iManufacturer);
+ usbi_dbg (ctx, " iProduct: 0x%02x", dev->dev_descriptor.iProduct);
+ usbi_dbg (ctx, " iSerialNumber: 0x%02x", dev->dev_descriptor.iSerialNumber);
+ usbi_dbg (ctx, " bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations);
+
+ dev->can_enumerate = 1;
+
+ return LIBUSB_SUCCESS;
+}
+
+/* Returns 1 on success, 0 on failure. */
+static bool get_device_port (io_service_t service, UInt8 *port) {
+ IOReturn kresult;
+ io_service_t parent;
+ bool ret = false;
+
+ if (get_ioregistry_value_number (service, CFSTR("PortNum"), kCFNumberSInt8Type, port)) {
+ return true;
+ }
+
+ kresult = IORegistryEntryGetParentEntry (service, kIOServicePlane, &parent);
+ if (kIOReturnSuccess == kresult) {
+ ret = get_ioregistry_value_data (parent, CFSTR("port"), 1, port);
+ IOObjectRelease (parent);
+ }
+
+ return ret;
+}
+
+/* Returns 1 on success, 0 on failure. */
+static bool get_device_parent_sessionID(io_service_t service, UInt64 *parent_sessionID) {
+ IOReturn kresult;
+ io_service_t parent;
+
+ /* Walk up the tree in the IOService plane until we find a parent that has a sessionID */
+ parent = service;
+ while((kresult = IORegistryEntryGetParentEntry (parent, kIOUSBPlane, &parent)) == kIOReturnSuccess) {
+ if (get_ioregistry_value_number (parent, CFSTR("sessionID"), kCFNumberSInt64Type, parent_sessionID)) {
+ /* Success */
+ return true;
+ }
+ }
+
+ /* We ran out of parents */
+ return false;
+}
+
+static enum libusb_error darwin_get_cached_device(struct libusb_context *ctx, io_service_t service, struct darwin_cached_device **cached_out,
+ UInt64 *old_session_id) {
+ struct darwin_cached_device *n