darwin: use detach kernel APIs for capture
When libusb_detach_kernel_driver() is called on the first interface, we use
the capture re-enumerate APIs to force kernel drivers to detach. On
subsequent calls, a counter is incremented to keep track of the number of
detach calls. When libusb_attach_kernel_driver() is called for the same
number of times, then we re-enumerate (reset) and let kernel drivers
re-attach.
darwin_kernel_driver_active() is changed to attempt to claim an interface
and return 1 only if a kIOReturnExclusiveAccess status is returned. The old
implementation which looks for a CFBundleID in the IORegistryEntry does not
seem to work in all cases.
darwin_reset_device() is aware of the capture status and will re-set twice
if capture mode is enabled: once to do a USB reset and again to re-capture.
Signed-off-by: Nathan Hjelm <hjelmn@google.com>
diff --git a/libusb/core.c b/libusb/core.c
index e04ec67..7a2f6c3 100644
--- a/libusb/core.c
+++ b/libusb/core.c
@@ -1994,7 +1994,7 @@
* Detach a kernel driver from an interface. If successful, you will then be
* able to claim the interface and perform I/O.
*
- * This functionality is not available on Darwin or Windows.
+ * This functionality is not available on Windows.
*
* Note that libusb itself also talks to the device through a special kernel
* driver, if this driver is already attached to the device, this call will
@@ -2030,10 +2030,9 @@
/** \ingroup libusb_dev
* Re-attach an interface's kernel driver, which was previously detached
- * using libusb_detach_kernel_driver(). This call is only effective on
- * Linux and returns LIBUSB_ERROR_NOT_SUPPORTED on all other platforms.
+ * using libusb_detach_kernel_driver().
*
- * This functionality is not available on Darwin or Windows.
+ * This functionality is not available on Windows.
*
* \param dev_handle a device handle
* \param interface_number the interface to attach the driver from
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index 0a3b79f..988ca9e 100644
--- a/libusb/os/darwin_usb.c
+++ b/libusb/os/darwin_usb.c
@@ -1674,10 +1674,11 @@
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;
@@ -1701,14 +1702,30 @@
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;
@@ -1744,30 +1761,25 @@
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) {
@@ -2280,9 +2292,98 @@
}
#endif
+#if InterfaceVersion >= 700
+
+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) {
+ /* 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;
+ } else {
+ dpriv->capture_count = 0;
+ }
+ /* 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) {
+ return 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;
+
+ ret = darwin_release_interface (dev_handle, iface);
+ if (ret != LIBUSB_SUCCESS) {
+ return ret;
+ }
+ if (dev_handle->auto_detach_kernel_driver) {
+ ret = darwin_attach_kernel_driver (dev_handle, iface);
+ }
+ return ret;
+}
+
+#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,
@@ -2293,12 +2394,10 @@
.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,
@@ -2307,6 +2406,16 @@
.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,
diff --git a/libusb/os/darwin_usb.h b/libusb/os/darwin_usb.h
index b799bfd..179341c 100644
--- a/libusb/os/darwin_usb.h
+++ b/libusb/os/darwin_usb.h
@@ -148,6 +148,16 @@
#define IO_OBJECT_NULL ((io_object_t) 0)
#endif
+/* Testing availability */
+#ifndef __has_builtin
+ #define __has_builtin(x) 0 // Compatibility with non-clang compilers.
+#endif
+#if __has_builtin(__builtin_available)
+ #define HAS_CAPTURE_DEVICE() __builtin_available(macOS 10.10, *)
+#else
+ #define HAS_CAPTURE_DEVICE() 0
+#endif
+
typedef IOCFPlugInInterface *io_cf_plugin_ref_t;
typedef IONotificationPortRef io_notification_port_t;
@@ -166,6 +176,7 @@
int can_enumerate;
int refcount;
bool in_reenumerate;
+ int capture_count;
};
struct darwin_device_priv {
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 8ed8ddc..fcbf3cd 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11611
+#define LIBUSB_NANO 11612