darwin: fix behavior of libusb_set_interface_alt_setting when it stalls the pipe
In some versions of macOS a pipe stall returned when setting the alternate interface
causes the interface to become unusable. To handle this case the backend was always
re-claiming the interface before clearing the pipe stall. In macOS Monterey
unconditionally re-claiming the interface leads to an error due to the process already
having exclusive access. To resolve this issue we attempt to clear the halt and only
re-claim the interface if clearing the pipe stall returns kIOUSBUnknownPipeErr. Tested
with 12.0.1 and 10.13 and get the expected results in both cases with a custom USB
device that has this behavior.
Fixes #1011
Closes #1031
Signed-off-by: Nathan Hjelm <hjelmn@google.com>
diff --git a/libusb/os/darwin_usb.c b/libusb/os/darwin_usb.c
index 15457f7..5f2fa3c 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
@@ -160,11 +160,12 @@
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;
}
@@ -1586,12 +1587,34 @@
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;
enum libusb_error ret;
- int i;
- uint8_t old_alt_setting;
/* current interface */
struct darwin_interface *cInterface = &priv->interfaces[iface];
@@ -1623,23 +1646,18 @@
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. */
- /* For some reason we need to reclaim the interface after the pipe error */
- ret = darwin_claim_interface (dev_handle, iface);
-
- if (ret) {
- darwin_release_interface (dev_handle, iface);
- usbi_err (HANDLE_CTX (dev_handle), "could not reclaim interface");
+ 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 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;
+ return ret;
}
static int darwin_clear_halt(struct libusb_device_handle *dev_handle, unsigned char endpoint) {
diff --git a/libusb/version_nano.h b/libusb/version_nano.h
index 04fa88c..09bf359 100644
--- a/libusb/version_nano.h
+++ b/libusb/version_nano.h
@@ -1 +1 @@
-#define LIBUSB_NANO 11679
+#define LIBUSB_NANO 11680