#include "darwin_usb.h"
-static pthread_mutex_t libusb_darwin_init_mutex = PTHREAD_MUTEX_INITIALIZER;
static int init_count = 0;
/* async event thread */
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);
(*(cached_dev->device))->Release(cached_dev->device);
cached_dev->device = NULL;
}
+ IOObjectRelease (cached_dev->service);
free (cached_dev);
}
}
struct darwin_cached_device *old_device;
io_service_t device;
- UInt64 session;
+ UInt64 session, locationID;
int ret;
usbi_mutex_lock(&active_contexts_lock);
/* 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;
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 ("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) {
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;
--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);
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) {
(*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
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);
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;
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);
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
}
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++)
}
if (!usbInterface) {
- usbi_err (HANDLE_CTX (dev_handle), "interface not found");
+ usbi_info (HANDLE_CTX (dev_handle), "interface not found");
return LIBUSB_ERROR_NOT_FOUND;
}
/* 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 (HANDLE_CTX (dev_handle), "USBInterfaceOpen: %s", darwin_error_str(kresult));
return darwin_to_libusb (kresult);
}
struct darwin_device_handle_priv *priv = usbi_get_device_handle_priv(dev_handle);
IOReturn kresult;
enum libusb_error ret;
+ int i;
+ uint8_t old_alt_setting;
/* current interface */
struct darwin_interface *cInterface = &priv->interfaces[iface];
return LIBUSB_ERROR_NO_DEVICE;
kresult = (*(cInterface->interface))->SetAlternateInterface (cInterface->interface, altsetting);
+ 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;
+ }
+ else
+ usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult));
+
+ if (kresult != kIOUSBPipeStalled)
+ 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.
- Mimick the behaviour in e.g. the Linux kernel: in such case, reset all endpoints,
- and hide errors.Current implementation resets the entire device, instead of single
- interface, due to historic reasons. */
- if (kresult != kIOReturnSuccess) {
- usbi_warn (HANDLE_CTX (dev_handle), "SetAlternateInterface: %s", darwin_error_str(kresult));
- darwin_reset_device (dev_handle);
- }
+ Mimick 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. */
+
+ /* For some reason we need to reclaim the interface after the pipe error */
+ ret = darwin_claim_interface (dev_handle, iface);
- /* 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_err (HANDLE_CTX (dev_handle), "could not reclaim interface");
}
- return ret;
+ /* Return error if a change to another value was attempted */
+ kresult = (*(cInterface->interface))->GetAlternateSetting (cInterface->interface, &old_alt_setting);
+ if (kresult == kIOReturnSuccess && altsetting != old_alt_setting)
+ return LIBUSB_ERROR_PIPE;
+
+ for (i = 0 ; i < cInterface->num_endpoints ; i++)
+ darwin_clear_halt(dev_handle, cInterface->endpoint_addrs[i]);
+
+ return LIBUSB_SUCCESS;
}
static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) {
return LIBUSB_SUCCESS;
}
-static int darwin_reenumerate_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;
memcpy (cached_configurations + i, cached_configuration, sizeof (cached_configurations[i]));
}
+ /* if we need to release capture */
+ if (HAS_CAPTURE_DEVICE()) {
+ if (capture) {
+ options |= kUSBReEnumerateCaptureDeviceMask;
+ }
+ } 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));
dpriv->in_reenumerate = false;
return darwin_to_libusb (kresult);
}
+ /* capture mode does not re-enumerate but it does require re-open */
+ if (capture) {
+ usbi_dbg ("darwin/reenumerate_device: restoring state...");
+ dpriv->in_reenumerate = false;
+ return darwin_restore_state (dev_handle, active_config, claimed_interfaces);
+ }
+
usbi_dbg ("darwin/reenumerate_device: waiting for re-enumeration to complete...");
time = 0;
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;
- 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));
-
+ if (dpriv->capture_count > 0) {
+ /* we have to use ResetDevice as USBDeviceReEnumerate() loses the authorization for capture */
+ kresult = (*(dpriv->device))->ResetDevice (dpriv->device);
return darwin_to_libusb (kresult);
+ } else {
+ return darwin_reenumerate_device (dev_handle, false);
}
+}
- driver = IORegistryEntryCreateCFProperty (usbInterface, kIOBundleIdentifierKey, kCFAllocatorDefault, 0);
- IOObjectRelease (usbInterface);
-
- if (driver) {
- CFRelease (driver);
-
- return 1;
+static int darwin_kernel_driver_active(struct libusb_device_handle *dev_handle, uint8_t interface) {
+ enum libusb_error ret = darwin_claim_interface (dev_handle, interface);
+ if (ret == LIBUSB_SUCCESS) {
+ darwin_release_interface (dev_handle, interface);
}
-
- /* no driver */
- return 0;
+ return (ret == LIBUSB_ERROR_ACCESS);
}
static void darwin_destroy_device(struct libusb_device *dev) {
}
#endif
+#if InterfaceVersion >= 700
+
+/* macOS APIs for getting entitlement values */
+
+#if TARGET_OS_OSX
+#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 (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;
+
+ if (HAS_CAPTURE_DEVICE()) {
+ } else {
+ return LIBUSB_ERROR_NOT_SUPPORTED;
+ }
+
+ if (dpriv->capture_count == 0) {
+ usbi_dbg ("attempting to detach kernel driver from device");
+
+ if (!darwin_has_capture_entitlements ()) {
+ usbi_info (HANDLE_CTX (dev_handle), "no capture entitlements. can not detach the kernel driver for this device");
+ return LIBUSB_ERROR_NOT_SUPPORTED;
+ }
+
+ /* request authorization */
+ kresult = IOServiceAuthorize (dpriv->service, kIOServiceInteractionAllowed);
+ if (kresult != kIOReturnSuccess) {
+ usbi_warn (HANDLE_CTX (dev_handle), "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;
+ }
+
+ /* 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 ("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) {
+ 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,
.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,
- .reset_device = darwin_reenumerate_device,
+ .reset_device = darwin_reset_device,
#if InterfaceVersion >= 550
.alloc_streams = darwin_alloc_streams,
.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,