diff options
Diffstat (limited to 'libusb/os/darwin_usb.c')
-rw-r--r-- | libusb/os/darwin_usb.c | 668 |
1 files changed, 517 insertions, 151 deletions
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c index e415589..388dbca 100644 --- a/libusb/os/darwin_usb.c +++ b/libusb/os/darwin_usb.c @@ -1,8 +1,8 @@ /* -*- Mode: C; indent-tabs-mode:nil -*- */ /* * darwin backend for libusb 1.0 - * Copyright © 2008-2020 Nathan Hjelm <hjelmn@cs.unm.edu> - * Copyright © 2019-2020 Google LLC. All rights reserved. + * 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 @@ -41,6 +41,10 @@ * 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 (10 * USEC_PER_SEC) + #include <AvailabilityMacros.h> #if MAC_OS_X_VERSION_MIN_REQUIRED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 101200 #include <objc/objc-auto.h> @@ -48,9 +52,11 @@ #include "darwin_usb.h" -static pthread_mutex_t libusb_darwin_init_mutex = PTHREAD_MUTEX_INITIALIZER; 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; @@ -77,14 +83,17 @@ 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 int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface); 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(io_service_t service, struct darwin_cached_device **cached_out, +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) @@ -102,6 +111,9 @@ static const char *darwin_error_str (IOReturn result) { 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"; @@ -141,16 +153,20 @@ static enum libusb_error darwin_to_libusb (IOReturn result) { 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: - case kIOUSBUnknownPipeErr: default: return LIBUSB_ERROR_OTHER; } @@ -167,6 +183,7 @@ static void darwin_deref_cached_device(struct darwin_cached_device *cached_dev) (*(cached_dev->device))->Release(cached_dev->device); cached_dev->device = NULL; } + IOObjectRelease (cached_dev->service); free (cached_dev); } } @@ -183,7 +200,9 @@ static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, ui uint8_t i, iface; - usbi_dbg ("converting ep address 0x%02x to pipeRef and interface", ep); + 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]; @@ -199,7 +218,7 @@ static int ep_to_pipeRef(struct libusb_device_handle *dev_handle, uint8_t ep, ui if (interface_out) *interface_out = cInterface; - usbi_dbg ("pipe %d on interface %d matches", *pipep, iface); + usbi_dbg (ctx, "pipe %d on interface %d matches", *pipep, iface); return LIBUSB_SUCCESS; } } @@ -240,7 +259,7 @@ static IOReturn usb_setup_device_iterator (io_iterator_t *deviceIterator, UInt32 CFRelease (locationCF); } - return IOServiceGetMatchingServices(kIOMasterPortDefault, matchingDict, deviceIterator); + return IOServiceGetMatchingServices(darwin_default_master_port, matchingDict, deviceIterator); } /* Returns 1 on success, 0 on failure. */ @@ -281,7 +300,7 @@ static bool get_ioregistry_value_data (io_service_t service, CFStringRef propert return success; } -static usb_device_t **darwin_device_from_service (io_service_t service) +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; @@ -300,14 +319,14 @@ static usb_device_t **darwin_device_from_service (io_service_t service) break; } - usbi_dbg ("set up plugin for service retry: %s", darwin_error_str (kresult)); + 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 ("could not set up plugin for service: %s", darwin_error_str (kresult)); + usbi_dbg (ctx, "could not set up plugin for service: %s", darwin_error_str (kresult)); return NULL; } @@ -330,7 +349,7 @@ static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { usbi_mutex_lock(&active_contexts_lock); while ((service = IOIteratorNext(add_devices))) { - ret = darwin_get_cached_device (service, &cached_device, &old_session_id); + ret = darwin_get_cached_device (NULL, service, &cached_device, &old_session_id); if (ret < 0 || !cached_device->can_enumerate) { continue; } @@ -341,7 +360,7 @@ static void darwin_devices_attached (void *ptr, io_iterator_t add_devices) { } if (cached_device->in_reenumerate) { - usbi_dbg ("cached device in reset state. reset complete..."); + usbi_dbg (NULL, "cached device in reset state. reset complete..."); cached_device->in_reenumerate = false; } @@ -358,7 +377,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { struct darwin_cached_device *old_device; io_service_t device; - UInt64 session; + UInt64 session, locationID; int ret; usbi_mutex_lock(&active_contexts_lock); @@ -368,6 +387,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { /* 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; @@ -380,7 +400,8 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { 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 ("detected device detached due to re-enumeration"); + 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) { @@ -403,7 +424,7 @@ static void darwin_devices_detached (void *ptr, io_iterator_t rem_devices) { } for_each_context(ctx) { - usbi_dbg ("notifying context %p of device disconnect", ctx); + usbi_dbg (ctx, "notifying context %p of device disconnect", ctx); dev = usbi_get_device_by_session_id(ctx, (unsigned long) session); if (dev) { @@ -426,7 +447,7 @@ static void darwin_hotplug_poll (void) /* 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 (kIOMasterPortDefault, &timeout); + (void) IOKitWaitQuiet (darwin_default_master_port, &timeout); } static void darwin_clear_iterator (io_iterator_t iter) { @@ -473,7 +494,8 @@ static void *darwin_event_thread_main (void *arg0) { io_iterator_t libusb_rem_device_iterator; io_iterator_t libusb_add_device_iterator; - usbi_dbg ("creating hotplug event source"); + /* ctx must only be used for logging during thread startup */ + usbi_dbg (ctx, "creating hotplug event source"); runloop = CFRunLoopGetCurrent (); CFRetain (runloop); @@ -486,7 +508,7 @@ static void *darwin_event_thread_main (void *arg0) { CFRunLoopAddSource(runloop, libusb_shutdown_cfsource, kCFRunLoopDefaultMode); /* add the notification port to the run loop */ - libusb_notification_port = IONotificationPortCreate (kIOMasterPortDefault); + libusb_notification_port = IONotificationPortCreate (darwin_default_master_port); libusb_notification_cfsource = IONotificationPortGetRunLoopSource (libusb_notification_port); CFRunLoopAddSource(runloop, libusb_notification_cfsource, kCFRunLoopDefaultMode); @@ -494,7 +516,7 @@ static void *darwin_event_thread_main (void *arg0) { kresult = IOServiceAddMatchingNotification (libusb_notification_port, kIOTerminatedNotification, IOServiceMatching(darwin_device_class), darwin_devices_detached, - ctx, &libusb_rem_device_iterator); + NULL, &libusb_rem_device_iterator); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); @@ -507,7 +529,7 @@ static void *darwin_event_thread_main (void *arg0) { kresult = IOServiceAddMatchingNotification(libusb_notification_port, kIOFirstMatchNotification, IOServiceMatching(darwin_device_class), darwin_devices_attached, - ctx, &libusb_add_device_iterator); + NULL, &libusb_add_device_iterator); if (kresult != kIOReturnSuccess) { usbi_err (ctx, "could not add hotplug event source: %s", darwin_error_str (kresult)); @@ -520,7 +542,7 @@ static void *darwin_event_thread_main (void *arg0) { darwin_clear_iterator (libusb_rem_device_iterator); darwin_clear_iterator (libusb_add_device_iterator); - usbi_dbg ("darwin event thread ready to receive events"); + 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); @@ -532,7 +554,7 @@ static void *darwin_event_thread_main (void *arg0) { /* run the runloop */ CFRunLoopRun(); - usbi_dbg ("darwin event thread exiting"); + usbi_dbg (NULL, "darwin event thread exiting"); /* signal the main thread that the hotplug runloop has finished. */ pthread_mutex_lock (&libusb_darwin_at_mutex); @@ -567,23 +589,20 @@ static void darwin_cleanup_devices(void) { list_for_each_entry_safe(dev, next, &darwin_cached_devices, list, struct darwin_cached_device) { darwin_deref_cached_device(dev); } - - darwin_cached_devices.prev = darwin_cached_devices.next = NULL; } static int darwin_init(struct libusb_context *ctx) { bool first_init; int rc; - pthread_mutex_lock (&libusb_darwin_init_mutex); - first_init = (1 == ++init_count); do { if (first_init) { - assert (NULL == darwin_cached_devices.next); - list_init (&darwin_cached_devices); - + 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; @@ -632,16 +651,12 @@ static int darwin_init(struct libusb_context *ctx) { --init_count; } - pthread_mutex_unlock (&libusb_darwin_init_mutex); - return rc; } static void darwin_exit (struct libusb_context *ctx) { UNUSED(ctx); - pthread_mutex_lock (&libusb_darwin_init_mutex); - if (0 == --init_count) { /* stop the event runloop and wait for the thread to terminate. */ pthread_mutex_lock (&libusb_darwin_at_mutex); @@ -659,8 +674,6 @@ static void darwin_exit (struct libusb_context *ctx) { mach_port_deallocate(mach_task_self(), clock_monotonic); #endif } - - pthread_mutex_unlock (&libusb_darwin_init_mutex); } static int get_configuration_index (struct libusb_device *dev, UInt8 config_value) { @@ -744,7 +757,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, not usable anyway */ if (0x05ac == libusb_le16_to_cpu (dev->dev_descriptor.idVendor) && 0x8005 == libusb_le16_to_cpu (dev->dev_descriptor.idProduct)) { - usbi_dbg ("ignoring configuration on root hub simulation"); + usbi_dbg (ctx, "ignoring configuration on root hub simulation"); dev->active_config = 0; return LIBUSB_SUCCESS; } @@ -787,7 +800,7 @@ static enum libusb_error darwin_check_configuration (struct libusb_context *ctx, /* not configured */ dev->active_config = 0; - usbi_dbg ("active config: %u, first config: %u", dev->active_config, dev->first_config); + usbi_dbg (ctx, "active config: %u, first config: %u", dev->active_config, dev->first_config); return LIBUSB_SUCCESS; } @@ -812,7 +825,7 @@ static IOReturn darwin_request_descriptor (usb_device_t **device, UInt8 desc, UI return (*device)->DeviceRequestTO (device, &req); } -static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_device *dev) { +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 @@ -850,7 +863,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de 0 == dev->dev_descriptor.bcdUSB)) { /* work around for incorrectly configured devices */ if (try_reconfigure && is_open) { - usbi_dbg("descriptor appears to be invalid. resetting configuration before trying again..."); + usbi_dbg(ctx, "descriptor appears to be invalid. resetting configuration before trying again..."); /* set the first configuration */ (*device)->SetConfiguration(device, 1); @@ -881,7 +894,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de if (kIOReturnSuccess != ret2) { /* prevent log spew from poorly behaving devices. this indicates the os actually had trouble communicating with the device */ - usbi_dbg("could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); + usbi_dbg(ctx, "could not retrieve device descriptor. failed to unsuspend: %s",darwin_error_str(ret2)); } else unsuspended = 1; @@ -890,7 +903,7 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de } if (kIOReturnSuccess != ret) { - usbi_dbg("kernel responded with code: 0x%08x. sleeping for %ld ms before trying again", ret, delay/1000); + 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); } @@ -906,10 +919,10 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de if (ret != kIOReturnSuccess) { /* a debug message was already printed out for this error */ if (LIBUSB_CLASS_HUB == bDeviceClass) - usbi_dbg ("could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + usbi_dbg (ctx, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", idVendor, idProduct, darwin_error_str (ret), ret); else - usbi_warn (NULL, "could not retrieve device descriptor %.4x:%.4x: %s (%x). skipping device", + 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); } @@ -922,20 +935,20 @@ static enum libusb_error darwin_cache_device_descriptor (struct darwin_cached_de return LIBUSB_ERROR_NO_DEVICE; } - usbi_dbg ("cached device descriptor:"); - usbi_dbg (" bDescriptorType: 0x%02x", dev->dev_descriptor.bDescriptorType); - usbi_dbg (" bcdUSB: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdUSB)); - usbi_dbg (" bDeviceClass: 0x%02x", dev->dev_descriptor.bDeviceClass); - usbi_dbg (" bDeviceSubClass: 0x%02x", dev->dev_descriptor.bDeviceSubClass); - usbi_dbg (" bDeviceProtocol: 0x%02x", dev->dev_descriptor.bDeviceProtocol); - usbi_dbg (" bMaxPacketSize0: 0x%02x", dev->dev_descriptor.bMaxPacketSize0); - usbi_dbg (" idVendor: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idVendor)); - usbi_dbg (" idProduct: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.idProduct)); - usbi_dbg (" bcdDevice: 0x%04x", libusb_le16_to_cpu (dev->dev_descriptor.bcdDevice)); - usbi_dbg (" iManufacturer: 0x%02x", dev->dev_descriptor.iManufacturer); - usbi_dbg (" iProduct: 0x%02x", dev->dev_descriptor.iProduct); - usbi_dbg (" iSerialNumber: 0x%02x", dev->dev_descriptor.iSerialNumber); - usbi_dbg (" bNumConfigurations: 0x%02x", dev->dev_descriptor.bNumConfigurations); + 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; @@ -979,7 +992,7 @@ static bool get_device_parent_sessionID(io_service_t service, UInt64 *parent_ses return false; } -static enum libusb_error darwin_get_cached_device(io_service_t service, struct darwin_cached_device **cached_out, +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 *new_device; UInt64 sessionID = 0, parent_sessionID = 0; @@ -996,28 +1009,28 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d (void) get_ioregistry_value_number (service, CFSTR("sessionID"), kCFNumberSInt64Type, &sessionID); (void) get_ioregistry_value_number (service, CFSTR("locationID"), kCFNumberSInt32Type, &locationID); if (!get_device_port (service, &port)) { - usbi_dbg("could not get connected port number"); + usbi_dbg(ctx, "could not get connected port number"); } - usbi_dbg("finding cached device for sessionID 0x%" PRIx64, sessionID); + usbi_dbg(ctx, "finding cached device for sessionID 0x%" PRIx64, sessionID); if (get_device_parent_sessionID(service, &parent_sessionID)) { - usbi_dbg("parent sessionID: 0x%" PRIx64, parent_sessionID); + usbi_dbg(ctx, "parent sessionID: 0x%" PRIx64, parent_sessionID); } usbi_mutex_lock(&darwin_cached_devices_lock); do { list_for_each_entry(new_device, &darwin_cached_devices, list, struct darwin_cached_device) { - usbi_dbg("matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", + usbi_dbg(ctx, "matching sessionID/locationID 0x%" PRIx64 "/0x%x against cached device with sessionID/locationID 0x%" PRIx64 "/0x%x", sessionID, locationID, new_device->session, new_device->location); if (new_device->location == locationID && new_device->in_reenumerate) { - usbi_dbg ("found cached device with matching location that is being re-enumerated"); + usbi_dbg (ctx, "found cached device with matching location that is being re-enumerated"); *old_session_id = new_device->session; break; } if (new_device->session == sessionID) { - usbi_dbg("using cached device for device"); + usbi_dbg(ctx, "using cached device for device"); *cached_out = new_device; break; } @@ -1026,9 +1039,9 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d if (*cached_out) break; - usbi_dbg("caching new device with sessionID 0x%" PRIx64, sessionID); + usbi_dbg(ctx, "caching new device with sessionID 0x%" PRIx64, sessionID); - device = darwin_device_from_service (service); + device = darwin_device_from_service (ctx, service); if (!device) { ret = LIBUSB_ERROR_NO_DEVICE; break; @@ -1052,6 +1065,9 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d (*device)->GetLocationID (device, &new_device->location); new_device->port = port; new_device->parent_session = parent_sessionID; + } else { + /* release the ref to old device's service */ + IOObjectRelease (new_device->service); } /* keep track of devices regardless of if we successfully enumerate them to @@ -1060,9 +1076,13 @@ static enum libusb_error darwin_get_cached_device(io_service_t service, struct d new_device->session = sessionID; new_device->device = device; + new_device->service = service; + + /* retain the service */ + IOObjectRetain (service); /* cache the device descriptor */ - ret = darwin_cache_device_descriptor(new_device); + ret = darwin_cache_device_descriptor(ctx, new_device); if (ret) break; @@ -1094,14 +1114,14 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct break; if (0 != old_session_id) { - usbi_dbg ("re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64, + usbi_dbg (ctx, "re-using existing device from context %p for with session 0x%" PRIx64 " new session 0x%" PRIx64, ctx, old_session_id, cached_device->session); /* save the libusb device before the session id is updated */ dev = usbi_get_device_by_session_id (ctx, (unsigned long) old_session_id); } if (!dev) { - usbi_dbg ("allocating new device in context %p for with session 0x%" PRIx64, + usbi_dbg (ctx, "allocating new device in context %p for with session 0x%" PRIx64, ctx, cached_device->session); dev = usbi_alloc_device(ctx, (unsigned long) cached_device->session); @@ -1114,6 +1134,8 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct priv->dev = cached_device; darwin_ref_cached_device (priv->dev); dev->port_number = cached_device->port; + /* the location ID encodes the path to the device. the top byte of the location ID contains the bus number + (numbered from 0). the remaining bytes can be used to construct the device tree for that bus. */ dev->bus_number = cached_device->location >> 24; assert(cached_device->address <= UINT8_MAX); dev->device_address = (uint8_t)cached_device->address; @@ -1127,10 +1149,13 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct usbi_localize_device_descriptor(&dev->device_descriptor); dev->session_data = cached_device->session; + if (NULL != dev->parent_dev) { + libusb_unref_device(dev->parent_dev); + dev->parent_dev = NULL; + } + if (cached_device->parent_session > 0) { dev->parent_dev = usbi_get_device_by_session_id (ctx, (unsigned long) cached_device->parent_session); - } else { - dev->parent_dev = NULL; } (*(priv->dev->device))->GetDeviceSpeed (priv->dev->device, &devSpeed); @@ -1139,7 +1164,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct case kUSBDeviceSpeedLow: dev->speed = LIBUSB_SPEED_LOW; break; case kUSBDeviceSpeedFull: dev->speed = LIBUSB_SPEED_FULL; break; case kUSBDeviceSpeedHigh: dev->speed = LIBUSB_SPEED_HIGH; break; -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1070 +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1080 case kUSBDeviceSpeedSuper: dev->speed = LIBUSB_SPEED_SUPER; break; #endif #if MAC_OS_X_VERSION_MAX_ALLOWED >= 101200 @@ -1153,7 +1178,7 @@ static enum libusb_error process_new_device (struct libusb_context *ctx, struct if (ret < 0) break; - usbi_dbg ("found device with address %d port = %d parent = %p at %p", dev->device_address, + usbi_dbg (ctx, "found device with address %d port = %d parent = %p at %p", dev->device_address, dev->port_number, (void *) dev->parent_dev, priv->dev->sys_path); } while (0); @@ -1180,7 +1205,7 @@ static enum libusb_error darwin_scan_devices(struct libusb_context *ctx) { return darwin_to_libusb (kresult); while ((service = IOIteratorNext (deviceIterator))) { - ret = darwin_get_cached_device (service, &cached_device, &old_session_id); + ret = darwin_get_cached_device (ctx, service, &cached_device, &old_session_id); if (ret < 0 || !cached_device->can_enumerate) { continue; } @@ -1232,14 +1257,14 @@ static int darwin_open (struct libusb_device_handle *dev_handle) { CFRetain (libusb_darwin_acfl); - /* add the cfSource to the aync run loop */ + /* add the cfSource to the async run loop */ CFRunLoopAddSource(libusb_darwin_acfl, priv->cfSource, kCFRunLoopCommonModes); } /* device opened successfully */ dpriv->open_count++; - usbi_dbg ("device open for access"); + usbi_dbg (HANDLE_CTX(dev_handle), "device open for access"); return 0; } @@ -1257,6 +1282,10 @@ static void darwin_close (struct libusb_device_handle *dev_handle) { } dpriv->open_count--; + if (NULL == dpriv->device) { + usbi_warn (HANDLE_CTX (dev_handle), "darwin_close device missing IOService"); + return; + } /* make sure all interfaces are released */ for (i = 0 ; i < USB_MAXINTERFACES ; i++) @@ -1362,28 +1391,39 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + UInt8 dont_care1, dont_care3; + UInt16 dont_care2; +#endif IOReturn kresult; UInt8 numep, direction, number; - UInt8 dont_care1, dont_care3; - UInt16 dont_care2; int rc; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + - usbi_dbg ("building table of endpoints."); + usbi_dbg (ctx, "building table of endpoints."); /* retrieve the total number of endpoints on this interface */ kresult = (*(cInterface->interface))->GetNumEndpoints(cInterface->interface, &numep); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); + usbi_err (ctx, "can't get number of endpoints for interface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } /* iterate through pipe references */ for (UInt8 i = 1 ; i <= numep ; i++) { +#if InterfaceVersion >= 550 + kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, i, &pipeProperties); + number = pipeProperties.bEndpointNumber; + direction = pipeProperties.bDirection; +#else kresult = (*(cInterface->interface))->GetPipeProperties(cInterface->interface, i, &direction, &number, &dont_care1, &dont_care2, &dont_care3); - +#endif if (kresult != kIOReturnSuccess) { /* probably a buggy device. try to get the endpoint address from the descriptors */ struct libusb_config_descriptor *config; @@ -1401,6 +1441,10 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, return rc; } + if (iface >= config->bNumInterfaces) { + usbi_err (HANDLE_CTX (dev_handle), "interface %d out of range for device", iface); + return LIBUSB_ERROR_NOT_FOUND; + } endpoint_desc = config->interface[iface].altsetting[alt_setting].endpoint + i - 1; cInterface->endpoint_addrs[i - 1] = endpoint_desc->bEndpointAddress; @@ -1408,7 +1452,7 @@ static enum libusb_error get_endpoints (struct libusb_device_handle *dev_handle, cInterface->endpoint_addrs[i - 1] = (UInt8)(((kUSBIn == direction) << kUSBRqDirnShift) | (number & LIBUSB_ENDPOINT_ADDRESS_MASK)); } - usbi_dbg ("interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, + usbi_dbg (ctx, "interface: %i pipe %i: dir: %i number: %i", iface, i, cInterface->endpoint_addrs[i - 1] >> kUSBRqDirnShift, cInterface->endpoint_addrs[i - 1] & LIBUSB_ENDPOINT_ADDRESS_MASK); } @@ -1429,30 +1473,32 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* current interface */ struct darwin_interface *cInterface = &priv->interfaces[iface]; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); if (kresult != kIOReturnSuccess) return darwin_to_libusb (kresult); /* make sure we have an interface */ if (!usbInterface && dpriv->first_config != 0) { - usbi_info (HANDLE_CTX (dev_handle), "no interface found; setting configuration: %d", dpriv->first_config); + usbi_info (ctx, "no interface found; setting configuration: %d", dpriv->first_config); /* set the configuration */ ret = darwin_set_configuration (dev_handle, (int) dpriv->first_config); if (ret != LIBUSB_SUCCESS) { - usbi_err (HANDLE_CTX (dev_handle), "could not set configuration"); + usbi_err (ctx, "could not set configuration"); return ret; } kresult = darwin_get_interface (dpriv->device, iface, &usbInterface); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); + usbi_err (ctx, "darwin_get_interface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } } if (!usbInterface) { - usbi_err (HANDLE_CTX (dev_handle), "interface not found"); + usbi_info (ctx, "interface not found"); return LIBUSB_ERROR_NOT_FOUND; } @@ -1464,12 +1510,12 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 (void)IOObjectRelease (usbInterface); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); + usbi_err (ctx, "IOCreatePlugInInterfaceForService: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } if (!plugInInterface) { - usbi_err (HANDLE_CTX (dev_handle), "plugin interface not found"); + usbi_err (ctx, "plugin interface not found"); return LIBUSB_ERROR_NOT_FOUND; } @@ -1481,14 +1527,14 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* Use release instead of IODestroyPlugInInterface to avoid stopping IOServices associated with this device */ (*plugInInterface)->Release (plugInInterface); if (kresult != kIOReturnSuccess || !cInterface->interface) { - usbi_err (HANDLE_CTX (dev_handle), "QueryInterface: %s", darwin_error_str(kresult)); + usbi_err (ctx, "QueryInterface: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } /* claim the interface */ kresult = (*(cInterface->interface))->USBInterfaceOpen(cInterface->interface); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "USBInterfaceOpen: %s", darwin_error_str(kresult)); + usbi_info (ctx, "USBInterfaceOpen: %s", darwin_error_str(kresult)); return darwin_to_libusb (kresult); } @@ -1497,7 +1543,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 if (ret) { /* this should not happen */ darwin_release_interface (dev_handle, iface); - usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + usbi_err (ctx, "could not build endpoint table"); return ret; } @@ -1506,7 +1552,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* create async event source */ kresult = (*(cInterface->interface))->CreateInterfaceAsyncEventSource (cInterface->interface, &cInterface->cfSource); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "could not create async event source"); + usbi_err (ctx, "could not create async event source"); /* can't continue without an async event source */ (void)darwin_release_interface (dev_handle, iface); @@ -1517,7 +1563,7 @@ static int darwin_claim_interface(struct libusb_device_handle *dev_handle, uint8 /* add the cfSource to the async thread's run loop */ CFRunLoopAddSource(libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); - usbi_dbg ("interface opened"); + usbi_dbg (ctx, "interface opened"); return LIBUSB_SUCCESS; } @@ -1540,6 +1586,7 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin if (cInterface->cfSource) { CFRunLoopRemoveSource (libusb_darwin_acfl, cInterface->cfSource, kCFRunLoopDefaultMode); CFRelease (cInterface->cfSource); + cInterface->cfSource = NULL; } kresult = (*(cInterface->interface))->USBInterfaceClose(cInterface->interface); @@ -1555,6 +1602,30 @@ static int darwin_release_interface(struct libusb_device_handle *dev_handle, uin return darwin_to_libusb (kresult); } +static int check_alt_setting_and_clear_halt(struct libusb_device_handle *dev_handle, uint8_t altsetting, struct darwin_interface *cInterface) { + enum libusb_error ret; + IOReturn kresult; + uint8_t current_alt_setting; + + kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, ¤t_alt_setting); + if (kresult == kIOReturnSuccess && altsetting != current_alt_setting) { + return LIBUSB_ERROR_PIPE; + } + + for (int i = 0 ; i < cInterface->num_endpoints ; i++) { + ret = darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]); + if (LIBUSB_SUCCESS != ret) { + usbi_warn(HANDLE_CTX (dev_handle), "error clearing pipe halt for endpoint %d", i); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* may need to re-open the interface */ + return ret; + } + } + } + + return LIBUSB_SUCCESS; +} + static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_handle, uint8_t iface, uint8_t altsetting) { struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle); IOReturn kresult; @@ -1567,19 +1638,41 @@ static int darwin_set_interface_altsetting(struct libusb_device_handle *dev_hand return LIBUSB_ERROR_NO_DEVICE; kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting); - if (kresult != kIOReturnSuccess) - darwin_reset_device (dev_handle); + if (kresult == kIOReturnSuccess) { + /* update the list of endpoints */ + ret = get_endpoints (dev_handle, iface); + if (ret) { + /* this should not happen */ + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + } + return ret; + } - /* update list of endpoints */ - ret = get_endpoints (dev_handle, iface); - if (ret) { - /* this should not happen */ - darwin_release_interface (dev_handle, iface); - usbi_err (HANDLE_CTX (dev_handle), "could not build endpoint table"); + usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult)); + + ret = darwin_to_libusb(kresult); + if (ret != LIBUSB_ERROR_PIPE) { return ret; } - return darwin_to_libusb (kresult); + /* If a device only supports a default setting for the specified interface, then a STALL + (kIOUSBPipeStalled) may be returned. Ref: USB 2.0 specs 9.4.10. + Mimic the behaviour in e.g. the Linux kernel: in such case, reset all endpoints + of the interface (as would have been done per 9.1.1.5) and return success. */ + + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + if (LIBUSB_ERROR_NOT_FOUND == ret) { + /* For some reason we need to reclaim the interface after the pipe error with some versions of macOS */ + ret = darwin_claim_interface (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + darwin_release_interface (dev_handle, iface); + usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface: %s", darwin_error_str(kresult)); + } + ret = check_alt_setting_and_clear_halt(dev_handle, altsetting, cInterface); + } + + return ret; } static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) { @@ -1610,6 +1703,8 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t int open_count = dpriv->open_count; int ret; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + /* clear claimed interfaces temporarily */ dev_handle->claimed_interfaces = 0; @@ -1629,16 +1724,16 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t } if (dpriv->active_config != active_config) { - usbi_dbg ("darwin/restore_state: restoring configuration %d...", active_config); + usbi_dbg (ctx, "darwin/restore_state: restoring configuration %d...", active_config); ret = darwin_set_configuration (dev_handle, active_config); if (LIBUSB_SUCCESS != ret) { - usbi_dbg ("darwin/restore_state: could not restore configuration"); + usbi_dbg (ctx, "darwin/restore_state: could not restore configuration"); return LIBUSB_ERROR_NOT_FOUND; } } - usbi_dbg ("darwin/restore_state: reclaiming interfaces"); + usbi_dbg (ctx, "darwin/restore_state: reclaiming interfaces"); if (claimed_interfaces) { for (uint8_t iface = 0 ; iface < USB_MAXINTERFACES ; ++iface) { @@ -1646,11 +1741,11 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t continue; } - usbi_dbg ("darwin/restore_state: re-claiming interface %u", iface); + usbi_dbg (ctx, "darwin/restore_state: re-claiming interface %u", iface); ret = darwin_claim_interface (dev_handle, iface); if (LIBUSB_SUCCESS != ret) { - usbi_dbg ("darwin/restore_state: could not claim interface %u", iface); + usbi_dbg (ctx, "darwin/restore_state: could not claim interface %u", iface); return LIBUSB_ERROR_NOT_FOUND; } @@ -1658,21 +1753,24 @@ static int darwin_restore_state (struct libusb_device_handle *dev_handle, int8_t } } - usbi_dbg ("darwin/restore_state: device state restored"); + usbi_dbg (ctx, "darwin/restore_state: device state restored"); return LIBUSB_SUCCESS; } -static int darwin_reset_device(struct libusb_device_handle *dev_handle) { +static int darwin_reenumerate_device (struct libusb_device_handle *dev_handle, bool capture) { struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); unsigned long claimed_interfaces = dev_handle->claimed_interfaces; int8_t active_config = dpriv->active_config; + UInt32 options = 0; IOUSBDeviceDescriptor descriptor; IOUSBConfigurationDescriptorPtr cached_configuration; IOUSBConfigurationDescriptor *cached_configurations; IOReturn kresult; UInt8 i; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + if (dpriv->in_reenumerate) { /* ack, two (or more) threads are trying to reset the device! abort! */ return LIBUSB_ERROR_NOT_FOUND; @@ -1689,62 +1787,152 @@ static int darwin_reset_device(struct libusb_device_handle *dev_handle) { memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i])); } + /* if we need to release capture */ + if (HAS_CAPTURE_DEVICE()) { + if (capture) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= 101000 + options |= kUSBReEnumerateCaptureDeviceMask; +#endif + } + } else { + capture = false; + } + /* from macOS 10.11 ResetDevice no longer does anything so just use USBDeviceReEnumerate */ - kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, 0); + kresult = (*(dpriv->device))->USBDeviceReEnumerate (dpriv->device, options); if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); + usbi_err (ctx, "USBDeviceReEnumerate: %s", darwin_error_str (kresult)); dpriv->in_reenumerate = false; return darwin_to_libusb (kresult); } - usbi_dbg ("darwin/reset_device: waiting for re-enumeration to complete..."); + /* capture mode does not re-enumerate but it does require re-open */ + if (capture) { + usbi_dbg (ctx, "darwin/reenumerate_device: restoring state..."); + dpriv->in_reenumerate = false; + return darwin_restore_state (dev_handle, active_config, claimed_interfaces); + } + + usbi_dbg (ctx, "darwin/reenumerate_device: waiting for re-enumeration to complete..."); + + struct timespec start; + usbi_get_monotonic_time(&start); while (dpriv->in_reenumerate) { struct timespec delay = {.tv_sec = 0, .tv_nsec = 1000}; nanosleep (&delay, NULL); + + struct timespec now; + usbi_get_monotonic_time(&now); + unsigned long elapsed_us = (now.tv_sec - start.tv_sec) * USEC_PER_SEC + + (now.tv_nsec - start.tv_nsec) / 1000; + + if (elapsed_us >= DARWIN_REENUMERATE_TIMEOUT_US) { + usbi_err (ctx, "darwin/reenumerate_device: timeout waiting for reenumerate"); + dpriv->in_reenumerate = false; + return LIBUSB_ERROR_TIMEOUT; + } } /* compare descriptors */ - usbi_dbg ("darwin/reset_device: checking whether descriptors changed"); + usbi_dbg (ctx, "darwin/reenumerate_device: checking whether descriptors changed"); if (memcmp (&descriptor, &dpriv->dev_descriptor, sizeof (descriptor))) { /* device descriptor changed. need to return not found. */ - usbi_dbg ("darwin/reset_device: device descriptor changed"); + usbi_dbg (ctx, "darwin/reenumerate_device: device descriptor changed"); return LIBUSB_ERROR_NOT_FOUND; } for (i = 0 ; i < descriptor.bNumConfigurations ; ++i) { (void) (*(dpriv->device))->GetConfigurationDescriptorPtr (dpriv->device, i, &cached_configuration); if (memcmp (cached_configuration, cached_configurations + i, sizeof (cached_configurations[i]))) { - usbi_dbg ("darwin/reset_device: configuration descriptor %d changed", i); + usbi_dbg (ctx, "darwin/reenumerate_device: configuration descriptor %d changed", i); return LIBUSB_ERROR_NOT_FOUND; } } - usbi_dbg ("darwin/reset_device: device reset complete. restoring state..."); + usbi_dbg (ctx, "darwin/reenumerate_device: device reset complete. restoring state..."); return darwin_restore_state (dev_handle, active_config, claimed_interfaces); } -static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) { +static int darwin_reset_device (struct libusb_device_handle *dev_handle) { struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); - io_service_t usbInterface; - CFTypeRef driver; IOReturn kresult; + enum libusb_error ret; - kresult = darwin_get_interface (dpriv->device, interface, &usbInterface); - if (kresult != kIOReturnSuccess) { - usbi_err (HANDLE_CTX (dev_handle), "darwin_get_interface: %s", darwin_error_str(kresult)); - - return darwin_to_libusb (kresult); +#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 + if (dpriv->capture_count > 0) { + /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */ + kresult = (*(dpriv->device))->ResetDevice (dpriv->device); + ret = darwin_to_libusb (kresult); + } else { + ret = darwin_reenumerate_device (dev_handle, false); + } +#else + /* ResetDevice() is missing on non-macOS platforms */ + ret = darwin_reenumerate_device (dev_handle, false); + if ((ret == LIBUSB_SUCCESS || ret == LIBUSB_ERROR_NOT_FOUND) && dpriv->capture_count > 0) { + int capture_count; + int8_t active_config = dpriv->active_config; + unsigned long claimed_interfaces = dev_handle->claimed_interfaces; + + /* save old capture_count */ + capture_count = dpriv->capture_count; + /* reset capture count */ + dpriv->capture_count = 0; + /* attempt to detach kernel driver again as it is now re-attached */ + ret = darwin_detach_kernel_driver (dev_handle, 0); + if (ret != LIBUSB_SUCCESS) { + return ret; + } + /* restore capture_count */ + dpriv->capture_count = capture_count; + /* restore configuration */ + ret = darwin_restore_state (dev_handle, active_config, claimed_interfaces); } +#endif + return ret; +} + +static io_service_t usb_find_interface_matching_location (const io_name_t class_name, UInt8 interface_number, UInt32 location) { + CFMutableDictionaryRef matchingDict = IOServiceMatching (class_name); + CFMutableDictionaryRef propertyMatchDict = CFDictionaryCreateMutable (kCFAllocatorDefault, 0, + &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks); + CFTypeRef locationCF = CFNumberCreate (NULL, kCFNumberSInt32Type, &location); + CFTypeRef interfaceCF = CFNumberCreate (NULL, kCFNumberSInt8Type, &interface_number); - driver = IORegistryEntryCreateCFProperty (usbInterface, kIOBundleIdentifierKey, kCFAllocatorDefault, 0); - IOObjectRelease (usbInterface); + CFDictionarySetValue (matchingDict, CFSTR(kIOPropertyMatchKey), propertyMatchDict); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBDevicePropertyLocationID), locationCF); + CFDictionarySetValue (propertyMatchDict, CFSTR(kUSBHostMatchingPropertyInterfaceNumber), interfaceCF); - if (driver) { - CFRelease (driver); + CFRelease (interfaceCF); + CFRelease (locationCF); + CFRelease (propertyMatchDict); + + return IOServiceGetMatchingService (darwin_default_master_port, matchingDict); +} + +static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + io_service_t usb_interface, child = IO_OBJECT_NULL; + + /* locate the IO registry entry for this interface */ + usb_interface = usb_find_interface_matching_location (kIOUSBHostInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + /* check for the legacy class entry */ + usb_interface = usb_find_interface_matching_location (kIOUSBInterfaceClassName, interface, dpriv->location); + if (0 == usb_interface) { + return LIBUSB_ERROR_NOT_FOUND; + } + } + /* if the IO object has a child entry in the IO Registry it has a kernel driver attached */ + (void) IORegistryEntryGetChildEntry (usb_interface, kIOServicePlane, &child); + IOObjectRelease (usb_interface); + if (IO_OBJECT_NULL != child) { + IOObjectRelease (child); return 1; } @@ -1873,11 +2061,17 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); IOReturn kresult; - uint8_t direction, number, interval, pipeRef, transferType; - uint16_t maxPacketSize; + uint8_t pipeRef, interval; UInt64 frame; AbsoluteTime atTime; int i; +#if InterfaceVersion >= 550 + IOUSBEndpointProperties pipeProperties = {.bVersion = kUSBEndpointPropertiesVersion3}; +#else + /* None of the values below are used in libusb for iso transfers */ + uint8_t direction, number, transferType; + uint16_t maxPacketSize; +#endif struct darwin_interface *cInterface; @@ -1909,8 +2103,20 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { } /* determine the properties of this endpoint and the speed of the device */ - (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, +#if InterfaceVersion >= 550 + kresult = (*(cInterface->interface))->GetPipePropertiesV3 (cInterface->interface, pipeRef, &pipeProperties); + interval = pipeProperties.bInterval; +#else + kresult = (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, &transferType, &maxPacketSize, &interval); +#endif + if (kresult != kIOReturnSuccess) { + usbi_err (TRANSFER_CTX (transfer), "failed to get pipe properties: %d", kresult); + free(tpriv->isoc_framelist); + tpriv->isoc_framelist = NULL; + + return darwin_to_libusb (kresult); + } /* Last but not least we need the bus frame number */ kresult = (*(cInterface->interface))->GetBusFrameNumber(cInterface->interface, &frame, &atTime); @@ -1922,9 +2128,6 @@ static int submit_iso_transfer(struct usbi_transfer *itransfer) { return darwin_to_libusb (kresult); } - (*(cInterface->interface))->GetPipeProperties (cInterface->interface, pipeRef, &direction, &number, - &transferType, &maxPacketSize, &interval); - /* schedule for a frame a little in the future */ frame += 4; @@ -2051,8 +2254,10 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { uint8_t pipeRef, iface; IOReturn kresult; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + if (ep_to_pipeRef (transfer->dev_handle, transfer->endpoint, &pipeRef, &iface, &cInterface) != 0) { - usbi_err (TRANSFER_CTX (transfer), "endpoint not found on any open interface"); + usbi_err (ctx, "endpoint not found on any open interface"); return LIBUSB_ERROR_NOT_FOUND; } @@ -2060,7 +2265,7 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { if (!dpriv->device) return LIBUSB_ERROR_NO_DEVICE; - usbi_warn (ITRANSFER_CTX (itransfer), "aborting all transactions on interface %d pipe %d", iface, pipeRef); + usbi_warn (ctx, "aborting all transactions on interface %d pipe %d", iface, pipeRef); /* abort transactions */ #if InterfaceVersion >= 550 @@ -2070,7 +2275,7 @@ static int darwin_abort_transfers (struct usbi_transfer *itransfer) { #endif (*(cInterface->interface))->AbortPipe (cInterface->interface, pipeRef); - usbi_dbg ("calling clear pipe stall to clear the data toggle bit"); + usbi_dbg (ctx, "calling clear pipe stall to clear the data toggle bit"); /* newer versions of darwin support clearing additional bits on the device's endpoint */ kresult = (*(cInterface->interface))->ClearPipeStallBothEnds(cInterface->interface, pipeRef); @@ -2099,7 +2304,7 @@ static void darwin_async_io_callback (void *refcon, IOReturn result, void *arg0) struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); - usbi_dbg ("an async io operation has completed"); + usbi_dbg (TRANSFER_CTX(transfer), "an async io operation has completed"); /* if requested write a zero packet */ if (kIOReturnSuccess == result && IS_XFEROUT(transfer) && transfer->flags & LIBUSB_TRANSFER_ADD_ZERO_PACKET) { @@ -2122,6 +2327,8 @@ static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer if (itransfer->timeout_flags & USBI_TRANSFER_TIMED_OUT) result = kIOUSBTransactionTimeout; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); + switch (result) { case kIOReturnUnderrun: case kIOReturnSuccess: @@ -2129,17 +2336,17 @@ static enum libusb_transfer_status darwin_transfer_status (struct usbi_transfer case kIOReturnAborted: return LIBUSB_TRANSFER_CANCELLED; case kIOUSBPipeStalled: - usbi_dbg ("transfer error: pipe is stalled"); + usbi_dbg (ctx, "transfer error: pipe is stalled"); return LIBUSB_TRANSFER_STALL; case kIOReturnOverrun: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: data overrun"); + usbi_warn (ctx, "transfer error: data overrun"); return LIBUSB_TRANSFER_OVERFLOW; case kIOUSBTransactionTimeout: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: timed out"); + usbi_warn (ctx, "transfer error: timed out"); itransfer->timeout_flags |= USBI_TRANSFER_TIMED_OUT; return LIBUSB_TRANSFER_TIMED_OUT; default: - usbi_warn (ITRANSFER_CTX (itransfer), "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); + usbi_warn (ctx, "transfer error: %s (value = 0x%08x)", darwin_error_str (result), result); return LIBUSB_TRANSFER_ERROR; } } @@ -2148,22 +2355,23 @@ static int darwin_handle_transfer_completion (struct usbi_transfer *itransfer) { struct libusb_transfer *transfer = USBI_TRANSFER_TO_LIBUSB_TRANSFER(itransfer); struct darwin_transfer_priv *tpriv = usbi_get_transfer_priv(itransfer); const unsigned char max_transfer_type = LIBUSB_TRANSFER_TYPE_BULK_STREAM; - const char *transfer_types[max_transfer_type + 1] = {"control", "isoc", "bulk", "interrupt", "bulk-stream"}; + const char *transfer_types[] = {"control", "isoc", "bulk", "interrupt", "bulk-stream", NULL}; bool is_isoc = LIBUSB_TRANSFER_TYPE_ISOCHRONOUS == transfer->type; + struct libusb_context *ctx = ITRANSFER_CTX (itransfer); if (transfer->type > max_transfer_type) { - usbi_err (TRANSFER_CTX(transfer), "unknown endpoint type %d", transfer->type); + usbi_err (ctx, "unknown endpoint type %d", transfer->type); return LIBUSB_ERROR_INVALID_PARAM; } if (NULL == tpriv) { - usbi_err (TRANSFER_CTX(transfer), "malformed request is missing transfer priv"); + usbi_err (ctx, "malformed request is missing transfer priv"); return LIBUSB_ERROR_INVALID_PARAM; } - usbi_dbg ("handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result); + usbi_dbg (ctx, "handling transfer completion type %s with kernel status %d", transfer_types[transfer->type], tpriv->result); - if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result) { + if (kIOReturnSuccess == tpriv->result || kIOReturnUnderrun == tpriv->result || kIOUSBTransactionTimeout == tpriv->result) { if (is_isoc && tpriv->isoc_framelist) { /* copy isochronous results back */ @@ -2262,9 +2470,159 @@ static int darwin_free_streams (struct libusb_device_handle *dev_handle, unsigne } #endif +#if InterfaceVersion >= 700 + +/* macOS APIs for getting entitlement values */ + +#if !defined(TARGET_OS_OSX) || TARGET_OS_OSX == 1 +#include <Security/Security.h> +#else +typedef struct __SecTask *SecTaskRef; +extern SecTaskRef SecTaskCreateFromSelf(CFAllocatorRef allocator); +extern CFTypeRef SecTaskCopyValueForEntitlement(SecTaskRef task, CFStringRef entitlement, CFErrorRef *error); +#endif + +static bool darwin_has_capture_entitlements (void) { + SecTaskRef task; + CFTypeRef value; + bool entitled; + + task = SecTaskCreateFromSelf (kCFAllocatorDefault); + if (task == NULL) { + return false; + } + value = SecTaskCopyValueForEntitlement(task, CFSTR("com.apple.vm.device-access"), NULL); + CFRelease (task); + entitled = value && (CFGetTypeID (value) == CFBooleanGetTypeID ()) && CFBooleanGetValue (value); + if (value) { + CFRelease (value); + } + return entitled; +} + +static int darwin_reload_device (struct libusb_device_handle *dev_handle) { + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + enum libusb_error err; + + usbi_mutex_lock(&darwin_cached_devices_lock); + (*(dpriv->device))->Release(dpriv->device); + dpriv->device = darwin_device_from_service (HANDLE_CTX (dev_handle), dpriv->service); + if (!dpriv->device) { + err = LIBUSB_ERROR_NO_DEVICE; + } else { + err = LIBUSB_SUCCESS; + } + usbi_mutex_unlock(&darwin_cached_devices_lock); + + return err; +} + +/* On macOS, we capture an entire device at once, not individual interfaces. */ + +static int darwin_detach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + IOReturn kresult; + enum libusb_error err; + struct libusb_context *ctx = HANDLE_CTX (dev_handle); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + if (dpriv->capture_count == 0) { + usbi_dbg (ctx, "attempting to detach kernel driver from device"); + + if (darwin_has_capture_entitlements ()) { + /* request authorization */ + kresult = IOServiceAuthorize (dpriv->service, kIOServiceInteractionAllowed); + if (kresult != kIOReturnSuccess) { + usbi_warn (ctx, "IOServiceAuthorize: %s", darwin_error_str(kresult)); + return darwin_to_libusb (kresult); + } + + /* we need start() to be called again for authorization status to refresh */ + err = darwin_reload_device (dev_handle); + if (err != LIBUSB_SUCCESS) { + return err; + } + } else { + usbi_info (ctx, "no capture entitlements. may not be able to detach the kernel driver for this device"); + if (0 != geteuid()) { + usbi_warn (ctx, "USB device capture requires either an entitlement (com.apple.vm.device-access) or root privilege"); + return LIBUSB_ERROR_ACCESS; + } + } + + /* reset device to release existing drivers */ + err = darwin_reenumerate_device (dev_handle, true); + if (err != LIBUSB_SUCCESS) { + return err; + } + } + dpriv->capture_count++; + return LIBUSB_SUCCESS; +} + + +static int darwin_attach_kernel_driver (struct libusb_device_handle *dev_handle, uint8_t interface) { + UNUSED(interface); + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + if (HAS_CAPTURE_DEVICE()) { + } else { + return LIBUSB_ERROR_NOT_SUPPORTED; + } + + dpriv->capture_count--; + if (dpriv->capture_count > 0) { + return LIBUSB_SUCCESS; + } + + usbi_dbg (HANDLE_CTX (dev_handle), "reenumerating device for kernel driver attach"); + + /* reset device to attach kernel drivers */ + return darwin_reenumerate_device (dev_handle, false); +} + +static int darwin_capture_claim_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + if (dev_handle->auto_detach_kernel_driver && darwin_kernel_driver_active(dev_handle, iface)) { + ret = darwin_detach_kernel_driver (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + usbi_info (HANDLE_CTX (dev_handle), "failed to auto-detach the kernel driver for this device, ret=%d", ret); + } + } + + return darwin_claim_interface (dev_handle, iface); +} + +static int darwin_capture_release_interface(struct libusb_device_handle *dev_handle, uint8_t iface) { + enum libusb_error ret; + struct darwin_cached_device *dpriv = DARWIN_CACHED_DEVICE(dev_handle->dev); + + ret = darwin_release_interface (dev_handle, iface); + if (ret != LIBUSB_SUCCESS) { + return ret; + } + + if (dev_handle->auto_detach_kernel_driver && dpriv->capture_count > 0) { + ret = darwin_attach_kernel_driver (dev_handle, iface); + if (LIBUSB_SUCCESS != ret) { + usbi_info (HANDLE_CTX (dev_handle), "on attempt to reattach the kernel driver got ret=%d", ret); + } + /* ignore the error as the interface was successfully released */ + } + + return LIBUSB_SUCCESS; +} + +#endif + const struct usbi_os_backend usbi_backend = { .name = "Darwin", - .caps = 0, + .caps = USBI_CAP_SUPPORTS_DETACH_KERNEL_DRIVER, .init = darwin_init, .exit = darwin_exit, .get_active_config_descriptor = darwin_get_active_config_descriptor, @@ -2275,8 +2633,6 @@ const struct usbi_os_backend usbi_backend = { .close = darwin_close, .get_configuration = darwin_get_configuration, .set_configuration = darwin_set_configuration, - .claim_interface = darwin_claim_interface, - .release_interface = darwin_release_interface, .set_interface_altsetting = darwin_set_interface_altsetting, .clear_halt = darwin_clear_halt, @@ -2289,6 +2645,16 @@ const struct usbi_os_backend usbi_backend = { .kernel_driver_active = darwin_kernel_driver_active, +#if InterfaceVersion >= 700 + .detach_kernel_driver = darwin_detach_kernel_driver, + .attach_kernel_driver = darwin_attach_kernel_driver, + .claim_interface = darwin_capture_claim_interface, + .release_interface = darwin_capture_release_interface, +#else + .claim_interface = darwin_claim_interface, + .release_interface = darwin_release_interface, +#endif + .destroy_device = darwin_destroy_device, .submit_transfer = darwin_submit_transfer, |